原文地址:https://qunxinghu.github.io/2016/09/12/C++%20%E4%B8%89%E5%A4%A7%E7%89%B9%E6%80%A7%E4%B9%8B%E7%BB%A7%E6%89%BF/ios
继承 : 类的继承,就是新的类从已有类那里获得已有的特性。原有的类称为基类或父类,产生的新类称为派生类或子类。c++
派生类的声明:git
class 派生类名:继承方式 基类名1, 继承方式 基类名2,...,继承方式 基类名n { 派生类成员声明; };
在 c++ 中,一个派生类能够同时有多个基类,这种状况称为多重继承。若是派生类只有一个基类,称为单继承。派生类继承基类中除构造和析构函数之外的全部成员。github
继承方式规定了如何访问基类继承的成员。继承方式有public, private, protected。若是不显示给出继承方式,默认为private继承。继承方式指定了派生类成员以及类外对象对于从基类继承来的成员的访问权限。函数
派生类名::派生类名(参数总表):基类名1(参数表1),基类名(参数名2)....基类名n(参数名n),内嵌子对象1(参数表1),内嵌子对象2(参数表2)....内嵌子对象n(参数表n) { 派生类新增成员的初始化语句; }
注:构造函数的初始化顺序并不以上面的顺序进行,而是根据声明的顺序初始化。 2. 若是基类中没有不带参数的构造函数,那么在派生类的构造函数中必须调用基类构造函数,以初始化基类成员。 3. 派生类构造函数执行的次序: 1. 调用基类构造函数,调用顺序按照它们 被继承时声明的顺序 (从左到右); 2. 调用内嵌成员对象的构造函数,调用顺序按照它们在类中声明的顺序; 3. 派生类的构造函数体中的内容。spa
派生类的析构函数的功能是在该对象消亡以前进行一些必要的清理工做,析构函数没有类型,也没有参数。析构函数的执行顺序与构造函数相反。指针
实例:code
#include <iostream> #include <time.h> using namespace std; // 基类 B1 class B1 { public: B1(int i) { cout<<"constructing B1 "<<i<<endl; } ~B1() { cout<<"destructing B1"<<endl; } }; //基类 B2 class B2 { public: B2(int j) { cout<<"constructing B2 "<<j<<endl; } ~B2() { cout<<"destructing B2"<<endl; } }; //基类 B3 class B3 { public: B3() { cout<<"constructing B3"<<endl; } ~B3() { cout<<"destructing B3"<<endl; } }; //派生类 C, 继承B2, B1,B3(声明顺序从左至右。 B2->B1->B3) class C: public B2, public B1, public B3 { public: C(int a, int b, int c, int d):B1(a), memberB2(d), memberB1(c),B2(b) { //B1,B2的构造函数有参数,B3的构造函数无参数 //memberB2(d), memberB1(c)是派生类对本身的数据成员进行初始化的过程、 //构造函数执行顺序, 基类(声明顺序)-> 内嵌成员对象的构造函数(声明顺序) -> 派生类构造函数中的内容 } private: B1 memberB1; B2 memberB2; B3 memberB3; }; int main() { C obj(1,2,3,4); return 0; } /* 输出结果 */ /* constructing B2 2 constructing B1 1 constructing B3 constructing B1 3 constructing B2 4 constructing B3 destructing B3 destructing B2 destructing B1 destructing B3 destructing B1 destructing B2 */
在单继承下,基类的public 和protected 成员能够直接被访问,就像它们是派生类的成员同样,对多继承这也是正确的。可是在多继承下,派生类能够从两个或者更多个基类中继承同名的成员。然而在这种状况下,直接访问是二义的,将致使编译时刻错误。 示例:对象
#include <iostream> using namespace std; class A { public: void f(); }; class B { public: void f(); void g(); }; class C : public A, public B { public: void g(); void h(); }; int main(){ C c1; // c1.f(); 产生二义性问题,访问A中的 f()? or B的 f() ? //经过指定成员名,限定消除二义性 c1.A::f(); c1.B::f(); }
使用成员名限定法能够消除二义性,可是更好的解决办法是在类C中定义一个同名函数 f(), 类C中的 f() 再根据须要来决定调用 A::f()
or B::f()
, 这样 c1.f()
将调用 C::f()
.blog
当一个派生类从多个基类派生类,而这些基类又有一个共同的基类,则对该基类中说明的成员进行访问时,也可能会出现二义性。 示例:
// 派生类 B1,B2 继承相同的基类 A, 派生类 C 继承 B1, B2 class A { public: int a; }; class B1 : public A { private: int b1; }; class B2 : public A { private: int b2; }; class C : public B1, public B2 { public: int f(); private: int c; }; int main(){ C c1; c1.a(); c1.A::a(); c1.B1::a(); c1.B2::a(); return 0; }
c1.a;
c1.A::a;
这两个访问都有二义性,c1.B1::a;
c1.B2::a;
是正确的: 类C的成员函数 f()
用以下定义能够消除二义性:
int C::f() { retrun B1::a + B2::a; }
因为二义性的缘由,一个类不能够从同一个类中直接继承一次以上。
多继承时很容易产生命名冲突,即便咱们很当心地将全部类中的成员变量和成员函数都命名为不一样的名字,命名冲突依然有可能发生,好比很是经典的菱形继承层次。以下图所示:
graph TD; A-->B; A-->C; B-->D; C-->D;
类A派生出类B和类C,类D继承自类B和类C,这个时候类A中的成员变量和成员函数继承到类D中变成了两份,一份来自 A-->B-->D 这一路,另外一份来自 A-->C-->D 这一条路。当D访问从A中继承的数据时,变一块儿将没法决定采用哪一条路传过来的数据,因而便出现了虚基类。
在一个派生类中保留间接基类的多份同名成员,虽然能够在不一样的成员变量中分别存放不一样的数据,但大多数状况下这是多余的:由于保留多份成员变量不只占用较多的存储空间,还容易产生命名冲突,并且不多有这样的需求。使用虚基类,可使得在派生类中只保留间接基类的一份成员。
声明虚基类只须要在继承方式前面加上 virtual 关键字,以下面示例:
#include <iostream> using namespace std; class A{ protected: int a; public: A(int a):a(a){} }; class B: virtual public A{ //声明虚基类 protected: int b; public: B(int a, int b):A(a),b(b){} }; class C: virtual public A{ //声明虚基类 protected: int c; public: C(int a, int c):A(a),c(c){} }; class D: virtual public B, virtual public C{ //声明虚基类 private: int d; public: D(int a, int b, int c, int d):A(a),B(a,b),C(a,c),d(d){} void display(); }; void D::display(){ cout<<"a="<<a<<endl; cout<<"b="<<b<<endl; cout<<"c="<<c<<endl; cout<<"d="<<d<<endl; } int main(){ (new D(1, 2, 3, 4)) -> display(); return 0; } /* 运行结果: a=1 b=2 c=3 d=4 */
本例中咱们使用了虚基类,在派生类D中只有一份成员变量 a 的拷贝,因此在 display() 函数中能够直接访问 a,而不用加类名和域解析符。
虚基类的初始化 : 请注意派生类D的构造函数,与以往的用法有所不一样。 以往,在派生类的构造函数中只需负责对其直接基类初始化,再由其直接基类负责对间接基类初始化。如今,因为虚基类在派生类中只有一份成员变量,因此对这份成员变量的初始化必须由派生类直接给出。若是不禁最后的派生类直接对虚基类初始化,而由虚基类的直接派生类(如类B和类C)对虚基类初始化,就有可能因为在类B和类C的构造函数中对虚基类给出不一样的初始化参数而产生矛盾。因此规定:在最后的派生类中不只要负责对其直接基类进行初始化,还要负责对虚基类初始化。
在上述代码中,类D的构造函数经过初始化表调了虚基类的构造函数A,而类B和类C的构造函数也经过初始化表调用了虚基类的构造函数A,这样虚基类的构造函数岂非被调用了3次?你们没必要过虑,C++编译系统只执行最后的派生类对虚基类的构造函数的调用,而忽略虚基类的其余派生类(如类B和类C)对虚基类的构造函数的调用,这就保证了虚基类的数据成员不会被屡次初始化。
最后请注意: 为了保证虚基类在派生类中只继承一次,应当在该基类的全部直接派生类中声明为虚基类,不然仍然会出现对基类的屡次继承。
赋值兼容 : 赋值兼容规则是指在须要基类对象的任何地方均可以使用公有派生类的对象来替代。
赋值兼容规则中所指的替代包括: