C++核心技术个人笔记
Posted ZSYL
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了C++核心技术个人笔记相关的知识,希望对你有一定的参考价值。
【C++核心技术】个人笔记
四大内存分区&引用
- 全局区
// 全局变量,main函数外面定义的
// 静态变量
static int a = 10;
// 常量:字符串常量
"string"
// const修饰的全局变量(也放常量区)
const int a, b, c = 10;
- 栈区
由编译器自动分配和释放(形参也会放在栈区)
int* func()
int a = 10; // 局部变量 存放在栈区,栈区的数据在函数执行完后自动释放
return &a; // 返回局部变量的地址
int main()
// 接受func函数的返回值
int *p = func();
cout << *p << endl; // 解引用 第一次可以打印,编译器做了保留
cout << *p << endl; // 解引用 第二次不再保留
- 堆区
由程序员分配释放,程序结束时,由操作系统回收
使用new在堆区开辟内存
int* func()
int * p = new int(10); // 使用new开辟到堆区
return p; // 指针是局部变量,保存在堆区
- new 关键字
如何释放内存:delete 变量;
cout << *p << endl;
:内存已释放,再次访问会报错
int * arr = new int[10]; // 创建数组,也是一个指针
arr[i]:也可以操作数组
释放堆区数组:delete[] arr;
- 引用
数据类型 & 别名 = 原名:操作同一块内存
1.引用必须初始化
2.引用初始化之后不能改变
3.引用=c,赋值操作,而非更改引用
- 引用传递
// 地址传递,形参会修饰实参
void swap(int *a, int *b)
int temp = *a; // 解引用
*a = *b;
*b = temp;
// 引用(起别名)传递,形参会修饰实参
// 相当于起别名,修饰同一块内存
void swap(int &a, int &b)
int temp = a;
a = b;
b = temp;
- 引用做函数的返回值
// 1. 不要返回局部变量的返回值
int & test()
int a = 10; // 局部变量存放在栈区
return a;
int & test2()
static int a = 10; // 静态变量,存放在全局区,程序结束后会销毁
return a;
int main()
int &res = test2(); // 正常访问
// 如果函数做左值,那么必须返回引用
test02() = 1000; // 赋值操作,函数返回的就是static a的地址
// res = 1000;
- 引用的本质是指针常量,因此初始化后不能修改指向的地址。
int a = 10;
int& ref = a; // int * const ref = &a;
// 输出引用时,会默认解引用
ref = 20; // 内部发现ref是引用,会自动转化为:*ref = 20;
- 常量引用:用来修饰形参,防止误操作,改变实参
int a = 10;
// 加上const之后,编译器将代码修改为 int temp=10, const int & ref = temp;
const int & ref = 10; // 引用必须为一块的合法的内存空间
// ref = 20; 加上const之后为只读,不可以修改。
void show(const int & a)
// a = 100; 报错
// 不允许修改该引用
- 打印地址:
(int)&a
函数提高
默认参数
- 如果一个位置有了默认参数,那么这个位置往后都必须有默认值
- 如果函数声明有默认参数,函数实现就不能有默认参数(声明和实现只能有一个有默认参数)
// 函数声明(没有实现)让编译器知道有这个函数,后面就可以用了
int func(int a=10, int b=10);
// 实现(二义性有误,不知道声明和实现要哪个)
int func(int a=20, int b=20)
return a+b;
占位参数
形参列表可以有占位参数,用来占位,占位参数也有默认参数。
- 占位参数,传参数时必须填补
void func(int a, int = 10) // 无意义
函数重载
函数名可以相同,提高复用性
满足条件:
- 同一作用域下
- 函数名称相同
- 函数参数类型不同/个数不同/顺序不同
void func()
cout << "func调用" << endl;
void func(int a)
cout << "func的重载" << endl;
- 函数的返回值不能作为重载的条件,因为编译器不知道调用哪个(二义性)
- 引用作为重载的条件
void func(int &a)
// 类型不同
void func(const int &a) // const int &a=10; 合法操作
int main()
int a = 10;
func(a); // 调用第一个,变量可读可写
func(10); // 报错,第一个函数中,引用必须是合法的内存空间(栈/堆)10是常量区中,因此执行第二个重载函数
// const int &a = 10;是合法的,编译器创建一个临时的变量10
- 引用必须是合法的内存空间(栈/堆)10是常量区中
- 函数重载遇到默认参数,出现二义性,尽量避免。
面向对象
封装
class Circle
// 访问权限
public:
int r;
string name;
// 行为
double calculate()
return 2 * PI * r;
void setC(int c)
r = c;
;
int main()
// 创建对象
Circle c1;
c1.r = 1;
c1.calculate();
- 使用string,需要包含头文件
#include <string>
- 类中属性和行为统称为成员
- 属性:成员变量
- 行为:成员方法
权限
- public:全都可以访问
- protected:类内,子类可以访问
- private:类内可以访问
class Circle
// 访问权限
public:
int r;
private:
string name;
// 行为
protected:
double calculate()
return 2 * PI * r;
void setC(int c)
r = c;
;
struct和class区别:
- struct:默认public
- class:默认private
属性设置为私有,则设置公有接口来实现对其操作
- 头文件 name.h:里面函数只有声明
- cpp代码中引入头文件报错,需要加入作用域:
void Point::setX()
构造函数
析构函数,对象销毁前会调用一次。
~Person()
浅拷贝
问题:堆区重复释放
//拷贝构造函数
Person(const Person& p)
cout << "拷贝构造函数!" << endl;
//如果不利用深拷贝在堆区创建新内存,会导致浅拷贝带来的重复释放堆区问题
m_age = p.m_age;
m_height = new int(*p.m_height);
初始化
//初始化列表方式初始化
Person(int a, int b, int c) :m_A(a), m_B(b), m_C(c)
// 初始化
Person (string name, string pName): m_Name(name), m_Phone(pName)
string name;
Phone m_Phone;
静态函数
- 对象调用
- 类名调用
Person::func(); // 作用域
类内声明静态成员变量:
static int m_A; // 静态成员变量
// 类外初始化
int Person::m_A = 0;
所有对象共享一份数据。
静态成员变量两种访问方式:
- 对象
- 类名
静态函数也有权限,private: 类外不能访问。
静态成员函数特点:
- 程序共享一个函数
- 静态成员函数只能访问静态成员变量
静态成员函数两种访问方式:
- 通过对象
- 类名
Person::func();
类和对象
- 空对象占用内存空间为:1
- C++编译器也会给每一个对象分配一个字节空间,是为了区分空对象占内存的位置
- 每一个对象也应该有一个独一无二的内存地址
- 非静态成员变量占对象空间
- 静态成员变量不占对象空间
- 函数也不占对象空间,所有函数共享一个函数实例
- 如果用到this指针,需要加以判断保证代码的健壮性
- 空指针访问成员函数,但是如果成员函数中用到了this指针,就不可以了
const
- this指针的本质是一个指针常量,指针的指向不可修改,但是this指针指向的对象的数据是可以修改的
- 如果想让指针指向的值也不可以修改,需要声明常函数
- const修饰成员函数,表示指针指向的内存空间的数据不能修改,除了mutable修饰的变量
- const修饰对象 常对象
- 常对象不能修改成员变量的值,但是可以访问
- 常对象访问成员函数,常对象不能调用const的函数
友元
友元的目的就是让一个函数或者类访问另一个类中私有成员
运算符重载
bool operator==(Person & p)
if (this->m_Name == p.m_Name && this->m_Age == p.m_Age)
return true;
else
return false;
继承
class Java : public BasePage
.....
class A : public B;
A 类称为子类 或 派生类
B 类称为父类 或 基类
-
继承中先调用父类构造函数,再调用子类构造函数,析构顺序与构造相反。
-
当子类与父类拥有同名的成员函数,子类会隐藏父类中所有版本的同名成员函数
-
如果想访问父类中被隐藏的同名成员函数,需要加父类的作用域
s.Base::m_A
总结:
- 子类对象可以直接访问到子类中同名成员
- 子类对象加作用域可以访问到父类同名成员
- 当子类与父类拥有同名的成员函数,子类会隐藏父类中同名成员函数,加作用域可以访问到父类中同名函数
静态成员和非静态成员出现同名,处理方式一致
- 访问子类同名成员 直接访问即可
- 访问父类同名成员 需要加作用域
多继承
多继承容易产生成员同名的情况
- 通过使用类名作用域可以区分调用哪一个基类的成员
菱形继承
//继承前加virtual关键字后,变为虚继承
//此时公共的父类Animal称为虚基类
class Sheep : virtual public Animal ;
class Tuo : virtual public Animal ;
class SheepTuo : public Sheep, public Tuo ;
总结:
- 菱形继承带来的主要问题是子类继承两份相同的数据,导致资源浪费以及毫无意义
- 利用虚继承可以解决菱形继承问题
多态
多态是C++面向对象三大特性之一
多态分为两类
- 静态多态: 函数重载 和 运算符重载属于静态多态,复用函数名
- 动态多态: 派生类和虚函数实现运行时多态
静态多态和动态多态区别:
- 静态多态的函数地址早绑定 - 编译阶段确定函数地址
- 动态多态的函数地址晚绑定 - 运行阶段确定函数地址
- 我们希望传入什么对象,那么就调用什么对象的函数
- 如果函数地址在编译阶段就能确定,那么静态联编
- 如果函数地址在运行阶段才能确定,就是动态联编
总结:
多态满足条件
- 有继承关系
- 子类重写父类中的虚函数
多态使用条件
- 父类指针或引用指向子类对象
重写:函数返回值类型 函数名 参数列表 完全一致称为重写
void DoSpeak(Animal & animal)
animal.speak();
抽象类
在多态中,通常父类中虚函数的实现是毫无意义的,主要都是调用子类重写的内容
因此可以将虚函数改为纯虚函数
纯虚函数语法:virtual 返回值类型 函数名 (参数列表)= 0 ;
当类中有了纯虚函数,这个类也称为抽象类
抽象类特点:
- 无法实例化对象
- 子类必须重写抽象类中的纯虚函数,否则也属于抽象类
- base = new Base; // 错误,抽象类无法实例化对象
纯虚构
多态使用时,如果子类中有属性开辟到堆区,那么父类指针在释放时无法调用到子类的析构代码
解决方式:将父类中的析构函数改为虚析构或者纯虚析构
虚析构和纯虚析构共性:
- 可以解决父类指针释放子类对象
- 都需要有具体的函数实现
虚析构和纯虚析构区别:
- 如果是纯虚析构,该类属于抽象类,无法实例化对象
虚析构语法:
virtual ~类名()
纯虚析构语法:
virtual ~类名() = 0;
类名::~类名()
文件操作
C++中对文件操作需要包含头文件 < fstream >
写文件
写文件步骤如下:
1. 包含头文件
#include <fstream>
2. 创建流对象
ofstream ofs;
3. 打开文件
ofs.open("文件路径",打开方式);
4. 写数据
ofs << "写入的数据";
5. 关闭文件
ofs.close();
文件打开方式:
打开方式 | 解释 |
---|---|
ios::in | 为读文件而打开文件 |
ios::out | 为写文件而打开文件 |
ios::ate | 初始位置:文件尾 |
ios::app | 追加方式写文件 |
ios::trunc | 如果文件存在先删除,再创建 |
ios::binary | 二进制方式 |
- 二进制方式写文件主要利用流对象调用成员函数write
ofs.write((const char *)&p, sizeof(p));
- 文件输入流对象 可以通过read函数,以二进制方式读数据
Person p;
ifs.read((char *)&p, sizeof(p));
去年大三时候的东西,未完待续!
以上是关于C++核心技术个人笔记的主要内容,如果未能解决你的问题,请参考以下文章