C++父類與子類關係以及函式過載、覆蓋和隱藏規則
今天去HTC面試,筆試考了邏輯、c語言、c++語言幾個部分,C/C++考的全是基本語法,沒有考演算法。C語言部分做的還不錯,C++部分卻做的那麼多不確定,其中大部分是C++父子類之間關係以及函式過載、覆蓋以及隱藏規則。
在C++語言中,虛擬函式是非常重要的概念,虛擬函式是實現C++面向物件中多型性和繼承性的基石。而多型性和繼承性則是面嚮物件語言的精髓。掌握虛擬函式才算是真正掌握C++語言,而C++語言中虛擬函式的繼承覆蓋與函式過載有些類似,很多初學者搞不清他們之間的關係。
首先要明確覆蓋(override)與過載(overload)的定義,區別出什麼是覆蓋和過載:
覆蓋就是派生類中虛成員函式覆蓋基類中同名且引數相同
如下例:
class CBase { public: CBase(); virtual void Walk(){cout<<"CBase:Walk"<<endl;} virtual void Jump(){cout<<"CBase:Jump"<<endl;} }; class CDerivedA : public CBase { public: CDerivedA(); void Walk(){cout<<"CDerivedA:Walk"<<endl;} void Jump(){cout<<"CDerivedA:Jump"<<endl;} }; int main() { CBase *pTmp1=new CDerivedA ; pTmp1->Walk(); return 0; }
上例中,CDerived和CBase之間就是函式覆蓋關係,Walk和Jump函式被派生類覆蓋,輸出肯定是派生類函式的輸出。下面就這幾者關係著重學習。再看看下例:
這裡基類中Run與派生類中Run函式就是過載(overload)關係,當使用基類來呼叫派生類物件中過載函式,會根據其作用域來判斷,如基類CBase,其作用域就是CBase這個類的大括號包含的內容,派生類CDeriverdA其作用域就是CDerivedA大括號中包含的內容。因此在使用基類呼叫派生類物件中的Run方法時,會執行基類的Run方法。class CBase { public: CBase(); virtual void Walk(){cout<<"CBase:Walk"<<endl;} virtual void Jump(){cout<<"CBase:Jump"<<endl;} void Run(int speed){cout<<"CBase:Run:"<<"Speed="<<speed<<endl;} }; class CDerivedA : public CBase { public: CDerivedA(); void Walk(){cout<<"CDerivedA:Walk"<<endl;} void Jump(){cout<<"CDerivedA:Jump"<<endl;} void Run(int speed) {cout<<"CDerivedA:Run"<<"Speed="<<speed<<endl;} }; int main() { CBase *pTmp1=new CDerivedA ; pTmp1->Walk(); pTmp1->Run(20); return 0; }
關於作用域可見性規則(摘於網上:http://www.51laifu.cn/archives/2012/610/article-31092.html)如下:
如果存在兩個或多個具有包含關係的作用域,外層聲明瞭一個識別符號,而內層沒有再次宣告同名識別符號,那麼外層識別符號在內層依然可見,如果在內層聲明瞭同名識別符號,則外層識別符號在內層不可見,這時稱內層識別符號隱藏了外層同名識別符號,這種現象稱為隱藏規則。
在類的派生層次結構中,基類的成員和派生類新增的成員都具有類作用域。二者的作用範圍不同,是相互包含的兩個層,派生類在內層。這時,如果派生類聲明瞭一個和某個基類成員同名的新成員,派生的新成員就隱藏了外層同名成員,直接使用成員名只能訪問到派生類的成員。如果派生類中聲明瞭與基類同名的新函式,即使函式的引數表不同,從基類繼承的同名函式的所有過載形式也都被隱藏。如果要訪問被隱藏的成員,就需要使用類作用域分辨符和基類名來限定。
根據上面的解釋,CBase類處於作用域的外層,因此派生類的方法對於其將是不可見的,即隱藏的。而在派生類中,如果有過載函式時,基類函式將會被隱藏,否則基類函式就不被隱藏。
函式過載和虛擬函式都能實現物件的多型特性,但是函式過載只能實現靜態的多型,也就是在編譯時多型,即編譯器會給過載的函式在編譯時加上不同的字首,是函式名在執行時不同。而虛擬函式可以實現動態多型,即執行時多型,但是虛擬函式實現需要增加額外的虛擬函式指標等,會增加相應空間。
再看下例:
class CBase
{
public:
CBase();
virtual void Walk(){cout<<"CBase:Walk"<<endl;}
virtual void Jump(){cout<<"CBase:Jump"<<endl;}
void Run(int speed){cout<<"CBase:Run:"<<"Speed="<<speed<<endl;}
};
class CDerivedA : public CBase
{
public:
CDerivedA();
void Walk(){cout<<"CDerivedA:Walk"<<endl;}
void Jump(){cout<<"CDerivedA:Jump"<<endl;}
void Run(int speed) {cout<<"CDerivedA:Run"<<"Speed="<<speed<<endl;}
void Run(int speed,int direction){cout<<"CDerivedA:Run"<<"Speed="<<speed<<";Direction="<<direction<<endl;}
};
int main()
{
CBase *pTmp1=new CDerivedA ;
pTmp1->Walk();
pTmp1->Run(20);
CDerivedA *pTmp2=(CDerivedA*)pTmp1 ;
pTmp2->Run(20,3);//合法的
//pTmp1->Run(20,3);//不合法
return 0;
}
以上程式碼就可以看出,派生類中的過載方法,在基類是不能呼叫的,編譯的時候就會出錯。C++中實現多型的三種方式:
模板、函式過載、虛擬函式。
未完,待續。。。