C++ virtual關鍵字深入理解
技術標籤:C++
對virtual關鍵的理解需要結合其應用場景來分析。以下從多個角度來深入理解virtual關鍵字。
1.virtual關鍵字主要是什麼作用?
c++中的函式呼叫預設不適用動態繫結。要觸發動態繫結,必須滿足兩個條件:第一,指定為虛擬函式;第二,通過基類型別的引用或指標呼叫(多型產生的條件)。
由此可見,virtual主要主要是實現動態繫結。
2.哪些情況下可以使用virtual關鍵字?
virtual可用來定義類函式和應用到虛繼承(一個是多型應用到函式,一個虛繼承應用到類)。
友元函式 建構函式 static靜態函式 不能用virtual關鍵字修飾(請思考為什麼?—靜態鏈編);
3.virtual函式的效果
class GrandFather { public: GrandFather() {} virtual void fun() { cout << "GrandFather call function!" << endl; } }; class Father : public GrandFather { public: Father() {} void fun() { cout << "Father call function!" << endl; } }; class Son : public Father { public: Son() {} void fun() { cout << "Son call function!" << endl; } }; void print(GrandFather* father) { father->fun(); } int _t main(int argc, _TCHAR* argv[]) { Father * pfather = new Son; pfather->fun(); GrandFather * pgfather = new Father; print(pgfather); return 0; }
輸出為 Son call function
Father call function
這就是實現多型的效果!
4.virtual的繼承性
只要基函式定義了virtual,繼承類的該函式也就具有virtual屬性
即 GrandFather Father Son同時定義virtual void fun()與GrandFather一個定義virtual void fun效果是一樣的
5.虛解構函式
class GrandFather { public: GrandFather() {} virtual void fun() { cout << "GrandFather call function!" << endl; } ~GrandFather() { cout << "GrandFather destruction!" << endl; } }; class Father : public GrandFather { public: Father() {} void fun() { cout << "Father call function!" << endl; } ~Father() { cout << "Father destruction!" << endl; } }; class Son : public Father { public: Son() {} void fun() { cout << "Son call function!" << endl; } ~Son() { cout << "Son destruction!" << endl; } }; void print(GrandFather* p) { p->fun(); } int _tmain(int argc, _TCHAR* argv[]) { Father * pfather = new Son; delete pfather; return 0; }
以上程式碼輸出:
Father destruction! GrandFather destruction!
執行了Son的建構函式,沒執行Son的解構函式,故把GrandFather的解構函式設定為virtual
則輸出:
Son destruction! Father Destruction! GrandFather destruction!
6. 純虛擬函式
純虛擬函式定義如下:
class GrandFather
{ public:
GrandFather() {}
virtual void fun() = 0
{ cout << "GrandFather call function!" << endl;
}
virtual ~GrandFather()
{
cout << "GrandFather destruction!" << endl;
}
};
純虛擬函式為後代類提供可覆蓋的介面,但這個類中的版本決不會呼叫。
含有(或繼續)一個或多個純虛擬函式的類是抽象基類,抽象基類不能例項化!
繼承類只有重寫這個接口才能被例項化
7.虛繼承
虛繼承主要解決交叉繼承帶來的問題。這裡給出一片參考文章c++虛繼承。
給一個例子如下
class GrandFather
{ public:
GrandFather() {}
void fun()
{ cout << "GrandFather call function!" << endl;
}
virtual ~GrandFather()
{ cout << "GrandFather destruction!" << endl;
}
};
class Father1 : public GrandFather
{ public:
Father1() {}
void fun()
{ cout << "Father call function!" << endl;
}
};
class Father2 : public GrandFather
{ public:
Father2() {}
void fun()
{ cout << "Father call function!" << endl;
}
};
class Son : public Father1, public Father2
{ public:
Son() {}
//void fun()
//{
// cout << "Son call function!" << endl;
//}
};
void print(GrandFather* p)
{ p->fun();
}
int _tmain(int argc, _TCHAR* argv[])
{ Son* son = new Son;
son->fun();
return 0;
}
編譯時會提示報錯對fun的訪問不明確
如果Father1和Father2都用虛繼承繼承GrandFather類則可以解決這個問題
8. 建構函式和解構函式中的虛擬函式
如果在建構函式或解構函式中呼叫虛擬函式,則執行的是為建構函式或解構函式自身型別定義的版本
9.虛擬函式的實現機制
關於虛擬函式的實現機制,我們以後在介紹。
10.小結
關於virtual關鍵字的用法總結如上,有錯誤或者總結不到位的情況請能幫本人指出!
11.例子
class classA
{ public:
classA()
{ clear();
}
virtual ~classA()
{
}
void clear()
{ memset(this , 0 , sizeof(*this));
}
virtual void func()
{ printf("func\n");
}
};
class classB : public classA
{
};
int main(void)
{
classA oa;
classB ob;
classA * pa0 = &oa;
classA * pa1 = &ob;
classB * pb = &ob;
oa.func(); // 1
ob.func(); // 2
pa0->func(); // 3
pa1->func(); // 4
pb->func(); // 5
return 0;
}
補充一個例子,這個程式輸出依次是
func
func
出錯
func
func
談談我的理解,當
classA oa;
oa.func();
不存在動態呼叫的過程,所以func雖然是虛擬函式,但是函式呼叫不通過虛表訪問,所以即使
memset(this , 0 , sizeof(*this));
找不到虛表地址也沒有關係
在執行classB ob;的時候,注意memset的是classA的地址,所有ob的虛表是存在的
即是如下,通過指標或引用(動態繫結)訪問oa的func函式(需要從虛表訪問),會出錯
訪問ob的func和函式,無論靜態訪問還是動態訪問,都不會出錯
當把classB的程式碼改成如下時
class classB : public classA
classB()
{ clear();
}
virtual ~classB()
{
}
void clear()
{ memset(this , 0 , sizeof(*this));
}
輸出為
func
func
出錯
出錯
出錯