《C++ Primer中文版》(第四版)資訊匯總(五)

來源:互聯網
上載者:User

編寫自己的物件導向類型或泛型型別需要對C++的充分理解,幸運的是,我們可以使用物件導向和泛型型別而無須瞭解它們的構建細節。本章主要介紹物件導向編程與泛型程式設計。

15、物件導向編程

物件導向編程基於三個基本概念:資料抽象、繼承和動態綁定。在C++中,用類進行資料抽象,用類派生從一個類繼承另一個類:衍生類別繼承基類的成員,動態綁定使編譯器能夠在運行時決定是使用基類中定義的函數還是衍生類別中定義的函數。繼承和動態綁定在兩個方面簡化了我們的程式:能夠容易地定義與其他類似但又不相同的新類,能夠更容易地編寫忽略這些相似類型之間區別的程式。

1、在C++中,基類必須指出希望衍生類別重定義哪些函數,定義為virtual的函數是基類期待衍生類別重新定義的,基類希望衍生類別繼承的函數不能定義為虛函數。

2、通過動態綁定我們能夠編寫程式使用繼承層次中任意類型的對象,無須關心對象的具體類型,使用這些類的程式無須區分函數是在基類還是在衍生類別中定義的。在C++中,通過基類的引用(或指標)調用虛函數時,發生動態綁定,引用(或指標)既可以指向基類對象也可以指向衍生類別對象,這一事實是動態綁定的關鍵。用引用(或指標)調用的虛函數在運行時確定,被調用的函數時引用(或指標)所指對象的實際類型所定義的。

3、保留字virtural的目的是啟用動態綁定,成員預設為非虛函數,對非虛函數的調用在編譯時間確定,為了指明函數為虛函數,在其傳回型別前面加上保留字virtual。除了建構函式之外,任意非static成員函數都可以是虛函數。保留字virtual只在類內部的成員函式宣告中出現,不能在類定義體外部出現的函數定義上。

class Item_base{<br />public:<br /> Item_base(const string &book="",double sales_price=0.0):isbn(book),price(sales_price){}<br /> string book() const{return isbn;}<br /> virtual double net_price(std::size_t n) const<br /> {return n*price;}<br /> virtual ~Item_base(){}<br />private:<br /> string isbn;<br />protected:<br /> double price;<br />};

4、protected成員可以被衍生類別對象訪問但不能被該類型的普通使用者訪問。

