Skip to main content

10-2 存取控制——public 與 private

一、class 裡的東西,不是誰都可以動的

先看一個銀行帳戶的例子。

#include <iostream>

using namespace std;

class BankAccount {
public:
    string owner;
    int balance;
};

int main() {
    BankAccount acc;
    acc.owner   = "小明";
    acc.balance = 1000;

    acc.balance = -99999;   // 沒有任何阻擋,帳戶餘額變成負的!

    cout << acc.balance << endl;
}

這是因為我們之前在 class 裡用了 public: 這個存取修飾詞。 public 的意思就是「公開的」,誰想看、誰想改都可以。

private: 將存取限制改為「私有的」看看差別。

#include <iostream>

using namespace std;

class BankAccount {
private:
    string owner;
    int balance;
};

int main() {
    BankAccount acc;
    acc.owner   = "小明";
    acc.balance = 1000;

    acc.balance = -99999;   // 沒有任何阻擋,帳戶餘額變成負的!

    cout << acc.balance << endl;
}

修改之後,連編譯都失敗,出現了大量如下的錯誤訊息。

因為使用 privte 修飾的屬性和成員函數,在該 class 之外(第 5~9 行之外)都無法存取,在編譯時就攔下了不合法的存取。

main.cpp:13:9: error: 'std::string BankAccount::owner' is private within this context
   13 |     acc.owner   = "小明";
      |         ^~~~~
main.cpp:7:12: note: declared private here
    7 |     string owner;
      |            ^~~~~
main.cpp:14:9: error: 'int BankAccount::balance' is private within this context
   14 |     acc.balance = 1000;
      |         ^~~~~~~
main.cpp:8:9: note: declared private here
    8 |     int balance;
      |         ^~~~~~~
main.cpp:16:9: error: 'int BankAccount::balance' is private within this context
   16 |     acc.balance = -99999;   // 沒有任何阻擋,帳戶餘額變成負的!
      |         ^~~~~~~
main.cpp:8:9: note: declared private here
    8 |     int balance;
      |         ^~~~~~~
main.cpp:18:17: error: 'int BankAccount::balance' is private within this context
   18 |     cout << acc.balance << endl;
      |                 ^~~~~~~
main.cpp:8:9: note: declared private here
    8 |     int balance;
      |         ^~~~~~~

二、提供外界有限的操作

設成 public 太危險,設成 private 又動不了,怎麼辦呢?

我們可以

  • 把內部屬性設為 private
  • 用 public 開放幾個成員函數,給外界有限能力操作內部屬性
#include <iostream>

using namespace std;

class BankAccount {
private:
    string owner;
    int balance;    // 外部無法直接存取

public:
    // 以下成員函數,開放給外部使用,提供存取 balance 的管道
    bool withdraw(int amount) {
        if (amount > balance) {
            cout << "餘額不足!" << endl;
            return false;
        }
        balance -= amount;
        return true;
    }

    void deposit(int amount) {
        if (amount <= 0) {
            cout << "存款金額必須大於 0!" << endl;
            return;
        }
        balance += amount;
    }

    int getBalance() { return balance; }
};

int main()
{
    BankAccount account;
    account.deposit(1000);  // 存款 1000 元
    cout << "目前餘額: " << account.getBalance() << " 元" << endl;

    if (account.withdraw(500)) {  // 提款 500 元
        cout << "提款成功!" << endl;
    }
    cout << "目前餘額: " << account.getBalance() << " 元" << endl;

    if (!account.withdraw(600)) {  // 嘗試提款 600 元,應該會失敗
        cout << "提款失敗!" << endl;
    }
    cout << "目前餘額: " << account.getBalance() << " 元" << endl;

    return 0;
}

現在外部程式只能透過 withdraw()deposit() 來改變餘額,透過 getBalance() 來取得餘額,非法操作會被擋下來。

public、private 存取修飾詞的作用範圍

class Example {
// 這裡沒寫 → class 預設是 private
    int secret;

public:
    int visible;
    void doSomething() { }

private:
    int alsoSecret;
};

public:private: 後面的所有成員,都套用該修飾子,直到遇到下一個修飾子為止。

三、封裝(encapsulate)的概念

有時候我們不希望 class 的使用者直接去更動某些 data member 的資料,這時我們會將這些 data member 宣告在 private 區域中,並在 public 區域中提供 member function 來操作這些 data member。

以手錶為例,一般來說製表師傅不會希望使用者自己去撥動錶內的齒輪,所以他會把手錶內部的複雜機構用錶殼緊緊的封裝起來,不過他還是會提供一些按鈕或旋鈕來讓我們調整時間。

  • private – 手錶的內部複雜機構
  • public – 調整時間的按鈕或旋鈕

當我們試著在非成員函數裡存取 private 的 data member 或 member function 時,在編譯時期就會出現錯誤訊息。

這就是「物件導向程式設計」中很重要的「封裝」概念。我們設計了一個很精巧的 class,包含了很多的屬性和成員函數,但是對外界來說,它被一個「盒子」封裝起來,你只操作這個盒子外漏的那幾個界面。