[前篇]C++筆記:物件導向編程基礎
轉換與繼承衍生類別到基類到自動轉換
衍生類別指標 ----> 基類指標 --/-> 衍生類別指標
衍生類別對象 --/-> 基類對象 --/-> 衍生類別對象
衍生類別對象的引用或指標可以自動轉換為基類子物件的引用或指標。(因為衍生類別對象也是基類對象)
沒有從基類引用或指標到衍生類別引用或指標的自動轉換。(一個基類對象可能是也可能不是一個衍生類別對象的部分)
- 雖然一般可以使用衍生類別型的對象對基類類型的對象進行初始化或賦值,但對象轉換的情況更為複雜,
沒有從衍生類別型對象到基類類型對象的直接轉換。
- 可以使用
衍生類別對象的地址對基類類型的指標進行賦值或初始化。
- 可以使用
衍生類別型的引用或對象初始化基類類型的引用。
引用轉換不同於轉換對象
- 將對象傳給希望接受
引用的函數時,引用直接綁定到該對象,雖然看起來在傳遞對象,實際上實參是該對象的引用,對象本身未被複製,並且轉換不會在任何方面改變衍生類別型對象,該對象仍是衍生類別型對象。
- 將衍生類別對象傳給希望接受基
類類型對象(而不是引用)的函數時,形參的類型是固定的,在編譯時間和運行時形參都是基類類型對象。如果用衍生類別型對象調用這樣的函數,則該衍生類別對象的基類部分被複製到形參。
- 一個是
衍生類別對象轉換為基類類型引用,一個是用衍生類別對象對基類對象進行初始化或賦值,理解它們之間的區別很重要。
用衍生類別對象對基類對象進行初始化或賦值
- 對基類對象進行初始化或賦值,實際上是在調用函數:初始化時調用建構函式,賦值時調用賦值操作符
- 用衍生類別對象對基類對象進行初始化或賦值時,有兩種可能性。
衍生類別到基類轉換到可訪問性
- 像繼承的成員函數一樣,從衍生類別到基類的轉換可能是也可能不是可訪問的。轉換是否訪問取決於在衍生類別的衍生的資料行表中指定的訪問標號。
- 如果是public繼承,則使用者代碼和後代類都可以使用衍生類別到基類的轉換。
- 如果類是使用private或protected繼承派生的,則使用者代碼不能將衍生類別型對象轉換為基類對象。
- 如果是private繼承,則從private繼承類派生的類不能轉換為基類。
- 如果是protected繼承,則後續衍生類別的成員可以轉換為基類類型。
- 要確定到基類的轉換是否可訪問,可以考慮
基類的public成員是否訪問,如果可以,轉換是可訪問的,否則,轉換是不可訪問的。
- 無論是什麼派生訪問標號,衍生類別本身都可以訪問基類的public成員,因此,衍生類別本身的成員和友元總是可以訪問衍生類別到基類的轉換。
詳解
衍生類別到基類的轉換
- 從
基類到衍生類別的自動轉換是不存在的
- 原因在於基類對象只能是基類對象,它不能包含衍生類別型成員。如果允許用基類對象給衍生類別型對象賦值,那 麼就可以試圖使用該衍生類別對象訪問不存在的成員。
基類指標或引用實際綁定到綁定到衍生類別對象時,從基類到衍生類別的轉換也存在限制
- 編譯器在編譯時間無法知道特定轉換在運行時實際上是安全的。編譯器確定轉換是否合法,只看指標或引用的靜態類型
- 如果
知道從基類到衍生類別的轉換是安全的,就可以使用static_cast強制編譯器進行轉換。或者,可以用 dynamic_cast申請在運行時進行。
建構函式和複製控制
- 每個衍生類別對象由衍生類別中定義的
(非static)成員加上一個或多個基類子物件構成,這一事實影響著衍生類別型對象的構造、複製、賦值和撤銷。
- 當構造、複製、賦值和撤銷衍生類別對象時,
也會構造、複製、賦值和撤銷這些基類當子物件。
- 建構函式和複製控製成員不能繼承,每個類定義自己的建構函式和複製控製成員。如果類
不定義自己的預設建構函式和複製控製成員,就將使用合成版本。
- 只希望衍生類別使用的特殊建構函式,基類當建構函式應定義為protected。
衍生類別的建構函式
合成的衍生類別預設建構函式
- 衍生類別的合成預設建構函式與非派生的建構函式只有一點不同:除了初始化衍生類別的資料成員之外,它還初始化衍生類別對象的基類部分。基類部分由基類的預設建構函式初始化。
- 對於Bulk_item 類,
合成的預設建構函式會這樣執行:
- 調用
Item_base的預設建構函式,將isbn成員初始化空串,將price成員初始化為 0。
- 用常規變數初始化規則
初始化Bulk_item的成員,也就是說,qty和discount成員會是未初始化的。
定義預設建構函式[Code3]
- Code3中隱式調用Item_base的預設建構函式初始化對象的基類部分,再初始化Bulk_item部分的成員並執行建構函式的函數體。
- 向基類建構函式傳遞實參[Code4]
- 建構函式初始化列表為類的基類和成員提供初始值,它並不指定初始化的執行次序。
首先初始化基類,然後根據聲明次序初始化衍生類別的成員。
- 在衍生類別建構函式中使用預設實參。[Code5]
只能初始化直接基類
複製控制和繼承
- 如果衍生類別顯式定義自己的複製建構函式或賦值操作符,則該定義將完全覆蓋預設定義。
被繼承類的複製建構函式和賦值操作符負責對基類成分以及類自己的成員進行複製或賦值。
- 如果衍生類別定義了自己的複製建構函式,該複製建構函式一般應
顯式使用基類複製建構函式初始化對象的基類部分,否則運行基類的預設建構函式,則新構造的對象將具有奇怪的配置:它的Base部分將儲存預設值,而它的 Derived成員是另一對象的副本。[Code6]
- 如果衍生類別定義了自己的
賦值操作符,則該操作符必須對基類部分進行顯式賦值。[Code7]
- 解構函式的工作與複製建構函式和賦值操作符不同,
衍生類別解構函式不負責撤銷基類對象的成員。
繼承情況下的類範圍
- 在繼承情況下,衍生類別的範圍嵌套在基類範圍中。如果不能在衍生類別範圍中確定名字,就在外圍基類範圍中尋找該名字的定義。
對象、引用或指標的靜態類型決定了對象能夠完成的行為。
- 當靜態類型和動態類型可能不同的時候,就像使用基類類型的引用或指標時可能會發生的, 靜態類型仍然決定著可以使用什麼成員。[Code8]
- 與基類成員同名的衍生類別成員將屏蔽對基類成員的直接存取。
- 可以使用範圍操作符訪問被屏蔽的基類成員,例如:
Base::mem
- 設計衍生類別時,只要可能,最好避免與基類成員的名字衝突。
- 基類和衍生類別中使用同一名字的成員函數,在衍生類別範圍中衍生類別成員將
屏蔽基類成員。即使函數原型不同,基類成員也會被屏蔽。[Code9]
- 局部範圍中聲明的函數不會重載全域範圍中定義的函數,同樣,衍生類別中定義的函數也不重載基類中定義的成員。
- 通過衍生類別對象調用函數時,實參必須與衍生類別中定義的版本相匹配,只有在衍生類別根本沒有定義該函數時,才考慮基類函數。
- 衍生類別可以重定義所繼承的0個或多個版本。
- 如果衍生類別重定義了重載成員,則通過衍生類別型
只能訪問衍生類別中重定義的那些成員。
- 如果衍生類別想通過自身類型使用的重載版本,則衍生類別必須要麼
重定義所有重載版本,要麼一個也不重定義。
- 有時類需要僅僅重定義一個重載集中某些版本的行為,並且想要繼承其他版本的含義,可以為重載成員提供
using聲明
- 一個 using 聲明
只能指定一個名字,不能指定形參表,因此,為基類成員函數名稱而作的using聲明將該函數的所有重載執行個體加到衍生類別的範圍。將所有名字加入範圍之後,衍生類別只需要重定義本類型確實必 須定義的那些函數,對其他版本可以使用繼承的定義。
容器與繼承
- 希望使用容器(或內建數組)儲存因繼承而相關聯的對象。但是,對象不是多態的,這一事實對將容器用於繼承層次中的類型有影響。[Code10]
- 若是基類容器則衍生類別會被截斷,如是衍生類別容器則不能放入基類
- 唯一可行的選擇可能是
使用容器儲存對象的指標。但代價是需要使用者面對管理對象和指標的問題,使用者必須保證只要容器存在,被指向的對象就存在。如果對象是動態分配的,使用者必須保證在容器消失時適當地釋放對象。
CodeCode1:基類顯式定義了將衍生類別型對象複製或賦值給基類對象的含義
class Derived; class Base { public: Base(const Derived&); // create a new Base from a Derived Base &operator=(const Derived&); // assign from a Derived // ... };
Code2:基類一般(顯式或隱式地)定義自己的複製建構函式和賦值操作符
Item_base item; // object of base typeBulk_item bulk; // object of derived type// ok: uses Item_base::Item_base(const Item_base&) constructorItem_base item(bulk); // bulk is "sliced down" to its Item_base portion// ok: calls Item_base::operator=(const Item_base&)item = bulk; // bulk is "sliced down" to its Item_base portion
Code3:定義預設建構函式
class Bulk_item : public Item_base { public: Bulk_item(): min_qty(0), discount(0.0) { } // as before };
Code4:向基類建構函式傳遞實參
class Bulk_item : public Item_base { public: Bulk_item(const std::string& book, double sales_price, std::size_t qty = 0, double disc_rate = 0.0): Item_base(book, sales_price), min_qty(qty), discount(disc_rate) { } // as before };
Code5:在衍生類別建構函式中使用預設實參
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), min_qty(qty), discount(disc_rate) { } // as before };//這裡為每個形參提供了預設值,因此,可以用 0 至 4 個實參使用該建構函式。
Code6:定義衍生類別的複製建構函式,一般應顯式使用基類複製建構函式初始化對象的基類部分
class Base { /* ... */ }; class Derived: public Base { public: // Base::Base(const Base&) not invoked automatically Derived(const Derived& d): Base(d) /* other member initialization */ { /*... */ }};/*初始化函數 Base(d) 將衍生類別對象 d 轉換為它的基類部分的引用,並調用基類複製建構函式。如果省略基類初始化函數,如下代碼:*/// probably incorrect definition of the Derived copy constructorDerived(const Derived& d) /* derived member initizations */{/* ... */ }/*效果是運行 Base 的預設建構函式初始化對象的基類部分。假定 Derived 成員的初始化從 d 複製對應成員,則新構造的對象將具有奇怪的配置:它的 Base 部分將儲存預設值,而它的 Derived 成員是另一對象的副本。*/
Code7:衍生類別的賦值操作符
// Base::operator=(const Base&) not invoked automatically Derived &Derived::operator=(const Derived &rhs) { if (this != &rhs) { Base::operator=(rhs); // assigns the base part // do whatever needed to clean up the old value in the derived part // assign the members from the derived } retrun *this;}/*基類操作符將釋放左運算元中基類部分 的值,並賦以來自 rhs 的新值。該操作符執行完畢後,接著要做的是為衍生類別 中的成員賦值。*/
Code8:名字尋找在編譯時間發生
/*例如,可以給 Disc_item 類增加一個 成員,該成員返回一個儲存最小(或最大)數量和折扣價格的 pair 對象:*/ class Disc_item : public Item_base { public: std::pair<size_t, double> discount_policy() const { return std::make_pair(quantity, discount); } // other members as before };/*只能通過 Disc_item 類型或 Disc_item 衍生類別型的對象、指標或引用訪問 discount_policy:*/ Bulk_item bulk;Bulk_item*bulkP=&bulk; //ok:staticanddynamictypesarethe sameItem_base *itemP = &bulk; // ok: static and dynamic types differ bulkP->discount_policy(); // ok: bulkP has type Bulk_item* itemP->discount_policy(); // error: itemP has type Item_base*/*重新定義 itemP 的訪問是錯誤的,因為基類類型的指標(引用或對象)只 能訪問對象的基類部分,而在基類中沒有定義 discount_policy 成員。*/
Code9:範圍與成員函數
struct Base { int memfcn(); }; struct Derived : Base { int memfcn(int); // hides memfcn in the base };Derived d; Base b;b.memfcn();// ok: calls Base::memfcnd.memfcn(10);// calls Base::memfcnd.memfcn();// calls Derived::memfcnd.Base::memfcn(); // error: memfcn with no arguments is hidden/*可能比較奇怪的是第三個調用:d.memfcn(); // error: Derived has no memfcn that takes no arguments要確定這個調用,編譯器需要尋找名字 memfcn,並在 Derived 類中找到。 一旦找到了名字,編譯器就不再繼續尋找了。這個調用與 Derived 中的 memfcn 定義不匹配,該定義希望接受 int 實參,而這個函數調用沒有提供那樣的實參, 因此出錯。*/
From:
http://blog.csdn.net/liufei_learning/article/details/22364861