B站:黑马程序员C++教程
栈区,全局区,堆区和代码区
析构、构造和static
对象成员与类本身构造顺序,先成员后自己;析构则相反
static修饰成员变量,所有对象共享一份内存,编译阶段分配内存,类内声明类外初始化,静态成员变量也有作用域
static修饰成员函数,所有对象共享同一个函数且静态成员函数只能访问静态成员变量,静态成员函数也有作用域
int A::m_A = 100;
//可通过直接使用类名来访问静态成员变量
cout << A::m_A << endl;
//同理,静态成员函数也可通过类名访问
cout << A::func()<<endl;
类上的成员变量和成员函数分开存储,空对象占1字节,静态成员不与非静态成员存放在一起
const修饰成员函数
常函数不可修改成员属性
原因:this指针本质为一个指针常量,指针常量本身的指向是不可修改的
常对象中也可以修改
等号运算符重载,防止浅拷贝问题,返回自身的对象解决连等操作
运算符重载也可以发生函数重载,如类与类相加,类与int相加
实现了链式编程将函数的返回值改为输出流对象的引用,则后续可一直调用左移输出运算符,友元技术可以让重载左移运算符的函数可以访问私有数据
继承中构造和析构调用顺序
构造函数先父后子,析构函数则相反
子类中如果出现于父类同名的成员函数,子类的同名成员会将父类的所有同名成员隐藏掉,像访问必须加上作用域如:s.Base::func()
同名静态成员与非静态成员处理方法一致
虚继承能解决菱形继承带来的多份数据重复的问题,虚继承同名成员只会保留一份
虚继承发生后,子类继承的是虚基类指针,虚基类指针指向虚基类表,虚基类表中记录的是偏移量,通过指针+偏移量可以得到同一份成员数据
虚继承语法如下
多态
静态多态
函数重载和运算符重载
动态多态
派生类和虚函数
区别
C++中父类引用/指针不需要转换类型就能指向子类对象
这个函数的运行结果仍然为父类的speak(),因为地址在其编译阶段就已经确定了,无论传入父类还是子类的对象只会执行父类的函数
但是我们想调用子类的speak(),需要在父类中加virtual使得成员函数speak()变成虚函数
动态多态满足条件
重写:函数返回值类型 函数名和参数列表必须完全相同。子类重写函数时可写可不写virtual
父类的指针或者引用指向子类的对象
底层原理
不发生重写时
虚函数有对应的虚函数(表指针),这个指针指向虚函数表,当发生调用时,虚函数指针就会根据函数表的指向找到对应的虚函数
虚函数存放虚函数的入口地址
发生重写后
重写函数后,继承的虚函数表会替换成重写的虚函数,由于传入的是子类对象,所以会从子类的虚函数表中找对应的函数地址,即调用的自身的speak函数
虚函数内部结构
未发生重写时,子类内部结构,可以发现,仍然是Animal(父类)作用域下的虚函数
发生重写后,子类虚函数表变成了自身作用域下的虚函数入口地址
虚析构和纯虚析构
问题:父类指针指向子类对象后,在delete时不会调用子类的析构函数,若子类有堆区的数据,此时发生了内存泄漏
解决方法:把父类的析构函数前加一个virtual关键字改为虚析构
只有虚析构的类,不算是抽象类
读取文件数据的方法
既然有了函数模板,最好就不要提供普通函数了,否则容易产生二义性
类模板作为参数函数参数
第一种使用较多
主流方法是第二种,将类模板声明和实现写在一个文件里,并将后缀名改成.hpp
最好直接类内实现全局函数,用法简单且编译器容易识别