舉例:假定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)<br />{<br /> double ret=price;//ok,uses this->price<br /> ret=d.price;//ok,uses price from a Bulk_item object<br /> ret=b.price;//error,no access to price from an Item_base<br />}

5、定義衍生類別Bulk_item,每個Bulk_item對象包含四個資料成員:從Item_base繼承的isbn和price,自己定義的min_qty和discount.

class Bulk_item : public Item_base{<br />public:<br /> //redefines base vesion so as to implement ...<br /> double net_price(std::size_t) const;<br />private:<br /> std::size_t min_qty;<br /> double discount;<br />};

6、衍生類別一般會重定義所繼承的虛函數,如果衍生類別沒有重定義某個虛函數,則使用基類中定義的版本。衍生類別必須對想要重定義的每個繼承成員進行聲明。衍生類別中虛函數的聲明必須與基類中的定義方式完全符合,但有個例外:返回對基底類型的引用(或指標)的虛函數,衍生類別中的虛函數可以返回基類函數所傳回型別的衍生類別的引用(或指標) 。

7、如果需要聲明(但不實現)一個衍生類別,則聲明包含類名但不包含衍生的資料行表。

class Bulk_item:public Item_base;//Error:編譯錯誤</p><p>//正確的前向聲明<br />class Bulk_item;<br />class Item_base;

8、C++中函數電泳預設不適用動態綁定,要觸發動態綁定,必須滿足兩個條件:第一,只有指定為虛函數的成員函數才能進行動態綁定,成員函數預設為非虛函數,非虛函數不進行動態綁定;第二,必須通過基類類型的引用或指標進行函數調用。

(1) 從衍生類別到基類的轉換:因為每個衍生類別對象都包含基類部分,所以可將基類類型的引用綁定到衍生類別對象的基類部分,也可以用指向基類的指標指向衍生類別對象。

double print_total(const Item_base&,size_t);<br />Item_base item;<br />print_total(item,10);<br />Item_base *p=&item;<br />Bulk_item bulk;<br />print_total(bulk,10);<br />p=&bulk;

(2) 可以在運行時確定virtual函數的調用:

void print_total(ostream &os,const Item_base &item,size_t n)<br />{<br /> os<<item.net_price(n)<<endl;<br />}</p><p>Item_base base;<br />Bulk_item derived;<br />print_total(count,base,10);//calls Item_base::net_price<br />print_total(count,derived,10);//calls Bulk_item::net_price

(3) 覆蓋虛函數機制:有時候希望覆蓋虛函數機制並強制函數調用使用虛函數的特定版本,這時可以使用範圍操作符,如果衍生類別忽略了這樣做,則函數調用會在運行時確定並且將是一個自身調用,從而導致無窮遞迴。

Item_base *baseP=&derived;<br />double d=baseP->Item_base::net_price(42);

(4) 虛函數與預設實參:與其他任何函數一樣,虛函數也可以有預設實參。通過基類的引用或指標調用虛函數時,預設實參為在基類虛函式宣告中指定的值,如果通過衍生類別的指標或引用調用虛函數,則預設實參是在衍生類別的版本中聲明的值。

9、如果進行private或protected繼承,則基類成員的存取層級在衍生類別中比在基類中更受限:

class Base{<br />public:<br /> std::size_t size() const{return n;}<br />protect:<br /> std::size_t n;<br />};<br />class Derived:private Base{...}

在這個繼承層次中,size在Base中為public,但在Derived中為private。為了使size在Derived中成為public,可以在Derived中的public部分增加一個using聲明。如下這樣改變Derived的定義,可以使size成員能夠被使用者訪問,並使n能夠被Derived派生的類訪問。

class Derived: private Base{<br />public:<br /> using Base::size;<br />protected:<br /> using Base::n;<br />};

10、友元關係不能繼承:基類的友元對衍生類別的成員沒有特殊存取權限,如果基類被授予友元關係,則只有基類具有特殊存取權限,該基類的衍生類別不能訪問授予友元關係的類。如果衍生類別想要將自己成員的訪問權授予其基類的友元,衍生類別必須顯式地這樣做。

11、繼承與靜態成員:如果基類定義了static成員,則整個繼承層次中只有一個這樣的成員。無論從基類派生出多少個衍生類別,每個static成員只有一個執行個體。static成員遵循常規存取控制:如果成員在基類中為private,則衍生類別不能訪問它。假定可以訪問成員,則既可以通過基類訪問static成員,也可以通過衍生類別訪問static成員。

struct Base{<br /> static void statmem();<br />};</p><p>struct Derived:Base{<br /> void f(const Derived&);<br />};</p><p>void Derived::f(const Derived &derived_obj)<br />{<br /> Base::statmem();//ok,Base defines statmem<br /> Derived::statmem();//ok,Derived inherits statmem<br /> derived_obj.statmem();//accessed through Derived object<br /> statmem();//accessed through this class<br />}

12、理解基類類型和衍生類別型之間的轉換,對於理解物件導向編程在C++中如何工作非常關鍵:基類類型對象既可以作為獨立對象存在,也可以作為衍生類別的一部分存在,因此,一個基類對象可能是也可能不是一個衍生類別對象的部分,結果,沒有從基類引用或基類指標到衍生類別引用或者衍生類別指標的自動轉換。相對於引用或指標而言,對象轉換的情況更為複雜。雖然一般可以使用衍生類別型的對象對基類類型的對象進行初始化或賦值,但沒有從衍生類別型對象到基類類型對象的直接轉換。

(1) 衍生類別到基類的轉換:如果有一個衍生類別型的對象,則可以使用它的地址對基類類型的指標進行賦值或者初始化。

Item_base item;<br />Bulk_item bulk;<br />Item_base item(bulk);<br />item=bulk;<br />Item_base item=new Bulk_item();

(2) 基類到衍生類別的轉換:從基類到衍生類別的自動轉換是不存在的,需要衍生類別對象時不能使用基類對象。沒有從基類類型到衍生類別型的自動轉換,原因在於基類對象只能是基類對象,它不能包含衍生類別型的成員。

Item_base base;<br />Bulk_item* bulkP=&base;//error:can't convert base to derived<br />Bulk_item& bulkRef=base;//error:can't convert base to derived<br />Bulk_item bulk=base;//error:can't convert base to derived

甚至當基類指標或引用實際綁定到衍生類別對象時,從基類到衍生類別的轉換也存在限制:

Bulk_item bulk;<br />Item_base *itemP=&bulk;//ok,dynamic type is Bulk_item<br />Bulk_item *bulkP=itemP;//error,can't convert base to derived

13、本身不是衍生類別的基類,其建構函式和複製控制基本上不受繼承影響,繼承對基類建構函式的唯一影響是,在確定提供哪些建構函式時,必須考慮一類新客戶,像任意其他成員一樣,建構函式可以為protected或private,某些類需要只希望衍生類別使用的特殊建構函式,這樣的建構函式應定義為protected.

Item_base(const string &book="",double sales_price=0.0):isbn(book),price(sales_price){}

14、衍生類別的建構函式受繼承關係的影響,每個衍生類別建構函式除了初始化自己的資料成員之外,還要初始化基類。

(1) 合成的衍生類別預設建構函式,對於Bulk_item類,合成的預設建構函式會這樣執行:首先調用Item_base的預設建構函式,然後用常規變數初始化規則初始化Bulk_item的成員。

(2) 定義預設建構函式。

(3) 向基類建構函式傳遞實參,衍生類別建構函式的初始化列表只能初始化衍生類別的成員,不能直接初始化繼承成員。相反,衍生類別建構函式通過將基類包含在建構函式初始化列表中來間接初始化繼承成員。

class Bulk_item : public Item_base{<br />public:<br /> Bulk_item(const std::string& book,double sales_price,<br /> std::size_t qty=0,double disc_rate=0.0):<br /> Item_base(book,sales_price),<br /> min_qty(qty),discount(disc_rate){}<br /> ....<br />};

(4) 一個類只能初始化自己的直接基類,直接基類就是在衍生類別中指定的類。

15、複製控制和繼承

(1) 定義衍生類別複製建構函式:如果衍生類別定義了自己的複製建構函式,該複製建構函式一般應顯式使用基類複製建構函式初始化對象的基類部分:

class Base {/* ... */}<br />class Derived : public Base{<br /> Derived(const Derived& d):<br /> base(d){/* ... */}<br />};

(2) 衍生類別賦值操作符:如果衍生類別定義了自己的賦值操作符,則該操作符必須對基類部分進行顯式賦值。賦值操作符必須防止自身賦值。

Derived &Derived::operator=(const Derived &rhs)<br />{<br /> if(this!=&rhs){<br /> Base::operator=(rhs);<br /> }<br /> return *this;<br />}

(3) 衍生類別解構函式:解構函式的工作與複製建構函式和賦值操作符不同,衍生類別解構函式不負責撤銷基類對象的成員,編譯器總是顯式調用衍生類別對象基類部分的解構函式。每個解構函式只負責清除自己的成員,對象的撤銷順序與構造順序相反,首先運行衍生類別解構函式,然後按繼承層次依次向上調用各基類解構函式。

class Derived : public Base{<br />public:<br /> //Base::~Base invoked automatically<br /> ~Derived(){}<br />};

16、虛解構函式:自動調用基類部分的解構函式對於基類的設計有重要影響,如果刪除基類指標,則需要運行基類解構函式並清除基類的成員,如果對象實際上是衍生類別型的,則沒有定義該行為。為了保證運行適當的解構函式,基類中的解構函式必須為虛函數。

class Item_base{<br />public:<br /> virtual ~Item_base(){}<br />};<br />

如果解構函式為虛函數,那麼通過指標調用時,運行哪個解構函式將因指標所指物件類型的不同而不同。

Item_base *itemP=new Item_base;<br />delete itemP;//ok,destructor for Item_base called<br />itemP=new Bulk_item;<br />delete itemP;//ok,destructor for Bulk_item called

注意:建構函式和賦值操作符不是虛函數。建構函式不能定義為虛函數。雖然可以在基類中將成員函數operator=定義為虛函數,但這樣做並不影響衍生類別中使用的賦值操作符。將類的賦值操作符設為虛函數很可能會令人混淆,而且不會有什麼好處。

17、與基類成員同名的衍生類別成員將屏蔽對基類成員的直接存取。當然可以使用範圍操作符訪問被屏蔽的基類成員。

struct Base{<br /> Base():mem(0){}<br />protected:<br /> int mem;<br />};</p><p>struct Derived : Base{<br /> Derived(int i):mem(i){}<br /> int get_mem(){return mem;}<br /> int get_base_mem(){return Base::mem;//訪問基類成員}<br />protected:<br /> int mem;<br />};</p><p>Derived d(42);<br />count<<d.get_mem()<<endl;//輸出42<br />cout<<d.get_base_mem()<<endl;//輸出0

18、在基類和衍生類別中使用同一名字的成員函數,其行為與資料成員一樣:在衍生類別範圍中衍生類別成員將屏蔽基類成員,即使函數原型不同,基類成員也會被屏蔽。

struct Base{<br /> int memfcn();<br />};<br />struct Derived : Base{<br /> int memfcn(int);<br />};<br />Derived d;<br />Base b;<br />b.memfcn();//call Base::memfcn<br />d.memfcn(10);//calls Derived::memfcn<br />d.memfcn();//error:memfcn with no arguments is hidden<br />d.Base::memfcn();//ok,calls Base::memfcn

19、虛函數與範圍,虛函數在基類和衍生類別中必須擁有同一原型,如果基類成員與衍生類別成員接受的實參不同,就沒有辦法通過基類類型的引用或指標調用衍生類別函數。如下:

class Base{<br />public:<br /> virtual int fcn();<br />};</p><p>//D1中的fcn版本沒有重定義Base的虛函數fcn,相反,它屏蔽了基類的fcn<br />class D1 : public Base{<br />public:<br /> int fcn(int);<br />};</p><p>//D2重定義了它繼承的兩個函數:它重定義了Base中定義的fcn的原始版本並重定義了D1中定義的非虛版本<br />class D2 : public D1{<br />public:<br /> int fcn(int);<br /> int fcn();<br />};

20、C++中一個通用的技術是定義封裝類或控制代碼類。控制代碼類儲存和管理基類指標。指標所指對象的類型可以變化,它既可以指向基類類型對象又可以指向衍生類別型對象,使用者通過控制代碼類訪問繼承層次的操作。因為控制代碼類使用指標執行操作,虛成員的行為將在運行時根據控制代碼實際綁定的對象的類型而變化,因此,控制代碼的使用者可以獲得動態行為但無須操心指標的管理。       

16、模板與泛型程式設計

所謂泛型程式設計就是以獨立於任何特定類型的方式編寫代碼。模板是泛型程式設計的基礎,使用模板時無需瞭解模板的定義,本章將介紹怎樣定義自己的模板類和模板函數。

1、定義函數模板

(1) 以關鍵字template開始,後接模板形參表,模板形參表是用角括弧括住的一個或多個模板形參的列表,形參之間以逗號分隔。

template <typename T><br />int compare(const T &v1,const T &v2)<br />{<br /> if(v1<v2) return -1;<br /> if(v2<v1) return 1;<br /> return 0;<br />}

(2) 函數模板可以用非模板函數一樣的方式聲明為inline.說明符放在模板形參表之後、傳回型別之前,不能放在關鍵字template之前。

template<typename T> inline T min(const T&,const T&);//ok<br />inline template<typename T> T min(const T&,const T&);//error

2、定義類模板

(1) 以Queue類為例,先定義它的介面:以關鍵字template開頭,後接模板形參表。

template<class Type> class Queue{<br />public:<br /> Queue();<br /> Type &front();<br /> const Type &front() const;<br /> const push(const Type &);<br /> void pop();<br /> bool empty() const;<br />private:<br /> //....<br />};

(2) 使用類模板,與調用函數模板形成對比,使用類模板時,必須為模板形參顯式指定實參。

Queue<int> qi;<br />Queue< vector<double> > qc;<br />Queue<string> qs;

(3) 模板形參:像函數形參一樣,程式員為模板形參選擇的名字沒有本質含義。

template <class Glorp><br />int compare(const Glorp &v1,const Glorp &v2)<br />{<br /> if(v1 < v2) return -1;<br /> if(v2 < v1) return 1;<br /> return 0;<br />}

(4) 模板形參遵循常規名字屏蔽規則,如下所示,tmp不是double型,相反,tmp的類型是綁定到模板形參的任意類型。

typedef double T;<br />template <class T> T calc(const T &a,const T &b)<br />{<br /> T tmp=a;<br /> //...<br /> return tmp;<br />}

(5) 用作模板形參的名字不能在模板內部重用,這一限制意味著模板形參的名字只能在同一模板形參表中使用一次。

template <class T> T calc(const T &a,const T &b)<br />{<br /> typedef double T;//error,redeclares template parameter T<br /> T tmp=a;<br /> //....<br /> return tmp;<br />}

(6) 模板聲明,聲明必須指出函數或類是一個模板。

template <class T> int compare(const T&,const T&);

每個模板類型形參前面必須帶上關鍵字class或typename,每個非類型形參前面必須帶上類型名字,省略關鍵字或類型說明符是錯誤的:

template <typename T,U> T calc(const T&,const U&);//error

3、typename和class的區別:在函數模板形參中,關鍵字typename和class具有相同含義,可以互動使用,兩個關鍵字都可以在同一模板形參表中使用:

template <typename T,class U> calc(const T&,const U&);

4、非類型模板形參:模板形參不必都是類型,在調用函數時非類型形參將用值代替,值的類型在模板形參表中指定。

template <class T,size_t N> void array_init(T (&parm) [N])<br />{<br /> for(size_t i=0;i=N;++i)<br /> {<br /> parm[i]=0;<br /> }<br />}

5、函數模板的特化:我們並不總是能夠寫出對所有可能被執行個體化的執行個體都最合適的模板,如下面的代碼:

template <typename T><br />int compare(const T &v1,const T &v2)<br />{<br /> if(v1<v2) return -1;<br /> if(v2<v1) return 1;<br /> return 0;<br />}

如果用兩個const char*實參調用這個模板的定義,函數將比較指標值,它將告訴我們這兩個指標在記憶體中的相對位置,但沒有說明與指標所指數值的內容相關的任何事情,為了將compare函數應用於字串,必須提供一個知道怎樣比較C風格字串的特殊定義。這些版本是特化的。

模板特化是這樣一個定義,該定義中一個或多個模板形參的實際類型或實際值是指定的。如下:

template<><br />int compare<const char*>(const char* const &v1,const char* const &v2)<br />{<br /> return strcmp(v1,v2);<br />}

特化的聲明必須與對應的模板相匹配,現在當調用compare函數的時候,傳給它兩個字元指標,編譯器將調用特化版本。編譯器將為任意其他實參類型(包括普通char*)調用泛型版本。

const char *cp1="world",*cp2="hi";<br />int i1,i2;<br />compare(cp1,cp2);//調用特化模板<br />compare(i1,i2);//調用泛型版本模板

小結

函數模板是建立演算法庫的基礎,類模板是建立標準庫容器和迭代器類型的基礎。

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.