OOP1(定義基類和派生類)
面向對象程序設計基於三個基本概念:數據抽象,繼承和動態綁定
數據抽象是一種依賴於接口和實現分離的編程技術。繼承和動態綁定對程序的編號有兩方面的影響:一是我們可以更容易地定義與其它類相似但不完全相同的類;二是在使用這些彼此相似的類編寫程序時,我們可以在一定程度上忽略掉它們的區別。
在 c++ 語言中,當我們使用基類的引用或指針調用一個虛函數時將發生動態綁定
定義基類:
1 class Quote { 2 public: 3 Quote() = default; 4 Quote(const std::string &book, double sales_price) :View Code5 bookNo(book), price(sales_price) {} 6 7 std::string isbn() const { 8 return bookNo; 9 } 10 11 virtual double net_price(std::size_t n) const {//定義成虛函數,運行2時進行動態綁定 12 return n * price; 13 } 14 15 virtual ~Quote() = default;//基類通常都應該定義一個虛析構函數,即使該函數不執行任何實際操作16 17 private: 18 std::string bookNo;//書籍的isbn編號 19 20 protected://可被派生類訪問 21 double price = 0.0;//代表普通狀態下不打折的價格 22 };
註意:基類通常都應該定義一個虛析構函數,即使該函數不執行任何實際操作
基類通過在其成員函數的聲明之前加上關鍵字 virtual 使得該函數執行動態綁定。任何構造函數之外的非靜態函數都可以是虛函數。如果基類把一個函數聲明成虛函數,則該函數在派生類中隱式地也是虛函數
派生類可以繼承定義在基類中的成員,但是派生類不一定有權訪問從基類繼承而來的成員。派生類只能訪問公有成員和受保護的成員,不能訪問私有成員
定義派生類:
1 class Bulk_quote : public Quote { 2 public: 3 Bulk_quote() = default; 4 Bulk_quote(const std::string&, double, std::size_t, double); 5 6 double net_price(std::size_t) const override;//override顯式註明該成員函數覆蓋它繼承的虛函數 7 8 // ~Bulk_quote(); 9 10 private: 11 std::size_t min_qty = 0;//適用折扣政策的最低購買量 12 double discount = 0.0;//以小數表示的折扣額 13 14 };View Code
註意:override 顯式註明該成員函數覆蓋它繼承的虛函數(只能對繼承自虛函數的成員使用 override 關鍵字)
派生類對象及派生類向基類的類型轉換:
因為在派生類對象中含有與其基類對應的組成部分,所以我們能把派生類的對象當作基類對象使用,而且我們也能將基類的指針或引用綁定到派生類對象中的基類部分上:
1 Quote item;//基類對象 2 Bulk_quote bulk;//派生類對象 3 4 Quote *p = &item;//p指向Quote對象 5 p = &bulk;//p指向bulk中的Quote部分 6 7 Quote &r = bulk;//r綁定到bulk中的Quote部分View Code
註意:這種轉換通常稱為派生類到基類的類型轉換。和其它類型轉換一樣,編譯器會隱式地執行派生類到基類的轉換。這種隱式特性意味著我們可以把派生類對象或者派生類對象的引用用在需要基類引用的地方;同樣的,我們可以把派生類對象的指針用在需要基類指針的地方
派生類構造函數:
盡管在派生類對象中含有從基類繼承而來的成員,但是派生類並不能直接初始化這些成員。和其它創建了基類對象的代碼一樣,派生類也必須使用基類的構造函數類初始化它的基類部分
1 Bulk_quote::Bulk_quote(const std::string &book, double p, std::size_t qty, double disc) : 2 Quote(book, p), min_qty(qty), discount(disc) {}View Code
註意:首先初始化基類的部分,然後按照聲明的順序依次初始化派生類的成員
派生類中基類數據成員如果沒有顯式構造,則會執行默認初始化
派生類使用基類的成員:
1 double Bulk_quote::net_price(size_t cnt) const { 2 if(cnt >= min_qty) return cnt * (1 - discount) * price; 3 return cnt * price; 4 }View Code
註意:派生類可以訪問基類的公有成員和受保護成員
派生類的作用域嵌套在基類的作用域之內。因此,對於派生類的一個成員來說,它使用派生類成員的方式與使用基類成員的方式是一樣的
繼承與靜態成員:
1 #include <iostream> 2 using namespace std; 3 4 class Base{ 5 public: 6 // Base(); 7 // ~Base(); 8 static void statmem(); 9 10 }; 11 12 void Base::statmem() { 13 // 14 } 15 16 class Derived : public Base{ 17 public: 18 // Derived(); 19 // ~Derived(); 20 void f(const Derived&); 21 }; 22 23 void Derived::f(const Derived &derived_obj) { 24 Base::statmem();//正確,Base定義了stamem 25 Derived::statmem();//正確,Derived繼承了stamem 26 derived_obj.statmem();//通過Derived對象訪問 27 statmem();//通過this對象訪問 28 } 29 30 int main(void){ 31 32 }View Code
註意:如果基類定義了一個靜態成員,則在整個繼承體些中只存在該成員的唯一定義。無論從基類中派生出多少個派生類,對於每個靜態成員來說都只存在唯一的實例
靜態成員遵循通用的訪問控制規制,如果基類中的成員是 private 的,則派生類無權訪問它
派生類的聲明:
派生類的聲明中包含類名但不包含派生列表:
1 class Bulk_quote : public Quote;//錯誤,派生列表不能出現在這裏 2 class Bulk_quote;//正確View Code
被用作基類的類:
如果我們想將某個類用作基類,則該類必須已經定義而非僅僅聲明:
1 class Quote; //聲明但未定義 2 class Bulk_quote : public Quote {//錯誤,Quote必須被定義 3 // 4 }View Code
一個類是基類,同時也可以是一個派生類。最終的派生類將包含它的直接基類的子對象以及每個簡介基類的子對象
防止繼承的發生:
在類名後面跟一個關鍵字 final 能防止繼承發生:
1 class NoDerived final {};//NoDerived不能作為基類 2 class Base {}; 3 4 class Last final : Base {};//Last是final的,我們不能繼承Last 5 6 // class Bad : NoDerived {};//錯誤,NoDerived是final的 7 // class Bad2 : Last {};//錯誤,Last是final的View Code
類型轉換與繼承:
可以將基類的指針或引用綁定到派生類對象上,當使用基類的引用或指針時,實際上我們並不清楚該引用或指針所綁定對象的真實類型。該對象可能是基類的對象,也可能是派生類的對象
和內置指針一樣,只能指針類也支持派生類想基類的類型轉換,這意味著我們可以將一個派生類對象的指針存儲在一個基類的智能指針內
靜態類型與動態類型:
當使用存在繼承關系的類型時,必須將一個變量或其它表達式的靜態類型與該表達式表示對象的動態類型區分開來。表達式的靜態類型在編譯時總是已知的,它是變量聲明時類型或表達式生成的類型;動態類型則是變量或表達式的內存中對象的類型。動態類型直到運行時才可知。
如果達式既不是引用也不是指針,則它的動態類型永遠與靜態類型一致。而基類的指針或引用的靜態類型可能與其動態類型不一致。如:
1 Bulk_quote bulk;//派生類對象 2 Quote q* = &bulk;//q的靜態類型為Quote,動態類型為Bulk_quote 3 Quote &p = bulk;//p的靜態類型為Quote,動態類型為Bulk_quoteView Code
不存在從基類向派生類的隱式類型轉換:
之所以存在派生類向基類的類型轉換是因為每個派生類對象都包含一個基類部分,而基類的引用或指針可以綁定到該基類部分上。但基類不一定包含派生類(派生類可能定義了新的成員),所以如果我們將一個基類的對象向派生類的類型轉換,則我們有可能會使用基類中沒有的成員,所以不存在從基類向派生類的自動類型轉換:
1 Quote base; 2 // Bulk_quote *bulkp = &base;//錯誤,不能將基類轉換成派生類 3 // Bulk_quote &bulkref = base;//錯誤,不能將基類轉換成派生類 4 5 // 即使一個基類指針或引用綁定在一個派生類對象上,也不能執行從基類向派生類的轉換 6 Bulk_quote bulk; 7 Quote *itemp = &bulk;//正確,從派生類轉換到基類 8 // Bulk_quote *bulkp = itemp;//錯誤,不能將基類轉換成派生類View Code
註意:即使一個基類指針或引用綁定在一個派生類對象上,也不能執行從基類向派生類的轉換
我們可以通過 dynamic_cast 或 static_cast 顯式地將一個基類對象轉換為派生類類型。詳見百度百科:https://baike.baidu.com/item/dynamic_cast/4473047?fr=aladdin
在對象之間不存在類型轉換:
派生類向基類的自動類型轉換只對指針或引用類型有效,在派生類類型和基類類型之間不存在這樣的轉換。但我們可以通過拷貝構造函數、移動構造函數、拷貝賦值運算符或移動賦值運算符將一個派生類類型轉換成基類類型,因為這些拷貝控制成員中通常包含一個本類類型的 const 引用或右值引用:
1 #include <iostream> 2 using namespace std; 3 4 class Quote { 5 public: 6 Quote() = default; 7 Quote(const std::string &book, double sales_price) : 8 bookNo(book), price(sales_price) {} 9 10 std::string isbn() const { 11 return bookNo; 12 } 13 14 virtual double net_price(std::size_t n) const {//定義成虛函數,運行2時進行動態綁定 15 return n * price; 16 } 17 18 virtual ~Quote() = default;//基類通常都應該定義一個虛析構函數,即使該函數不執行任何實際操作 19 20 private: 21 std::string bookNo;//書籍的isbn編號 22 23 protected://可被派生類訪問 24 double price = 0.0;//代表普通狀態下不打折的價格 25 }; 26 27 class Bulk_quote : public Quote { 28 public: 29 Bulk_quote() = default; 30 Bulk_quote(const std::string&, double, std::size_t, double); 31 32 double net_price(std::size_t) const override;//override顯式註明該成員函數覆蓋它繼承的虛函數 33 34 // ~Bulk_quote(); 35 36 private: 37 std::size_t min_qty = 0;//適用折扣政策的最低購買量 38 double discount = 0.0;//以小數表示的折扣額 39 40 }; 41 42 Bulk_quote::Bulk_quote(const std::string &book, double p, std::size_t qty, double disc) : 43 Quote(book, p), min_qty(qty), discount(disc) {} 44 45 double Bulk_quote::net_price(size_t cnt) const { 46 if(cnt >= min_qty) return cnt * (1 - discount) * price; 47 return cnt * price; 48 } 49 50 int main(void){ 51 52 Bulk_quote bulk;//派生類對象 53 Quote item(bulk);//使用合成拷貝構造函數Quote::Quote(const Quote&) 54 item = bulk;//使用合成拷貝賦值運算符Quote& Quote::operator=(const Quote&) 55 56 //顯然我們不能將基類類型通過拷貝控制成員轉換成派生類對象 57 // Quote cnt; 58 // Bulk_quote gg(cnt); 59 60 return 0; 61 }View Code
註意:在上述過程中會忽略 Bulk_quote 中新定義的成員,即 bulk 中只有從基類中繼承來的部分被賦值給了 item
顯然,我們不能將基類類型通過拷貝控制成員轉換成派生類對象
OOP1(定義基類和派生類)