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$