《C++面向物件程式設計-基於Visual C++ 2010》讀書筆記
資料型別與基本運算
字串常量按字元書寫順序依次儲存在記憶體中,並在最後存放空字元’\0’表示字串常量的結束。ASCII字元在記憶體中佔1個位元組,而中文字元佔2個位元組
有名常量是指用關鍵字
const
修飾的變數。由於該變數只能讀取,而不能被修改,所以 也稱為常變數。有名常量必須在定義時進行初始化,之後不再允許賦值。例如:
const double PI=3.1415926;
const int Max=1000;
有名常量與變數一樣,儲存在程式的資料區中,可以按地址進行訪問。變數在初始化之後還可以對其進行修改,但對有名常量的任何修改都會引發編譯器報錯。
使用有名常量的好處在於:
- 增加程式的可讀性——用具有實際含義的識別符號代替具體的數值,程式的可讀性大大增強;
便於程式的維護——假設程式中多處用到圓周率,如果需要提高它的精度,則只需在有名常量的定義處修改即可。對於大型軟體,程式的可讀性和可維護性是兩個極其重要的評價指標。
位運算的運算元只能是
bool
、char
、short
或int
型別數值,不能是float
和double
實型數。支援的運算有按位取反(~
)、左移(<<
)、 右移(>>
)、按位與(&
)、按位或(|
)和按位異或(^
)。有符號數時,向左移動n位,丟棄左邊n位資料,並在右邊填充0,同時把最高位作為符號位;向右移動n位,丟棄右邊n位資料,而左邊正數補0,負數補1。不能用變數來定義陣列大小
指標變數與整數相加或相減的結果是指標前移或後移若干個單元,單元大小為
sizeof(type)
在輸入輸出語句中插入
hex
(十六進位制)、oct
(八進位制)和dec
(十進位制)指明輸入輸出資料認定的格式。例如:
//以十六進位制輸入資料
//若輸入f 11,則x和y的值分別為15和17
cin>>hex>>x>>y;
//hex為十六進位制格式控制符,輸出100
//設定過hex後,整數均以十六進位制格式輸出,除非用oct或dec重新設定
cout<<hex<<256<<endl;
- 對於字元陣列,不能用等號運算子對其賦值。
strcpy(引數1,引數2)
是系統提供的函式,其功能是將引數2的內容複製到引數1所指定的字元陣列中。例如:
//初始化的時候可以直接賦值
char str[20] = "";
strcpy(str, "星期日");
基本控制結構和函式
跳轉語句
break
:在迴圈語句中,break
語句的作用是終止迴圈,流程跳轉至迴圈語句之後。需要注意的是,對於迴圈巢狀語句,如果break
語句是在內迴圈中,則其只能終止其所在的迴圈語句的執行,流程跳轉至外迴圈。continue
:其功能是將流程跳轉至當前迴圈語句的條件表示式處,判斷是否繼續進行迴圈。
continue
語句與break
語句的區別是:continue
語句是終止本輪迴圈,而break
語句是終止本層迴圈。此外,continue
語句只能用在迴圈語句中。
^
表示按位異或C++容許在函式定義時為形參指定預設值。預設值的指定遵守“從右到左連續定義”的規則。例如:
double max(double a, double b=0, double c=0);
某些情況下,我們需要修改實參的值,而某些情況下,我們不想修改實參的值:
- 在C語言中:如果不想修改我們就直接傳遞實參,如果物件太大我們就傳遞指標,並且宣告指標是指向const物件的;如果想修改實參的值,我們只能傳遞實參的指標,然而此時指標就不能用const修飾了。
- 在C++中,我們仍然可以使用C中的方式,然而也可以使用“引用”方式:如果不想修改實參的值,我們使用const引用(就是“常量引用”)方式,這樣就不可以通過引用修改被引用物件的值;如果想修改實參的值,則使用平常性的引用實參就可以了。
行內函數:通常
inline
限定符只用於那些非常小並且被頻繁使用的函式。例如:
inline bool isNumber(char ch) {
return ch>=’0’ && ch<=’9’?true:false;
}
類與物件
- 類與物件
C++編譯器在生成程式時是將反映物件特徵的資料成員分開,獨立保存於程式的資料儲存區域,而在程式的程式碼區僅儲存一份成員函式。也就是說,物理上物件的資料成員和成員函式是分離的,並且成員函式是分享的。
程式在生成過程中,在類的成員函式形參表的最前端,編譯器為其新增一個指向物件的指標,並命名該形參名為this
,稱為this
指標。當通過物件呼叫成員函式時,系統將物件的地址傳遞給所呼叫成員函式的this
指標,從而實現物件與成員函式的正確繫結。
類的物件在邏輯上是相互獨立的。在物理上,物件的資料是獨立的,不同的物件擁有不同的資料,但是,類的成員函式卻只有一份,為類的所有物件共有。
如果有參的建構函式的所有形參都指定了預設值,那麼該建構函式即可充當預設建構函式的角色。此時,不需要(其實也不能)再定義預設建構函式。如果再定義預設建構函式,同樣會被視為類中有兩個預設建構函式,引發呼叫匹配錯誤。
拷貝建構函式的形參只能說明為類的物件的引用,如
Student(Student&);
拷貝建構函式是在物件被複制時被呼叫,如果拷貝建構函式是以傳值方式傳遞實參,由於在呼叫類的拷貝建構函式時,實參要被複制給形參,這種複製的結果就是導致再一次呼叫該類的拷貝建構函式,產生無窮的遞迴呼叫。
為避免在拷貝建構函式中不小心改變了原物件中的資料成員,通常在拷貝建構函式的形參前加上const
修飾符。
//類中宣告
Student(const Student&);
建構函式的呼叫順序與定義順序一致。物件呼叫解構函式的順序正好與建構函式的呼叫順序相反。這是由於這些物件都儲存在程式的棧區,先定義的物件先被壓棧,而銷燬的過程與物件從棧中彈出的順序一致。
類的複用技術——組合
Class Point {
private:
int x;
int y;
};
Class Circle {
private:
double radius;
Point center;
};
從建構函式Circle(int a,int b,double r):center(a,b),radius(r){}
的設計可知,成員初始化列表不僅能對成員物件進行初始化,而且也可以用於對普通資料成員賦初值。對於普通的資料成員即可以在建構函式的函式中對其賦值,也可以在成員初始化列表中完成,但對於物件成員卻只能在列表。物件成員建構函式的呼叫先於類的建構函式。
Circle(int a,int b,double r) {
center=Point(a,b);
radius=r;
}
上面的建構函式雖然也完成了物件的拷貝功能,但由於其沒有在成員初始化列表中明確成員物件的賦值,因此先呼叫了成員物件的預設拷貝函式對其賦初值。實際完成賦值任務的語句是center=Point(a,b);
,該語句先生成一臨時無名物件,再呼叫系統提供的賦值功能把臨時無名物件的資料成員值拷貝給物件的物件成員center
,之後臨時無名物件被銷燬。
含有物件成員的類在對其物件初始化時,建構函式是先呼叫物件成員的建構函式對成員物件初始化。呼叫順序與成員物件在初始化列表中的次序無關,與其在類中宣告的次序一致。
- 類中的靜態成員
類的靜態資料成員與全域性變數相比具有兩個優點:
- 不存在與程式中其他全域性名字衝突的可能性;
- 類中的資料成員可設定為私有,實現資訊隱藏。
對於非靜態資料成員,每個類的物件都有自己獨立的資料部分,而靜態資料成員對類的所有物件只有一份,儲存在程式的資料區。
無論類的物件定義與否,類的靜態資料成員都存在。在類中,靜態資料成員可以實現多個物件之間的資料共享,並且使用靜態資料成員還不會破壞資料隱藏的原則,保證了資料的安全性。
靜態資料成員是在類定義中用static
關鍵字修飾的資料成員,靜態資料成員的初始化與一般資料成員初始化不同。
類的靜態資料成員屬於類。即使在程式中沒有定義類的物件,類的靜態資料成員也會在資料區生成並被初始化,因此無論類的物件是否已定義,類的靜態資料成員在程式載入時生成。
class staticMemberExample {
private:
int no;
static int total;
};
//在類外對靜態資料成員進行初始化,注意:前面不加static
int staticMemberExample::total=0;
物件的靜態資料成員和普通資料成員儲存在不同的區域,前者在資料區,後者在棧區。從sizeof(object)
的值為4可知,物件object
中僅儲存了物件的no
資訊,因為int
型別資料的大小就是4。物件的靜態資料成員與普通資料成員在記憶體中的儲存位置不同,前者與全域性變數相同,在資料區,後者可在棧或自由儲存區。
- 靜態成員函式
類的靜態函式成員屬於類,與類的物件無關。即使在程式中沒有定義類的物件,也可以通過類名直接呼叫靜態成員函式。靜態成員函式無法訪問類的非靜態資料成員,也不能直接呼叫類的非靜態成員函式,只能訪問靜態資料成員和呼叫其他的靜態成員函式。若要訪問類中非靜態的成員時,必須通過函式引數傳遞類的物件給靜態成員函式,通過物件才能訪問非靜態成員(資料成員和成員函式)。
<類名>::<靜念成員函式名>(實參表);
<類的物件>.<靜態成員函式名>(實參表);
類的靜態成員函式提供了一種訪問靜態資料成員的方式。此外,它還避免使用全域性函式,為函式設定了一個類域的訪問許可權。
類的靜態成員函式可以在類內定義,也可以在類外定義。在類外定義時,不能再用static
關鍵字作為其字首。
- 類的友元
類的友元不是類的成員,但如同類的成員函式一樣,它可以訪問類的私有或保護的成員。類的友元分為友元函式和友元類。
友元函式是類中用關鍵字friend
修飾的非成員函式,該函式可以是普通函式,也可以是另一個類的成員函式。其在類中的宣告格式如下:
friend <返回型別> [<類名>::]<函式名>([形參表]);
C++中,友元函式的主要用途是過載運算子和生成迭代器類,以及用友元函式同時訪問兩個或多個類的私有資料,使程式的邏輯關係更清晰,其餘情況應慎用友元函式。
Point& Point::setX(double a) {
X=a;
//便於“瀑布式”呼叫
return*this;
}
Point
類中的setX
和setY
函式均返回了*this
,即物件自身。在主函式中利用該設計實現了所謂的“瀑布式”呼叫point2.setX(1).setY(20);
。
在類中宣告另一個類是該類的友元類,則友元類中的所有成員函式都是該類的友元函式,可以訪問類的所有成員。
friend class<類名>;
類的友元關係是單向的,不具有傳遞性。類A
是類B
的友元類,並不意味著類B
一定是類A
的友元類,除非在類A
中也宣告B
是友元類。同樣,如果類A
是類B
的友元類,類B
又是類C
的友元類,並不能確定類A
也是類C
的友元類,友元關係不傳遞。類的友元關係不被繼承,也就是說派生類不繼承類的友元關係。
- 成員函式實現運算子過載
<返回型別> <類名>::operator<運算子>(<形參表>){<函式體>}
對於雙目運算子,<形參表>
中應有一個形參,以當前物件作為左運算元,而形參為右運算元。對於單目運算,是以當前物件為運算元,<形參表>
中無形參。
對於“++
”和“--
”運算子,分為前置和後置運算。為能正確區別二者,C++規定用無參成員函式格式表示實現前置運算,用帶一個int
形參的函式表示實現後置運算,這裡的形參不起任何作用。
由於成員函式隱含的第1個引數是this
指標,運算子過載函式的左運算元只能是物件,不可能是實數,解決方法是採用友元函式過載運算子。
實際上,形參const Complex&
與complex&
雖然僅一字之差,但實現方式是不一樣的。Complex&
形參是引用傳遞,實現時系統是將引用物件的地址壓入呼叫堆疊中。const Complex&
由於是const
引用,禁止修改被引用物件。為防止修改,編譯器在實現const
引用時,生成無名臨時物件供呼叫函式訪問。事實上,系統在引用const
物件時,訪問的是一個由系統產生的複製品。
- 友元函式實現運算子過載
在引用const
物件時,系統會呼叫建構函式生成無名臨時物件。利用該技術可以把實數傳給const
引用複數型別形參,再由建構函式生成臨時複數物件。
friend Complex operator+(const Complex&c1,const Complex&c2);
後置++
運算子過載函式的返回型別宣告為const Complex&
,加const
的目的是阻止對物件的連續後置++
操作。
- 特殊運算子
賦值運算子=
、型別轉換運算子<型別>()
、下標運算子[]
和函式呼叫運算子()
,它們都只能過載為成員函式。
Merchandise myGood2=myGoodl;
中的等號並不呼叫賦值運算子過載函式,它等價於Merchandise myGood2(myGoodl);
,是通過呼叫拷貝建構函式實現物件複製。
- 多檔案結構與編譯預處理
檔案包含指令的作用是用指定的檔案內容替換該指令,其中尖括號<>
表示在系統目錄的include
子目錄下尋找該檔案,而雙引號""
表示先在當前檔案所在的目錄下查詢,如果找不到,再到系統指定的檔案目錄下尋找。
C++的編譯預處理指令主要有檔案包含指令(#include
)、巨集定義(#define
)以及條件編譯指令。所有預處理指令都以“#
”開頭,以回車符結束,且每條指令單獨佔一行。
#defime max(a,b) (((a)>(b))?(a):(b))
需要注意的是,巨集替換可能產生錯誤。如果將上式寫成#define max(a,b)a>b?a:b
,則語句10+max(x,y)+5
被替換成10+x>y:?x:y+5
,結果錯誤。
#undef<巨集名>
的含義是:取消某個巨集名的定義,其後的<巨集名>
不再進行替換和不再有巨集定義。
條件編澤主要用於編譯前處理器根據某個條件滿足與否來確定某一段程式碼是否參與編譯。常用的條件編譯指令包括#if
、#else
、#elif
、#ifdef
、#ifndef
、#endif
等。
陣列、指標及動態記憶體
int* ap
改為int ap[]
含義相同,系統視其為int*
型別
void show(int* ap,int size){}
C++程式在執行時,其所佔用的記憶體空間被劃分為4個區域:程式碼區、全域性資料區、棧區和自由儲存區。函式中使用的區域性變數多數都分配在棧區,靜態變數和全域性變數被儲存在全域性資料區。自由儲存區又稱為堆區,是由程式設計師根據需要進行分配和釋放的記憶體區域。
在
delete
運算子中的方括號[]
是告訴編譯器回收整個陣列所佔用的記憶體空間,並且方括號中不需要填寫陣列的元素數。例如:
int *ptr,n;
//一維陣列的動態分配。陣列元素的個數可以是變數
ptr=new int[n];
//ptrB是指向int[20]陣列型別的指標
int (*ptrB)[20];
//ptrB指向行數為n,列數為20的二維陣列
ptrB=new int[n][20];
//回收一維陣列int[n]的記憶體空間
delete []ptr;
//回收二維陣列int[n][20]的記憶體空間
delete []ptrB;
ptr
在棧中,所指向的單元在堆中。
- 深複製與淺複製
對於沒有提供拷貝建構函式或賦值運算子過載函式的類,系統將提供一個預設的對應函式。系統所提供的函式只是簡單地實現兩個物件資料成員的複製,對於沒有使用堆區的類,這樣的函式能很好地完成複製任務。對於使用了自由儲存區的類,如果僅是簡單地對指標變數進行賦值操作,則會導致兩個甚至更多物件中指向堆區的指標指向同一塊記憶體,即所謂的淺複製。
深複製是複製一個完整且獨立的物件的副本,其實質是每個物件都應擁有自己獨立的堆空間,並且通過複製保持兩個記憶體區域的內容一致。
const
修飾符在C++程式中用途較廣,其主要作用是防止對變數或物件的修改操作。const Array& operator=(const Array&)
函式形參中的const
是防止傳遞的物件被修改,函式返回型別中的const
是禁止修改函式返回的物件。函式bool operator==(const Array&) const
後面的const
是指該成員函式不能修改類中的任何資料成員,其實質是為成員函式中由編譯器為之增加的隱式形參this
指標新增const
修飾。由於遞迴在實現中存在大量的函式呼叫,而函式呼叫會帶來引數壓棧和彈棧的開銷,因此,遞迴函式在執行過程中的記憶體空間佔用和機時開銷高於非遞迴方式,程式碼的執行效率相對較低。
函式指標
如同陣列名是陣列的首地址一樣,函式名代表的是該函式的首地址,也就是函式執行程式碼的入口地址。
void sort(int n,double array[]);
void (*funPtr)(int,double[])=sort;
函式指標funPtr
指向sort
函式。這裡,所指函式的形參和函式返回型別需要完全匹配。在函式首地址賦給函式指標時,既可在函式名前新增取地址運算子“&
”,也可以不加。
函式本身不能作為函式的形參,但函式指標可以。利用函式指標可以將函式作為實參傳遞給另一個函式。
typedef
關鍵字含義是“型別定義”,作用是將一種資料型別定義為某一個識別符號,在程式中可使用該識別符號來實現相應資料型別變數的定義。typedef
能簡化較為複雜型別的宣告,用有明確意義的識別符號代替,增強程式的可讀性。
類的繼承
- 繼承方式有3種:公有繼承(
public
)、私有繼承(private
)和保護繼承(protected
)。預設的繼承方式是私有繼承,即不指明繼承方式等同於私有繼承。
class<派生類名>:[<繼承方式1>]<基類名1>,...,[<繼承方式n>][<基類名n>]{<派生類的成員>};
C++中下列特殊的成員函式不被派生類所繼承:
- 建構函式;
- 解構函式;
- 私有函式:
- 賦值運算子(=)過載函式。
私有的成員函式不能被繼承的原因十分自然,因為它僅屬於基類,在派生類中也不能直接訪問它,否則破壞了基類的封裝性(友元類與友元函式是以犧牲封裝性為代價換取效能)。建構函式、解構函式和賦值運算子過載函式不被繼承的主要原因是基類的對應函式不能處理在派生類引入的新的資料成員,不能完全正確地完成相應的功能(只能正確地處理基類的資料成員)。
對繼承到派生類中基類成員的修改包括兩個方面:
- 基類成員的訪問方式問題,這由派生類定義時的繼承方式來控制;
- 對基類成員的覆蓋,也就是在派生類中定義了與基類中同名的資料成員或成員函式,由於作用域不同,於是發生同名覆蓋(
Override
)。
無論採用什麼樣的繼承方式,基類中的所有成員均被派生類所繼承,派生類物件一定含有基類的資料成員和成員函式。繼承方式僅僅影響到基類成員在派生類中的訪問控制屬性。對於在派生類中不可直接訪問的私有成員,正確的方法是通過基類提供的公有成員函式進行問接的訪問。
- 成員函式的同名覆蓋與隱藏
在派生類中重新定義基類的同名成員函式後,基類中的同名成員函式將被同名覆蓋(Override
)或隱藏(Hide
)。
同名覆蓋是由於派生類與基類的同名成員函式的函式簽名相同,派生類物件在呼叫同名成員函式時,系統呼叫派生類的同名成員函式,而基類的相應函式被遮蓋。
隱藏是由於派生類與基類的同名成員函式的函式簽名不同(即函式名相同而形參不同),派生類物件在呼叫同名成員函式時,系統只往派生類中查詢,不再深入到基類。派生類的同名成員函式阻止了對基類中同名函式的訪問。
函式過載只能出現在同一個類中,基類與派生類的同名函式之問不存在過載關係。
編澤器呼叫類的成員函式的方法是:根據函式名(不是函式簽名)沿著類的繼承鏈逐級向上查詢相匹配的函式定義。
如果在類層次結構的某個類中找到了同名的成員函式,則停止查詢,否則沿著繼承鏈向上繼續查詢。派生中的同名函式阻止編譯器到其基類繼續查詢,這就是出現同名覆蓋和隱藏現象的原因。
(1)在派生類中沒有找到成員函式,再到基類中查詢。如果在基類中找到並且實參與形參正確匹配,則函式呼叫成功,否則出錯。
(2)在派生類中找到了同名的成員函式,不再到基類中查詢。此時又有兩種情況:
- 函式呼叫實參與形參正確匹配,則呼叫派生類中的同名成員函式(同名覆蓋);
- 實參與形參匹配不成功,編譯器報告錯誤(隱藏)。
在派生類中可利用作用域識別符號(::
)直接呼叫基類的同名成員函式。
- 派生類與基類的賦值相容
派生類物件向基類物件、指標或引用賦值滿足以下相容規則:
- 派生類物件可以賦值給基類物件,它是把派生類物件中從對應基類中繼承來的成員賦值給基類物件;
- 派生類物件的地址可以賦給指向基類的指標變數,即基類指標可以指向派生類物件。但通過該指標只能訪問派生類中從基類繼承的成員,不能訪問派生類中的新增成員;
- 派生類物件可以代替基類物件向基類物件的引用進行賦值或初始化。但它只能引用包含在派生類物件中基類部分的成員。
在派生類中定義正確的轉換建構函式或賦值運算子過載函式,則能確保將基類物件賦給派生類物件語句通過編譯。此時,派生類物件中資料成員的內容與所定義的建構函式或賦值運算子過載函式相關。
用強制型別轉換運算子轉換基類物件為派生類物件並賦給派生類指標或引用,格式如下:
<派生類物件指標> = static_cast<派生類*>(&<基類物件>);
<派生類>&<派生類引用> = static_cast<派生類&>(<基類物件>);
用static_cast
運算子能實現類層次結構中基類(父類)和派生類(子類)之間指標或引用的轉換。這種轉換分為“上行”和“下行”兩種。上行轉換是指把派生類指標或引用轉換成基類指標或引用,是安全的;下行轉換是指把基類指標或引用轉換成派生類指標或引用,是不安全的。
在建立派生類物件時,建構函式的呼叫順序為:
- 按照在派生類定義時的先後次序呼叫基類建構函式。
- 按照在類定義中排列的先後順序依次呼叫成員物件的建構函式。
- 執行派生類建構函式中的操作。
派生類物件在撤消時是按照建構函式呼叫相反的次序呼叫類的解構函式。
- 虛基類
商品類的資料成員price
分別通過手機類和播放器類派生給音樂手機類,同樣的資料成員在音樂手機派生類物件中將出現兩個,並且儲存地址也不相同。這樣不僅浪費儲存空問,而且還會因為需要維護資料的一致性增加額外的開銷。
C++語言通過引入虛基類(Virtual Base Class)來支援派生類物件在記憶體中僅有基類資料成員的一份拷貝,以消除鑽石繼承所產生的資料重複儲存問題。
MusicPhone
類的建構函式在定義時需要顯式地說明呼叫虛基類Merchandise
的建構函式Merchandise(n,p)
,否則myObj
物件中name
和price
成員將不賦初值。雖然Merchanaise
類的直接派生類MobilePhone
和MusicPlayer
的建構函式均包含了對Merchandise
建構函式的呼叫,但在MusicPhone
類物件構造時不呼叫基類MobilePhone
和MusicPlayer
建構函式中說明的虛基類Merchandise
的建構函式。若Merchandise
不是虛基類,則Merchandise
建構函式將被呼叫二次(通過MobilePhone
和MusicPlayer
類的建構函式)。此時MusicPhone
類的建構函式定義時不再需要直接說明對Merchandise
類建構函式的呼叫。
多型性
從程式實現的角度,多型可分為兩類:
- 編譯時的多型;
- 執行時的多型。
編譯時的多型性是通過靜態繫結實現的,而執行時的多型性則是在程式執行過程中通過動態繫結實現的。
這裡的繫結(Binding,又稱聯編)是指函式呼叫與執行程式碼之間關聯的過程。靜態繫結(Static Binding)是在程式的編譯與連線時就已確定函式呼叫和執行該呼叫的函式之間的關聯。在生成的可執行檔案中,函式呼叫所關聯執行的程式碼是已確定的,因此靜態繫結也稱為早繫結(Early Binding)。函式過載(含運算子過載)就屬於編譯時的多型。編譯器在判定應當呼叫多個過載函式中哪一個時,是根據源程式中函式呼叫所傳遞的實參型別查詢到與之相匹配的過載函式並連線。動態繫結(Dynamic Binding)是在程式執行時根據具體情況才能確定函式呼叫所關聯的執行程式碼,因而也稱為晚繫結(Late Binding)。
虛擬函式的使用應注意:
- 在派生類中重定義的虛擬函式要求函式簽名和返回值必須與基類虛擬函式完全一致,而關鍵字
virtual
可以省略。在類的層次結構中,成員函式一旦在某個類中被宣告為虛擬函式,那麼在該類之後派生出來的新類中它都是虛擬函式; - 虛擬函式不能是友元函式或靜態成員函式;
- 建構函式不能是虛擬函式,而解構函式可以是虛擬函式;
- 基類的虛擬函式在派生類中可以不重新定義。若在派生類中沒有重新改寫基類的虛擬函式,則呼叫的仍然是基類的虛擬函式;
- 通過類的物件呼叫虛擬函式僅屬於正常的成員函式呼叫,呼叫關係是在編譯時確定的,屬於靜態繫結。動態繫結(動態多型性)僅發生在使用基類指標或基類引用呼叫虛擬函式的過程中。
- 在派生類中重定義的虛擬函式要求函式簽名和返回值必須與基類虛擬函式完全一致,而關鍵字
VC++處理動態繫結的基本方法是:編譯器為擁有虛擬函式的類建立一個虛擬函式表,在物件中封裝
vfptr
指標,用於指向類的虛擬函式表‘vftable
’。虛擬函式表中儲存了該類所擁有的虛擬函式的入口地址,即函式指標。如果派生類重新定義了基類的虛擬函式,那麼虛擬函式表中儲存的是指向該類虛擬函式的指標,否則儲存的是其父類的對應虛擬函式指標。
在基類中定義其解構函式是虛擬函式,其所有派生類中的解構函式將都是虛擬函式,儘管它們的名稱並不相同。如果對一個基類指標應用
delete
運算子顯式地銷燬其類層次結構中的一個物件,則系統會依次呼叫派生類和基類的虛解構函式撤消各自建立的物件。純虛擬函式與抽象類
virtual <返回值> 函式名([(<形參表>)]=0;
含一個或多個純虛擬函式的類稱為抽象類(Abstract Class)。
儘管無法例項化抽象類的物件,但是程式可以定義抽象基類的指標或引用,並通過它們訪問以其為基類的繼承層次結構中的所有派生類的物件。
抽象基類中宣告的純虛擬函式是所有派生類的公共介面,在類繼承層次結構中,不同層次的派生類可以提供不同的具體實現,但使用這些函式的方法則是一致的(用抽象基類的指標或引用訪問)。
模板與標準模板庫
- 函式模板
用函式模板可以實現一個不受資料型別限制的具有良好通用性的函式設計,其方法是在函式模板的形參表中用無型別的引數代替形參的資料型別,用函式模板生成可執行函式的過程是在程式編譯時,編譯器用具體的資料型別置換模板型別的形參,並對其進行嚴格的型別檢查。
template <模板形式引數表> 返回型別 函式名(形式引數表){函式體}
模板形式引數表的引數可以有多個,它們之間用逗號分隔。
模板型別形參是由關鍵字typename
或class
加識別符號組成的。模板非型別形參的宣告與普通函式的形參的宣告相同。
template<typename T>T max(T ary[],int size);
模板型別形參T
的作用是指代任何系統內建的資料型別或使用者定義型別。
函式模板的例項化(Instantiation)是指編譯器根據函式呼叫時所傳遞的實引數據型別生成具體函式的過程。例項化的結果是生成能處理某種特定資料型別的函式實體,這種函式稱為模板函式(Template Function)。
運算子過載在C++程式設計中非常重要。如果Student
類不支援“>
”和“<<
”運算子過載,則系統就不能應用函式模板於使用者自定義的類型別,只能處理系統內建的資料型別,函式模板的應用範圍將受到限制。
函式模板與函式模板過載。多個函式模板的函式名相同,但每個函式模板具有不同的形式引數。
函式模板與非模板函式過載。非模板函式與函式模板同名,但具有不同的函式形式引數。
編譯器如果能匹配到普通函式完成一個函式的呼叫,則不再尋找函式模板來例項化一個模板函式實現函式呼叫。
- 類模板
類模板是設計線性表、棧、佇列等基本資料結構的工具。
template<模板形式引數農> class類名{類成員;};
類模板中的形式引數表可以為形參指定預設值,如此可避免每次例項化時都要顯式地給出實參。例如:
template<typename T=int,int Row=5,int Col=8>
類模板的成員函式可以是函式模板,也可以是普通函式。
可用typedef
宣告模板類為程式引入的新的資料型別,提高程式的可讀性。
typedef TwoDimensionalArray<double,5,10> TwoDimArrayDouble;
TwoDimArrayDouble myTDArray;