P37-第15章友元、异常和其他-02嵌套类

1. 嵌套类

在C++中,可以将类声明放在另一个类中。在另ー个类中声明的类被称为嵌套类( nested class), 它通过提供新的类型类作用域来避免名称混乱。

包含类的成员函数可以创建和使用被嵌套类的对象;而仅当声明位于公有部分,才能在包含类的外面使用嵌套类,而且必须使用作用域解析运算符(然而,旧版本的C++不允许嵌套类或无法完全实现这种概念)。

对类进行嵌套与包含并不同。包含意味着将类对象作为另一个类的成员,而对类进行嵌套不创建类成员,而是定义了一种类型,该类型仅在包含嵌套类声明的类中有效。

对类进行嵌套通常是为了帮助实现另一个类,并避免名称冲突。

Queue类示例(第12章的程序清单12.8)嵌套了结构定义,从而实现了一种变相的嵌套类:

class Queue
{
private:
//class scope definitions
//Node is a nested structure definition local to this class
	struct Node {
		Item item; struct Node *next;
	}
};

由于结构是一种其成员在默认情况下为公有的类,所以Node实际上是一个嵌套类,但该定义并没有充分利用类的功能。具体地说,它没有显式构造函数,下面进行补救。

首先,找到 Queue示例中创建Node对象的位置。从类声明(程序清单11.10)和方法定义(程序清单12.11)可知,唯一创建了Node对象的地方是 enqueue()方法:

bool Queue::enqueue(const Item &item)
{
	Node *add = New Node;
	add->item = item;
	add->next = NULL;
}

上述代码创建Node后,显式地给Node成员赋值,这种工作更适合由构造函数来完成。
知道应在什么地方以及如何使用构造函数后,便可以提供一个适当的构造函数定义:

class Queue
{

// class scope definitions
//Node is a nested class definition local to this class
	class Node {
	public:
		Item item;
		Node *next;
		Node(const Item &i): item(i), next(0){}
	};
};

该构造函数将节点的item成员初始化为i,并将next指针设置为0,这是使用C++编写空值指针的方法之一(使用NULL时,必须包含一个定义NULL的头文件;如果您使用的编译器支持C++11,可使用nullptr)。

由于使用 Queue类创建的所有节点的next的初始值都被设置为空指针,因此这个类只需要该构造函数。

接下来,需要使用构造函数重新编写 enqueue()

bool Queue::enqueue(const Item & item)
{
	Node *add = new Node(item); //create, initialize node
}

这使得 enqueue()的代码更短,也更安全,因为它自动进行初始化,无需程序员记住应做什么。

这个例子在类声明中定义了构造函数。假设想在方法文件中定义构造函数,则定义必须指出Node类 是在 Queue类中定义的。这是通过使用两次作用域解析运算符来完成的:

Queue::Node::Node(const Item & i): item(i), next(0) {}

2. 嵌套类和访问权限

有两种访问权限适合于嵌套类。首先,嵌套类的声明位置决定了嵌套类的作用域,即它决定了程序的哪些部分可以创建这种类的对象。其次,和其他类一样,嵌套类的公有部分、保护部分和私有部分控制了对类成员的访问。在哪些地方可以使用嵌套类以及如何使用嵌套类,取决于作用域和访问控制。

下面将更详细地进行介绍。

1.作用域

如果嵌套类是在另一个类的私有部分声明的,则只有后者知道它。在前一个例子中,被嵌套在 Queue声明中的Node类就属于这种情况(看起来Node是在私有部分之前定义的,但别忘了,类的默认访问权限是私有的),因此, Queue成员可以使用Node对象和指向Node对象的指针,但是程序的其他部分甚至不知道存在Node类。

对于从 Queue派生而来的类,Node也是不可见的,因为派生类不能直接访问基类的私有部分。

class Queue
{
	class Node {
	};
};

如果嵌套类是在另一个类的保护部分声明的,则它对于后者来说是可见的,但是对于外部世界则是不可见的。然而,在这种情况中,派生类将知道嵌套类,并可以直接创建这种类型的对象。

如果嵌套类是在另一个类的公有部分声明的,则允许后者、后者的派生类以及外部世界使用它,因为它是公有的。然而,由于嵌套类的作用域为包含它的类, 因此在外部世界使用它时,必须使用类限定符。
例如,假设有下面的声明:

class Team
{
public:
	class Coach {};
};

现在假定有一个失业的教练,他不属于任何球队。要在Team类的外面创建 Coach对象,可以这样做:

Team::Coach forhire; // create a Coach object outside the Team class

嵌套结构和枚举的作用域与此相同。其实,很多程序员都使用公有枚举来提供可供客户程序员使用的类常数。

例如,很多类实现都被定义为支持 iostream使用这种技术来提供不同的格式选项,前面已经介绍过这方面的内容,第17章将更加全面地进行介绍。表15.1总结了嵌套类、结构和枚举的作用域特征。
在这里插入图片描述

2.访问控制

类可见后,起决定作用的将是访问控制。对嵌套类访问权的控制规则与对常规类相同。
在 Queue类声明中声明Node类并没有赋予 Queue类任何对Node类的访问特权,也没有赋予Node类任何对 Queue类的访问特权。

