本文講解內容的前提是派生類繼承基類的方式是公有繼承,關鍵字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不存在。會出現嚴重后果。