本文較為深入的探討了C++繼承中的存取控制,對深入掌握C++物件導向程式設計是非常必要的。具體內容如下:
通常來說,我們認為一個類有兩種不同的使用者:普通使用者 和 類的實現者。其中,普通使用者編寫的代碼使用類的對象,這部分代碼只能訪問類的公有(介面)成員;實現者則負責編寫類的成員和友元的代碼,成員和友元既能訪問類的公有部分,也能訪問類的私人部分。如果進一步考慮繼承的話就會出現第三種使用者,即衍生類別。衍生類別可以訪問基類的公有(public)成員和受保護(protected)成員,但不能訪問基類的私人(private)成員。
繼承相關點如下:
①.大多數類都只繼承自一個類,這種形式的繼承叫做“單繼承”。本文主要講的是單繼承。
②.一個衍生類別的對象中,包含繼承自基類的部分和衍生類別自訂的部分。正因為衍生類別含有基類部分,所以可以進行衍生類別到基類的類型轉換,這種轉換是隱式的。
③.不存在從基類向衍生類別的隱式類型轉換。
④.衍生類別向基類的自動類型轉換隻對指標或引用有效,對象之間不存在類型轉換。
⑤.如果基類定義了靜態成員,則不論派生出多少個衍生類別,每個靜態成員都只存在唯一執行個體。
⑥防止一個類被繼承可以使用關鍵字final,這時C++11新標準中提供的。
此外,讀者還需要瞭解一下前面文章所介紹的繼承中的虛函數與純虛函數。
一、公有、私人和受保護的成員
1 . 訪問說明符
在C++中通過使用訪問說明符public、protected、private來對類的成員進行存取控制,控製成員對於普通使用者或衍生類別來說是否可訪問:
public:定義為public的成員對普通使用者、類的實現者、衍生類別都是可訪問的。public通常用於定義類的外部介面。
protected:定義protected成員的目的是讓衍生類別可以訪問而禁止其他使用者訪問。所以類的實現者和衍生類別可以訪問,而普通使用者不能訪問。
private:定義為private的成員只能被類的實現者(成員和友元)訪問。private部分通常用於封裝(即隱藏)類的實現細節。
class People{ protected: string name; }; class Student : public People{ public: friend void Print(Student &s); friend void Print(People &p); }; // 正確,可以通過衍生類別對象訪問基類的protected成員 void Print(Student &s){ s.name="Songlee"; cout<< s.name << endl; } // 錯誤,不能通過基類對象訪問基類的protected成員 void Print(People &p){ p.name="Songlee"; cout<< p.name << endl; }
需要注意的是,衍生類別的成員或友元只能通過衍生類別對象來訪問基類的受保護的成員。衍生類別對於一個基類對象中的受保護的成員沒有任何存取權限。
2 . 改變成員的可訪問性
有時我們需要改變衍生類別繼承的某個名字的存取層級,通過使用using聲明:
class People{ protected: string name; }; class Student : public People{ public: using People::name; // 將繼承來的name成員的存取權限改為public }; int main() { Student me; me.name = "SongLee"; // 可以訪問name了 cout << me.name << endl; return 0; }
通過在類的內部使用using聲明語句,我們可以將該類的直接或間接基類中的任何可訪問成員(非私人成員)標記出來,改變其存取權限。
二、公有、私人和受保護繼承
我們注意到,在類的衍生的資料行表中用到了訪問說明符public、protected和private,它們分別表示不同的繼承方式:
class A : public B { /* */ }; // 公有繼承 class A : private B { /* */ }; // 私人繼承 class A : protected B { /* */ }; // 受保護繼承
衍生類別的衍生的資料行表中的訪問說明符對於衍生類別的成員(及友元)能否訪問其直接基類的成員沒什麼影響。衍生類別的成員(及友元)對基類成員的存取權限只與基類中的訪問說明符有關。
那麼衍生的資料行表中的訪問說明符有什麼作用呢?
衍生的資料行表中訪問說明符的作用是控制衍生類別使用者對於基類成員的存取權限,注意是衍生類別的使用者。下面給出不同的繼承方式導致的存取權限的變化:
public繼承:如果繼承是公有的,則成員將遵循其原有的訪問說明符。父類中的public、protected和private屬性在子類中不發生改變。
protected繼承:比protected層級高的存取權限會變成protected。即父類中的public屬性在子類中變為protected,父類中的protected和private屬性在子類中不變。
private繼承:比private層級高的存取權限會變成private。即父類中的三種訪問屬性在子類中都會變成private。
class A { // 基類 public: string A_public; // 公有成員 protected: string A_protected; // 受保護的成員 }; class B : private A { // 私人繼承 public: B(){ A_public="public"; A_protected="protected"; }; }; int main() { B b; // 通過B的對象訪問 cout << b.A_public <<" "<< b.A_protected << endl; // 錯誤,因為是私人繼承 return 0; }
如果我們在衍生的資料行表中不使用訪問說明符,則struct關鍵字預設的是公有繼承,class關鍵字預設的是私人繼承。不過建議在繼承時最好顯式地將訪問說明符寫出來。
另外,不同的繼承方式也會影響衍生類別向基類的轉換,假定Derive繼承自Base:
1.只有當Derive公有地繼承自Base時,使用者代碼才能使用衍生類別向基類的轉換;如果Derive繼承Base的方式是受保護的或者私人的,則使用者代碼不能使用該轉換。
2.不論Derive以什麼方式繼承Base,Derive的成員函數和友元都能使用衍生類別向基類的轉換;衍生類別向其直接基類的類型轉換對於衍生類別的成員和友元來說永遠是可訪問的。
3.如果Derive繼承Base的方式是公有的或者受保護的,則Derive的衍生類別的成員和友元可以使用Derive向Base的類型轉換;反之,如果Derive繼承Base的方式是私人的,則不能使用。