c++多繼承淺析


                     圖一                                                                                       圖二

先測試圖一結構的多繼承:

 1 #include<iostream>
 2 using namespace std;  3 
 4 class Parent {  5 public:  6     Parent():a(100),b(200),c(300)  7  {  8         cout << "parent 構造。。。\n";  9  } 10     ~Parent() 11  { 12         cout << "Parent 析構。。。\n"; 13  } 14     int a; 15     int b; 16     int c; 17     void p_print() 18  { 19         cout << "a b c is" << a << " " << b << " " << c << endl; 20  } 21 
22 }; 23 class Child1 :virtual public Parent 24 { 25 public: 26     Child1() :Parent(), a(0), b(0), c(0) { cout << "child 構造\n"; } 27     ~Child1() 28  { 29         cout << "child 析構,,,\n"; 30  } 31     void c_print() 32  { 33         cout << "a b c is" << a << " " << b << " " << c << endl; 34  } 35     int a; 36     int b; 37     int c; 38 }; 39 class Child2 :virtual public Parent 40 { 41 public: 42     Child2() :Parent(), a(1), b(2), c(3) { cout << "child 構造\n"; } 43     ~Child2() 44  { 45         cout << "child 析構,,,\n"; 46  } 47     void c_print() 48  { 49         cout << "a b c is" << a << " " << b << " " << c << endl; 50  } 51     int a; 52     int b; 53     int c; 54 }; 55 class Child3 :public Child1,public Child2 56 { 57 public: 58 Child3() :Parent(),Child1(),Child2(), a(10), b(20), c(30)
{ cout << "child 構造\n"; }//如果前面沒有使用虛繼承,這里初始化Parent構造函數將出錯 59 ~Child3() 60 { 61 cout << "child 析構,,,\n"; 62 } 63 void c_print() 64 { 65 cout << "a b c is" << a << " " << b << " " << c << endl; 66 } 67 int a; 68 int b; 69 int c; 70 }; 71 int main() 72 { 73 Child3 c3; 74 75 return 0; 76 }

虛繼承的目的是令某個類做出聲明,承諾願意共享它的基類。其中,共享的基類對象稱為虛基類。在這種機制下,無論虛基類在繼承體系中出現多少次,在派生類中都只包含唯一一個共享的虛基類對象。

為了說明情況,我們把上述代碼更改如下:

 1 #include<iostream>
 2 using namespace std;
 3 
 4 class Parent {
 5 public:
 6     Parent():a(100),b(200),c(300)
 7     {
 8         cout << "parent 無參構造。。。\n";
 9     }
10     Parent(int test) :a(1000), b(2000), c(3000)
11     {
12         cout << "parent 有參構造。。。\n";
13     }
14     ~Parent()
15     {
16         cout << "Parent 析構。。。\n";
17     }
18     int a;
19     int b;
20     int c;
21     void p_print()
22     {
23         cout << "a b c is" << a << " " << b << " " << c << endl;
24     }
25 
26 };
27 class Child1 :  public Parent
28 {
29 public:
30     Child1() :Parent(), b(0), c(0) { cout << "child 構造\n"; }
31     ~Child1()
32     {
33         cout << "child 析構,,,\n";
34     }
35     void c_print()
36     {
37         cout << "a b c is" << a << " " << b << " " << c << endl;
38     }
39 
40     //int a;
41     int b;
42     int c;
43 };
44 class Child2 : public Parent
45 {
46 public:
47     Child2() :Parent(), b(2), c(3) { cout << "child 構造\n"; }
48     ~Child2()
49     {
50         cout << "child 析構,,,\n";
51     }
52     void c_print()
53     {
54         cout << "a b c is" << a << " " << b << " " << c << endl;
55     }
56     //int a;
57     int b;
58     int c;
59 };
60 class Child3 : public Child1,  public Child2
61 {
62 public:
63     Child3() :Child1(),Child2(), b(20), c(30) { cout << "child 構造\n"; }
64     ~Child3()
65     {
66         cout << "child 析構,,,\n";
67     }
68     //int a;
69     int b;
70     int c;
71 };
72 int main()
73 {
74     Child3 c3;
75     c3.a = 100;
76     return 0;
77 }
View Code

報錯如下:

由於在parent類中a被Child1,Child2分別繼承,而用Chils3類定義對象c3要去訪問屬性a,編譯器發出抱怨也是應該的,因為它不知道這個a是Child1還是Child2還是parent中的。所以我們要去除這樣的二義性。通過把parent類變成虛基類,可以做到,代碼如下:

 1 #include<iostream>
 2 using namespace std;
 3 
 4 class Parent {
 5 public:
 6     Parent():a(100),b(200),c(300)
 7     {
 8         cout << "parent 無參構造。。。\n";
 9     }
10     Parent(int test) :a(1000), b(2000), c(3000)
11     {
12         cout << "parent 有參構造。。。\n";
13     }
14     ~Parent()
15     {
16         cout << "Parent 析構。。。\n";
17     }
18     int a;
19     int b;
20     int c;
21     void p_print()
22     {
23         cout << "a b c is" << a << " " << b << " " << c << endl;
24     }
25 
26 };
27 class Child1 : virtual public Parent
28 {
29 public:
30     Child1() :Parent(), b(0), c(0) { cout << "child 構造\n"; }
31     ~Child1()
32     {
33         cout << "child 析構,,,\n";
34     }
35     void c_print()
36     {
37         cout << "a b c is" << a << " " << b << " " << c << endl;
38     }
39 
40     //int a;
41     int b;
42     int c;
43 };
44 class Child2 : virtual public Parent
45 {
46 public:
47     Child2() :Parent(), b(2), c(3) { cout << "child 構造\n"; }
48     ~Child2()
49     {
50         cout << "child 析構,,,\n";
51     }
52     void c_print()
53     {
54         cout << "a b c is" << a << " " << b << " " << c << endl;
55     }
56     //int a;
57     int b;
58     int c;
59 };
60 class Child3 : public Child1,  public Child2
61 {
62 public:
63     Child3() :Child1(),Child2(), b(20), c(30) { cout << "child 構造\n"; }
64     ~Child3()
65     {
66         cout << "child 析構,,,\n";
67     }
68     //int a;
69     int b;
70     int c;
71 };
72 int main()
73 {
74     Child3 c3;
75     c3.a = 100;
76     return 0;
77 }
View Code

其實也就是在繼承時增加virtual關鍵字,讓派生類包含唯一的共享虛基類。

問題拋出:

在child3中的構造函數:

Child3() :Child1(),Child2(), b(20), c(30) { cout << "child 構造\n"; }
我們把child1和child2的基類構造函數改成有參的,看看child3繼承的老祖宗屬性是如何的:
 1 #include<iostream>
 2 using namespace std;
 3 
 4 class Parent {
 5 public:
 6     Parent():a(100),b(200),c(300)
 7     {
 8         cout << "parent 無參構造。。。\n";
 9     }
10     Parent(int test) :a(1000), b(2000), c(3000)
11     {
12         cout << "parent 有參構造。。。\n";
13     }
14     ~Parent()
15     {
16         cout << "Parent 析構。。。\n";
17     }
18     int a;
19     int b;
20     int c;
21     void p_print()
22     {
23         cout << "a b c is" << a << " " << b << " " << c << endl;
24     }
25 
26 };
27 class Child1 : virtual public Parent
28 {
29 public:
30     Child1() :Parent(1), b(0), c(0) { cout << "child 構造\n"; }
31     ~Child1()
32     {
33         cout << "child 析構,,,\n";
34     }
35     void c1_print()
36     {
37         cout << "a b c is" << a << " " << b << " " << c << endl;
38     }
39 
40     //int a;
41     int b;
42     int c;
43 };
44 class Child2 : virtual public Parent
45 {
46 public:
47     Child2() :Parent(1), b(2), c(3) { cout << "child 構造\n"; }
48     ~Child2()
49     {
50         cout << "child 析構,,,\n";
51     }
52     void c2_print()
53     {
54         cout << "a b c is" << a << " " << b << " " << c << endl;
55     }
56     //int a;
57     int b;
58     int c;
59 };
60 class Child3 : public Child1,  public Child2
61 {
62 public:
63     Child3() : Child1(),Child2(), b(20), c(30) { cout << "child 構造\n"; }
64     ~Child3()
65     {
66         cout << "child 析構,,,\n";
67     }
68     void c3_print()
69     {
70         cout << "a b c is" << a << " " << b << " " << c << endl;
71     }
72     //int a;
73     int b;
74     int c;
75 };
76 int main()
77 {
78     Child3 c3;
79     c3.c3_print();
80     c3.p_print();
81     return 0;
82 }
View Code

運行結果:

可以看到,child3的屬性a是100,等價於parent的無參構造函數,盡管我們的child1和child2用的有參構造函數初始化,但子類child3最終繼承的虛基類還是要通過自身的構造函數初始化列表來完成,要想child3中的屬性a是parent有參構造的屬性a,更改child3的構造函數初始化列表為:

 

1 Child3() : Parent(1),Child1(),Child2(), b(20), c(30) { cout << "child 構造\n"; }

 

運行結果:

這樣就調用了有參數的parent屬性a了。結論:類似於初始化成員的過程,派生類構造函數同樣是通過構造函數初始化列表來將實參傳遞給基類構造函數,在多繼承中,哪怕直接基類(child1和child2)構造了間接基類(parent)的無參構造函數,但要傳遞給派生類child3的屬性時,還是根據child3的構造函數初始化列表決定的。

 

 

對上面的訪問屬性a有歧義再探:

上面說得:由於在parent類中a被Child1,Child2分別繼承,而用Chils3類定義對象c3要去訪問屬性a,編譯器發出抱怨也是應該的,因為它不知道這個a是Child1還是Child2還是parent中的。所以我們要去除這樣的二義性。通過把parent類變成虛基類,可以做到.

當然,問題的關鍵就在於編譯器不知道屬性a是哪個對象中,我們除了增加virtual關鍵字外,還可以:

在child3類中定義同名成員a,這樣通過c3訪問a時,默認從child3類中尋找:

 

 1 #include<iostream>
 2 using namespace std;
 3 
 4 class Parent {
 5 public:
 6     Parent():a(100),b(200),c(300)
 7     {
 8         cout << "parent 無參構造。。。\n";
 9     }
10     Parent(int test) :a(1000), b(2000), c(3000)
11     {
12         cout << "parent 有參構造。。。\n";
13     }
14     ~Parent()
15     {
16         cout << "Parent 析構。。。\n";
17     }
18     int a;
19     int b;
20     int c;
21     void p_print()
22     {
23         cout << "a b c is" << a << " " << b << " " << c << endl;
24     }
25 
26 };
27 class Child1 :  public Parent
28 {
29 public:
30     Child1() :Parent(1), b(0), c(0) { cout << "child 構造\n"; }
31     ~Child1()
32     {
33         cout << "child 析構,,,\n";
34     }
35     void c1_print()
36     {
37         cout << "a b c is" << a << " " << b << " " << c << endl;
38     }
39 
40     //int a;
41     int b;
42     int c;
43 };
44 class Child2 :  public Parent
45 {
46 public:
47     Child2() :Parent(1), b(2), c(3) { cout << "child 構造\n"; }
48     ~Child2()
49     {
50         cout << "child 析構,,,\n";
51     }
52     void c2_print()
53     {
54         cout << "a b c is" << a << " " << b << " " << c << endl;
55     }
56     //int a;
57     int b;
58     int c;
59 };
60 class Child3 : public Child1,  public Child2
61 {
62 public:
63     Child3() : Child1(),Child2(), b(20), c(30) { cout << "child 構造\n"; }
64     ~Child3()
65     {
66         cout << "child 析構,,,\n";
67     }
68     void c3_print()
69     {
70         cout << "a b c is" << a << " " << b << " " << c << endl;
71     }
72     int a;
73     int b;
74     int c;
75 };
76 int main()
77 {
78     Child3 c3;
79     c3.a = 123;
80     c3.c3_print();
81     
82     return 0;
83 }
View Code

 

這樣可以編譯通過了,但此時的a是child3中的,僅僅是逃過編譯器的錯誤檢測,並沒有解決多繼承的問題;

我們可能在想,能否通過域作用符來訪問去除歧義呢?

test:

int main() { Child3 c3; c3.Child1::a = 123; c3.c3_print(); return 0; }

依舊報錯:

 

 

 由此可見,多繼承是復雜繁瑣的,好在一般工程中都會盡量避免使用多繼承,但是多繼承也是有應用的,至少Qt中就有多繼承,就像C語言中的goto語句一樣,一般不建議使用,但總會有它上場的時候,goto用於跳出多重循環或者檢錯,多繼承用在一個類想用時擁有其他類的某些功能。所以,必要的多繼承語法還是得了解。

 

對於圖2:

 1 #include<iostream>
 2 using namespace std;
 3 
 4 class Child1 
 5 {
 6 public:
 7     Child1() :a(0), b(0), c(0) { cout << "child 構造\n"; }
 8     ~Child1()
 9     {
10         cout << "child 析構,,,\n";
11     }
12     void c1_print()
13     {
14         cout << "a b c is" << a << " " << b << " " << c << endl;
15     }
16 
17     int a;
18     int b;
19     int c;
20 };
21 class Child2 
22 {
23 public:
24     Child2() :a(1), b(2), c(3) { cout << "child 構造\n"; }
25     ~Child2()
26     {
27         cout << "child 析構,,,\n";
28     }
29     void c2_print()
30     {
31         cout << "a b c is" << a << " " << b << " " << c << endl;
32     }
33     int a;
34     int b;
35     int c;
36 };
37 class Child3 : public Child1,  public Child2
38 {
39 public:
40     Child3() : Child1(),Child2(), b(20), c(30) { cout << "child 構造\n"; }
41     ~Child3()
42     {
43         cout << "child 析構,,,\n";
44     }
45     void c3_print()
46     {
47         //cout << "a b c is" << a << " " << b << " " << c << endl;
48     }
49     //int a;
50     int b;
51     int c;
52 };
53 int main()
54 {
55     Child3 c3;
56     c3.a = 123;
57     //c3.Child1::a = 123;
58     //c3.c3_print();
59     
60     return 0;
61 }
View Code

可以看到還是報錯,繼續剖析,更改代碼如下:

 

 1 #include<iostream>
 2 using namespace std;
 3 
 4 class Child1 
 5 {
 6 public:
 7     Child1() :a(0), b(0), c(0) { cout << "child 構造\n"; }
 8     ~Child1()
 9     {
10         cout << "child 析構,,,\n";
11     }
12     void c1_print()
13     {
14         cout << "a b c is" << a << " " << b << " " << c << endl;
15     }
16 
17     int a;
18     int b;
19     int c;
20 };
21 class Child2 
22 {
23 public:
24     Child2() :a(1), b(2), c(3) { cout << "child 構造\n"; }
25     ~Child2()
26     {
27         cout << "child 析構,,,\n";
28     }
29     void c2_print()
30     {
31         cout << "a b c is" << a << " " << b << " " << c << endl;
32     }
33     int a;
34     int b;
35     int c;
36 };
37 class Child3 : public Child1,  public Child2
38 {
39 public:
40     Child3() : Child1(),Child2(), b(20), c(30) { cout << "child 構造\n"; }
41     ~Child3()
42     {
43         cout << "child 析構,,,\n";
44     }
45     void c3_print()
46     {
47         //cout << "a b c is" << a << " " << b << " " << c << endl;
48     }
49     //int a;
50     int b;
51     int c;
52 };
53 int main()
54 {
55     Child3 c3;
56     //c3.a = 123;
57     c3.Child1::a = 123;
58     c3.c1_print();
59     
60     return 0;
61 }
View Code

 

可以看到,通過域作用符可以消除歧義,那么問題又來了;是否可以通過virtual關鍵字或者自己定義一個屬性a達到消除錯誤呢?

test:

 1 #include<iostream>
 2 using namespace std;
 3 
 4 class Child1 
 5 {
 6 public:
 7     Child1() :a(0), b(0), c(0) { cout << "child 構造\n"; }
 8     ~Child1()
 9     {
10         cout << "child 析構,,,\n";
11     }
12     void c1_print()
13     {
14         cout << "a b c is" << a << " " << b << " " << c << endl;
15     }
16 
17     int a;
18     int b;
19     int c;
20 };
21 class Child2 
22 {
23 public:
24     Child2() :a(1), b(2), c(3) { cout << "child 構造\n"; }
25     ~Child2()
26     {
27         cout << "child 析構,,,\n";
28     }
29     void c2_print()
30     {
31         cout << "a b c is" << a << " " << b << " " << c << endl;
32     }
33     int a;
34     int b;
35     int c;
36 };
37 class Child3 : public Child1,  public Child2
38 {
39 public:
40     Child3() : Child1(),Child2(), b(20), c(30) { cout << "child 構造\n"; }
41     ~Child3()
42     {
43         cout << "child 析構,,,\n";
44     }
45     void c3_print()
46     {
47         //cout << "a b c is" << a << " " << b << " " << c << endl;
48     }
49     int a;
50     int b;
51     int c;
52 };
53 int main()
54 {
55     Child3 c3;
56     c3.a = 123;
57     //c3.Child1::a = 123;
58     //c3.c1_print();
59     
60     return 0;
61 }
View Code

自己定義屬性a,是可以做到消除歧義的,但屬性a還是屬於child3的而不是訪問繼承而來的;

增加virtual關鍵字

test:

 

1 class Child3 : virtual public Child1, virtual public Child2

還是報錯,因為virtual的作用是產生虛基類的,virtual說明符表達了一種願望,即在后續的派生類中共享虛基類的同一份唯一實例,至於什么樣的類能作為虛基類沒有明確的規定,顯然這里和圖一不同。

summary:

結論已經顯而易見,多繼承比單繼承復雜,一般不使用,但語法還是得掌握,因為你又可能會用到它,存在一定有它的道理。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM