巢狀、區域、匿名類別


在類別中可以再定義類別,稱為巢狀類別或內部類別,應用的場景之一是實作 IntLinkedList 時,內部節點可用 IntNode 來定義:

#include <iostream>
using namespace std;

class IntLinkedList {
    class IntNode {
    public:
        IntNode(int value, IntNode *next) : value(value), next(next) {}
        int value;
        IntNode *next;
    };

    IntNode *first = nullptr;

public:
    IntLinkedList& append(int value);
    int get(int i);
};

IntLinkedList& IntLinkedList::append(int value) {
    IntNode *node = new IntNode(value, nullptr);
    if(first == nullptr) {
        this->first = node; 
    }
    else {
        IntNode *last = this->first;
        while(last->next != nullptr) {
            last = last->next;
        }
        last->next = node;
    }
    return *this;
}

int IntLinkedList::get(int i) {
    IntNode *last = this->first;
    int count = 0;
    while(true) {
        if(count == i) {
            return last->value;
        }
        last = last->next;
        count++;
    }
}

int main() {
    IntLinkedList lt;

    lt.append(1).append(2).append(3);
    cout << lt.get(1) << endl;

    return 0;
}

範例中 appendnew 的方式建構了 IntNode 實例,應該要有個解構式,在不需要 IntLinkedList 時,將這些動態建立的 IntNode 清除,這在之後的文件再來詳加探討,目前暫時忽略這個議題。

內部類別也可以獨立於外部類別定義,例如:

class IntLinkedList {
    class IntNode;
    IntNode *first = nullptr;

public:
    IntLinkedList& append(int value);
    int get(int i);
};

class IntLinkedList::IntNode {
public:
    IntNode(int value, IntNode *next) : value(value), next(next) {}
    int value;
    IntNode *next;
};

在範例中,IntNode 的值域是 public,這是為了便於給外部類別取用 IntNode 的值域,因為內部類別中若有 private 成員,外部類別預設也是不可存取的。

IntLinkedList 的使用者不需要知道 IntNode 的存在,因此 IntNode 被設定為 IntLinkedListprivate 成員,這將直接將 IntNode 的值域設為 public,也只有 IntLinkedList 可以存取。

然而有時候,內部類別會是 public,你又不想公開某些值域,又想允許外部類別存取內部類別的 private 值域,怎麼辦呢?可以宣告外部類別是內部類別的朋友,例如:

#include <iostream>
#include <string>
using namespace std;

class Screen {
public:
    class Pixel {
        int x;
        int y;
        friend Screen;  // 朋友類別
    public:
        Pixel(int x, int y) : x(x), y(y) {}
    };

    string info(Pixel px) {
        return "Pixel(" + to_string(px.x) + ", " + to_string(px.y) + ")";
    }
};

int main() {
    Screen screen;
    Screen::Pixel px(10, 10);
    cout << screen.info(px) << endl;

    return 0;
}

friend 修飾的對象並不是類別成員的一部份,單純是種存取控制,在這個範例中,Screen::Pixel 的值域不希望被公開存取,然而,允許 Screen 存取,當然,允許存取 private 成員,表示之間有強烈的耦合關係,就範例來說,螢幕包含像素資訊,這邊設計為這種的耦合關係是可以允許的。

friend 修飾的對象可以是類別、函式或者是另一類別的方法,例如重載運算子時,若選擇以非成員函式實作,就有可能需要將非成員函式設為 friend,在〈運算子重載〉中就有個例子;然而要記得,允許存取 private 成員,表示之間有強烈的耦合關係,只有在有充分理由之下,才定義哪些該設定為朋友。

類別也可以在定義在函式之中,也就是區域類別,主要用來臨時封裝一組資料,然而,不可以存取函式中的區域變數:

#include <iostream>
using namespace std;

int main() {
    class Point {
    public:
        Point(int x, int y) : x(x), y(y) {}
        int x;
        int y;
    };

    Point p1(10, 10);
    Point p2(20, 20);

    return 0;
}

必要時,區域類別也可以匿名,也就是匿名類別:

#include <iostream>
using namespace std;

int main() {
    const int dx = 10;
    const int dy = 20;

    class {
    public:
        int x = dx;
        int y = dy;
    } p;

    cout << p.x << endl;

    return 0;
}

範例中的 const 是必要的,因為類別中出現的 dxdy 實際上並不是外部的 dxdy,編譯器在類別中建立了新的 dxdy,將外部 dxdy 的值複製,為了避免類別中試圖參考或取址後進行變更,誤以為外部的 dxdy 取值時也會隨之變化,故要求加上 const,這麼一來類別中試圖參考或取址也得加上 const,這樣就沒有變更的問題了。