C++(四十一) — 多態、虛函數、虛析構函數、純虛函數

 一、多態ios

     面向對象程序設計中,多態性表現爲:安全

  (1)重載多態:函數重載、運算符重載;ide

  (2)運行多態:經過基類的指針(或引用)調用不一樣派生類的同名函數,表現出不一樣的行爲;函數

  (3)模板多態:參數多態,經過一個模板獲得不一樣的函數或不一樣的類,具備不一樣的特性和不一樣的行爲;測試

二、同名覆蓋與重載編碼

 (1)override(同名覆蓋)spa

  在類的繼承中才會出現,多個函數的原型是相同的。設計

 (2)overload(重載)指針

   在同一做用域範圍內,由參數個數或類型不一樣的多個同名函數構成。code

三、虛函數

   緣由:經過指針調用成員函數時,只能訪問到基類的同名成員函數。在同名覆蓋現象中,經過某個類的對象(指針及引用)調用同名函數,編譯器會將該調用靜態聯編到該類的同名函數,也就是說,經過基類對象指針是沒法訪問派生類的同名函數的,即便這個指針是用派生類對象來初始化的。

  虛函數是C++中用於實現多態(polymorphism)的機制。核心理念就是經過基類訪問派生類定義的函數。

  虛函數給基類指針訪問派生類同名函數的一個機會

  指向基類的指針在操做它的多態類對象時,會根據不一樣的類對象,調用其相應的函數,這個函數就是虛函數。

  能夠說,基類聲明的虛函數,在派生類中也是虛函數,即便再也不使用virtual關鍵字。

  虛函數實現多態的原理:

  1. 當類中聲明虛函數時,編譯器會在類中生成一個虛函數表,用於存儲類成員函數的指針,由編譯器自動生成和維護;
  2. 存在虛函數時,每一個對象都有一個指向虛函數表的指針(對於派生類的虛函數表,先放父類,後方子類,函數覆蓋時就用子類的同名函數代替父類的);
  3. 編譯器肯定是否爲虛函數,如是則根據對象的指針,找到所指虛函數表查找函數並調用。

  動態聯編:一個類函數的調用並非在編譯時刻被肯定的,而是在運行時刻被肯定的。因爲編寫代碼的時候並不能肯定被調用的是基類的函數仍是哪一個派生類的函數,因此被成爲「虛」函數。

   虛函數只能藉助於指針或者引用來達到多態的效果。但會爲程序引入較大的開銷,實際中應儘可能避免。

  注意:

  虛函數在基類中聲明,構造函數、靜態成員函數不能夠爲虛函數,但析構函數能夠

  使用角度:虛函數經過父類指針調用子類的成員函數,而構造函數在建立時自動調用,無需父類指針;

  存儲角度:虛函數對應一個指向虛函數表的指針,虛函數表經過構造函數初始化,若構造函數爲虛,則須要經過虛函數表來找到虛構造函數的入口地址,而此時無虛函數表,因此構造函數不能爲虛函數。

四、虛析構函數

   只有虛析構函數,沒有虛構造函數。

  建立派生類對象時,調用基類構造->派生類構造->派生類析構->基類析構。

  若是用new運算符動態建立派生類對象,並以此對象地址初始化基類指針,構造沒問題,但用delete運算符刪除派生類對象時,因爲指針是指向基類的,經過靜態聯編,調用基類析構函數,不調用派生類析構函數,使得派生類沒法執行某些清理工做,例如:派生類中申請的內存沒機會還給系統。

  虛析構函數:基類設置虛析構函數,派生類都是。此時使用基類對象指針銷燬派生類對象時,會經過動態聯編調用派生類析構函數,完成派生類的清理工做。

五、純虛函數

  以下聲明表示一個函數爲純虛函數:

class A
{
public:
    virtual void foo()=0;   // =0 標誌一個虛函數爲純虛函數,沒有函數體,不可實例化,不可被調用。
};

  一個函數聲明爲純虛後,純虛函數的意思是:我是一個抽象類!不要把我實例化!純虛函數用來規範派生類的行爲,實際上就是所謂的「接口」。它告訴使用者,個人派生類都會有這個函數。

  純虛函數的引入,是出於兩個目的:
  一、爲了安全,由於避免任何須要明確可是由於不當心而致使的未知的結果,提醒子類去作應作的實現。其實是限制了派生類的功能,規範接口,把實現留給子類。
  二、爲了效率,不是程序執行的效率,而是爲了編碼的效率。

 六、運行時多態的應用實例

 (1)頭文件 shape.h

#pragma once

//#ifdef SHAPE_H
 #include <iostream>
using namespace std; class Shape { public: virtual double getArea() const = 0;//純虛函數
    void print() const; virtual ~Shape() {}                //虛析構函數
}; class Circle :public Shape { public: Circle(int xv= 0, int yv= 0, double rv= 0.0); double getArea() const; void print() const; protected: int x, y; double r; }; class Rectangle :public Shape { public: Rectangle(int av= 0, int bv = 0); double getArea() const; void print() const; protected: int a, b; }; //#endif // DEBUG

 

(2)函數定義 shape.c

#include <iostream>
using namespace std; #include "shape.h"

void Shape::print() const { cout << "Base class Object" << endl; } Circle::Circle(int xv, int yv, double rv) { x = xv; y = yv; r = rv; } double Circle::getArea() const { return 3.14*r*r; } void Circle::print() const { cout << "center is" << x << "  " << y << endl; } Rectangle::Rectangle(int av, int bv) { a = av; b = bv; } double Rectangle::getArea() const { return a*b; } void Rectangle::print() const { cout << "h is " << a << "  " << b << endl; }

 

三、測試文件

#include <iostream>
using namespace std; #include "shape.h"

void creat_object(Shape **ptr); void display_area(Shape *ptr); void delete_object(Shape *ptr); void main() { Shape *shape_ptr; creat_object(&shape_ptr); display_area(shape_ptr); delete_object(shape_ptr); system("pause"); } void creat_object(Shape **ptr) { char type; *ptr = nullptr; do { cout << "建立對象:" << endl; cin >> type; switch (type) { case 'c': { int xx, yy; double rr; cout << "請輸入圓心及半徑:"; cin >> xx >> yy >> rr; *ptr = new Circle(xx, yy, rr); break; } case 'r': { int aa, bb; cout << "請輸入矩形的長和寬:"; cin >> aa >> bb; *ptr = new Rectangle(aa,bb); break; } default:cout << "請從新選擇\n" << endl; } } while (*ptr == nullptr); } void display_area(Shape *ptr) { cout << ptr->getArea() << endl; } void delete_object(Shape *ptr) { delete(ptr); }
相關文章
相關標籤/搜索