前言?
- 面向程序設計基于三個基本概念:數據抽象、繼承和動態綁定。繼承和動態綁定可以使得程序定義與其他類相似但是不完全相同的類;使用彼此相似的類編寫程序時候,可以在一定程度上忽略掉他們的區別。
OOP概述
- oop(面向程序的設計)核心思想是數據抽象、繼承和動態綁定。使用數據抽象可以將類和接口實現分離;使用繼承,可以定義相似的類型并對相似的類型關系進行建模;使用動態綁定,可以一定程度上忽略相似的內容,而采用統一的方式使用他們的對象。
繼承
-
通過繼承聯系在一起的類構成了一種層次關系。層次關系的根部叫做基類,其他類則直接或者間接從基類中繼承來,通過繼承得到的類叫做派生類。基類負責定義在層次關系中所有類共同具備的成員,而每一個派生類則會定義各自特有的屬性。
- 如果派生類想改變基類中包含的特定的函數,根據自己的實際情況進行定制化的開發,基類需要將這個函數聲明為虛函數。派生類需要使用類派生列表,明確指出他是從哪個或者哪些基類派生出來的。類派生的形式是:首先一個分號,后面緊跟著以逗號分隔的基類列表,其中每個基類的前面都可以有訪問說明符。
#include<iostream>
#include "Sales_item.h"
using namespace std;class Quote{
public:string isbn() const;virtual double net_price(size_t n) const;
};
class Bulk_quote : public Quote{//Bulk_quote繼承了Quote
public:double net_price(size_t n) const override;
};int main(){return 0;
}
- 派生類必須在其內部對于所有重新定義的虛函數進行聲明。派生類顯示的注明它將使用哪個成員函數改寫基類的虛擬函數,具體的措施是在該函數的形參列表之后增加一個override的關鍵字。
動態綁定
-
使用動態綁定可以使用相同的代碼分別處理基類和派生類。
double print_total(ostream &os,const Quote &item,size_t n){//計算并且打印給定數量的某種書籍所得的費用//根據傳入item的形參的對象類型調用Quote::net_price//或者使用Bulk_quote::net_pricedouble ret = item.net_price(n);os << "ISBN: " << item.isbn() << " # sold: " << n << "total due :" << ret << endl;return ret;
}
- 可以使用如下兩種的調用方式,因為下面的函數運行版本是由實參決定的,即在運行的時候選擇函數的版本,所以動態綁定有時候又被稱作運行時綁定。在使用基類的引用(或指針)調用一個虛函數的時候將會發生動態綁定。
//basic的類型是Quote;bulk的類型是Bulk_quote類型
print_total(cout,basic,20);//調用Quote的net_price
print_total(cout,bulk,20);//調用Bulk_quote的net_price
定義基類和派生類
- 完成對于Quote的定義
- 繼承關系中根節點的類通常會定義一個虛析構函數,即使該函數不執行任何實際操作也需要定義析構函數。
class Quote{
public:Quote() = default;Quote(const string &book,double sales_price):bookNo(book),price(sales_price){}string isbn() const{return bookNo};//返回給定數量的書籍的銷售總額//派生類負責改寫并且使用不同的折扣計算方法virtual double net_price(size_t n) const{return n * price;}virtual ~Quote() = default;//對于析構函數進行動態綁定
private:string bookNo;//書籍的ISBN序列號
protected:double price = 0.0;//代表普通狀態下不打折的價格
};
成員函數和繼承
- 基類需要將兩種成員函數區別開來:1,基類希望派生類進行覆蓋的函數;2,基類需要派生類直接繼承但是不要改變的函數。對于前者,基類通常將其定義為虛函數,在使用指針或者引用調用虛擬函數的時候,該調用將會動態綁定。根據引用或者指針所綁定的對象不同,該引用可能會執行基類或者執行某個派生類。
- 任何構造函數之外的非靜態函數都可以是虛函數。關鍵字virtual只可以出現在類內部的聲明之前而不能用于對于類外部的函數的定義。對于基類聲明的虛函數,則該函數在派生類中隱式地也是虛函數。成員函數沒有被聲明為虛函數,那么解析過程發生在編譯的時候而不是運行的時候;即虛函數解析過程發生在運行的時候,動態綁定,動態執行。
訪問控制與繼承
-
派生類可以繼承定義在基類中的成員,但是派生類成員不一定具有對于從基類繼承而來的成員的訪問權。派生類可以訪問公有成員但是不能訪問私有成員。protected用于派生類可以訪問,但是禁止其他用戶進行訪問。
定義派生類
- 派生類必須通過派生列表明確指出它是從哪個(哪些)基類派生而來的。類派生的形式是:首先一個分號,后面緊跟著以逗號分隔的基類列表,其中每個基類的前面都可以有訪問說明符之一,比如public、protected和private。
- 派生類必須將其繼承而來的成員函數中需要覆蓋的那些函數重新聲明
- 大多數類都繼承自一個類,這種機制叫做單繼承
派生類中的虛函數
- 派生類經常(但不是總是)覆蓋定義它繼承的虛函數,如果派生類中沒有覆蓋基類中的繆一個虛函數,派生類會直接繼承自基類中的對于虛函數的定義。
- 在形參列表后面、或者在const成員函數的const關鍵字后面、或者在引用成員函數的引用限定符后面添加一個關鍵字override。
派生類對象以及派生類向基類的類型轉換
- 派生類所具有的對象,不僅包含繼承自基類的對象還具有自己新創建的對象。但是在一個對象中,繼承自基類的部分和派生類自定義的部分不一定是連續存儲的,就是在物理存儲邏輯上,二者不一定占據連續一段存儲空間。
Quote item; //基類對象Bulk_quote bulk; //派生類對象Quote *p = &item; //p指向item(Quote)對象p = &bulk; //p指向bulk(Bulk_quote)對象的Quote部分Quote *r = &bulk; // r綁定到bulk(Bulk_quote)對象的Quote部分
- 上面這段代碼通常稱之為派生類到基類的類型轉換,這個過程是由編譯器隱式執行的。即可以將把派生類對象或者派生類對象的引用在需要基類引用的地方。同樣可以將派生類對象的指針用在需要基類指針的地方。
派生類構造函數
-
盡管派生類對象中含有從基類繼承而來的成員,但是派生類不可以直接初始化這些成員,派生類需要使用基類的構造函數來初始化他的基類部分。派生類對象的基類部分與派生類對象的自己的數據成員都是在構造函數的初始化階段執行初始化操作的。
-
派生類將自己繼承自基類的那部分交由基類進行初始化,然后初始化派生類自己定義的成員,最后運行派生類的構造函數。除非我們特定指出,否則派生類對象的基類部分會像數據成員一樣被默認初始化。但是如果想使用其他的基類構造函數,需要以類名加圓括號的實參列表的形式為構造函數提供初始化數值。這些實參會幫助編譯器決定使用哪個構造函數來初始化派生類對象的基類部分。
派生類使用基類的成員
- 派生類可以訪問基類的公有成員和受保護的成員。即派生類的作用域嵌套在基類的作用域之內。即對于派生類的成員來說,使用派生類的成員和使用基類成員的方式是沒有不同的。
繼承與靜態成員
- 基類定義了一個靜態成員,則在整個繼承體系中只存在該成員的唯一定義。從基類中派生出多少個派生類,對于每個靜態成員來說都只存在唯一的實例。
派生類的聲明
- 派生類的聲明需要包含類名但是不包含他的派生列表。
被用作基類的類
- 如果要將某一個類用作基類,則該類必須已經定義而不是簡簡單單的聲明。
- 一個類不可以派生它本身。即一個類是基類,同時他也可以是一個派生類。比如D1是Base的派生類,而D2又是D2的派生類。那么Base是D1的直接基類,同時也是D2的間接基類。
- 對于最終的一個派生類來說,他會包含直接基類的所有成員和每一個間接基類的所有對象。
防止繼承的發生
- 防止自定義的類被繼承,那么就在類的名字后面跟上一個關鍵字final。
- class NoDerived final{} //NoDerived不能作為基類
類型轉換與繼承
- 常規情形下,將引用/指針綁定到一個對象上,則引用或者指針的類型應該與對象的類型是一致的。或者對象的類型含有一個可以接受的const類型轉換規則。存在繼承關系的類是一個特殊的存在。
- 基類的指針或者引用綁定到派生類的對象上,有一層很重要的含義就是不確定被綁定的對象的真實類型是基類還是派生類。
靜態類型與動態類型
- 表達式的靜態類型是編譯的時候已知的,是變量聲明時的類型或則表達式生成的類型;動態類型是變量或者表達式表示內存中的對象類型,動態類型直到運行時才可知。
- 如果表達式既不是指針也不是引用,那么他的動態類型和靜態類型是一致的。
不存在從基類向派生類的隱式類型轉換
- 因為一個基類對象是派生類中的一部分,所以不存在基類向派生類的類型轉換,因為基類不可以訪問派生類中不存在的成員。
- 既是一個基類指針或者引用綁定在一個派生類度向上,也不可以執行從基類到派生類的轉換。
- 如果基類中存在多個虛擬函數,在進行類型轉換的時候,可以使用dynamic_cast請求一個類型轉換,該轉換的安全檢查將會在運行的時候進行檢查。如果我們已經知道將某個基類裝換為派生類是安全的,可以使用static_cast來強制覆蓋掉編譯器的檢查工作。
在對象之間不存在類型轉換
- 使用派生類對象為一個基類對象進行初始化或者賦值的時候,只有派生類對象中的基類部分會被拷貝、移動和賦值;他的派生類部分將會被忽略掉。
?
?
?
?
?
?
?
?
?
?