探討 C++ 虛函式 virtual
有無虛函式的對比
C++ 中的虛函式用于解決動態多型問題,虛函式的作用是允許在派生類中重新定義與積累同名的函式,并且可以通過基類指標或參考來訪問基類和派生類中的同名函式,
首先寫兩個簡單的類,類 B 繼承自類 A,即 A 是基類,B 是派生類,
class A{
public:
void print(){
cout << "A" << endl;
}
};
class B : public A {
public:
void print(){
cout << "B" << endl;
}
};
int main()
{
B b; //創建一個 B 類物件 b;
A &a = b; //a 是 b 的一個 A 類參考;
A *pa = &b; //pa 是一個指向 A 類物件的指標;
a.print();
pa->print();
b.print();
return 0;
}
程式中,A 類和 B 類均定義了一個同名函式 print ,但兩個函式的功能不同,編譯系統按照同名覆寫原則決定呼叫物件,
另外一點,參考的本質是指標常量,可以認為 a,pa 都指向了 b,( 注意區分常量指標與指標常量,常量指標可以類比于整型指標,即指向一個常量的指標,指標的指向可以修改;指標常量類比于整型常量,即一個指標是個常量,也就是指標只能固定的指向某一單元,指標常量的指向不可改而指向的值可以修改,)
int a, b;
int * const p1 = &a; //指標常量
const int *p2 = &b; //常量指標
執行函式后,我們發現結果為

因為 a 是 A 類的一個參考,所以 a 的 print( ) 依舊是 A 類的成員函式;pa 是 A 類的指標,同理;而 b 是 B 類的物件,呼叫的 print( ) 為 B 類的成員函式,簡言之就是,沒有 virtual 時,呼叫哪一類的成員函式取決于呼叫物件 a ,pa,b 在定義時的型別,而此時,若 B 類物件 b 想呼叫直接基類 A 的 print 函式,則應當 b.A::print( ),
這種 a,pa,b 能呼叫哪個同名函式在物件定義時已經確定好了的多型,我們稱之為靜態多型,什么是多型?同一個 print 函式在不同的物件中有不同的作用,這就呈現了多型,
這里再提一點,原本基類指標是用來指向基類物件的,如果用它指向派生類物件,此時基類指標指向的是派生類物件中的基類部分,在沒有虛函式時,基類指標是無法呼叫派生類物件中的成員函式的,
而當我們在 A 類中 print( ) 前加上關鍵字 virtual,變成虛函式時
class A{
public:
virtual void print(){
cout << "A" << endl;
}
};
class B : public A{
public:
void print(){
cout << "B" << endl;
}
};
再次執行主函式,結果為

這是因為 virtual 跟著物件走,即呼叫的 print( ) 究竟是 A 類還是 B 類的成員函式取決于“ 呼叫者 ” a,pa 所指的物件 b 屬于哪一類,而不再是取決于 a,pa 本身在定義時的型別了,
這種用基類指標或參考指向某一派生類物件,從而能夠呼叫指標指向的派生類物件中的函式的多型,我們成為動態多型,virtual 正是實作動態多型的關鍵字,
虛函式表
接著剛才的話題,在 A 類中有虛函式的前提下,我們繼續討論
class C{
public:
void print(){
cout << "C" << endl;
}
};
int main()
{
cout << "sizeof(A): " << sizeof(A) << endl;
cout << "sizeof(B): " << sizeof(B) << endl;
cout << "sizeof(C): " << sizeof(C) << endl;
A a;
B b;
C c;
cout << "sizeof(a): " << sizeof(a) << endl;
cout << "sizeof(b): " << sizeof(b) << endl;
cout << "sizeof(c): " << sizeof(c) << endl;
return 0;
}
執行結果為

為什么有虛函式的 A 類大小為 8 位元組,繼承 A 的 B 為 8 位元組,而沒有虛函式的 C 類是 1 位元組呢?聯想到 64 位作業系統下指標占8個位元組記憶體,而 A 大小也是 8 位元組,是巧合嗎?事實上,在包含虛函式的類中,在該類的存盤空間中,會有一個指向虛函式表的指標,正是這個指標使 A 的大小變為 8 位元組,而指標所指的虛函式表本質上是 A 類中定義的所有虛函式名構成的串列,A 中只定義了一個虛函式 print( ) ,所以虛函式表中也只有一個虛函式名 print,通過這個虛函式名,再找到整個虛函式 print( ) 在記憶體中的存盤位置 ( B 同理 ) ,
驗證虛函式表的存在性
虛函式表看不見摸不著,怎么確定它的存在呢?
int main()
{
A a;
B b;
a.print();
b.print();
cout << "-------------------" << endl;
typedef void (*func)(); //利用函式指標 func;
((func**)(&a))[0][0](); //((func**)(&a))[0] 代表物件 a 的記憶體空間中的第一個元素:指向虛函式表的指標;
((func**)(&b))[0][0](); //((func**)(&a))[0][0] 表示虛函式表中第一個函式名;
return 0;
}

從結果中我們可以發現,((func**)(&a))[0][0](); 等效于 a.print();,即確實證明了物件 a 的記憶體空間中存有一個指向虛函式表的指標,虛函式表的第一個函式名正是 print ,
文章略顯晦澀,有疑問的同學可以留言一起探討
轉載請註明出處,本文鏈接:https://www.uj5u.com/qita/266324.html
標籤:AI
上一篇:ImportError: cannot import name ‘cross_validation‘ 解決方法
下一篇:智能車AI電磁部署學習 (二)
