日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當(dāng)前位置: 首頁 > 编程语言 > c/c++ >内容正文

c/c++

C/C++学习之路: 多态

發(fā)布時間:2024/4/11 c/c++ 32 豆豆
生活随笔 收集整理的這篇文章主要介紹了 C/C++学习之路: 多态 小編覺得挺不錯的,現(xiàn)在分享給大家,幫大家做個參考.

C/C++學(xué)習(xí)之路: 多態(tài)


目錄

  • 多態(tài)基本概念
  • 向上類型轉(zhuǎn)換及問題
  • 如何實現(xiàn)動態(tài)綁定
  • 抽象基類和純虛函數(shù)
  • 純虛函數(shù)和多繼承
  • 虛析構(gòu)函數(shù)
  • 重寫,重載,重定義

  • 1. 多態(tài)基本概念

  • 多態(tài)是面向?qū)ο蟪绦蛟O(shè)計語言中數(shù)據(jù)抽象和繼承之外的第三個基本特征。
  • 多態(tài)性(polymorphism)提供接口與具體實現(xiàn)之間的另一層隔離。
  • 多態(tài)性改善了代碼的可讀性和組織性,同時也使創(chuàng)建的程序具有可擴展性,項目不僅在最初創(chuàng)建時期可以擴展,而且當(dāng)項目在需要有新的功能時也能擴展。
  • c++支持編譯時多態(tài)(靜態(tài)多態(tài))和運行時多態(tài)(動態(tài)多態(tài)),運算符重載和函數(shù)重載就是編譯時多態(tài),而派生類和虛函數(shù)實現(xiàn)運行時多態(tài)。
  • 靜態(tài)多態(tài)和動態(tài)多態(tài)的區(qū)別就是函數(shù)地址是早綁定(靜態(tài)聯(lián)編)還是晚綁定(動態(tài)聯(lián)編)。
  • 如果函數(shù)的調(diào)用,在編譯階段就可以確定函數(shù)的調(diào)用地址,并產(chǎn)生代碼,就是靜態(tài)多態(tài)(編譯時多態(tài)),就是說地址是早綁定的。
  • 而如果函數(shù)的調(diào)用地址不能編譯不能在編譯期間確定,而需要在運行時才能決定,就屬于晚綁定(動態(tài)多態(tài),運行時多態(tài))。
  • //計算器 class Caculator { public:void setA(int a) {this->mA = a;}void setB(int b) {this->mB = b;}void setOperator(string oper) {this->mOperator = oper;}int getResult() {if (this->mOperator == "+") {return mA + mB;} else if (this->mOperator == "-") {return mA - mB;} else if (this->mOperator == "*") {return mA * mB;} else if (this->mOperator == "/") {return mA / mB;}}private:int mA;int mB;string mOperator; };//這種程序不利于擴展,維護困難,如果修改功能或者擴展功能需要在源代碼基礎(chǔ)上修改 //面向?qū)ο蟪绦蛟O(shè)計一個基本原則:開閉原則(對修改關(guān)閉,對擴展開放)//抽象基類 class AbstractCaculator { public:void setA(int a) {this->mA = a;}virtual void setB(int b) {this->mB = b;}virtual int getResult() = 0;protected:int mA;int mB; };//加法計算器 class PlusCaculator : public AbstractCaculator { public:virtual int getResult() {return mA + mB;} };//減法計算器 class MinusCaculator : public AbstractCaculator { public:virtual int getResult() {return mA - mB;} };//乘法計算器 class MultipliesCaculator : public AbstractCaculator { public:virtual int getResult() {return mA * mB;} };void DoBussiness(AbstractCaculator *caculator) {int a = 10;int b = 20;caculator->setA(a);caculator->setB(b);cout << "計算結(jié)果:" << caculator->getResult() << endl;delete caculator; }

    2. 向上類型轉(zhuǎn)換及問題

    1. 問題

  • 對象可以作為自己的類或者作為它的基類的對象來使用,還能通過基類的地址來操作它。
  • 取一個對象的地址(指針或引用),并將其作為基類的地址來處理,這種稱為向上類型轉(zhuǎn)換。
  • 父類引用或指針可以指向子類對象,通過父類指針或引用來操作子類對象。
  • class Animal { public:void speak() {cout << "動物在唱歌..." << endl;} };class Dog : public Animal { public:void speak() {cout << "小狗在唱歌..." << endl;} };void DoBussiness(Animal &animal) {animal.speak(); }void test3() {Dog dog;DoBussiness(dog); }
  • 我們給DoBussiness傳入的對象是dog,而不是animal對象,輸出的結(jié)果應(yīng)該是Dog::speak。
  • 2. 問題解決(虛函數(shù),virtual function)

  • 把函數(shù)體與函數(shù)調(diào)用相聯(lián)系稱為綁定(捆綁,binding)
  • 當(dāng)綁定在程序運行之前(由編譯器和連接器)完成時,稱為早綁定(early binding),C語言中只有一種函數(shù)調(diào)用方式,就是早綁定。
  • 上面的問題就是由于早綁定引起的,因為編譯器在只有Animal地址時并不知道要調(diào)用的正確函數(shù)。
  • 編譯是根據(jù)指向?qū)ο蟮闹羔樆蛞玫念愋蛠磉x擇函數(shù)調(diào)用。這個時候由于DoBussiness的參數(shù)類型是Animal&,編譯器確定了應(yīng)該調(diào)用的speak是Animal::speak的,而不是真正傳入的對象Dog::speak。
  • 解決方法就是遲綁定(遲捆綁,動態(tài)綁定,運行時綁定,late binding),意味著綁定要根據(jù)對象的實際類型,發(fā)生在運行。
  • C++語言要實現(xiàn)這種動態(tài)綁定,必須有某種機制來確定運行時對象的類型并調(diào)用合適的成員函數(shù)。
  • C++動態(tài)多態(tài)性是通過虛函數(shù)來實現(xiàn)的,虛函數(shù)允許子類(派生類)重新定義父類(基類)成員函數(shù),而子類(派生類)重新定義父類(基類)虛函數(shù)的做法稱為覆蓋(override),或者稱為重寫。
  • 對于特定的函數(shù)進行動態(tài)綁定,c++要求在基類中聲明這個函數(shù)的時候使用virtual關(guān)鍵字,動態(tài)綁定也就對virtual函數(shù)起作用.
  • 為創(chuàng)建一個需要動態(tài)綁定的虛成員函數(shù),可以簡單在這個函數(shù)聲明前面加上virtual關(guān)鍵字,定義時候不需要.
  • 如果一個函數(shù)在基類中被聲明為virtual,那么在所有派生類中它都是virtual的.
  • 在派生類中virtual函數(shù)的重定義稱為重寫(override),Virtual關(guān)鍵字只能修飾成員函數(shù),構(gòu)造函數(shù)不能為虛函數(shù)
  • 注意: 僅需要在基類中聲明一個函數(shù)為virtual.調(diào)用所有匹配基類聲明行為的派生類函數(shù)都將使用虛機制。
  • class Animal { public:virtual void speak() {cout << "動物在唱歌..." << endl;} };class Dog : public Animal { public:virtual void speak() {cout << "小狗在唱歌..." << endl;} };void DoBussiness(Animal &animal) {animal.speak(); }void test3() {Dog dog;DoBussiness(dog); }

    3. 如何實現(xiàn)動態(tài)綁定

  • 當(dāng)編譯器發(fā)現(xiàn)我們的類中有虛函數(shù)的時候,編譯器會創(chuàng)建一張?zhí)摵瘮?shù)表,把虛函數(shù)的函數(shù)入口地址放到虛函數(shù)表中,并且在類中秘密增加一個指針,這個指針就是虛表指針(縮寫vptr),這個指針是指向?qū)ο蟮奶摵瘮?shù)表。
  • 在多態(tài)調(diào)用的時候,根據(jù)vptr指針,找到虛函數(shù)表來實現(xiàn)動態(tài)綁定。
  • 在編譯階段,編譯器秘密增加了一個vptr指針,但是此時vptr指針并沒有初始化指向虛函數(shù)表(vtable),
  • 在對象構(gòu)建的時候,也就是在對象初始化調(diào)用構(gòu)造函數(shù)的時候。編譯器首先默認會在我們所編寫的每一個構(gòu)造函數(shù)中,增加一些vptr指針初始化的代碼。
  • 如果沒有提供構(gòu)造函數(shù),編譯器會提供默認的構(gòu)造函數(shù),那么就會在默認構(gòu)造函數(shù)里做此項工作,初始化vptr指針,使之指向本對象的虛函數(shù)表。
  • 子類繼承基類,子類繼承了基類的vptr指針,這個vptr指針是指向基類虛函數(shù)表,當(dāng)子類調(diào)用構(gòu)造函數(shù),使得子類的vptr指針指向了子類的虛函數(shù)表。
  • 當(dāng)子類無重寫基類虛函數(shù)時:

  • Animal* animal = new Dog;animal->fun1();當(dāng)程序執(zhí)行到這里,會去animal指向的空間中尋找vptr指針,通過vptr指針找到func1函數(shù),此時由于子類并沒有重寫也就是覆蓋基類的func1函數(shù),所以調(diào)用func1時,仍然調(diào)用的是基類的func1.
  • 當(dāng)子類重寫基類虛函數(shù)時:
  • Animal* animal = new Dog;animal->fun1();當(dāng)程序執(zhí)行到這里,會去animal指向的空間中尋找vptr指針,通過vptr指針找到func1函數(shù),由于子類重寫基類的func1函數(shù),所以調(diào)用func1時,調(diào)用的是子類的func1.
  • 多態(tài)的成立條件:
  • 有繼承
  • 子類重寫父類虛函數(shù)函數(shù):返回值,函數(shù)名字,函數(shù)參數(shù),必須和父類完全一致(析構(gòu)函數(shù)除外),子類中virtual關(guān)鍵字可寫可不寫,建議寫
  • 類型兼容,父類指針,父類引用 指向 子類對象

  • 4. 抽象基類和純虛函數(shù)

  • 在設(shè)計時,常常希望基類僅僅作為其派生類的一個接口。這就是說,僅想對基類進行向上類型轉(zhuǎn)換,使用它的接口,而不希望用戶實際的創(chuàng)建一個基類的對象。

  • 同時創(chuàng)建一個純虛函數(shù)允許接口中放置成員原函數(shù),而不一定要提供一段可能對這個函數(shù)毫無意義的代碼。

  • 做到這點,可以在基類中加入至少一個純虛函數(shù)(pure virtual function),使得基類稱為抽象類(abstract class).

  • 純虛函數(shù)使用關(guān)鍵字virtual,并在其后面加上=0。如果試圖去實例化一個抽象類,編譯器則會阻止這種操作。
  • 當(dāng)繼承一個抽象類的時候,必須實現(xiàn)所有的純虛函數(shù),否則由抽象類派生的類也是一個抽象類。
  • Virtual void fun() = 0;告訴編譯器在vtable中為函數(shù)保留一個位置,但在這個特定位置不放地址。
  • 建立公共接口目的是為了將子類公共的操作抽象出來,可以通過一個公共接口來操縱一組類,且這個公共接口不需要事先(或者不需要完全實現(xiàn)),可以創(chuàng)建一個公共類。

  • //抽象制作飲品 class AbstractDrinking{ public://燒水virtual void Boil() = 0;//沖泡virtual void Brew() = 0;//倒入杯中virtual void PourInCup() = 0;//加入輔料virtual void PutSomething() = 0;//規(guī)定流程void MakeDrink(){Boil();Brew();PourInCup();PutSomething();} };//制作咖啡 class Coffee : public AbstractDrinking{ public://燒水virtual void Boil(){cout << "煮農(nóng)夫山泉!" << endl;}//沖泡virtual void Brew(){cout << "沖泡咖啡!" << endl;}//倒入杯中virtual void PourInCup(){cout << "將咖啡倒入杯中!" << endl;}//加入輔料virtual void PutSomething(){cout << "加入牛奶!" << endl;} };//制作茶水 class Tea : public AbstractDrinking{ public://燒水virtual void Boil(){cout << "煮自來水!" << endl;}//沖泡virtual void Brew(){cout << "沖泡茶葉!" << endl;}//倒入杯中virtual void PourInCup(){cout << "將茶水倒入杯中!" << endl;}//加入輔料virtual void PutSomething(){cout << "加入食鹽!" << endl;} };//業(yè)務(wù)函數(shù) void DoBussiness(AbstractDrinking* drink){drink->MakeDrink();delete drink; }void test(){DoBussiness(new Coffee);cout << "--------------" << endl;DoBussiness(new Tea); }

    5. 純虛函數(shù)和多繼承

  • 絕大數(shù)面向?qū)ο笳Z言都不支持多繼承,但是絕大數(shù)面向?qū)ο髮ο笳Z言都支持接口的概念,c++中沒有接口的概念,但是可以通過純虛函數(shù)實現(xiàn)接口。
  • 接口類中只有函數(shù)原型定義,沒有任何數(shù)據(jù)定義。
  • 多重繼承接口不會帶來二義性和復(fù)雜性問題。接口類只是一個功能聲明,并不是功能實現(xiàn),子類需要根據(jù)功能說明定義功能實現(xiàn)。
  • 注意:除了析構(gòu)函數(shù)外,其他聲明都是純虛函數(shù)。

  • 6. 虛析構(gòu)函數(shù)

    1. 虛析構(gòu)函數(shù)作用

  • 虛析構(gòu)函數(shù)是為了解決基類的指針指向派生類對象,并用基類的指針刪除派生類對象。
  • class People{ public:People(){cout << "構(gòu)造函數(shù) People!" << endl;}virtual void showName() = 0;virtual ~People(){cout << "析構(gòu)函數(shù) People!" << endl;} };class Worker : public People{ public:Worker(){cout << "構(gòu)造函數(shù) Worker!" << endl;pName = new char[10];}virtual void showName(){cout << "打印子類的名字!" << endl;}~Worker(){cout << "析構(gòu)函數(shù) Worker!" << endl;if (pName != NULL){delete pName;}} private:char* pName; };void test(){People* people = new Worker;people->~People(); }

    2. 純虛析構(gòu)函數(shù)

  • 純虛析構(gòu)函數(shù)在c++中是合法的,但是在使用的時候有一個額外的限制:必須為純虛析構(gòu)函數(shù)提供一個函數(shù)體。
  • 那么問題是:如果給虛析構(gòu)函數(shù)提供函數(shù)體了,那怎么還能稱作純虛析構(gòu)函數(shù)呢?
  • 純虛析構(gòu)函數(shù)和非純析構(gòu)函數(shù)之間唯一的不同之處在于純虛析構(gòu)函數(shù)使得基類是抽象類,不能創(chuàng)建基類的對象。
  • 如果類的目的不是為了實現(xiàn)多態(tài),作為基類來使用,就不要聲明虛析構(gòu)函數(shù),反之,則應(yīng)該為類聲明虛析構(gòu)函數(shù)。
  • //非純虛析構(gòu)函數(shù) class A{ public:virtual ~A(); };A::~A(){}//純析構(gòu)函數(shù) class B{ public:virtual ~B() = 0; };B::~B(){}void test(){A a; //A類不是抽象類,可以實例化對象B b; //err,B類是抽象類,不可以實例化對象 }

    7. 重寫,重載,重定義

  • 重載,同一作用域的同名函數(shù)
  • 同一個作用域
  • 參數(shù)個數(shù),參數(shù)順序,參數(shù)類型不同
  • 和函數(shù)返回值,沒有關(guān)系
  • const也可以作為重載條件 //do(const Teacher& t){} do(Teacher& t)
  • 重定義(隱藏)
  • 有繼承
  • 子類(派生類)重新定義父類(基類)的同名成員(非virtual函數(shù))
  • 重寫(覆蓋)
  • 有繼承
  • 子類(派生類)重寫父類(基類)的virtual函數(shù)
  • 函數(shù)返回值,函數(shù)名字,函數(shù)參數(shù),必須和基類中的虛函數(shù)一致
  • 總結(jié)

    以上是生活随笔為你收集整理的C/C++学习之路: 多态的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網(wǎng)站內(nèi)容還不錯,歡迎將生活随笔推薦給好友。