虛函數與動態綁定

來源:互聯網
上載者:User

在定義基類時,我們希望基類中的有些函數可以在衍生類別中重新定義。比如,我們定義了基類記錄的書,可以求出買了多少書花了多少錢;而在衍生類別中,我們定義的是打折的書,還是要計算買了多少書花了多少錢。這時,就需要重新定義計算錢數的函數了。注意,這裡的重新定義,與之前講過的函數重載或者操作符重載不同:後面兩類,是通過不同的形參,傳回值類型來讓編譯器判斷到底使用的是哪個函數,在程式執行前就能判斷;而這裡的重新定義,只是函數體的內容不同,參數、傳回值都與積累完全相同。只有程式運行時才能判斷使用的到底是哪個層次中的函數(這個過程成為動態綁定),這樣做是為了讓程式的介面統一。
通過把一個函式宣告為虛函數,(這個函數不能使這個類的建構函式,也不能是static函數)我們就可以在衍生類別中重新定義它。虛函數在聲明時,需要加上關鍵字virtual,不能在類的外部出現這個關鍵字。這一點和友元的聲明不同:友元可以既可以在類內不聲明,也可以在類外部聲明。當一個函數被聲明為虛函數後,這個函數在對於基類的後代而言,就都是虛函數了,不用加上virtual關鍵字。(當然,你也可以加上以強調它是虛函數)。

只有通過基類的引用或者指標調用虛函數時,才能發生動態綁定。因為引用或者指標既可以指向基類對象,也可以指向衍生類別對象的基類部分,用引用或者指標調用的虛函數。
舉一個例子:

class Item_base{public://建構函式Item_base(const std::string &book = "",double sales_price = 0.0):isbn(book),price(sales_price){ }//返回isbn號std::string book(){return isbn;}//基類不需要折扣策略virtual double net_price(std::size_t n)const{return n * price;}//解構函式virtual ~Item_base(){};private:std::string isbn;protected:double price;};class Bulk_item:public Item_base{public://建構函式Bulk_item(const std::string& book = "",double sales_price = 0.0,std::size_t qty = 0,double disc_rate = 0.0):Item_base(book,sales_price),quantity(qty),discount(disc_rate){ }~Bulk_item(){}double net_price(std::size_t)const;private://買多少書以後才有折扣std::size_t quantity;//折扣幅度double discount;};

 

其中:

double Bulk_item::net_price(std::size_t cnt)const{if(cnt > quantity)return cnt * (1 - discount) * price;elsereturn cnt * price;}

我們再定義一個列印結果的函數:
我們再定義一個列印結果的函數:我們再定義一個列印結果的函數:我們再定義一個列印結果的函數:我們再定義一個列印結果的函數:

void print_total(std::ostream& os,const Item_base& item,std::size_t n){os<<"ISBN: "<<item.book()<<"\tnumber sold: "<<n<<"\ttotal price: "<<item.net_price(n)<<std::endl;}

那麼在主函數中:

int main(){Item_base b1("aaa",10);Bulk_item d1("bbb",10,5,0.5);print_total(cout,b1,10);print_total(cout,d1,10);return 0;}

可以發現,雖然print_total函數的輸入參數是基類的引用,我們能夠也能夠通過衍生類別來訪問它。當使用基類訪問它時,調用的是基類的net_price函數,衍生類別訪問它時,調用的是衍生類別的net_price函數。這個調用規則不是在編譯時間可以確定的,必須在程式運行時才能確定。所以稱之為“動態關聯”。與之相對的,是靜態關聯,比如函數的重載,操作符的重載等等。程式執行之前,就能確定調用的是哪個函數。

特別的,有時我們會遇到這種情況:我們定義了一個類,這個類也有自己的資料和函數,但是我們並不希望使用者建立這個類的對象,而只能建立從這個基類派生出的類的對象。有人覺得,既然我們不希望建立一個一個類的對象,那麼我們幹嘛要定義這個類呢?其實很多時候,基類,是對一些問題的抽象,對它建立對象是沒有意義的。我們只是用它派生其他的,具體的類。比如,我們建立了“動物”類,定義了動物的“身高”、“體重”,定義了動物的動作:“吃飯”、“睡覺”。從“動物”類派生出“老虎”等類。此時,大家也應該覺得,定義一個動物類對象沒有多大的意義。
言歸正傳,如何?上面的構想呢?就是使用純虛函數。純虛函數的定義很簡單,就是在某個函數的形參列表後面加上=0。但這一舉動卻意義非凡:首先我們定義了這個函數(而不是在衍生類別中定義),是得我們為這類函數提供了統一的、可覆蓋的介面,以便於後面的管理;其次,當一個類中含有(或者)純虛函數時,這個類就被成為“抽象基類”,我們不能建立這個類的對象,只能用它來派生別的類,建立衍生類別的對象,這樣規定,也防止我們誤用了這個類(因為他是被用來繼承的,很多成員都不完善。)舉一個例:

class annimal{public:virtual void eat()=0;void sleep(){};private:double height;double weight;};class tiger:public annimal{public:void eat(){std::cout<<"eat meat"<<std::endl;}void sell(){std::cout<<"sleep at night"<<std::endl;};};class bat:public annimal{public:void eat(){std::cout<<"eat blood"<<std::endl;};void sell(){std::cout<<"sleep at day"<<std::endl;};};int main(){//不能建立抽象類別的對象//annimal a1;tiger t1;t1.eat();bat b1;b1.eat();return 0;}

 

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.