C++继承之钻石继承和虚继承

C++中继承在之前博客里分享过,主要说的是连个特殊的继承:钻石继承和虚继承两个t特殊的继承。下面我们先分别看一下这两个特殊情况,最后在结合起来看一下二者联合使用:

1、钻石继承:

1)首先我们先看一个简单的继承:

   1 #include <iostream>                                                                                                 
  2 using namespace std;
  3 
  4 class A
  5 {
  6     public:
  7         A():n(0)
  8         {
  9             cout<<"create object A!"<<endl;
 10         }
 11         ~A()
 12         {
 13             cout<<"delete object A!"<<endl;
 14         }
 15     public:
 16         int n;
 17         void get_ip()
 18         {
 19             cout<< &n;
 20         }
 21 };
 22 class B
 23 {
 24     public:
 25         B():n(0)
 26         {
 27             cout<<"create object B!"<<endl;
 28         }
 29         ~B()
 30         {
 31             cout<<"delete object B!"<<endl;
 32         }
 33     public:
 34         int n;
 35          void get_ip()
 36         {
 37             cout<< &n;
 38         }
 39 };
 40 class C: public B, public A//查看继承的顺序
 41 {
 42     public:
 43         C():x(0)
 44         {
 45             cout<<"create object C!"<<endl;
 46         }
 47         ~C()
 48         {
 49             cout<<"delete object C!"<<endl;
 50         }
 51     public:
 52         void fun()
 53         {
 54             cout<<"&A::n = ";
 55             A::get_ip();
 56             cout.flags(ios::dec);
 57             cout<<"  A::n = "<<A::n<<endl;
 58             cout<<"&B::n = ";
 59             B::get_ip();
 60             cout<<"  B::n = "<<B::n<<endl;
 61         }
 62     private:
 63         int x;
 64 };
 65 
 66 int main()
 67 { 
 68     C c;
 69     c.n = 10;//0
 70     //c.A::n=10;//1
 71     //c.B::n=20;//2
 72     //c.fun();//3
 73     return 0;
 74 }

编译运行结果:
这里写图片描述
编译出错,很明显,是因为C类继承了A 和B类,同时也继承了两个类除了构造析构函数除外的所有成员,所有继承了两个n,这时因为没有指明所以产生了二异性,编译不知道是给那个n赋值,我们屏蔽掉标号为0的这句,同时放开编号 1 2 3。
编译运行结果:

这里写图片描述
通过结果我们可以看到两个n的地址是不同的。还有这里因为子类继承了父类,所以在子类构造之前先构造了父类,且顺序是按着继承的顺序进行的,其次才是构造子类的对象;析构时是反着的,因为是子类对象析构才会产生杀死对象的信号,所以是按着构造的你顺序进行析构的。

2) 通过上面简单的一个例子我们接下来简单的看一下什么是钻石继承:
(1)概念: 继承的结构呈现是一个菱形,就是子类继承两个父类,而这两个父类同时继承了一个类所形成的结构。
(2)代码如下:

  1 #include <iostream>                                                                                                 
  2 using namespace std;
  3 
  4 class A
  5 {
  6     public:
  7         A():m(0)
  8     {
  9         cout<<"create object A !"<<endl;
 10     }
 11         ~A()
 12         {
 13             cout<<"delete object A !"<<endl;
 14         }
 15     public:
 16         void get_ip()
 17         {
 18             cout<<&m;
 19         }
 20         int m;
 21 };
 22 class B1:public A
 23 {
 24     public:
 25         B1()
 26         {
 27             cout<<"create object B1 !"<<endl;
 28         }
 29         ~B1()
 30         {
 31             cout<<"delete object B1 !"<<endl;
 32         }
 33     
 34 };
 35 class B2:public A
 36 {
 37     public:
 38         B2()
 39         {
 40             cout<<"create object B2 !"<<endl;
 41         }
 42         ~B2()
 43         {
 44             cout<<"delete object B2 !"<<endl;
 45         }
 46 };
 47 class C:public B1,public B2
 48 {
 49     int x;
 50     public:
 51     C():x(0)
 52     {
 53         cout<<"create object C!"<<endl;
 54     }
 55     ~C()
 56     {
 57         cout<<"delete object C!"<<endl;
 58     }
 59     public:
 60     void fun()
 61     {
 62         cout<<"&B1::m = ";
 63         B1::get_ip();
 64         cout<<"  B1::m = "<<B1::m<<endl;
 65         cout<<"&B2::m = ";
 66         B2::get_ip();
 67         cout<<"  B2::m = "<<B2::m<<endl;
 68     }
 69 };
 70 int main()
 71 {
 72     C c;
 73     c.m=3;//0
 74     //c.B1::m = 20;//1
 75     //c.B2::m = 10;//2
 76     //c.fun();//3
 77 
 78     return 0;
 79 }

编译运行结果:
这里写图片描述
编译出错和上面一样,产生了二异性,编译器无法确定给那个m赋值,通过A的构造函数可以体现出来,每次在构造B1和B2的时候都会调用A的构造函数,所以在B1和B2中都真实的存在这一个m,所以无法直接赋值。
我们屏蔽掉这里的0号语句,放开1 2 3语句,编译运行结果:
这里写图片描述
通过运行结果我们可以看到两个m的地址是不一样的。
代码运行是正常的,但是有没有想过这里的B1和B2都继承的是一个A类,C类继承的时候能不能只继承一个m?答案是可以的,这就要引出我们的虚继承了。

