多态的摸索之路( 甲 )
C++ 中的多態是極為復雜、難纏、龐大的,就像深海里的鯊魚,兇猛而強大。那么到底如何才能游刃有余地駕于好這頭潛伏在深海的遠古猛獸呢?!別著急,咱們慢慢來,一點一點地摸索清它的性格和脾氣,再控制就容易啦。
例甲:
#include<iostream>
using namespace std;
class TradesPerson {
public:
?????? virtual void sayHi() { cout << "Just Hi, and nothing." << endl; }
};
class Tinker : public TradesPerson {
public:
?????? virtual void sayHi() { cout << "Hi, I'm a Tinker." << endl; }
};
class Tailor : public TradesPerson {
public:
?????? virtual void sayHi() { cout << "Hi, I'm a Tailor." << endl; }
};
?
int main() {
?????? TradesPerson* p;
?????? int which;
?????? do{
????????????? cout << "1 = TradesPerson, 2 = Tinker, 3 = Tailor, 4 = quit; /n? ";
????????????? _input: cin >> which;
?????? }while( which < 1 || which >4 );
?????? switch( which ) {
????????????? case 1: p = new TradesPerson; break;
????????????? case 2: p = new Tinker; break;
????????????? case 3: p = new Tailor; break;
????????????? case 4: exit( 0 );
?????? }
?????? p->sayHi();
?????? goto _input;
?????? delete p;
?????? return 0;
}
?
模型一:
class TradesPerson {
public:
?????? virtual void sayHi() { cout << "Just Hi, and nothing." << endl; }
};
class Tinker : public TradesPerson {
public:
?????? virtual void sayHi() { cout << "Hi, I'm a Tinker." << endl; }
};
class Tailor : public TradesPerson {
public:
?????? virtual void sayHi() { cout << "Hi, I'm a Tailor." << endl; }
};
?
模型二:
class TradesPerson {
public:
?????? virtual void sayHi() { cout << "Just Hi, and nothing." << endl; }
};
class Tinker : public TradesPerson {
public:
?????? ?void sayHi() { cout << "Hi, I'm a Tinker." << endl; }
};
class Tailor : public TradesPerson {
public:
?????? ?void sayHi() { cout << "Hi, I'm a Tailor." << endl; }
};
?
模型三:
class TradesPerson {
public:
?????? void sayHi() { cout << "Just Hi, and nothing." << endl; }
};
class Tinker : public TradesPerson {
public:
?????? ?void sayHi() { cout << "Hi, I'm a Tinker." << endl; }
};
class Tailor : public TradesPerson {
public:
?????? ?void sayHi() { cout << "Hi, I'm a Tailor." << endl; }
};
?
模型四:
class TradesPerson {
public:
?????? void sayHi() { cout << "Just Hi, and nothing." << endl; }
};
class Tinker : public TradesPerson {
public:
?????? virtual void sayHi() { cout << "Hi, I'm a Tinker." << endl; }
};
class Tailor : public TradesPerson {
public:
?????? virtual void sayHi() { cout << "Hi, I'm a Tailor." << endl; }
};
?
實驗結果:模型一和模二相等,模三和模四相等。那么也就是說,當聲明了基類的一個成員函數為虛后,那么如果該函數在派生類中再次出現并得到新定義,它將自動成為虛函數!可如果在基類沒有聲明為虛,那么到派生類再怎么聲明是虛的都變得毫無意義了——根本起不到多態的神奇妙用。
說到多態,咱們在此再重復一下多態的概念:多態性也稱后約束或動態約束,它常用虛函數來實現。在C++中,多態是指C++的代碼可以根據運行情況的不同而執行不同的操作。C++的多態性就是為同一個函數和運算符定義幾個版本。C++支持兩種多態性,編譯時的多態性和運行時的多態性。編譯時的多態性通過使用重載函數獲得,運行時的多態性通過使用繼承和虛函數獲得。要獲得多態性的對象,必須建立一個類等級,然后在派生類中重定義基類函數,該函數可以被定義為重載函數或虛函數,以獲得編譯時的多態性對象或運行時的多態性對象。
?
?
一次非常成功而快樂的多態實驗!趕快來先睹為快吧!
例甲:
class A {
public:
?????? void disp() { cout << "This is A speaking!/n"; }
};
class B : public A {
public:
?????? void disp() { cout << "This is B speaking!/n"; }
};
?
int main() {
?????? A* pa = new B;
?????? pa->disp(); ???????? //輸出為This is A speaking!
?????? delete pa;
?????? return 0;
}
如果為了讓其輸出"This is B speaking!/n"的話,你可能會把那個關鍵句改成pa->B::disp();,可實驗證明,這么做編譯為報錯的。而且顯示三個錯誤,什么“'B' : is not a member of 'A'”、“ 'B' : is not a class or namespace name”等一派胡言亂語!那好,既然讓其輸出B speaking已經不可能了,接下來就virtual伺候吧!
例乙:
class A {
public:
?????? virtual void disp() { cout << "This is A speaking!/n"; }
};
class B : public A {
public:
?????? void disp() { cout << "This is B speaking!/n"; }
};
?
int main() {
?????? A* pa = new B;
?????? pa->disp(); ???????? //輸出為This is B speaking!
?????? delete pa;
?????? return 0;
}
很明顯,無非多了個關鍵字兒virtual,世界競如此大變!指針pa調用了B類里的成員函數,這也是應該的、應得的、正常的!因為它pa是B類給的它初始化!雖然A類是pa的親生母親,但是,是B類一把屎一把尿養活了pa!所以當pa長大成人、能做事能掙錢時,它理應贍養孝順B類!!這沒什么好商量的!!
如果想讓其輸出This is A speaking!,也可以,直接修改關鍵句為: pa->A::disp(); 就行了!
對比例甲與例乙,我們看到,當沒有用虛函數時,只能輸出A speaking,不能企圖通過用域解析符::來改變函數調用輸出B speaking;但使用了virtual后,代碼好像自己長眼睛了,知道程序員心里真正想調用的是誰并把程序員的心聲正確付諸于實踐,而且函數調用也變得來去自如,想調誰調誰!(欲調其他類的同名成員函數,只需加個域解析符即可!)。
不過也別高興的太早,這兩道例子看起來簡單無比,可我硬是花了幾個星期地功夫才摸索出來。難的、使我在大門外急得直跺腳就是進不來的東西是:你能否正確實驗并探究出虛函數的精要!也就是說,你很容易做出錯誤的實驗!廢話少說,來看看一條“不該地實驗,不該地愛……”
例甲:
int main() {
?????? B* pb = new A;
?????? pb->disp();
?????? delete pb;
?????? return 0;
}
用派生類B創建指針對象!用基類A給它初始化!結果一編譯呢——錯!“cannot convert from 'class A *' to 'class B *'”。所以呢,我估且得出一個結論:如果不想跟編譯器過不去,就不能用派生類創建對象然后基類初始化。只能是 A* pa = new B; ,謹記謹記!
?
?
?
以下三個例子中,主函均為:
int main() {
?????? A* pa = new B;
?????? pa->disp();
?????? delete pa;
?????? return 0;
}
因而全部省略。
?
例甲:
class A {
public:
?????? A() { cout << "A constructed,yeah!/n"; }?
?????? virtual void disp() { cout << "This is A speaking!/n"; }
};
?
class B : public A {
public:
?????? B() { cout << "B constructed,yeah!/n"; }?
?????? virtual void disp() { cout << "This is B speaking!/n"; }
};
輸出:
A constructed,yeah!
B constructed,yeah!
This is B speaking!
?
例乙:
class A {
public:
?????? ~A() { cout << "A destructed/n"; }
?????? virtual void disp() { cout << "This is A speaking!/n"; }
};
?
class B : public A {
public:
?????? ~B() { cout << "B destructed/n"; }
?????? virtual void disp() { cout << "This is B speaking!/n"; }
};
輸出:
This is B speaking!
A destructed
?
例丙:
class A {
public:
?????? A() { cout << "A constructed,yeah!/n"; }
?????? ~A() { cout << "A destructed/n"; }
?????? virtual void disp() { cout << "This is A speaking!/n"; }
};
class B : public A {
public:
?????? B() { cout << "B constructed,yeah!/n"; }
?????? ~B() { cout << "B destructed/n"; }
?????? virtual void disp() { cout << "This is B speaking!/n"; }
};
輸出:
A constructed,yeah!
B constructed,yeah!
This is B speaking!
A destructed
?
來個簡單明晰點的:
class A {
public:
?????? ~A() { cout << "A destructed/n"; }
};
class B : public A {
public:
?????? ~B() { cout << "B destructed/n"; }
};
?
?
int main() {
?????? A* pa = new B;
?????? delete pa;????? // 輸出 A destructed
?????? return 0;
}
接下來我不禁要問了,為什么析構函數不像構造函數那樣兩個類的統統都調用呢?
答案明日揭曉!!
?
?
?
總結
以上是生活随笔為你收集整理的多态的摸索之路( 甲 )的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 让工程师沉思的68个经典小故事
- 下一篇: 我的2018新年计划