面向對象編程基於三個基本概念:
面向對象編程的關鍵思想是多態性(polymorphism)
。多態性派生於一個希臘單詞,意思是“許多形態”,之所以稱通過繼承而相關聯的類型為多態類型,是因為在許多情況下可以互換地使用派生類型或基類型的“許多形態”。在C++中,多態性僅用於通過繼承而相關聯的類型的引用或指針。
在C++中,基類必須指出希望派生類重寫哪些函數,定義為virtual
的函數是基類期待派生類重新定義的,基類希望派生類繼承的函數不能定義為虛函數。
在C++中,通過基類的引用(或指針)調用虛函數時,發生動態綁定
。引用(或指針)既可以指向基類對象也可以指向派生類對象
,這一事實是動態綁定的關鍵。用引用(或指針)調用的虛函數在運行時確定,被調用的函數是引用(或指針)所指對象的實際類型所定義的。
引用和指針的靜態類型與動態類型可以不同,這是 C++ 用以支持多態性的基石。
virtual
的目的是啟用動態綁定。成員默認為非虛函數
,對非虛函數的調用在編譯時確定。除了構造函數之外,任意非static成員函數都可以是虛函數
。類內部的成員函數聲明中出現,不能用在類定義體外部出現的函數定義上。
訪問控制
派生類對基類的public和private成員的訪問權限
與程序中任意其他部分一樣:它可以訪問public成員
而不能訪問private成員
。
有時作為基類的類具有一些成員,它希望允許派生類訪問但仍禁止其他用戶訪問這些成員。對於這樣的成員應使用受保護的訪問標號。protected成員可以被派生類對象訪問但不能被該類型的普通用戶訪問。
protected
成員和public
成員的組合class
classname: access-label base-class
虛函數的聲明必須與基類中的定義方式完全匹配
,
但有一個例外:返回對基類型的引用(或指針)的虛函數。派生類中的虛函數可以返回基類函數所返回類型的派生類的引用(或指針)。
虛函數的成員函數才能進行動態綁定
基類類型的引用或指針進行函數調用
編譯器都將它當作基類類型對象
。靜態類型
(在編譯時可知的引用類型或指針類型)和動態類型
(指針或引用所綁定的對象的類型這是僅在運行時可知的)可能不同。覆蓋虛函數機制
虛函數與默認實參
編譯時確定
。類型定義,與對象的動態類型無關
。通過基類的引用或指針調用虛函數時,默認實參為在基類虛函數聲明中指定的值,如果通過派生類的指針或引用調用虛函數,則默認實參是在派生類的版本中聲明的值
。
為什麼會希望覆蓋虛函數機制?
最常見的理由是為了派生類虛函數調用基類中的版本。在這種情況下,基類版本可以完成繼承層次中所有類型的公共任務,而每個派生類型只添加自己的特殊工作。
例如,可以定義一個具有虛操作的Camera類層次。Camera類中的display函數可以顯示所有的公共信息,派生類(如PerspectiveCamera)可能既需要顯示公共信息又需要顯示自己的獨特信息。可以顯式調用Camera版本以顯示公共信息,而不是在PerspectiveCamera的display實現中復制Camera的操作。 在這種情況下,已經確切知道調用哪個實例,因此,不需要通過虛函數機制。
共同控制
。派生類可以進一步限制但不能放松對所繼承的成員的訪問
。公用繼承
,基類的public成員為派生類的public成員,基類的protected成員為派生類的protected成員。受保護繼承
,基類的public和protected成員在派生類中為protected成員。私有繼承
,基類的的所有成員在派生類中為private成員。
在派生類的成員函數內部訪問基類的成員,訪問級別取決於基類的類型(可訪問public/private)
[Code4]在派生類定義與實現的外部,即派生類的對象或者繼承自派生類的類,派生類成員訪問級別取決於派生類繼承基類的訪問標號
[Code5]class
保留字定義的派生默認具有private繼承
,而用struct
保留字定義的類默認具有public繼承
。
友元關系不能繼承
。基類的友元對派生類的成員沒有特殊訪問權限。如果基類被授予友元關系,則只有基類具有特殊訪問權限,該基類的派生類不能訪問授予友元關系的類。
一個實例
。作用域操作符
也可以使用點或箭頭成員訪問操作符
。
/*假定 Bulk_item 定義了一個成員函數,接受一個 Bulk_item 對象的 引用和一個 Item_base 對象的引用,該函數可以訪問自己對象的 protected 成 員以及 Bulk_item 形參的 protected 成員,但是,它不能訪問 Item_base 形 參的 protected 成員。*/ void Bulk_item::memfcn(const Bulk_item &d, const Item_base &b) { // attempt to use protected member double ret = price; // ok: uses this->price ret = d.price; // ok: uses price from a Bulk_item object ret = b.price; // error: no access to price from an Item_base } /*d.price 的使用正確,因為是通過 Bulk_item 類型對象引用 price;b.price 的 使用非法,因為對 Base_item 類型的對象沒有特殊訪問訪問權限。*/
//error: a forward declaration must not include the derivation list class Bulk_item : public Item_base; //正確的前向聲明為: //forward declarations of both derived and nonderived class class Bulk_item; class Item_base;
Item_base *baseP = &derived; //calls version from the base class regardless of the dynamic type of baseP double d = baseP->Item_base::net_price(42);
class Base { public: void basemem(); protected: int i; // ... }; struct Public_derived : public Base { int use_base() { return i; } // ok: derived classes can access i // ... }; struct Private_derived : private Base { int use_base() { return i; } // ok: derived classes can access i };
Base b; Public_derived d1; Private_derived d2; b.basemem(); // ok: basemem is public d1.basemem(); // ok: basemem is public in the derived class d2.basemem(); // error: basemem is private in the derived class //派生訪問標號還控制來自非直接派生類的訪問: struct Derived_from Private : public Private_derived { // error: Base::i is private in Private_derived int use_base() { return i; } }; struct Derived_from_Public : public Public_derived { // ok: Base::i remains protected in Public_derived int use_base() { return i; } };
class Base { public: std::size_t size() const { return n; } protected: std::size_t n; }; class Derived : private Base { . . . }; /*在這一繼承層次中,size 在 Base 中為 public,但在 Derived 中為 private。為了使 size 在 Derived 中成為 public,可以在 Derived 的 public 部分增加一個 using 聲明。如下這樣改變 Derived 的定義,可以使 size 成員 能夠被用戶訪問,並使 n 能夠被從 Derived 派生的類訪問:*/ class Derived : private Base { public: // maintain access levels for members related to the size of the object using Base::size; protected: using Base::n; // ... };