2、虚继承:

1) 概念:虚继承就是在子类继承父类的时候在关键字前或者关键字和父类名字中间加virtual关键字 ,这种继承就叫做虚继承。如下B和C类都是虚继承了A类:

#include<iostream>
using namespace std;
class A
{};
class B:virtual public A
{};
class C:public virtual A
{};

2) 执行顺序:在派生类对象的创建中,首先是虚基类的构造函数并按它们声明的顺序构造。第二是非虚基类的构造函数按它们声明的顺序调用。第三是成员对象的构造函数。最后是派生类自己的构造函数被调用
3) 使用规则说明:
a. 派生类的对象可以赋值给基类的对象,这时是把派生类对象中 从对应基类中继承来的隐藏对象赋值给基类对象。反过来不行,因为派生类的新成员无值可赋。
b. 可以将一个派生类的对象的地址赋给其基类的指针变量,但只能通过这个指针访问派生类中由基类继承来的隐藏对象,不能访问派生类中的新成员。同样也不能反过来做。
c. 派生类对象可以初始化基类的引用。引用是别名,但这个别名只能包含派生类对象中的由基类继承来的隐藏对象。
4) 代码:

  1 #include <iostream>                                                                                                 
  2 using namespace std;
  3 
  4 class A
  5 {
  6     public:
  7         A():n(0)
  8         {
  9             cout<<"    create object A !"<<endl;
 10         }
 11         ~A()
 12         {
 13             cout<<"    delete object A !"<<endl;
 14         }
 15     public:
 16         void fun()
 17         {
 18             cout<<"    A:: fun() !"<<endl;
 19         }
 20     private:
 21         int n;
 22 };
 23 class B
 24 {
 25     public:
 26     B() { cout<<"    create object B !"<<endl;}
 27     ~B() { cout<<"    delete object B !"<<endl;}
 28 };
 29 class C:public B,virtual public A
 30 {
 31     public:
 32     C() { cout<<"    create object C !"<<endl;}
 33     ~C() { cout<<"    delete object C !"<<endl;}
 34     public:
 35     void fun()
 36     {
 37         cout<<"    C::fun() !"<<endl;
 38     }
 39 };
 40 
 41 int main()
 42 {
 43     cout<<"*************派生对象*************"<<endl;
 44     C c;
 45     c.fun();
 46     cout<<"*************基类对象*************"<<endl;
 47     A a;
 48     a.fun();
 49     cout<<"******派生对象赋值给基类对象******"<<endl;
 50     a= c;
 51     a.fun();
 52     cout<<"***派生对象地址赋给基类指针变量***"<<endl;
 53     A *a1 = &c;
 54     a1->fun();
 55     cout<<"******基类对象引用派生类对象******"<<endl;
 56     A &a2 = c;
 57     a2.fun();
 58 
 59     return 0;
 60 }

编译运行结果:
这里写图片描述
验证上面的规则说明,子类对象给父类的是它继承了父类的那些成员,从父类对象、指针变量、引用调用的fun()函数可以看得出。

3、二者结合:

最后我们再结合钻石继承和虚继承来看一下怎么让上面的C类只继承一个m的问题吧:
1) 代码:

  1 #include <iostream>
  2 using namespace std;
  3 class A
  4 {
  5     public:
  6         A():m(0)
  7     {
  8             cout<<"create object A!"<<endl;
  9     }
 10         ~A()
 11         {
 12             cout<<"delete object A!"<<endl;
 13         }
 14     public:
 15         int m;
 16         void get_ip()
 17         {
 18             cout<<&m;
 19         }
 20 };
 21 class B1:public virtual A                                                                                           
 22 {
 23     public:
 24         B1()
 25     {
 26         cout<<"create object B!"<<endl;
 27     }
 28         ~B1()
 29         {
 30             cout<<"delete object B!"<<endl;
 31         }
 32 };
 33 class B2:public virtual A
 34 {
 35     public:
 36         B2()
 37      {
 38          cout<<"create object B2!"<<endl;
 39      }
 40          ~B2()
 41          {
 42              cout<<"delete object B2!"<<endl;
 43          }
 44 
 45 };
  46 class C:public B1, public B2
 47 {
 48     public:
 49          C()
 50       {
 51           cout<<"create object C !"<<endl;
 52       }
 53           ~C()
 54           {
 55               cout<<"delete object C !"<<endl;
 56           }
 57     public:
 58           void fun()
 59           {
 60               cout<<" m = "<<m<<endl;
 61               cout<<" &B1::m";
 62               B1::get_ip();
 63               cout<<endl;
 64               cout<<" &B2::m";
 65               B2::get_ip();
 66               cout<<endl;
 67           }
 68 
 69 };
 70 int main()
 71 
 72 {
 73     C c;
 74     c.m = 0;//0
 75     c.fun();
 76     return 0;
 77 }

2) 编译运行结果:
这里写图片描述
发现这里的0号语句也可以执行了,而且发现m的地址是一样的,这就是虚函数的杰作了,子类在虚继承时父类只创建一次,所以两个m的地址是一样的,而且m没有产生二异性,可以直接给赋值。

猜你喜欢

转载自blog.csdn.net/magiclyj/article/details/77367863