核心思想:数据抽象,继承和动态绑定(一定程度上忽略类型的区别)。
虚函数:基类希望它的派生类各自定义适合自己的版本,则将函数声明为虚函数。函数声明前加上
virtual
派生类中实现虚函数时在函数声明尾加上
override
,前面可以加上virtual
也可以不加。一旦某个函数被声明成虚函数,则在所有派生类中它都是虚函数。protected:派生类有权访问该成员,但是禁止其他用户访问。
虚函数的解析过程在运行时,派生类如果没有重写虚函数则使用基类的。
派生类对象的构造函数:首先初始化基类的部分,然后按照声明的顺序依次初始化派生类的成员。
基类的静态成员无论派生出多少个类都只有一个唯一实例。
使用
final
使得一个类是不可以继承的。把基类的函数声明为final,则子类不可以重写该函数。静态类型:编译时已知。动态类型:变量表示的内存中的对象的类型,运行时才可知。
如果虚函数使用默认实参,则基类和派生类中定义的默认实参最好一致,如果某次函数调用使用了默认实参,则该实参值由本次调用的静态类型决定。
回避虚函数的机制
double a = base->Quote::net_price(33); //强行调用基类版本,而不管base的类型是什么
纯虚函数:函数声明后面加上
=0
,有纯虚函数的类是抽象基类。protected:派生类的成员或友元只能通过派生类对象来访问基类的受保护对象。派生类对于一个基类对象中的受保护成员没有任何访问特权(例如成员函数接受一个基类对象的指针,但不可以直接通过指针访问protected成员数据)。公有继承其实就是继承了基类的公有接口和protected成员(基类的public为子类的public,可以直接被子类对象调用。基类的protected为子类的protected,只能子类内部用),private子类是不可以直接用的。
#include <iostream> #include <memory> #include <string> using namespace std; class A { private: int a; int b; protected: int c; public: int d; }; class B :public A { public: void f(B & b) { b.c = 5; // 对的,在自己成员函数内部 b.d = 6; //b.a = 1; 这句话是错误的,私有成员 } void ff(A & a) { a.d = 7; //a.c = 6; 这也是错误的! } }; int main() { B b; b.d = 6; //b.c = 6;错的 }
私有继承:子类继承来的东西都是子类的private(基类的public和protected成了子类的private),子类的子类就无法使用这些基类东西了(相当于对子类的子类来说,继承的东西都是父类的private)。
保护继承:子类继承来的东西都是子类的protected(基类的public和protected成了子类的prote),子类的子类成员函数可以使用这些东西,但是不可以通过子类的子类对象直接调用原来是public的东西了。
友元关系不可以继承。基类声明另一个类为友元。则另一个类可以访问基类成员以及基类派生类的基类成员部分。
改变子类个别成员的可访问性:
public : using Base::size; //将子类继承来的size函数变为public的
名字查找优于类型检查:子类重载父类函数,则父类函数永远被隐藏。这也是子类虚函数要与基类虚函数有相同的形参列表的原因。否则基类指针接受子类对象,调用的函数只能是基类的函数,因为从子类的角度看,并没有重写虚函数,而是定义了一个新函数。
#include <iostream> using namespace std; class A { public: virtual void f(int a = 6) { cout << "base" << a; } }; class B :public A { public: void f(int a = 7) { cout << "Nobase" << a; } }; void text(A * p) { p->f(); } int main() { B temp; text(&temp); int a; cin >> a; return 0; } //输出结果:Nobase6 //将基类函数的参数去掉:base
成员函数不管是否是虚函数都可以被重载。派生类要重载一个基类函数版本,则需要重载同个函数的所有版本。可以通过using声明直接使用基类的各个版本,而只是改变其中一个。如果不通过虚函数而直接重载基类函数,那么将子类传给基类指针,通过基类指针访问到的是基类版本的函数。
因此,应该给基类一个虚析构函数。析构函数体自身并不直接销毁成员(在析构函数体执行完毕后,成员会被自动销毁)。当我们delete一个动态分配的对象的指针时将执行析构函数。将虚构函数定义为虚,这样子在使用基类指针delete一个子类对象时才可以找到正确的子类虚构函数。
派生类的赋值运算符必须显示为其基类部分赋值,或者在函数内直接调用基类相应函数:
Base::operator=(rhs) // 基类的赋值运算符是默认的,也可以使用。