嵌套、局部、匿名类


在类中可以再定义类,称为嵌套类或内部类,应用的场景之一是实现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,这样就没有变更的问题了。


展开阅读全文