【C++grammar】多态、联编、虚函数
目錄
- 1、多態(tài)概念
- 1.多態(tài)性有兩種表現(xiàn)的方式
- 2、聯(lián)編(實(shí)現(xiàn)多態(tài))
- 1.靜態(tài)聯(lián)編
- 2.動(dòng)態(tài)聯(lián)編
- 3、實(shí)現(xiàn)運(yùn)行時(shí)多態(tài)
- 1.為何要使用運(yùn)行時(shí)多態(tài)?
- 2.如何實(shí)現(xiàn)運(yùn)行時(shí)多態(tài)
- 3.多態(tài)的例子
- 1.調(diào)用哪個(gè)同名虛函數(shù)?
- 2. 用途:可以用父類指針訪問子類對(duì)象成員
- 4. 虛函數(shù)的傳遞性
- 虛函數(shù)的缺點(diǎn):
- 4、代碼示例
- 1、繼承與重載函數(shù)的例子
- 任務(wù)1、2
- 任務(wù)3
- 2、使用運(yùn)行時(shí)多態(tài)
- 3、override覆寫
- 6、運(yùn)行時(shí)多態(tài)的總結(jié)
- 1.Summary: static binding v.s. dynamic binding
- 2. Summary: 靜態(tài)聯(lián)編的簡(jiǎn)單示例
- 3. Summary: 通過基類指針訪問同名函數(shù)的示例
- 4. Summary:動(dòng)態(tài)聯(lián)編的簡(jiǎn)單示例-指針形式
- 5. Summary:動(dòng)態(tài)聯(lián)編的簡(jiǎn)單示例-引用形式
- 7、C++11:使用override和final
- 1. override顯式聲明覆寫
- 2. final 顯式聲明禁止覆寫
1、多態(tài)概念
廣義的多態(tài):不同類型的實(shí)體/對(duì)象對(duì)于同一消息(可以理解為同一個(gè)名字的函數(shù))有不同的響應(yīng),就是OOP中的多態(tài)性。
1.多態(tài)性有兩種表現(xiàn)的方式
1. 重載多態(tài):調(diào)用參數(shù)不同的同名函數(shù),表現(xiàn)出不同的行為
class C { public: int f(int x);int f( ); };2.子類型多態(tài):不同的對(duì)象調(diào)用同名重定義函數(shù),表現(xiàn)出不同的行為
class A { virtual int f() {return 1;} }; class B: public A { virtual int f() {return 8;} }; A a; B b; A* p = &b; //p雖然是a類型,但是它指向B類型的對(duì)象 a.f() // call A::f() b.f() // call B::f() p->f(); // call B::f() //a和p是同類型,但是調(diào)用同名函數(shù)的響應(yīng)是不一樣的。2、聯(lián)編(實(shí)現(xiàn)多態(tài))
確定具有多態(tài)性的語(yǔ)句調(diào)用哪個(gè)函數(shù)的過程稱為聯(lián)編。
1.靜態(tài)聯(lián)編
靜態(tài)聯(lián)編在程序編譯時(shí)(Compile-time)確定調(diào)用哪個(gè)函數(shù)。
如:函數(shù)重載
2.動(dòng)態(tài)聯(lián)編
在程序運(yùn)行時(shí)(Run-time),才能夠確定調(diào)用哪個(gè)函數(shù)
用動(dòng)態(tài)聯(lián)編實(shí)現(xiàn)的多態(tài),也稱為運(yùn)行時(shí)多態(tài)(Run-time Polymorphism)。
3、實(shí)現(xiàn)運(yùn)行時(shí)多態(tài)
1.為何要使用運(yùn)行時(shí)多態(tài)?
我們有三個(gè)類ABC,繼承鏈如下:
A<-B<-C;
我們想使用print()調(diào)用toString()輸出信息,就需要寫三個(gè)重載函數(shù):
這樣比較麻煩。
我們可以使用運(yùn)行時(shí)多態(tài)解決這個(gè)問題:
2.如何實(shí)現(xiàn)運(yùn)行時(shí)多態(tài)
實(shí)現(xiàn)運(yùn)行時(shí)多態(tài)有兩個(gè)要素:
(1) virtual function (虛函數(shù))
(2) Override (覆寫) : redefining a virtual function in a derived class. (在派生類中重定義一個(gè)虛函數(shù))
在正常的函數(shù)名字前加上virtual關(guān)鍵字,這個(gè)函數(shù)就會(huì)變?yōu)樘摵瘮?shù):
struct A{virtual std::string toString(){return "A";} };覆寫是在派生類中定義一個(gè)與基類虛函數(shù)同名,參數(shù)一樣,返回值一樣的函數(shù)。
注意這里我們傳遞的是ABC類型的指針:
3.多態(tài)的例子
1.調(diào)用哪個(gè)同名虛函數(shù)?
(1) 不由指針類型決定;
(2) 而由指針?biāo)傅摹緦?shí)際對(duì)象】的類型決定
(3) 運(yùn)行時(shí),檢查指針?biāo)笇?duì)象類型
2. 用途:可以用父類指針訪問子類對(duì)象成員
注意print函數(shù)的參數(shù)是基類類型的指針。
然后將ABC類型的對(duì)象的地址作為函數(shù)參數(shù)傳入。
后面兩個(gè)print函數(shù)指針會(huì)做隱式類型轉(zhuǎn)換。將派生類的地址轉(zhuǎn)化為基類類型的指針,這個(gè)轉(zhuǎn)換是安全的。
返回 的結(jié)果是不一樣的,是和對(duì)象的實(shí)際情況相關(guān)的。
4. 虛函數(shù)的傳遞性
基類定義了虛同名函數(shù),那么派生類中的同名函數(shù)自動(dòng)變?yōu)樘摵瘮?shù)。
注意:只需要在繼承連上最頂端的基類上加上關(guān)鍵字即可:
虛函數(shù)的缺點(diǎn):
1、類中保存著一個(gè)Virtual function table (虛函數(shù)表)。
2、運(yùn)行時(shí)聯(lián)編/動(dòng)態(tài)聯(lián)編,會(huì)有額外的邏輯。
所以調(diào)用虛函數(shù)比非虛函數(shù)開銷大
4、代碼示例
1、繼承與重載函數(shù)的例子
任務(wù)1、2
1、創(chuàng)建A/B/C三個(gè)類,B繼承A,C繼承B,ABC均有toString函數(shù)
2、創(chuàng)建print函數(shù),接受A類型的參數(shù),調(diào)用A對(duì)象的toString()
不管傳進(jìn)來什么類型,都是打印基類類型的數(shù)據(jù)
//本部分要展示的內(nèi)容如下: //1、創(chuàng)建A/B/C三個(gè)類,B繼承A,C繼承B,ABC均有toString函數(shù) //2、創(chuàng)建print函數(shù),接受A類型的參數(shù),調(diào)用A對(duì)象的toString() //3、重載print函數(shù),接受B/C類型參數(shù),調(diào)用toStirng()#include<iostream> #include<string> using std::cout; using std::endl; class A { public:std::string toString() { return "A"; } }; class B : public A { public:std::string toString() { return "B"; } }; class C : public B { public:std::string toString() { return "C"; } };void print(A a) {cout << a.toString() << endl; } int main() {A a;B b;C c;print(a);print(b);print(c); }任務(wù)3
重載print函數(shù),接受B/C類型參數(shù),調(diào)用toStirng()
//本部分要展示的內(nèi)容如下: //1、創(chuàng)建A/B/C三個(gè)類,B繼承A,C繼承B,ABC均有toString函數(shù) //2、創(chuàng)建print函數(shù),接受A類型的參數(shù),調(diào)用A對(duì)象的toString() //3、重載print函數(shù),接受B/C類型參數(shù),調(diào)用toStirng()#include<iostream> #include<string> using std::cout; using std::endl; class A { public:std::string toString() { return "A"; } }; class B : public A { public:std::string toString() { return "B"; } }; class C : public B { public:std::string toString() { return "C"; } };void print(A a) {cout << a.toString() << endl; } void print(B b) {cout << b.toString() << endl; } void print(C c) {cout << c.toString() << endl; } int main() {A a;B b;C c;print(a);print(b);print(c); }
通過三個(gè)重載函數(shù),將打印的數(shù)據(jù)與類型匹配。
2、使用運(yùn)行時(shí)多態(tài)
本部分要展示的內(nèi)容如下:
1、將基類A的toStirng函數(shù)改為虛函數(shù)
2、將print函數(shù)參數(shù)改為基類指針類型
main()中調(diào)用print(),實(shí)參為指向?qū)ο蟮幕愔羔?br /> 3、添加一個(gè)print函數(shù),參數(shù)是基類引用類型
在main()中調(diào)用print(),參數(shù)為對(duì)象的基類引用
可以發(fā)現(xiàn)參數(shù)是基類引用類型與參數(shù)是基類引用類型實(shí)現(xiàn)的效果是一樣的。這些就是多態(tài)的具體展示。
注意如果在print函數(shù)中沒有找到該對(duì)象的同名函數(shù),那么就會(huì)順著繼承鏈,直到在基類中找到這個(gè)同名函數(shù)。
也就是說,在繼承鏈中如果某個(gè)類的虛函數(shù)名字寫錯(cuò)了就會(huì)出現(xiàn)許多BUG。
3、override覆寫
為了解決上面的問題,c++提供了覆寫操作。這在第7大點(diǎn)將詳細(xì)講述:
表明了對(duì)基類函數(shù)的覆寫,如果函數(shù)名字或者參數(shù)或者返回值不一樣,編譯器就會(huì)報(bào)錯(cuò),提醒程序員。
6、運(yùn)行時(shí)多態(tài)的總結(jié)
1.Summary: static binding v.s. dynamic binding
基類與派生類中有同名函數(shù)
(1) 通過派生類對(duì)象訪問同名函數(shù),是靜態(tài)聯(lián)編
(2) 通過基類對(duì)象的指針訪問同名函數(shù),是靜態(tài)聯(lián)編
(3) 通過基類對(duì)象的指針或引用訪問同名虛函數(shù),是動(dòng)態(tài)聯(lián)編
2. Summary: 靜態(tài)聯(lián)編的簡(jiǎn)單示例
class P { public: f(){…} }; //父類 class C: public P { public: f(){…} }; //子類 main () {P p; C c;p.f(); //調(diào)用P::f()c.f(); //調(diào)用C::f() }說明: 對(duì)象是什么類型,就調(diào)什么類型
3. Summary: 通過基類指針訪問同名函數(shù)的示例
class P { public: f(){…} }; //父類 class C: public P { public: f(){…} }; //子類 main () {P* ptr; P p; C c;ptr = &p;ptr->f(); // 調(diào)用P::f()ptr=&c;ptr->f(); // 調(diào)用P::f() }說明: 指針是什么類型,就調(diào)什么類型
4. Summary:動(dòng)態(tài)聯(lián)編的簡(jiǎn)單示例-指針形式
class P { public: virtual f(){…} }; //父類 class C: public P { public: f(){…} }; //子類,f自動(dòng)virtual main () {P* ptr; P p; C c;ptr = &p;ptr->f(); //調(diào)用P::f()ptr=&c;ptr->f(); //調(diào)用C::f() }說明: 函數(shù)虛,不看指針看真對(duì)象
5. Summary:動(dòng)態(tài)聯(lián)編的簡(jiǎn)單示例-引用形式
class P { public: virtual f(){…} }; //父類 class C:public P { public: f(){…} }; //子類,f自動(dòng)virtual main () {P p; C c;P& pr1 = p;pr1.f(); //調(diào)用P::f()P& pr2 = c;pr2.f(); //調(diào)用C::f() }說明: 函數(shù)虛,不看引用看真對(duì)象
7、C++11:使用override和final
1. override顯式聲明覆寫
C++11引入override標(biāo)識(shí)符,指定一個(gè)虛函數(shù)覆寫另一個(gè)虛函數(shù)。
class A { public:virtual void foo() {}void bar() {} }; class B : public A { public://此處foo為常函數(shù),與基類的foo函數(shù)不是同名覆寫函數(shù)void foo() const override { // 錯(cuò)誤: B::foo 不覆寫 A::foo} // (簽名不匹配)void foo() override; // OK : B::foo 覆寫 A::foovoid bar() override {} // 錯(cuò)誤: A::bar 非虛 }; void B::foo() override {// 錯(cuò)誤: override只能放到類內(nèi)使用 }override的價(jià)值在于:避免程序員在覆寫時(shí)錯(cuò)命名或無虛函數(shù)導(dǎo)致隱藏bug
summary:
1、override標(biāo)識(shí)符應(yīng)該寫到派生類同名虛函數(shù)的后面
2、非虛函數(shù)不能覆寫
3、覆寫只能在類的內(nèi)部使用
2. final 顯式聲明禁止覆寫
C++11引入final特殊標(biāo)識(shí)符,指定派生類不能覆寫虛函數(shù)。
struct Base {virtual void foo(); };struct A : Base { void foo() final; // A::foo 被覆寫且是最終覆寫void bar() final; // 錯(cuò)誤:非虛函數(shù)不能被覆寫或是 final }; struct B final : A // struct B 為 final,不能被繼承 {void foo() override; // 錯(cuò)誤: foo 不能被覆寫,因?yàn)樗?A 中是 final };final標(biāo)識(shí)符的作用:
總結(jié)
以上是生活随笔為你收集整理的【C++grammar】多态、联编、虚函数的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 开封看多囊卵巢最好的医院推荐
- 下一篇: 【C++grammar】访问控制与抽象类