在繼承過程中,從基類派生出派生類,可能出現重名的成員,包括數據成員或成員函數,這些屬於作用域方面的內容。
一、基類的引用或指針訪問派生類
基類的引用或指針只能訪問派生類中屬於基類的部分成員,不能訪問派生類的部分,否則就會編譯出錯。
- #include <iostream>
- using namespace std;
- class CBase
- {
- public:
- void BasePrint() const
- {
- cout << "調用基類的成員函數" << endl;
- }
- };
- class CDerived : public CBase
- {
- public:
- void DerivedPrint() const
- {
- cout << "調用派生類的成員函數" << endl;
- }
- };
- int main()
- {
- CDerived obj;
- CBase *bObj = &obj;
- CDerived * dObj = &obj;
- bObj->BasePrint();
- // bObj->DerivedPrint();//錯誤,基類指針不能調用派生類的部分
- dObj->BasePrint();
- dObj->DerivedPrint();
- return 0;
- }
執行結果:
調用基類的成員函數
調用基類的成員函數
調用派生類的成員函數
二、重複的數據成員
有時基類和派生類有同名的數據成員,甚至可能在基類和間接基類中都有同名的數據成員。
這種情況一般會出現在:從 由另一個程序員創建的基類中派生自己的類,可能不知道在基類中有什麼私有數據成員,此時自己又創建了與基類當中同名的數據成員。
當然這種情況是不會阻礙繼承的,編譯器做了很多的工作。但這裏主要是介紹如何從派生類中調用派生類的同名私有成員或者基類的私有成員。
例子:
CBase是CDerived的父類,它們擁有一個同名成員爲mVal。
在派生類中
1.默認是訪問自己的數據成員mVal;
2.若要訪問基類的mVal,要用這種格式"<類名>::<數據成員>", CBase::mVal
- #include <iostream>
- using namespace std;
- class CBase
- {
- public:
- CBase(int x = 120) : mVal(x) {}
- int mVal;
- };
- class CDerived : public CBase
- {
- public:
- CDerived(int x = 100) : mVal(x) {}
- void PrintValue() const
- {
- cout << "犀利爹的年齡:" << CBase::mVal << endl
- << "犀利哥的年齡:" << mVal << endl;
- }
- int mVal;
- };
- int main()
- {
- CDerived human;
- human.PrintValue();
- return 0;
- }
執行結果:
犀利爹的年齡:120
犀利哥的年齡:100
三、重複的成員函數
基類與派生類若存在重複的成員函數,則有2種情況:
第一種情況:函數名相同,參數列表不同,如果將基類的成員函數的作用域引入派生類中,就是重載版本了
第二種情況:函數的所有方面都相同,包括函數名,參數列表與返回值
第一種情況:
函數名相同,參數列表不同,這跟函數的重載一樣。但這並不是函數的重載,因爲函數的重載必須在同一個作用域中定義,而基類和派生類定義了不同的作用域。在這裏,可以使用using在派生類中聲明基類的函數,將基類的函數的作用域引入派生類中的作用域,讓這裏的函數成爲重載版本,來個小例子。
- #include <iostream>
- using namespace std;
- class CBase
- {
- public:
- void Print(const char *str)
- {
- cout << "調用基類的打印函數: str = " << str << endl;
- }
- };
- class CDerived : public CBase
- {
- public:
- using CBase::Print;
- void Print(int val)
- {
- cout << "調用派生類的打印函數: val = " << val << endl;
- }
- };
- int main()
- {
- CDerived obj;
- obj.Print(2);
- obj.Print("hello");
- return 0;
- }
執行結果:
調用派生類的打印函數: val = 2
調用基類的打印函數: str = hello
這裏如果沒有"using CBase::Print;"的聲明,那麼將會編譯錯誤。
第二種情況:
函數的所有方面都相同,包括函數名,參數列表與返回值,在派生類中,要調用基類的同名成員函數,方法跟同名數據成員的方法一樣,格式是:"<類名>::<成員函數>"。
- #include <iostream>
- using namespace std;
- class CBase
- {
- public:
- void Print()
- {
- cout << "調用基類的成員函數" << endl;
- }
- };
- class CDerived : public CBase
- {
- public:
- void Print()
- {
- cout << "調用派生類的成員函數" << endl;
- }
- };
- int main()
- {
- CDerived human;
- human.Print();
- human.CBase::Print();
- return 0;
- }
執行結果:
調用派生類的成員函數
調用基類的成員函數
四、虛函數的作用域
關於虛函數的內容,就會涉及到動態綁定的知識了,這裏先不介紹,主要還是說明類繼承的作用域的問題。在類的繼承過程中,基類中的成員函數是不是虛函數,將會起着非常大的作用,先來看2個虛函數的例子。
1.在基類中沒有使用virtual關鍵字
- #include <iostream>
- using namespace std;
- class CBase
- {
- public:
- void Print() const
- {
- cout << "調用CBase::Print()" << endl;
- }
- };
- class CDerived1 : public CBase
- {
- public:
- void Print() const
- {
- cout << "調用CDerived1::Print()" << endl;
- }
- };
- class CDerived2 : public CDerived1
- {
- public:
- void Print() const
- {
- cout << "調用CDerived2::Print()" << endl;
- }
- };
- int main()
- {
- CBase bobj;
- CDerived1 d1obj;
- CDerived2 d2obj;
- CBase *bp1 = &bobj;
- CBase *bp2 = &d1obj;
- CBase *bp3 = &d2obj;
- bp1->Print();
- bp2->Print();
- bp3->Print();
- return 0;
- }
執行結果:
調用CBase::Print()
調用CBase::Print()
調用CBase::Print()
2.在基類中使用virtual關鍵字
- #include <iostream>
- using namespace std;
- class CBase
- {
- public:
- virtual void Print() const
- {
- cout << "調用CBase::Print()" << endl;
- }
- };
- class CDerived1 : public CBase
- {
- public:
- void Print() const
- {
- cout << "調用CDerived1::Print()" << endl;
- }
- };
- class CDerived2 : public CDerived1
- {
- public:
- void Print() const
- {
- cout << "調用CDerived2::Print()" << endl;
- }
- };
- int main()
- {
- CBase bobj;
- CDerived1 d1obj;
- CDerived2 d2obj;
- CBase *bp1 = &bobj;
- CBase *bp2 = &d1obj;
- CBase *bp3 = &d2obj;
- bp1->Print();
- bp2->Print();
- bp3->Print();
- return 0;
- }
執行結果:
調用CBase::Print()
調用CDerived1::Print()
調用CDerived2::Print()
1,2兩個例子可以看到,一個virtual的關鍵字起着這麼大的作用。當基類的成員函數使用virtual關鍵字修飾的話,基類指針會根據指向的對象的實際類型來尋找相應的類的成員函數的定義。要獲得動態綁定,必須通過基類的引用或指針調用虛成員。
在第二個例子中,將Print函數聲明爲虛函數,這樣子編譯器會生成代碼,在運行時基於引用或指針所綁定的對象的實際類型進行調用。bp2指向CDerived1對象,bp3指向CDerived2對象,所以都是調用屬於自己的Print函數版本。
補充:
派生類虛函數調用基類版本時,必須顯示調用作用域操作符。如果派生類函數忽略了這樣做,則函數調用會在運行時確定並且將是一個自身調用,從而導致無窮遞歸。
class A{ public: virtual void f1() = 0; } class B: public A{ public: virtual void f1(); void f2(); }
派生類虛函數調用基類版本,相當於B中的f1調用A中的f1,如果不顯示作用域,那麼派生類中繼承的虛函數會屏蔽基類中的虛函數,於是就相當於自己調用自己,就無窮遞歸了。 如果你在f2中調用基類中的f1,不顯示作用域就相當於調用B中的f1。