本文講解內容的前提是派生類繼承基類的方式是公有繼承,關鍵字public
以下程序為講解用例。
1 #include<iostream>
2 using namespace std;
3
4 class A
5 {
6 public:
7 A(int m1, int n1):m(m1), n(n1){}
8 void display();
9 private:
10 int m;
11 int n;
12 };
13
14 void A::display()
15 {
16 cout << "m = " << m << endl;
17 cout << "n = " << n << endl;
18 }
19
20 class B :public A
21 {
22 public:
23 B(int m1, int n1, int p1) :A(m1, n1), p(p1){}
24 void display();
25 private:
26 int p;
27 };
28
29 void B::display()
30 {
31 A::display();
32 cout << "p = " << p << endl;
33 }
34
35 void print1(A& a)
36 {
37 a.display();
38 }
39
40 void print2(B& b)
41 {
42 b.display();
43 }
44
45 void print3(A a)
46 {
47 a.display();
48 }
49
50 void print4(B b)
51 {
52 b.display();
53 }
54
55 int main()
56 {
57 A a(3, 4);
58 // a.display();
59 B b(10, 20, 30);
60 // b.display();
61
62 A * pa;
63 B * pb;
64 pa = &a;
65 // pa->display();
66 pb = &b;
67 // pb->display();
68
69 // pa = &b;
70 // pa->display();
71
72 // pb = &a; //錯誤。派生類指針不能指向基類對象。
73
74 // print1(b);
75 // print2(a); //錯誤。不能用基類對象給派生類引用賦值。
76 // print3(b);
77 // print4(a); //錯誤。不能用基類對象給派生類對象賦值。
78
79 // pb = pa; //不能用基類指針給派生類指針賦值。
80
81 pb = (B*)pa; //可以強制轉換,但是非常不安全。
82 pb->display(); //出現安全問題,p無法訪問,因為a中沒有p成員
83 system("pause");
84 return 0;
85 }
切記:派生類對象是基類對象,派生類中包含有基類的成員。基類對象不是派生類對象,它不能包含派生類型的成員。
/**************派生類到基類的轉化**************/
1。派生類對象地址賦值給基類指針
main函數中執行以下代碼
1 A a(3, 4); 2 // a.display(); 3 B b(10, 20, 30); 4 // b.display(); 5 6 A * pa; 7 // B * pb; 8 // pa = &a; 9 // pa->display(); 10 // pb = &b; 11 // pb->display(); 12 13 pa = &b; 14 pa->display(); //會輸出 10 20
pa為基類指針,指向派生類對象是合法的,因為派生類對象也是基類對象。語句會輸出派生類對象中基類部分。
注意:這里並不會調用派生類的display函數,調用的是基類的display函數,因為指針pa是基類指針,編譯器在編譯階段只知道pa的類型。如果要實現調用派生類的display函數,
需要用到虛函數實現多態性。之后的文章會講到。
進一步解釋一下編譯時和運行時的區別。
編譯時編譯器能知道pa的類型為A *,但是不知道它指向了哪個對象,假如有以下語句
1 A a(3, 4); 2 B b(10, 20, 30); 3 A* pa; 4 int number; 5 cin >> number; 6 if (number >= 0) 7 pa = &a; 8 else 9 pa = &b;
pa指向的對象類型依賴於輸入,運行時才輸入,所以編譯器是沒有辦法知道pa指向哪個類型的。
2.派生類對象賦值給基類引用
/**引用跟指針基本沒有區別,引用本質上是指針,是個指針常量,具體可以參照我的另一篇C++中的引用和指針的聯系和區別**/
main函數中執行以下代碼
1 A a(3, 4); 2 B b(10, 20, 30); 3 print1(b); //會輸出 10 20
形參為基類引用,實參為派生類對象,派生類對象也是基類對象,可以賦值給基類引用。輸出派生類中基類部分。
注意:此時對象本身並未復制,b仍然是派生類對象,前面說過了引用就是一個指針。
3.派生類對象賦值給基類對象。
A a(3, 4); B b(10, 20, 30); print3(b);
派生類對象基類部分被復制給形參。
注意:實際上沒有從派生類對象到基類對象的直接轉換。對基類對象的賦值或初始化,實際上在調用函數,初始化時調用構造函數,賦值時調用賦值操作符。
/********************基類到派生類的轉化******************/
切記:這種轉換有可能引發嚴重的安全問題,編寫代碼時不要使用。沒有基類到派生類的自動轉換,原因在於基類對象只能是基類對象,不能包含派生類型的成員。
如果允許用基類對象給派生類對象賦值,那么就可以試圖使用該派生類對象訪問不存在的成員。
1 A a(3, 4); 2 B b(10, 20, 30); 3 A * pa; 4 B * pb; 5 // print2(a); //錯誤。不能用基類對象給派生類引用賦值。 6 // print4(a); //錯誤。不能用基類對象給派生類對象賦值。 7 // pb = &a; //錯誤。派生類指針不能指向基類對象。 8 9 pa = &a; 10 pb = &b; 11 12 //pb = pa; //錯誤。不能用基類指針給派生類指針賦值。 13 14 pb = (B*)pa; //可以強制轉換,但是非常不安全。 15 pb->display(); //出現安全問題,p無法訪問,因為a中沒有p成員
注意到我們使用強制轉換時,當派生類添加了基類中不存在的成員時,會出現安全問題。
pb->display();會調用派生類的display函數,但是它指向的內存是基類對象a的內存,p不存在。會出現嚴重后果。
