具体类型

  可以设计出许多用户定义类型去满足各种各样的需求。现在来考虑一个遵循 complex 那种定义方式的用户定义 Stack 类型。为了使这个例子更实际一点,这个 Stack 类型的定义以它的元素个数作为一个参数:

    class Stack
    {
        char* v;
        int top;
        int max_size;
    public:
        class Underflow{};    // 用做异常
        class Overflow{};     // 用做异常
        class Bad_size{};     // 用做异常

        Stack(int s);         // 构造函数
        ~Stack();             // 析构函数

        void push(char c);
        char pop();
    };

构造函数 Stack(int) 将在建立这个类的对象时被调用,它处理初始化问题。如果该类的一个对象出了其作用域,需要做某些清理时,就应该去声明构造函数的对应物---它被称为析构函数

    Stack::Stack(int s)    // 构造函数
    {
        top = 0;
        if(s < 0 || 10000 < s) throw Bad_size();    // "||"的意思是“或”
        max_size = s;
        v = new char[s];    // 在自由存储(堆,动态存储)中为元素分配存储
    }

    Stack::~Stack()         // 析构函数
    {
        delete [] v;        // 释放元素存储,使空间可能重新使用
    }

构造函数初始化新的 Stack 变量,在做这件事时,它用 new 运算符从自由空间(也称为堆或动态存储)分配一些存储。析构函数进行清理,方式就是释放这些存储。所有这些都能在无需 Stack 的用户干预的情况下完成。有关用户只要简单地建立和使用 Stack,就像它们是内部类型的变量似的。例如,

    Stack s_var1(10);              // 具有10个元素的全局堆栈

    void f(Stack& s_ref, int i)    // 对Stack的引用
    {
        Stack s_var2(i);           // 具有i个元素的局部堆栈
        Stack* s_ptr = new Stack(20);    // 指向在自由存储分配的Stack

        s_var1.push('a');
        s_var2.push('b');
        s_ref.push('c');
        s_ptr->push('d');
        // ...
    }

这个Stack类型遵循与内部类型(例如 intchar 等)同样的规则,包括命名、作用域、存储分配、生存时间、复制等。

当然,成员函数 push()pop() 也必须在某个地方给予定义:

    void Stack::push(char c)
    {
        if(top == max_size) throw Overflow();
        v[top] = c;
        top = top + 1;
    }

    char Stack::pop()
    {
        if(top == 0) throw Underflow();
        top = top - 1;
        return v[top]; 
    }

像 complex 和 Stack 这样的类型称为具体类型,它们与抽象类型相对应。抽象类型的界面能更完全地将用户与实现细节隔离。

🔚