构造函数
- 构造函数可以抛出异常,可以重载,如果在实例化时在类名后面加个括号,只是创建了一个匿名的对象。构造不能是虚函数,因为此时虚函数表还没有初始化。new对象会调解构造函数。
- 没有定义拷贝构造时,IDE会自动生成一个默认拷贝构造函数。当以值传递的类方式调用函数时,如果实参为对象,会调用拷贝构造函数。函数以值的方式返回对象时,vs会调用拷贝构造,g++则不会,默认拷贝属于浅拷贝
- 深拷贝:将原有对象的所有成员变量和持有内存都拷贝给新对象,并为新对象分配一块内存。原有对象和新对象所持有的动态内存是相互独立的。STL的容器都使用了深拷贝
- 浅拷贝:动态分配的内存时,指向其他数据的指针等,默认的拷贝构造函数就不能拷贝这些资源了
注:当类有指针类型的成员变量,就需要深拷贝
静态成员变量
- static 成员变量的内存既不是在声明类时分配,也不是在创建对象时分配,而是在(类外)初始化时分配。没有在类外初始化的 static 成员变量不能使用。
- static 成员变量不占用对象的内存,而是在所有对象之外开辟内存,即使不创建对象也可以访问。所有的对象都共享这些静态成员变量。
静态成员函数
- 每个对象有各自的一份普通成员变量,但是静态成员变量只有一份,被所有对象所共享。静态成员函数不具体作用于某个对象。即便对象不存在,也可以访问类的静态成员。静态成员函数内部不能访问非静态成员变量,也不能调用非静态成员函数。
- 静态成员函数内部不能出现 this 指针
this指针
- 其实在C++中。在定义类的成员函数时,是隐藏了一个参数的,该参数的定义为 const Name1* this,C++编译器根据不同的this指针执行不同对象的操作。
析构函数
- 没有参数,不能重载,不能抛出异常,内联。没有定义时,系统会默认生成一个析构函数。如果使用默认析构时,默认析构执行完成之后,会调用成员变量的析构,之后又调用派生类的析构,代码量会变大。会自动调用,也可以手工调用。
- 到析构时,虚函数表已初始化,可以把析构放在虚函数表里面来来调用。
- 当子类指针指向子类时,delete时基类和子类都会被释放。( SubClass* pObj = new SubClass();delete pObj;)
- 当基类指针指向子类时,若析构函数是虚函数(加上virtual关键词),delete时基类和子类都会被释放。若析构函数不是虚函数delete时只释放基类,不释放子类,会造成内存泄漏问题。(BaseClass* pObj = new SubClass();delete pObj;)
虚函数
- 虚函数唯一用处就是构成多态:在运行时才可以明确调用对象,根据传入的对象类型来调用函数,例如当使用基类指针指向派生类对象时,能够调用派生类的成员函数,如果不是虚函数,调用的则是基类自己的成员函数。
- 构造函数不能是虚函数:虚函数的调用是通过虚函数表来查找,虚函数是对象构造后生成的。
- 内联函数不能是虚函数:inline属于是静态编译的。而虚函数是动态调用的,在编译器并不知道需要调用的是父 类还是子类的虚函数,所以不能够inline声明展开。
- 静态成员函数不能是虚函数:因为没有this指针。虚函数依靠虚函数函数指针和虚函数表来处理。虚函数指针在类的构造函数中创建生成,并且只能用this指针来访问它。对于静态成员函数,它没有this指针,所以无法访问虚函数.
- final :修饰虚函数,表示该虚函数不能再被重写;修饰类,该类不能被继承。
- override:override写在子类中,要求严格检查是否完成重写,如果没有完成重写就报错。
纯虚函数
- 纯虚函数是一个抽象的接口,没有默认的实现,用=0赋值。
多态
- 多态:是指同一个名字的事物能完成不同的功能,C++中可以分编译时多态(函数重载、运算符重载、按实参调用重载函数)和运行时多态。
- 运行时多态:虚函数让基类指针指向基类对象时就使用基类的成员,指向派生类对象时就使用派生类的成员。
- 多态的目的:可以通过基类指针对所有派生类(包括直接派生和间接派生)的成员变量和成员函数进行“全方位”的访问,尤其是成员函数。如果没有多态,只能访问成员变量。
- 构成多态:必须存在继承关系,继承关系中必须有同名的虚函数,并且它们是覆盖关系(函数原型相同),存在基类的指针,通过该指针调用虚函数。
- 协变:子类重写父类虚函数时,与父类虚函数返回值类型不同。
单继承
- 继承方式:公有继承public 仍为public ,protected为protected。保护继承public 仍为protected ,protected为protected。私有继承public 仍为private,protected为private。所有继承方式private都不能用。基类的 private 成员是能够被继承的,并且(成员变量)会占用派生类对象的内存,它只是在派生类中不可见。类的构造函数不能被继承。(基类对派生类隐藏基类的实现,以体现面向对象的封装性。)
- 派生类中的成员(包括成员变量和成员函数)和基类中的成员重名,那么就会遮蔽从基类继承过来的成员。所以基类成员函数和派生类成员函数不构成重载。
- 派生类对继承过来的成员变量要构造函数完成,但是大部分基类都有 private 属性的成员变量,它们在派生类中无法访问,更不能使用派生类的构造函数来初始化。所以在派生类的构造中调用基类的构造。
- 创建对象时,首先调用基类的构造,其次调用派生类的构造。派生类构造函数中只能调用直接基类的构造函数,不能调用间接基类的。( C->B->A,A的构造被隐显的调用两次,浪费cpu时间,所以禁止。)
- 析构函数不能被继承。在派生类的析构函数中不用显式地调用基类的析构函数,因为每个类只有一个析构函数。
- 创建派生类对象时,构造函数的执行顺序和继承顺序相同,即先执行基类构造函数,再执行派生类构造函数。而销毁派生类对象时,析构函数的执行顺序和继承顺序相反,即先执行派生类析构函数,再执行基类析构函数。
多继承
- 多继承时,派生类的构造函数中调用多个基类的构造函数。基类构造函数的调用顺序和和它们在派生类构造函数中出现的顺序无关,而是和声明派生类时基类出现的顺序相同。
- 多继承时,当两个或多个基类中有同名的成员时,会产生命名冲突,要在成员名字前面加上类名和域解析符::,以显式地指明到底使用哪个类的成员,消除二义性。
虚继承
- 为了解决多继承命名冲突和冗余数据问题,在派生类中只保留一份间接基类的成员,虚继承的目的是让某个类(虚基类)做出声明,承诺愿意共享它的基类。不论虚基类在继承体系中出现了多少次,在派生类中都只包含一份虚基类的成员。
- 虚派生只影响从指定了虚基类的派生类中进一步派生出来的类,它不会影响派生类本身。因为在虚继承的最终派生类中只保留了一份虚基类的成员,所以该成员可以被直接访问,不会产生二义性。此外,如果虚基类的成员只被一条派生路径覆盖,那么仍然可以直接访问这个被覆盖的成员。但是如果该成员被两条或多条路径覆盖了,那就不能直接访问了,此时必须指明该成员属于哪个类。
- 虚基类是由最终的派生类初始化,最终派生类的构造函数必须要调用虚基类的构造函数。对最终的派生类来说,虚基类是间接基类,而不是直接基类。这跟普通继承不同,在普通继承中,派生类构造函数中只能调用直接基类的构造函数,不能调用间接基类的。
类的内存
成员函数
- 成员函数是不占用对象的内存空间的,只有成员变量占用对象的内存空间,在实例时,是按照类的成员变量所占用的总字节数申请内存(满足内存对齐)
虚函数
- 当一个类包括虚函数时,该类自动包含一个指向虚函数vptr指针,虚函数表中存放了指向该类中定义的所有虚函数的指针(函数指针),可以通过vptr快速访问到虚函数。
- 派生类没有重写基类的虚函数,没有继承基类的 vptr 指针,进行多态操作时,如果调用派生类的虚函数(与基类同名的虚函数),是不会发生多态行为的,调用的仍然是基类的虚函数。
- 派生类重写了基类的虚函数,编译器给重写的虚函数重新分配了内存空间,在进行多态操作时,如果调用派生类的虚函数,那就会发生多态行为,调用的就会是派生类的虚函数!
- 派生类重写了基类的部分虚函数,重写的部分是派生类。
内存
- 对象所占用的存储空间的大小等于各成员变量所占用的存储空间的大小之和(内存对齐)
- 当内存使用完之后,会使用虚拟内存,也就是往硬盘里面写,之后程序会被杀掉。
视频教程
链接:https://pan.baidu.com/s/1W0cfUPxnf9c8egzMm1rBVA 提取码:jpy3