因此, Queue类对象只能显示地访问Node对象的公有成员。由于这个原因,在 Queue示例中,Node类的所有成员都被声明为公有的。这样有悖于应将数据成员声明为私有的这一惯例,但Node类是Queue类内部实现的一项特性,对外部世界是不可见的。
这是因为Node类是在 Queue类的私有部分声明的。
所以,虽然 Queue的方法可直接访问Node的成员,但使用 Queue类的客户不能这样做.

总之,类声明的位置决定了类的作用域或可见性。类可见后,访问控制规则(公有、保抑、私有、友元)将决定程序对嵌套类成员的访问权限。

3. 模板中的嵌套

您知道,模板很适合用于实现诸如 Queue等容器类。您可能会问,将 Queue类定义转换为模板时,是否会由于它包含嵌套类而带来问题?

答案是不会。程序清单15.5演示了如何进行这种转换。和类模板一样,
该头文件也包含类模板和方法函数模板。
程序清单15.5 QueueTP.h

// QueueTP.h -- queue template with a nested class
#ifndef QueueTP_H_
#define QueueTP_H_

template <class Item>
class QueueTP
{
private:
    enum {Q_SIZE = 10};
    // Node is a nested class definition
    class Node
    {
    public:
        Item item;
        Node * next;
        Node(const Item & i):item(i), next(0){ }
    };
    Node * front;       // pointer to front of Queue
    Node * rear;        // pointer to rear of Queue
    int items;          // current number of items in Queue
    const int qsize;    // maximum number of items in Queue
    QueueTP(const QueueTP & q) : qsize(0) {}
    QueueTP & operator=(const QueueTP & q) { return *this; }
public:
    QueueTP(int qs = Q_SIZE);
    ~QueueTP();
    bool isempty() const
    {
        return items == 0;
    }
    bool isfull() const
    {
        return items == qsize;
    }
    int queuecount() const
    {
        return items;
    }
    bool enqueue(const Item &item); // add item to end
    bool dequeue(Item &item);       // remove item from front
};

// QueueTP methods
template <class Item>
QueueTP<Item>::QueueTP(int qs) : qsize(qs)
{
    front = rear = 0;
    items = 0;
}

template <class Item>
QueueTP<Item>::~QueueTP()
{
    Node * temp;
    while (front != 0)      // while queue is not yet empty
    {
        temp = front;       // save address of front item
        front = front->next;// reset pointer to next item
        delete temp;        // delete former front
    }
}

// Add item to queue
template <class Item>
bool QueueTP<Item>::enqueue(const Item & item)
{
    if (isfull())
        return false;
    Node * add = new Node(item);    // create node
// on failure, new throws std::bad_alloc exception
    items++;
    if (front == 0)         // if queue is empty,
        front = add;        // place item at front
    else
        rear->next = add;   // else place at rear
    rear = add;             // have rear point to new node
    return true;
}

// Place front item into item variable and remove from queue
template <class Item>
bool QueueTP<Item>::dequeue(Item & item)
{
    if (front == 0)
        return false;
    item = front->item;     // set item to first item in queue
    items--;
    Node * temp = front;    // save location of first item
    front = front->next;    // reset front to next item
    delete temp;            // delete former first item
    if (items == 0)
        rear = 0;
    return true; 
}

#endif

程序清单15.5中模板有趣的一点是,Node是利用通用类型Item来定义的。所以,下面的声明将导致 Node被定义成用于存储 double值:

QueueTP<double> dq;

而下面的声明将导致Node被定义成用于存储char值:

QueueTP<char> cq;

这两个Node类将在两个独立的 QueueTP类中定义,因此不会发生名称冲突。
即一个节点的类型为 QueueTP<double>::Node,另一个节点的类型为 QueueTP<char>::Node

程序清单15.6是一个小程序,可用于测试这个新的类。

程序清单15.6 nested.cpp

// nested.cpp -- using a queue that has a nested class
#include <iostream>

#include <string>
#include "queuetp.h"

int main()
{
    using std::string;
    using std::cin;
    using std::cout;

    QueueTP<string> cs(5);
    string temp;

    while(!cs.isfull())
    {
        cout << "Please enter your name. You will be "
                "served in the order of arrival.\n"
                "name: ";
        getline(cin, temp);
        cs.enqueue(temp);
    }
    cout << "The queue is full. Processing begins!\n";

    while (!cs.isempty())
    {
        cs.dequeue(temp);
        cout << "Now processing " << temp << "...\n";
    }
    // cin.get();
    return 0; 
}	

程序运行的结果:

book@book-desktop:~/meng-yue/c++/friend_abnormal/02$ g++ -o nested nested.cpp
book@book-desktop:~/meng-yue/c++/friend_abnormal/02$ ./nested
Please enter your name. You will be served in the order of arrival.
name: 1
Please enter your name. You will be served in the order of arrival.
name: 2
Please enter your name. You will be served in the order of arrival.
name: 3
Please enter your name. You will be served in the order of arrival.
name: 4
Please enter your name. You will be served in the order of arrival.
name: 5
The queue is full. Processing begins!
Now processing 1...
Now processing 2...
Now processing 3...
Now processing 4...
Now processing 5...
book@book-desktop:~/meng-yue/c++/friend_abnormal/02$

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值