結論
首先給出結論,請看下圖,看圖說話最容易理解了。
類眼中的自己
類中定義的所有成員,不論是以public, protected還是private修飾,對類自身而言,它們都是可見的。
對象眼中的類
站在類的對象的角度去觀察類,這時,只有類中的public成員是可見的。而其中的protected和private成員對對象而言,是不可見的。
友元眼中的類
站在友元的角度,類中所有的成員,不論是以public, protected還是private修飾,對友元而言,它們都是可見的。
派生類眼中的基類
派生類只能看見基類中的public和protected成員。而基類中的private成員,對於派生類而言,是不可見的。有一點必須注意,這里的public, protected和private均是指基類在被繼承之后所呈現出來的成員訪問權限。
下表展示了不同的繼承方式,對基類中各成員訪問權限的影響。
基類中所用訪問控制修飾符 | public繼承 | protected繼承 | private繼承 |
---|---|---|---|
public | public | protected | private |
protected | protected | protected | private |
private | private | private | private |
所以,在派生類中,對於基類中的各成員的訪問權限,我們可以按如下步驟去判斷:
- 根據上表,確定基類中各成員在被繼承之后訪問權限的變化。
- 根據“ 派生類只能看見基類中的public和protected成員” 這一點來確定哪些基類成員可以被訪問。
好了,到這里為止,結論就全部說完了。
接下來全是對結論的驗證,所以,無需從頭至尾瀏覽,請有針對性的選擇查看。
引入三種訪問控制符
C++中,存在三種訪問控制修飾符,它們分別是:
- public // 公有成員
- protected // 保護成員
- private // 私有成員
術語
為了使文章容易理解,我們對以下術語作出說明:
- 對象: 與類相對,對象是類的實例。
- 派生類:與基類相對,派生類就是子類。
- 繼承:繼承與派生是一個意思。繼承偏重指出此過程中不變的部分,而派生的意思則更偏向於在原有基礎上所新增加的部分。
- 成員:類中成員變量和成員函數的統稱。
實踐1--對象的訪問權限
在以下的例子中,我們創建了一個簡單的類。
下面,我們就來探究一下,對於該類中被不同訪問控制修飾符修飾的成員,它們對於該類的對象都有什么樣的訪問權限。
#include <iostream>
using namespace std;
class CBase
{
private:
int a_base_private;
protected:
int b_base_protected;
public:
int c_base_public;
public:
CBase(){a_base_private = 1; b_base_protected = 2; c_base_public = 3;}
~CBase(){}
int getA() const {return a_base_private;} // OK, 類可以訪問自身的所有成員
int getB() const {return b_base_protected;} // OK, 類可以訪問自身的所有成員
int getC() const {return c_base_public;} // OK, 類可以訪問自身的所有成員
};
int main()
{
int tmp;
CBase baseObj;
//baseObj.a_base_private = 1; // KO, 對象不能訪問類的private成員
//baseObj.b_base_protected = 1; // KO, 對象不能訪問類的protected成員
baseObj.c_base_public = 1; // OK, 對象可以訪問類的public成員
tmp = baseObj.getA(); // OK, 對象可以訪問類的public成員
tmp = baseObj.getB(); // OK, 對象可以訪問類的public成員
tmp = baseObj.getC(); // OK, 對象可以訪問類的public成員
}
從以上實踐中可以得出以下結論:
- 類可以訪問自身的所有成員,不論是private, protected 還是 public。
- 對象只能訪問類的public成員。
實踐2--友元的訪問權限
在以上例子的基礎上,讓我們來考慮一下,對於該類中被不同訪問控制修飾符修飾的成員,該類的友元函數和友元類對這些成員都有什么樣的訪問權限。
#include <iostream>
using namespace std;
class CBase;
class CFriend;
void ClearBaseA(CBase &obj);
class CBase
{
friend CFriend; // 聲明CFriend為自己的友元類
friend void ClearBaseB(CBase &obj); // 聲明ClearBaseA為自己的友元函數
private:
int a_base_private;
protected:
int b_base_protected;
public:
int c_base_public;
public:
CBase(){a_base_private = 1; b_base_protected = 2; c_base_public = 3;}
~CBase(){}
int getA() const {return a_base_private;} // OK, 類可以訪問自身的所有成員
int getB() const {return b_base_protected;} // OK, 類可以訪問自身的所有成員
int getC() const {return c_base_public;} // OK, 類可以訪問自身的所有成員
};
class CFriend
{
private:
CBase obj;
public:
CFriend(){}
~CFriend(){}
int setBaseA(int f) {obj.a_base_private = f;} // OK, 在友元類中,可以訪問Base類的私有成員
int getBaseA() const {return obj.getA();}
};
void ClearBaseB(CBase &obj)
{
obj.b_base_protected = 0; // OK, 在友元函數中,可以訪問Base類的保護成員
}
int main()
{
int tmp;
CBase baseObj;
CFriend friendObj;
cout << baseObj.getB() << endl; // 通過構造函數初始化為2
ClearBaseB(baseObj);
cout << baseObj.getB() << endl; // 被友元函數給清0了
cout << friendObj.getBaseA() << endl; // 通過構造函數初始化為1
friendObj.setBaseA(7);
cout << friendObj.getBaseA() << endl; // 被友元類給設置為了7
}
由上例中可以看出,友元可以訪問類中的private和protected成員,對於public成員,當然更是可以訪問的了,雖然以上例子中並沒有驗證這一點。
所以,我們可以得出以下結論:
- 友元函數或友元類可以訪問類中的所有成員。
另外,關於友元還有一點需要注意。在友元中,只能通過對象來訪問聲明友元的類成員。
小結
現在,讓我們換一個角度,通過以下表格總結一下。
訪問控制修飾符 | 類 | 對象 | 友元 |
---|---|---|---|
public | 可見 | 可見 | 可見 |
protected | 可見 | 不可見 | 可見 |
private | 可見 | 不可見 | 可見 |
引入三種繼承方式
在C++中,在繼承的過程中,有以下三種繼承方式,它們分別是:
- public (公有繼承)
- protected (保護繼承)
- private (私有繼承)
這三個關鍵字與之前的三種訪問控制修飾符剛好相同,但在這里,它們有不同的意義。
- 對於public繼承,基類中的成員的訪問控制修飾符不作任何改動,原樣繼承到派生類中。
也就是說,基類中的public成員,在被以public方式繼承之后,仍然是基類的public成員;基類中的protected成員,仍然是protected成員;基類中的private成員,它仍然是private成員。注意,在繼承之后,基類中的private成員對於派生類是不可見的。 - 對於protected繼承,基類中的public成員,在被以protected方式繼承之后,它變成了基類的protected成員;基類中的protected成員,則仍然是protected成員;基類中的private成員,則仍然是private成員。注意,在繼承之后,基類中的private成員對於派生類是不可見的。
- 對於private繼承,基類中的public和protected和private成員,在被以private方式繼承之后,在基類中均成為了private成員;而基類中的private成員,對派生類不可見。
public繼承方式
在第一個例子的基礎之上,我們通過public方式繼承出一個新的派生類。
#include <iostream>
using namespace std;
class CBase
{
private:
int a_base_private;
protected:
int b_base_protected;
public:
int c_base_public;
public:
CBase(){a_base_private = 1; b_base_protected = 2; c_base_public = 3;}
~CBase(){}
int getA() const {return a_base_private;} // OK, 類可以訪問自身的所有成員
int getB() const {return b_base_protected;} // OK, 類可以訪問自身的所有成員
int getC() const {return c_base_public;} // OK, 類可以訪問自身的所有成員
};
class CDerived:public CBase
{
private:
int x_derived_private;
protected:
int y_derived_protected;
public:
int z_derived_private;
public:
CDerived(){x_derived_private = 4; y_derived_protected = 5; z_derived_private = 6;}
~CDerived(){}
//void setBaseA(int t){a_base_private = t;} // KO, 派生類中不能訪問基類的private成員
void setBaseB(int t){b_base_protected = t;} // OK, 派生類中可以訪問基類的protected成員
void setBaseC(int t){c_base_public = t;} // OK, 派生類中可以訪問基類的public成員
int getX() const {return x_derived_private;}
int getY() const {return y_derived_protected;}
int getZ() const {return z_derived_private;}
};
int main()
{
CDerived derivedObj;
//derivedObj.a_base_private = 1; // KO, 基類中由private修飾的a_base_private,對派生類是不可見的,即使在派生類中都不能訪問,更別提派生類對象了。
//derivedObj.b_base_protected = 1; // KO, 對象不能訪問類的protected成員(public方式繼承的protected成員,在派生類中仍為protected成員)
derivedObj.c_base_public = 1; // OK, 對象可以訪問類的public成員(public方式繼承的public成員,在派生類中仍為public成員)
cout << derivedObj.getA() << endl; // OK, 對象可以訪問類的public成員(public方式繼承的public成員,在派生類中仍為public成員)
derivedObj.setBaseB(8); // OK, 對象可以訪問類的public成員
cout << derivedObj.getB() << endl; // OK, 對象可以訪問類的public成員(public方式繼承的public成員,在派生類中仍為public成員)
derivedObj.setBaseC(9); // OK, 對象可以訪問類的public成員
cout << derivedObj.getC() << endl; // OK, 對象可以訪問類的public成員(public方式繼承的public成員,在派生類中仍為public成員)
}
由以上例子可以看出:
- 基類中的private, protected, public成員,經由public繼承之后,在派生類中分別為不可見private, protected,public成員。
- 派生類中不能訪問基類的private成員,但可以訪問基類的private和protected成員。
protected繼承方式
在第一個例子的基礎之上,我們通過protected方式繼承出一個新的派生類。
#include <iostream>
using namespace std;
class CBase
{
private:
int a_base_private;
protected:
int b_base_protected;
public:
int c_base_public;
public:
CBase(){a_base_private = 1; b_base_protected = 2; c_base_public = 3;}
~CBase(){}
int getA() const {return a_base_private;} // OK, 類可以訪問自身的所有成員
int getB() const {return b_base_protected;} // OK, 類可以訪問自身的所有成員
int getC() const {return c_base_public;} // OK, 類可以訪問自身的所有成員
};
class CDerived:protected CBase
{
private:
int x_derived_private;
protected:
int y_derived_protected;
public:
int z_derived_private;
public:
CDerived(){x_derived_private = 4; y_derived_protected = 5; z_derived_private = 6;}
~CDerived(){}
//void setBaseA(int t){a_base_private = t;} // KO, 派生類中不能訪問基類的private成員
void setBaseB(int t){b_base_protected = t;} // OK, 派生類中可以訪問基類的protected成員
void setBaseC(int t){c_base_public = t;} // OK, 派生類中可以訪問基類的public成員
int getX() const {return x_derived_private;} // OK, 類可以訪問自身的所有成員
int getY() const {return y_derived_protected;} // OK, 類可以訪問自身的所有成員
int getZ() const {return z_derived_private;} // OK, 類可以訪問自身的所有成員
};
int main()
{
CDerived derivedObj;
//derivedObj.a_base_private = 1; // KO, 對象不能訪問類的private成員(protected方式繼承的private成員,在派生類中不可見)
//derivedObj.b_base_protected = 1; // KO, 對象不能訪問類的protected成員(protected方式繼承的protected成員,在派生類中仍為protected成員)
//derivedObj.c_base_public = 1; // KO, 對象不可以訪問類的protected成員(protected方式繼承的public成員,在派生類中成為protected成員)
//cout << derivedObj.getA() << endl; // KO, 對象不可以訪問類的protected成員(protected方式繼承的public成員,在派生類中成為protected成員)
//cout << derivedObj.getB() << endl; // KO, 對象不可以訪問類的protected成員(protected方式繼承的public成員,在派生類中成為protected成員)
//cout << derivedObj.getC() << endl; // KO, 對象不可以訪問類的protected成員(protected方式繼承的public成員,在派生類中成為protected成員)
}
由以上例子可以看出:
- 基類中的private, protected, public成員,經由protected繼承之后,在派生類中分別為不可見private, protected,protected成員。
- 派生類中不能訪問基類的private成員,但可以訪問基類的private和protected成員。
private繼承方式
在第一個例子的基礎之上,我們通過private方式繼承出一個新的派生類。
#include <iostream>
using namespace std;
class CBase
{
private:
int a_base_private;
protected:
int b_base_protected;
public:
int c_base_public;
public:
CBase(){a_base_private = 1; b_base_protected = 2; c_base_public = 3;}
~CBase(){}
int getA() const {return a_base_private;} // OK, 類可以訪問自身的所有成員
int getB() const {return b_base_protected;} // OK, 類可以訪問自身的所有成員
int getC() const {return c_base_public;} // OK, 類可以訪問自身的所有成員
};
class CDerived:private CBase
{
private:
int x_derived_private;
protected:
int y_derived_protected;
public:
int z_derived_private;
public:
CDerived(){x_derived_private = 4; y_derived_protected = 5; z_derived_private = 6;}
~CDerived(){}
//void setBaseA(int t){a_base_private = t;} // KO, 派生類中不能訪問基類的private成員,因為其在派生類中不可見
void setBaseB(int t){b_base_protected = t;} // OK, 派生類中可以訪問基類的protected成員
void setBaseC(int t){c_base_public = t;} // OK, 派生類中可以訪問基類的public成員
int getX() const {return x_derived_private;} // OK, 類可以訪問自身的所有成員
int getY() const {return y_derived_protected;} // OK, 類可以訪問自身的所有成員
int getZ() const {return z_derived_private;} // OK, 類可以訪問自身的所有成員
};
int main()
{
CDerived derivedObj;
//derivedObj.a_base_private = 1; // KO, (private方式繼承的private成員,在派生類中不可見)
//derivedObj.b_base_protected = 1; // KO, (private方式繼承的protected成員,在派生類中不可見)
//derivedObj.c_base_public = 1; // KO, (private方式繼承的public成員,在派生類中成為不可見)
//cout << derivedObj.getA() << endl; // KO, (private方式繼承的public成員,在派生類中不可見)
//cout << derivedObj.getB() << endl; // KO, (private方式繼承的public成員,在派生類中不可見)
//cout << derivedObj.getC() << endl; // KO, (private方式繼承的public成員,在派生類中不可見)
cout << derivedObj.getX() << endl;
cout << derivedObj.getY() << endl;
cout << derivedObj.getZ() << endl;
}
由以上例子可以看出:
- 基類中的private, protected, public成員,經由private繼承之后,在派生類中均不可見。
- 派生類中不能訪問基類的private成員,但可以訪問基類的private和protected成員。
小結
- 不論何種繼承方式,派生類都不能訪問基類的private成員,它只能訪問基類的public和protected成員。
- 三種繼承方式對不同訪問控制符修飾的成員的影響如下表所示。
基類中所用訪問控制修飾符 | public繼承 | protected繼承 | private繼承 |
---|---|---|---|
public | public | protected | private |
protected | protected | protected | private |
private | private | private | private |
總結
- 友元和類一樣,可以訪問類的所有成員。
- 對象只能訪問類的public成員。
- 派生類只能訪問基類的public和protected成員,而不能訪問基類的private成員。
- 對於派生出來的類,首先根據繼承方式,確定基類各成員在經指定的繼承方式繼承后的訪問控制權限(經繼承后基類各成員是變成了public,protected還是private),然后根據第1、2、3點對各成員進行訪問。
- 經繼承后,基類中的成員會根據繼承方式,對各成員的訪問控制符進行修改。修改之后,基類中的private成員對派生類不可見。
參考文獻
- 鄭莉 C++語言程序設計 類的友元 繼承方式簡介及公有繼承 私有繼承和保護繼承
- 菜鳥教程 C++ 類訪問修飾符