继承和派生的概念:C++中的继承(Inheritance)和派生(Derive)表示的是类与类之间的关系,也可以理解为接受和给予的意思。接受其他类的成员变量和成员函数称为继承,将自己类中的成员变量和成员函数给予给其他类称为派生。通俗的比喻,儿子继承了父亲的财产,父亲将自己的财产派生给儿子。这个比喻里财产可以看做类中的成员,儿子称作“派生类”,父亲称作“基类”。也可以直接称儿子为“子类”,父亲为“父类”。
继承和派生的主要作用:在创建一个新的类时,如果会用到已经创建了的类中的成员时,创建一个派生类就可以直接使用该成员变量或成员函数,不需要重复创建和定义,在这个派生类中还可以定义新的成员。
派生类的定义方法:
class 派生类名称:访问修饰符 基类名称 { 派生类新定义的成员 };//其中访问修饰符有三种,分别为Public,Protected和Private,即三种派生方式。
上述定义是基类只有一个的定义方法,如果有多个基类,采用基类间用逗号隔开的方式定义。
class 派生类名称:访问修饰符 基类名称1, 访问修饰符 基类名称2,...{ 派生类新定义的成员 };
例1:

1 #include2 3 using namespace std; 4 5 // 基类 Shape 6 class Shape 7 { 8 public: 9 void setWidth(int w) 10 { 11 width = w; 12 } 13 void setHeight(int h) 14 { 15 height = h; 16 } 17 protected: 18 int width; 19 int height; 20 }; 21 22 // 基类 PaintCost 23 class PaintCost 24 { 25 public: 26 int getCost(int area) 27 { 28 return area * 50; 29 } 30 }; 31 32 // 派生类(有两个基类) 33 class Rectangle: public Shape, public PaintCost 34 { 35 public: 36 int getArea() 37 { 38 return (width * height); 39 } 40 }; 41 42 int main(void) 43 { 44 Rectangle Rect;//创建派生类的对象Rect 45 int area; 46 47 Rect.setWidth(2);//使用基类shape中的成员 48 Rect.setHeight(5);//使用基类shape中的成员 49 50 area = Rect.getArea();//使用派生类Rectangle中的成员函数getarea 51 52 // 输出对象的面积 53 cout << "Total area: " << Rect.getArea() << endl; 54 55 // 输出总花费,使用基类paintcost基类中的成员函数getcost 56 cout << "Total paint cost: $" << Rect.getCost(area) << endl; 57 58 return 0; 59 }
不同派生方式下数据成员访问权限比较:
派生方式 | Private | Protected | Public | ||||||
基类成员 | Private | Protected | Public | Private | Protected | Public | Private | Protected | Public |
派生类成员 | 不可见 | Private | Private | 不可见 | Private | Protected | 不可见 | Protected | Public |
外部 | 不可见 | 不可见 | 不可见 | 不可见 | 不可见 | 不可见 | 不可见 | 不可见 | 可见 |
简单总结一下上表,基类中的Private成员不管在哪都是不可见的。Private派生使基类中的非Private类成员都变成派生类中的Private成员,在外部和其他类中无法访问。Protected派生使基类中的Protected类成员变为Private成员,Public成员变成Protected成员。Public派生使得基类成员中非Private成员保持原有类型。外部只能访问Public派生产生的派生类中的Public成员。
既然讲了多个基类的派生那肯定要讲一下可能出现的重复问题。
情况1:如果类A和类B为基类,类C为派生类,且三个类中都有同名成员函数print,这种情况下,类C使用的是类C自己的print函数,类A和类B的print函数被遮蔽了。(和重载不同,即使函数的参数不同也会被遮蔽,只要同名就会产生遮蔽)
情况2:如果类A和类B为基类,类C为派生类,类A和类B中有同名成员函数print,类C中没有,那类C可以继承类A和类B中的print函数。但会出现二义性的问题。
多基类派生的二义性问题:这个问题是指派生类在继承多个基类时,如果不同基类中有同名成员的情况,编译器无法判断到底要访问哪个基类中的成员。
二义性问题解决方法:在派生类使用不同基类中的同名成员时加入域操作进行限定。例如类A和类B是基类,且都有print函数,类C是它们的派生类,那么类C的对象在使用print函数时要用域操作符明确用的是类A中的print还是类B中的print。假设类C的对象为exampl_c,若使用类A中的print,则可以通过exampl_c.A::print的方式来指定。或者在类C中重新声明定义一个print函数,根据需要选择是使用类A还是类B中的print函数。
除了上述的同名成员导致的二义性问题,还有一种情况也会导致二义性问题。即存在共同基类时(也称菱形继承的情况)。若类B和类C是类A的派生类,类D为类B和类C的派生类,此时类A为类B和类C的共同基类,那么类D中就会有来自两条不同路径的类A的双重复制。
解决方法:在共同基类A派生时使用virtual关键字将其声明为虚基类,这时继承也不叫继承还是叫虚继承。这样在存储时可以仅保留一份复制,避免多重复制。

