記錄了學習虛函數與純虛函數中有一些疑問,以及平常可能不注意的地方。
Q0:虛函數是怎么實現的?
0:簡單的說,是通過虛函數表實現的。如果一個類中含有虛函數,則系統會為這個類分配一個指針成員指向一張虛函數表(vtbl),表中每一項指向一個虛函數的地址,實現上就是一個函數指針的數組。
Q1:基類函數加上virtual關鍵字,派生類不加,那么派生類的同名函數是虛函數嗎?
1.C++繼承中,如果基類定義了一個虛函數,那么派生類中的同名函數不用加virtual(也可以加)關鍵字,也可以使該函數為虛函數。因為派生類繼承了基類的屬性,所以派生類中同名函數被視為與基類有相同屬性的函數。
如果基類為純虛函數,那么派生類中也不用聲明virtual,但是必須實現純虛函數。
純虛函數基類中定義:virtual fun() = 0;派生類可以有不同的實現,即可以稱在基類中提供了該函數的接口。
Q2:那為什么需要這個接口呢,比如我直接可以在子類中分別寫同名函數,然后一一實現。
2.看到的一個很形象例子:比如基類的接口類似與電腦的USB接口,不同的設備都可以插入訪問數據,U盤、鼠標、鍵盤;如果分別實現的話,那么就需要在電腦上加三個不同類型的接口,而純虛函數的作用,則只需要這一個接口來對不同的需求進行操作,非常方便。
Q3:構造函數可以是虛函數嗎?析構函數呢?
3:構造函數不能是虛函數,因為要構造一個對象,就必須知道構造了什么;析構函數一般定義為虛函數,稱為虛析構函數(指向派生類對象的基類指針被delete時,會調用派生類析構函數、基類的析構函數,否則只會調用基類的析構函數),可以定義為純虛函數。
(可以參考 http://www.cnblogs.com/chio/archive/2007/09/10/888260.html)
講了平常不會注意的地方:
純虛函數也可以定義函數體;
6) 析構函數可以是純虛的,但純虛析構函數必須有定義體,因為析構函數的調用是在子類中隱含的。
7) 非純的虛函數必須有定義體,不然是一個錯誤。
虛函數的作用:(例子)
1 class Base 2 { 3 public: 4 virtual void Draw(){}; 5 }; 6 7 class DerivedA:public Base 8 { 9 public: 10 DerivedA(){}; 11 void Draw() 12 { 13 printf("A draw\n"); 14 } 15 }; 16 17 class DerivedB:public Base 18 { 19 public: 20 DerivedB(){}; 21 void Draw() 22 { 23 printf("B draw\n"); 24 } 25 };
如果要DerivedA或者DerivedB調用Draw(),可以分別new它們的對象,然后調用Draw(),但是如果還有其他的派生類,則需要定義並new很多個對象;
那么,虛函數的作用:
Base* base = new DerivedA; 或者 Base* base = new DerivedB;
base->Draw(); //按照new的對象,對應調用派生類的Draw()函數,只需要定義一個base指針即可。
純虛函數的作用:(例子)
class Vehicle { public: virtual void Print()=0; //純虛函數的定義 }; class Car:public Vehicle { public: virtual void Print(){cout<<"Car"<<endl;}; }; class Bike:public Vehicle { public: virtual void Print(){cout<<"Bike"<<endl;}; }; void main() { Car c; Bike b; b.Print(); //輸出Bike c.Print(); //輸出Car }
這里Vehicle為抽象類,也叫純虛類;定義了print()=0;派生類Car和Bike實現了print()函數,按照對象調用各自的print()函數。
Q4:如何通過指向派生類對象的指針或引用調用基類對象的虛函數?(參考https://blog.csdn.net/chijianxingfeng/article/details/8870387)
例一:

#include <iostream> using namespace std; class A { public: virtual void show() { cout<<"in A::show()\n"; } }; class B:public A { public: void show() { cout<<"in B::show()\n"; } }; int main() { A a; //通過派生類對象的引用pb 實現了調用基類中虛函數show(),, //如果把 A中show() 前面的virtual去掉, 則調用的就是B 中的show() B &pb = static_cast<B&>(a); pb.show(); //調用的是基類 A的 show(); return 0; }
輸出:
例二:

#include <iostream> using namespace std; class A { public: virtual void show() { cout<<"in A::show()\n"; } void callfunc() { show(); } }; class B:public A { public: void show() { cout<<"in B::show()\n"; } }; int main() { B b; b.callfunc(); //調用的是A::callfunc(),,但在A::callfunc()調用的是B::show() //這就是一個虛調用 A a; a.callfunc(); //這里調用的是A::show() return 0; }
輸出: