程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> C語言 >> C++ >> C++入門知識 >> C++基礎(五)虛函數、重載、覆蓋、隱藏

C++基礎(五)虛函數、重載、覆蓋、隱藏

編輯:C++入門知識

虛函數總是跟多態聯系在一起,引入虛函數可以使用基類指針對繼承類對象進行操作!

虛函數:繼承接口(函數名,參數,返回值),但是實現不繼承(函數體)

非虛函數:繼承接口,也繼承實現;

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)當虛函數出現在構造、析構函數中時(即在構造函數中調用虛函數),函數退化為普通函數。為什麼? <>中條款

例子:(轉自<>中條例9)

class Transaction

{
public:
Transaction();
virtual void LogTransaction() const = 0;
// ...
};
Transaction::Transaction()
{
// ...
LogTransaction();
}
class BuyTransaction : public Transaction

{
public:
virtual void LogTransaction() const;
// ...
};

這裡調用BuyTransaction的構造函數,因為BuyTransaction是繼承類所以先調用基類的構造函數,此時派生類獨有的那部分還未初始化,<>中這麼解釋:“這個對象內的BuyTransaction成分還未初始化,最安全的辦法是當他不存在,對象在derived成分還未初 始化時該對象不會成為一個derived對象”所以Transaction 的構造函數會調用基類的 LogTransaction() const;而且此處是個純虛函數,會報錯

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++》

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved