1. 概述
- OOP核心思想:數據抽象、繼承、動態綁定
- C++核心思想:封裝、繼承、多態(與OOP一一對應)
2. 定義基類和派生類
基類通過在成員函數聲明前加上virtual關鍵字使得該函數執行動態綁定,派生類需要對這些基類的成員函數重新定義以覆蓋其舊定義,方法是在該函數的參數列表後面、或const關鍵字後面、或者引用限定符後面加上override關鍵字。 ——《C++ Primer》
-
基類
如果一個類被用做基類,則該類必須已經定義而非聲明;class Quote{ public: Quote()=default; Quote(const string book, double p): bookNo(book),price(p){} string isbn() const{return bookNo;} virtual double net_price(size_t n) const{return n*price;} virtual ~Quote()=default; private: string bookNo; protected: double price=0.0; };
-
派生類
class Bulk_quote : public Quote{ //派生列表 public: Bulk_quote()=default; Bulk_quate(const string&, double, size_t, double); double net_price(size_t)const override; private: size_t min_qty=0; double discount=0.0; }; //派生類構造函數 Bulk_quote::Bulk_quote(const string& book, double p, size_t qty, double disc): Quote(book,p),min_qty(qty),discount(disc){} double Bulk_quote::net_price(size_t cnt) const{ if(cnt>=min_qty) return cnt*(1-discount)*price; else return cnt*price; }
派生類構造函數
- 派生類的初始化:儘管在派生類對象中含有從基類中繼承來的成員,但是派生類不能直接初始化這些成員,必須使用基類的構造函數來初始化派生類的基類部分。
派生類向基類的類型轉換(只對引用和指針有效)
- 基類的引用和指針可以綁定在派生類對象上;也叫做派生類向基類的類型轉換;
Quote q; Bulk_quote b; Quote *p=&q; p=&b; Quote &r=b;
可以將基類的指針或引用綁定到派生類對象上有一層極爲重要的含義:當使用基類的指針或引用時,實際上我們並不清楚該指針或引用所綁定對象的真實類型,有可能是基類對象,也有可能是派生類對象。——《C++ Primer》
派生列表
-
類的聲明中不應包含派生列表,但是定義中要包含(如果有);
class Bulk_quote:public Quote; //錯誤 class Bulk_quote;//正確
防止繼承的發生
-
final
關鍵字,跟在類名後面,表示該類不能作爲基類;//NoDerived類和Last類都不能作爲其他類的基類 class NoDerived final {/*...*/}; class Last final : Base {/*...*/};
3. 類型轉換
靜態類型與動態類型
-
靜態類型:編譯時已知,是變量聲明時的類型或者表達式生成的類型;
-
動態類型:運行時可知,是變量或表達式表示的內存中的對象類型;
double print_total(ostream &os, const Quote &item, size_t n) { double ret = item.net_price(n); os<<item.isbn()<<ret<<endl; return ret; }
-
在
print_total
函數中,item
的靜態類型是Quote&
,這是在編譯時已知的;但是其動態類型在運行時才知道,如果傳入print_total
的實參是Quote
對象,則item
的動態類型也是Quote
,如果傳入的實參是Bulk_quote
對象,則item
的動態類型是Bulk_quote
; -
如果表達式既不是引用也不是指針,則其靜態類型和動態類型永遠一致;
不存在基類向派生類的隱式類型轉換
- 派生類的引用或指針不能綁定在基類上,即不存在基類向派生類的隱式類型轉換;
Quote base; Bulk_quote* b=&base;//錯誤 Bulk_quote& r=base;//錯誤
- dynamic_cast可以申請一個從基類向派生類的類型轉換;
對象之間不存在類型轉換
- 只能用來拷貝,不會發生類型轉換
Bulk_quote bulk; Quote item(bulk);//調用Quote(const Quote&)構造函數,拷貝bulk中的Quote部分成員; item=bulk;//調用Quote::operator=(const Quote&),只有bulk中的Quote部分成員被賦值給item,bulk_quote部分被切掉了
總結:
- 從派生類向基類的類型轉換隻對指針和引用類型有效;
- 基類向派生類不存在隱式類型轉換;
4. 虛函數
C++的多態性
- 動態綁定只有在通過指針或引用調用虛函數的時候纔會發生;
- 指針或引用調用非虛函數在編譯的時候綁定,不會發生動態綁定;
- 當使用普通類型(對象)調用虛函數在編譯的時候綁定,不會發生動態綁定;
派生類中的虛函數
-
override
關鍵字標記派生類中的虛函數,用來覆蓋基類中的對應虛函數; -
派生類中用於覆蓋的虛函數必須和基類中要被覆蓋的虛函數有相同的返回類型和形參列表;
-
final
關鍵字,表示不允許派生類對其進行覆蓋;struct D1{ void f1(int) const final; }; struct D2:D1{ void f1(int) const;//錯誤,f1不能被覆蓋 };
迴避虛函數的機制
-
如果使用基類指針或引用調用虛函數,則會發生動態綁定,如果我們不希望其發生動態綁定,而是強迫其執行虛函數的某個特定版本,則使用作用域運算符:
double print_total(ostream &os, const Quote &item, size_t n) { double ret = item.Quote::net_price(n); //強制執行基類的虛函數版本 os<<item.isbn()<<ret<<endl; return ret; }
-
如果派生類的虛函數需要調用其基類版本,則必須使用作用域運算符,否則在運行該調用的時候將被解析爲對派生類版本自身的調用,從而導致無限遞歸;
5. 抽象基類
純虛函數
- 純虛函數:通過在函數體的位置(聲明的分號前)書寫
=0
就可以將一個虛函數說明爲純虛函數;
class Disc_quote:public Quote{
public:
Disc_quote()=default;
Disc_quote(const string& book, double p, size_t qty, double disc):
Quote(book, p), quantity(qty),discount(disc){}
double net_price(size_t) const =0;//聲明net_price爲純虛函數
protected:
size_t quantity=0;
double discount=0.0;
};
- 純虛函數由其派生類來重寫;
含有純虛函數的類是抽象基類
- 抽象基類負責定義接口,不能直接實例化,不能創建一個抽象基類的對象。抽象基類只能作爲其他類的基類;
- 重寫了純虛函數的派生類可以被實例化,如果派生類沒有重寫純虛函數,則該派生類仍然是一個抽象基類;
抽象基類的作用:接口和實現分離
- 之前的Bulk_quote派生類實現了一個打折策略,但還可能要設計其他的打折策略,因此需要一個通用的接口,這個接口就是抽象基類,它不能被實例化,只能由其派生類去實現裏面具體的功能。接口和實現分離,提高了程序的安全性。
重寫Bulk_quote類:
class Bulk_quote : public Disc_quote{
Bulk_quote()=default;
Bulk_quote(const string& book, double p, size_t qty, double disc):
Disc_quote(book, p, qty, disc){}
//重寫純虛函數,實現新的折扣策略
double net_price(size_t) const override;
};
6. 訪問控制和繼承
- 一個類對其繼承而來的成員的訪問權限受兩個因素影響:
- 基類中的訪問說明符;
- 派生類的派生列表中的訪問說明符;派生列表中的訪問說明符表示繼承來的基類成員在派生類中的表示,即,如果訪問說明符是public,則該派生類從基類繼承的protected成員在該派生類中仍然是protected,從基類繼承的public成員仍然是public的;如果訪問說明符是private,則該派生類從基類繼承的public成員和protected成員都是private的。