C++程式設計思想筆記之六 (轉)

worldblog發表於2007-12-13
C++程式設計思想筆記之六 (轉)[@more@]

第13章  繼承和組合

一、繼承和組合

組合:簡單地建立一個包含已存在的類的新類,這稱為組合,因為這個新類是由已存在類的物件組合的。對於新類的public介面,包含對嵌入物件的使用,但不必模仿這個嵌入物件的介面。

繼承:建立一個新類作為一個已存在類的型別,採取這個已存在類的形式,對它增加程式碼,但不修改它。

二、建構函式和解構函式的次序

構造在類層次的最根處開始,而在每一層,首先基類建構函式,然後呼叫成員物件建構函式。呼叫解構函式則嚴格按照建構函式相反的次序—這是很重要的,因為要考慮潛在的相關性。對於成員物件,建構函式呼叫的次序完全不受在建構函式
的初始化表中次序的影響。該次序是由成員物件在類中宣告的次序所決定的。

三、非自動繼承的函式

建構函式和解構函式是用來處理物件的建立和析構的,它們只知道對在它們的特殊層次的物件做什麼。所以,在整個層次中的所有的建構函式和解構函式都必須被呼叫,也就是說,建構函式和解構函式不能被繼承。operator= 也不能被繼承,因為它完成類似於建構函式的活動。

四、組合與繼承的選擇

組合通常在希望新類內部有已存在類時使用,而不希望已存在類作為它的介面。這就是說,嵌入一個計劃用於實現新類效能的物件,而新類的看到的是新定義的介面而不是來自老類的介面。

因為正在由一個已存在的類做一個新類,並且希望這個新類與已存在的類有嚴格相同的介面(希望增加任何我們想要加入的其他成員函式),所以能在已經用過這個已存在類的任何地方使用這個新類,這就是必須使用繼承的地方。

五、對私有繼承成員公有化


第14章  多型和虛擬函式

C++如何實現晚捆綁

對每個包含虛擬函式的類建立一個表(稱為VTABLE)。在VTABLE中,編譯器放置特定類的虛擬函式地址.在每個帶有虛擬函式的類中,編譯器秘密地置一指標,稱為vpointer(縮寫為VPTR),指向這個物件的VTABLE。透過基類指標做虛擬函式呼叫時(也就是做多型呼叫時),編譯器靜態地插入取得這個VPTR,並在VTABLE表中查詢函式地址的程式碼,這樣就能呼叫正確的函式使晚捆綁發生。為每個類設定VTABLE、初始化VPTR、為虛擬函式呼叫插入程式碼,所有這些都是自動發生的,所以我們不必擔心這些。利用虛擬函式,這個物件的合適的函式就能被呼叫,哪怕在編譯器還不知道這個物件的特定型別的情況下。

 

如果沒有資料成員, C + +編譯器會強制這個物件是非零長度,因為每個物件必須有一個互相區別的地址。如果我們想象在一個零長度物件的陣列中,我們就能理解這一點。一個“啞”成員被插入到物件中,否則這個物件就有零長度。

 

C + +對此提供了一種機制,稱為純虛擬函式。下面是它的宣告語法:
virtual void x() = 0;
這樣做,等於告訴編譯器在V TA B L E中為函式保留一個間隔,但在這個特定間隔中不放地址。只要有一個函式在類中被宣告為純虛擬函式,則V TA B L E就是不完全的。包含有純虛擬函式的類稱為純抽象基類。如果一個類的V TA B L E是不完全的,當某人試圖建立這個類的物件時,編譯器做什麼呢?由於它不能地建立一個純抽象類的物件,所以如果我們試圖製造一個純抽象類的物件,編譯器就發出一個出錯資訊。這樣,編譯器就保證了抽象類的純潔性,我們就不用擔心誤用它了。

純虛擬函式防止產生V TA B L E,但這並不意味著我們不希望對其他函式產生函式體。我們常常希望呼叫一個函式的基類版本,即便它是虛擬的。把公共程式碼放在儘可能靠近我們的類層次根的地方,這是很好的想法。這不僅節省了程式碼空間,而且能允許使改變的傳播變得容易。

物件切片:

當多型地處理物件時,傳地址與傳值有明顯的不同。所有在這裡已經看到的例子和將會看到的例子都是傳地址的,而不是傳值的。這是因為地址都有相同的長度,傳派生型別(它通常稍大一些)物件的地址和傳基類(它通常小一點)物件的地址是相同的.如果使用物件而不是使用地址或引用進行向上對映,發生的事情會使我們吃驚:這個物件被“切片”,直到所剩下來的是適合於目的的子物件。

#include
using namespace std;

class base
{public:
  base(int  j)
 {
  cout<  i=j;
 }
 base (base & b)
 { cout<  i=100;
 }
 virtual  int sum() {return i;};
private :
 int i;
};

class derive :public base
{
public:
 derive(int m,int k):base(k)
 {
  cout<  j=m;
 }
 int sum()
 {
  return(base::sum()+j);
 }
private:
 int j;
};

void call( base b)
{
 cout<< b.sum();
}

main()
{

 base  a(1);
 derive b(1,2);
 call(b);

}

 

三、建構函式呼叫次序
所有基類建構函式總是在繼承類建構函式中被呼叫。因為建構函式有一項專門的工作:確保物件被正確的建立。派生類只訪問它自己的成員,而不訪問基類的成員,只有基類建構函式能恰當地初始化它自己的成員。如果不在建構函式初始化表示式表中顯式地呼叫基類建構函式,它就呼叫預設建構函式。如果沒有預設建構函式,編譯器將報告出錯。當繼承時,我們必須完全知道基類和能訪問基類的任何public和protected成員。這也就是說,當我們在派生類中時,必須能肯定基類的所有成員都是
有效的。在通常的成員函式中,構造已經發生,所以這個物件的所有部分的成員都已經建立。然而,在建構函式內,必須想辦法保證所有我們的成員都已經建立。保證它的唯一方法是讓基類建構函式首先被呼叫。這樣,當我們在派生類建構函式中時,在基類中我們能訪問的所有成員都已經被初始化了。在建構函式中,“必須知道所有成員物件是有效的”也是下面做法的理由:只要可能,我們應當在這個建構函式初始化表示式表中初始化所有的成員物件(放在合成的類中的物件)。只要我們遵從這個做法,我們就能保證所有基類成員和當前物件的成員物件已經被初始化。

第15章  模板和包容器

一、包容器所有權問題

一個包容器所包含的指標所指向的物件都僅僅用於包容器本身。在這種情況下,所有權問題簡單而直觀:該包容器擁有這些指標所指向的物件。由於通常大多數都是上述情況,因此把包容器完全擁有包容器內的指標所指向的物件的情況定義為預設情形。處理所有權問題的最好方法是由使用者員來選擇。這常常用建構函式的一個引數來完成,它預設地指明所有權(對於典型理想化的簡單程式)。另外還有讀取和設定函式用來檢視和修正包容器的所有權。假若包容器內有刪除物件的函式,包容器所有權的狀態會影響刪除,所以我們還可以找到在刪除函式中控制析構的選項。我們可以對在包容器中的每一個成員新增所有權資訊,這樣,每個位置都知道它是否需要被銷燬,這是一個引用記數變數,在這裡是包容器而不是物件知道所指物件的引用數。


來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/10752043/viewspace-992752/,如需轉載,請註明出處,否則將追究法律責任。

相關文章