虛函數總是跟多態聯系在一起,引入虛函數可以使用基類指針對繼承類對象進行操作!
虛函數:繼承接口(函數名,參數,返回值),但是實現不繼承(函數體)非虛函數:繼承接口,也繼承實現;
1)虛析構函數(當一個類打算作為基類使用時候,其析構函數必須是虛函數)
構造函數可以為虛函數嗎? 不可以,在生成對象的時候,必須向編譯器明確指定要生成什麼類型的對象,因而不存在虛函數的問題;只有當對象已經存在,我用什麼接口去操作它的問題;
例子:
class A
{
public:
A();
virtual~A();
};
class B: public A
{
public:
B();
~B();
};
int main()
{
A *pA=new B; //調用B的構造函數
delete pA;
return 0;
}
這裡通過指針A去調用B類的析構函數,但是如果 ~A()不是虛析構函數,就不存在多態,就會去調用A中的析構函數,最後結果可能是B對象中的A成分被析構了,其他 還殘留;所以當一個類作為基類時候,其析構函數必須是虛函數,這樣防止出現析構不完全的情況;
2)當虛函數出現在構造、析構函數中時(即在構造函數中調用虛函數),函數退化為普通函數。為什麼? <
例子:(轉自<
class Transaction
{
public:
Transaction();
virtual void LogTransaction() const = 0;
// ...
};
Transaction::Transaction()
{
// ...
LogTransaction();
}
class BuyTransaction : public Transaction
{
public:
virtual void LogTransaction() const;
// ...
};
這裡調用BuyTransaction的構造函數,因為BuyTransaction是繼承類所以先調用基類的構造函數,此時派生類獨有的那部分還未初始化,<
3)虛函數與覆蓋,重載,隱藏
重載首先出現在非繼承關系當中,當同一個類中,兩個函數的參數不同,名字相同,返回值類型無所謂(函數返回值不作為重載的參考,因為函數調用時候不出現返回值);
注意兩個函數可以僅僅因為const與非const的差別來實現重載;
虛函數與覆蓋均出現在一個繼承體系中,覆蓋針對的是普通函數,當父類子類中出現同名(相同返回值,相同參數,相同函數名)要求絕對一致;
虛函數在父類子類中,首先在父類中聲明該函數為virtual,那麼子類可以重新定義該函數的實現,這裡主要涉及多態,就是覆蓋的情況加上virtual,通過指針或者引用實現多態;
隱藏顯得簡單粗暴,在繼承類中只要出現於父類同名(只要求同樣的函數名/變量名,其他返回值,virtual 非virtual不管)的函數,則用對象調用同名函數時候,基類對應的同名函數、變量隱藏,如果要訪問其父類的同名成員應該明確使用 基類名::成員來訪問;
例子:
class A
{
public:
virtual ~A(){};
void process(int i,char c); //重載,編譯期間即可確定該調用哪個函數
char process(double d,int c); //只要函數名相同,返回無所謂,參數不同
void process(int i);
void process(int i)const; // 此處相當於void process(const A *this,int i);
void process(consttint i); //這裡僅僅因為const屬性不同即可實現重載
virtual int foo(int, char){...};
int foo2(){};
void foo3(int,int ){};
}
class B: public A
{
public:
int foo(int ,char){...}; //虛函數,這裡函數接口要嚴格一致(大部分編譯器要求返回值也要一致)
int process(){...}; //隱藏基類函數
int foo2(){}; // 這裡不含虛屬性,會覆蓋
int foo3(){return 0;}; //會隱藏A類的foo3
}
int main()
{
B b;
A *pA=&b;
pA->foo2(); //這裡調用A類的foo2();不涉及多態;
pA->foo(3,'c'); //B類foo() 涉及多態
}
總結:
1)覆蓋與虛函數是一對兄弟,要求函數的返回值,函數名,參數嚴格一致,虛函數是覆蓋加上virtual的情況;
2) 隱藏是覆蓋的推廣,覆蓋是隱藏的特例,只要求函數名一樣,其他不管,在繼承體系中,子類的同名函數會將父類的同名函數隱藏;
3)當使用指針時候,指向基類的指針會根據實際對象的類型,選擇相應的虛函數執行,如果派生類沒有重新定義基類的虛函數,那麼依然執行基類的虛函數;
4)當不存在虛函數的情況下,使用基類的指針,不會下降到派生類中去搜索函數;所以virtual屬性相當於告訴基類指針:當執行我時,請到相應對象中搜索對應的虛函數;
5)純虛函數所在的類是抽象類,不能實例化,定義了純虛函數意味著這個函數只能為父類,其負責定義接口而不負責實現;
6)當使用對象來調用相應的函數時候,主要考慮的是對基類同名函數的隱藏(包括覆蓋),而不需要考慮多態;
7)注意構造析構函數中不能調用虛函數,當執行派生類的構造函數時候,先構造的是其基類成分,再執行派生類成分的構造,虛函數此時無意義;
8)c++是個細節非常多,非常復雜的語言;
參考:《effective c++》