1 //间接基类A 2 class A{ 3 protected: 4 int m_a; 5 }; 6 //直接基类B 7 class B: virtual public A{ //虚继承 8 protected: 9 int m_b; 10 }; 11 //直接基类C 12 class C: virtual public A{ //虚继承 13 protected: 14 int m_c; 15 }; 16 //派生类D 17 class D: public B, public C{ 18 public: 19 void seta(int a){ m_a = a; } //正确 20 void setb(int b){ m_b = b; } //正确 21 void setc(int c){ m_c = c; } //正确 22 void setd(int d){ m_d = d; } //正确 23 private: 24 int m_d; 25 }; 26 int main(){ 27 D d; 28 return 0; 29 }
两种二义性问题的比较:多基类派生的二义性主要是成员名重复导致,可以通过使用域操作符进行指定的方式解决。共同基类的二义性其实是由于共同基类的成员多重复制带来的存储二义性,可以使用virtual关键字进行派生来解决。
派生类的构造函数和析构函数:
首先定义一个名词,最终派生类。最终派生类是指建立对象时所指定的类。
派生时,构造函数和析构函数是不能继承的,因此对派生类必须重新定义构造函数和析构函数。派生类在定义构造函数时必须将基类的构造函数放在初始化列表中,以调用基类构造函数。不同类构造函数的调用顺序按照最终派生类中的初始化表中出现的顺序,同类的构造函数仍然遵循类定义时的顺序。但如果涉及到虚继承,那么编译器会优先调用虚基类的构造函数,再调用普通基类的构造函数。

1 #include2 using namespace std; 3 //虚基类A 4 class A{ 5 public: 6 A(int a); 7 protected: 8 int m_a; 9 }; 10 A::A(int a): m_a(a){ } 11 //直接派生类B 12 class B: virtual public A{ 13 public: 14 B(int a, int b); 15 public: 16 void display(); 17 protected: 18 int m_b; 19 }; 20 B::B(int a, int b): A(a), m_b(b){ } 21 void B::display(){ 22 cout<<"m_a="< ", m_b="< endl; 23 } 24 //直接派生类C 25 class C: virtual public A{ 26 public: 27 C(int a, int c); 28 public: 29 void display(); 30 protected: 31 int m_c; 32 }; 33 C::C(int a, int c): A(a), m_c(c){ } 34 void C::display(){ 35 cout<<"m_a="< ", m_c="< endl; 36 } 37 //最终派生类D 38 class D: public B, public C{ 39 public: 40 D(int a, int b, int c, int d); 41 public: 42 void display(); 43 private: 44 int m_d; 45 }; 46 D::D(int a, int b, int c, int d): B(90, b), C(100, c), A(a), m_d(d){ } 47 //D::D(int a, int b, int c, int d): A(a), B(90, b), C(100, c), m_d(d){ } 48 void D::display(){ 49 cout<<"m_a="< ", m_b="< ", m_c="< ", m_d="< endl; 50 } 51 int main(){ 52 B b(10, 20); 53 b.display(); 54 55 C c(30, 40); 56 c.display(); 57 D d(50, 60, 70, 80); 58 d.display(); 59 return 0; 60 }
析构函数的调用顺序和构造函数相反。
主要参考资料:菜鸟教程C++教程,C语言中文网c++教程,零基础学C++。