C++類別別成員的存取範圍(C++ private、public、protected)

2020-07-16 10:04:23
在類的定義中,可以用 private、public 和 protected 三種關鍵字來指定成員可被存取的範圍。

private:用來指定私有成員。一個類的私有成員,不論是成員變數還是成員函數,都只能在該類的成員函數內部才能被存取。

public:用來指定公有成員。一個類的公有成員在任何地方都可以被存取。

protected:用來指定保護成員。這需要等介紹“繼承”之後再解釋。

三種關鍵字出現的次數和先後次序都沒有限制。成員變數的可存取範圍由離它前面最近的那個存取範圍說明符決定。

如果某個成員前面沒有存取範圍說明符,則對 class 來說,該成員預設地被認為是私有成員;對 struct 來說,該成員預設地被認為是公有成員。例如:
class A
{
    int m, n;
public:
    int a, b;
    int func1();
private:
    int c, d;
    void func2();
public:
    char e;
    int f;
    int func3();
};
在上面的類 A 中,成員 a、b 和 func1 是公有的,c、d 和 func2 是私有的,e、f 和 func3 又是公有的。m 和 n 沒有指定可存取範圍,則是私有的。如果把 class 換成 struct,那麼 m 和 n 就是公有的。

下面的程式可以說明公有成員和私有成員的區別。假設一個企業員工管理程式的一小部分程式碼如下:
#include <iostream>
#include <cstring>
using namespace std;
class CEmployee {
private:
    char szName[30];  //名字
public:
    int salary;  //工資
    void setName(char* name);
    void getName(char* name);
    void averageSalary(CEmployee el, CEmployee e2);
};
void CEmployee::setName(char* name) {
    strcpy(szName, name);  //ok
}
void CEmployee::getName(char* name) {
    strcpy(name, szName);  //ok
}
void CEmployee::averageSalary(CEmployee el, CEmployee e2)
{
    salary = (el.salary + e2.salary) / 2;
}
int main()
{
    CEmployee e;
    strcpy(e.szName, "Tom1234567889");  //編譯出錯,不能存取私有成員
    e.setName("Tom");  //ok
    e.salary = 5000;  //ok
    return 0;
}
在上面的程式中,szName 是私有成員,其他成員都是公有的。

私有成員只能在成員函數內部存取,因此第 14 行和第 17 行沒有問題,這兩條語句都是在存取函數所作用的那個物件的 szName 私有成員。另外,類的成員函數內部可以存取任何同類物件的私有成員。

所謂成員函數內部,指的就是成員函數的函數體內部。main 函數中的語句,如第 26 行,當然就不在 CEmployee 的成員函數內部,因此該行試圖存取 e 這個物件的 szName 私有成員變數就會導致編譯錯誤。

而第 27 行雖然也不屬於 CEmployee 類的成員函數內部,但其存取的是物件 e 的公有成員 setName,因此沒有問題。同理,第 28 行也沒有問題。

在 CEmployee 類的成員函數外面,若要存取 CEmployee 物件的 szName 私有成員變數,不能直接存取,只能通過兩個成員函數 setName 和 getName 間接進行存取。

“隱藏”的作用

設定私有成員的機制叫作“隱藏”。“隱藏”的一個目的就是強制對成員變數的存取一定要通過成員函數進行。這樣做的好處是,如果以後修改了成員變數的型別等屬性,只需要更改成員函數即可;否則,所有直接存取成員變數的語句都需要修改。

以上面的企業員工管理程式為例,如果 szName 不是私有的,那麼整個程式中可能會有很多類似於第 26 行

strcpy(man1, szName, "Tom1234567889");

這樣的語句。假設需要將該程式移植到記憶體空間緊張的手持裝置上,希望將 CEmployee 類的成員變數 szName 改為 char szName[5],以便節約空間,那麼所有這樣的語句都要找出來檢查一番並修改,以防止陣列越界。這顯然很麻煩。

如果將 szName 變為私有的,則除了 CEmployee 類的成員函數內部,其他地方不可能出現第 26 行那樣對 szName 直接存取的語句,所有對 szName 的存取都是通過成員函數進行的。例如:

e.setName("Tom12345678909887");

就算 szName 的長度變短了,上面的語句也不需要修改,只要修改 setName 成員函數,在其中去掉超長的部分,確保陣列不越界就可以了。

可見,“隱藏”有利於程式的修改。

“隱藏”機制還可以避免對物件的不正確操作。有的成員函數只是設計用來讓同類的成員函數呼叫的,並不希望對外開放,因此就可以將它們宣告為私有的,隱藏起來。

現代軟體開發絕大多數是合作完成的,一個程式設計師設計了一個類,可能被許多程式設計師使用。在設計類的時候,應當盡可能隱藏使用者不需要知道的實現細節,只留下必要的介面(即一些成員函數)來對物件進行操作,這樣能夠避免類的使用者隨意使用成員函數和成員變數而導致錯誤。

就像數位照相機的設計者會用外殼將內部的電路全部封裝隱藏起來,使用者不需要知道數位照相機的具體工作原理以及其中有哪些器件,只要能通過設計者留下的介面,即外殼上的各種按鈕來使用照相機即可。如果把內部的電路、器件、開關都暴露給使用者,那麼外行使用者很可能會把照相機弄壞。