虛擬函式與多型課程筆記
虛擬函式與多型
一、基礎知識
1.多型性是指一個名字,多種語義;或介面相同,多種實現。
過載函式是多型性的一種簡單形式。
虛擬函式允許函式呼叫與函式體的聯絡在執行時才進行,稱為動態聯編,switch語句是一個動態聯編的例子。程式編譯階段不能預知switch表示式的值,一直要等到程式執行時,對錶達式求值之後,才能實現case子句的匹配,決定程式碼的執行分支。需要進行條件判斷決定程式流程的條件語句,迴圈語句的情況也相同
靜態聯編是指程式之間的匹配,連線在編譯階段,即程式執行之前完成,也成為早期匹配。例如呼叫一個已經說明的函式,編譯期間就能準確獲得函式入口地址,返回地址和引數傳遞資訊,從而完成匹配。
2. 冠以關鍵字 virtual 的成員函式稱為虛擬函式
實現執行時多型的關鍵首先是要說明虛擬函式,另外,必須用
基類指標呼叫派生類的不同實現版本
3.基類指標和派生類指標與基類物件和派生類物件有四種可能匹配的使用方式:
(1)直接用基類指標引用基類物件
(2)直接用派生類指標引用派生類物件
(3)用基類指標引用派生類物件
(4)用派生類指標引用基類物件
通過基類指標只能訪問從基類繼承的成員
4.冠以關鍵字virtual的成員函式被稱為虛擬函式
實現執行時多型的關鍵是要先說明虛擬函式,而且必須用基類指標呼叫派生類的不同實現版本 儘管可以向呼叫其他成員函式那樣,顯示的用物件名來呼叫一個虛擬函式,但只有使用同一個基類指標訪問的虛擬函式,才稱為執行時的多型。
5.一個虛擬函式,在派生類層介面相同的過載函式都保持虛特性
虛擬函式必須是類的成員函式
不能將友元說明為虛擬函式,但虛擬函式可以是另一個類的友元
解構函式可以是虛擬函式,但建構函式不能是虛擬函式
6.虛擬函式的過載特性:
在派生類中過載基類的虛擬函式要求函式名、返回型別、引數個數、
引數型別和順序完全相同
如果僅僅返回型別不同,C++認為是錯誤過載
如果函式原型不同,僅函式名相同,丟失虛特性
例如以下程式:
class base
{public :
virtual void vf1 ( ) ;
virtual void vf2 ( ) ;
virtual void vf3 ( ) ;
void f ( ) ;
} ;
class derived : public base
{public :
void vf1 ( ) ; // 虛擬函式
void vf2 ( int ) ; // 過載,引數不同,虛特性丟失
char vf3 ( ) ; //error,僅返回型別不同
void f ( ) ; // 非虛擬函式過載
} ;
7. 建構函式不能是虛擬函式。建立一個派生類物件時,必須從類層次的根開始,沿著繼承路徑逐個呼叫基類的建構函式
解構函式可以是虛的。虛解構函式用於指引 delete 運算子正確析構動態物件
例如 普通解構函式在刪除動態派生類物件的呼叫時的情況
#include<iostream>
usingnamespace std ;
classA
{ public:
~A(){ cout << "A::~A() iscalled.\n" ; }
} ;
classB : public A
{ public:
~B(){ cout << "B::~B() iscalled.\n" ; }
};
intmain() {
A *Ap = new B ;
B *Bp2 = new B ;
cout << "delete firstobject:\n" ;
delete Ap;
cout << "delete secondobject:\n" ;
delete Bp2 ;
}
輸出結果為:
delete first object:
A::~A()is called.
deletesecond() object:
B::~B()is called.
A::~A()is called.
虛解構函式在刪除動態派生類物件的呼叫情況
#include<iostream>
usingnamespace std ;
classA
{ public:
virtual ~A(){ cout <<"A::~A() is called.\n" ; }
} ;
classB : public A
{ public:
~B(){ cout << "B::~B() iscalled.\n" ; }
};
intmain()
{ A *Ap = new B ;
B *Bp2 = new B ;
cout << "delete firstobject:\n" ;
delete Ap;
cout << "delete secondobject:\n" ;
delete Bp2 ;
}
輸出結果為:
B::~B()is called.
A::~A()is called.
deletesecond object:
B::~B()is called.
A::~A()is called.
從程式執行結果可以看出,定義了基類虛解構函式之後,基類指標指向的派生類動態物件也可以正確的用delete析構。設計類層次結果時,往往不能預知使用它的各種複雜情況,所以為基類提供一個虛解構函式,能夠使派生類物件在不同狀態下正確呼叫解構函式。因此可以說,將基類的解構函式說明為虛擬函式沒有壞處
8. 虛擬函式是一種特殊的虛擬函式,
在許多情況下,在基類中不能對虛擬函式給出有意義的實現,而把它宣告為純虛擬函式,它的實現留給該基類的派生類去做。
這就是純虛擬函式的作用。
純虛擬函式是一個在基類中說明的虛擬函式,在基類中沒有定義, 要求任何派生類都定義自己的版本
純虛擬函式為各派生類提供一個公共介面
純虛擬函式說明形式:
virtual 型別 函式名(引數表)= 0 ;
一個具有純虛擬函式的基類稱為抽象類。
對於抽象類的使用C++有以下限制:
1. 抽象類只能用作其他類的基類;
2. 抽象類不能建立物件
3. 物件類不能用作引數型別,函式返回型別或顯示型別轉換
但是,可以說明抽象類的指標和引用:
例如,對Figure抽象類的使用:
Figurex; //錯誤,抽象類不能建立物件
Figure*p; //正確,可以說明抽象類物件
Figuref(); //錯誤,抽象類不能用作返回型別
voidg(Figure); //錯誤,抽象類不能作為引數型別
Figure&h(Figure &); //正確,可以說明抽象類的引用
class point { /*……*/ } ;
class shape ; // 抽象類
{point center ;
……
public :
point where ( ) { return center ; }
void move ( point p ) { center = p ; draw ( ) ; }
virtual void rotate ( int ) = 0 ; // 純虛擬函式
virtual void draw ( ) = 0 ; //純虛擬函式
};
…...
class ab_circle : public shape
{ int radius ;
public : void rotate ( int ) { } ;
};
ps:
ab_circle類仍為抽象類
ab_circle:: draw ( ) 、ab_circle :: rotate ( )
也是純虛擬函式
要使 ab_circle 成為非抽象類,
必須作以下說明:
class ab_circle : public shape
{ int radius ;
public :
void rotate ( int ) ;
void draw ( ) ;
} ;
並提供 ab_circle :: draw ( )
和 ab_circle :: rotate ( int )
的定義
二,總結
虛擬函式和多型性是軟體的設計易於擴充,使用類庫更為靈活
冠以關鍵字virtual的成員函式成為虛擬函式。派生類可以過載基類的虛擬函式
如果通過物件名和點運算子的方式呼叫虛擬函式,則呼叫關聯在編譯時由引用物件的型別確定,成為靜態聯編;
如果一個基類中包含虛擬函式,則通常把它的解構函式說明為虛解構函式。這樣,它的所有派生類解構函式也自動成為虛解構函式(即使它們與基類的解構函式名稱不同);
具有純解構函式的類稱為 抽象類,抽象類只能作為基類,不能建立例項化物件。如果抽象類的派生類不提供虛擬函式的實現,則他依然是抽象類。