類繼承-虛擬函式(1)
關於基類的引用和指標與派生類的指標和引用:
基類的引用可以初始化為派生類的物件,可以通過基類引用呼叫派生了新增的公有方法,也可以呼叫基類的方法,指標也是一樣。反之,將派生了的引用初始化為基類物件是不允許的。
Based based = Based(2);
Derived derived = Derived(1, 2);
Based&r1 = derived;//ok
Based*p1 = &derived;//ok
Derived&r2 = based;//error
Derived*p2 = &based;//error
在某些派生了中,可能要修改基類的方法實現新的功能,我們可以在派生類中使用相同的方法名。但是存在一個問題是什麼時候呼叫基類和派生類同名的方法,用什麼方式進行區分?
對於沒有任何關鍵字修飾的同名方法,通過物件型別確定使用哪個版本是一種區分方式,但是很多時候我們用的是指標或者引用,這時候是根據引用或者指標的型別進行呼叫:基類的引用或指標呼叫的是基類的方法,派生類的引用或者指標呼叫的是派生了的同名方法,但是基類的指標可能指向的是派生類物件,我們希望的是通過基類的指標或者引用呼叫派生類物件的同名方法。
Based based = Based(2);
Derived derived = Derived(1, 2);
Based&r1 = based;
Derived&r2 = derived;
r1.Show();//Based::show()
r2.Show();//Derived::Show()
我們更希望的是通過引用或者指標指向的物件來區分呼叫的同名方法,將函式宣告為虛擬函式是解決這個問題的方法
虛成員函式:
class Based
{
private:
int n;
public:
Based(int x) :n(x)
{
cout << "based constructed" << endl;
}
virtual ~Based()
{
cout << "Based disconstructed" << endl;
}
virtual void Show()
{
cout << "n=" <<n<<endl;
}
};
class Derived:public Based
{
private:
int m;
public:
Derived(int x, int y) :Based(x), m(y)
{
cout << "Derived constructed" << endl;
}
virtual ~Derived()
{
cout << "Derived disconstructed" << endl;
}
virtual void Show()
{
Based::Show();
cout << "m=" << m << endl;
}
};
通過將同名成員函式宣告為虛擬函式,實現了基類引用或者指標呼叫派生類的方法(當然基類引用和指標指向的是派生類物件),這就是多型,也叫做動態聯編。多型機制可以簡單地概括為“一個介面,多種方法”。
小結:如果沒有使用virtual關鍵字,程式將根據引用型別或者指標型別選擇方法;如果使用了virtual,程式將根據引用或者指標指向的物件型別選擇方法。
Based based = Based(2);
Derived derived = Derived(1, 2);
Based&r1 = based;
Based&r2 = derived;
r1.Show();//Based::Show()
r2.Show();//Derived ::Show()
cout << sizeof(based) << endl;//8
cout << sizeof(derived) << endl;//12
關於虛擬函式表:
(前提:無虛擬函式繼承,但有虛擬函式的情況)
sizeof(Based)=整型變數n(4位元組)+一個虛表指標(4位元組)=8位元組。
sizeof(Derived)=整型變數m(4位元組)+一個虛表指標(4位元組)+類Based中的整型變數n(4位元組)=12位元組
需要注意的一點是:派生類無法直接訪問基類的資料成員,我們希望在派生類的顯示函式show()中訪問基類的資料成員,使用到了作用域解析符,由於派生類的顯示函式show()除了要顯示新添的資料成員還要實現基類的資料成員,因此直接呼叫基類的顯示函式:
virtual void Show()
{
Based::Show();
cout << "m=" << m << endl;
}
虛解構函式:
基類聲明瞭一個虛的解構函式,這樣是為了確保釋放派生類物件時,按正確的順序呼叫解構函式。
派生類物件析構的順序是,先析構派生類物件,在析構基類物件,和構造的時候的順序相反。
如果解構函式不是虛的,則將呼叫對應於指標型別的解構函式。如果解構函式是虛的,將呼叫相應的物件型別的解構函式(即如果指標指向的是派生類的物件,將呼叫派生類的解構函式,然後自動呼叫基類的解構函式)。
總結:
如果派生類中重新定義了基類的方法,通常將基類的方法宣告為虛的。這樣,程式根據物件型別而不是引用或者指標的型別選擇方法。為基類宣告一個虛解構函式是一種慣例。