可以将类B声明在另一个类中。在另一个类A中声明的类B被称为嵌套类(nested class)。
类A的成员函数可以创建和使用嵌套类B的对象。
当且仅当声明为公有部分时,才能在类A的外面使用嵌套类。而且必须使用作用域解析运算符。(旧版C++不支持嵌套类概念)
对类进行嵌套和包含并不同。包含意味着将类C对象作为类A的成员。而对类B进行嵌套不创建类成员,而是定义了一种类型,该类型仅仅在包含嵌套类声明的类A中有效。
对类进行嵌套通常是为了帮助实现另一个类,并避免名称冲突。
1 class Queue 2 3 { 4 5 private: 6 7 //classs scope definitions 8 9 //Node is a nested structure definition local to this class 10 11 struct node {Item item; struct Node * next;}; 12 13 }
结构是其成员在默认情况下公有的类。结构可以理解成一种特殊的类。
所以Node实际上可以看成嵌套类。但该定义没有充分利用类的功能。
具体来说,它没有显式构造函数。接下来进行补救。
Queue的方法enqueue创建了Node:
1 bool Queue::enqueue(const Item & item) 2 { 3 if(isfull()) 4 return false; 5 Node * add = new Node; //create node 6 //on failure,new throws std::bad_alloc exception 7 add->item =item; //set node pointer 8 add->next =NULL; 9 ... 10 }
上述代码创建Node后,显式地给Node成员赋值,这种工作更适合由构造函数来完成。
于是可以改写Node如下:
1 class Queue 2 { 3 //Node is a nested calss definition local to this class 4 class Node 5 { 6 public: 7 Item item; 8 Node * next; 9 Node(const Item & i):item(i),next(0) { } 10 }; 11 ... 12 }
该构造函数节点的item成员被初始化为i,并将next指针置为0,这是C++编写空值指针的方法之一。
接下来,需要使用构造函数重新编写enqueue():
1 bool Queue::enqueue(const Item & item) 2 { 3 if(isfull()) 4 reutrn false; 5 Node * add = new Node(item); //create, initialize node 6 //on failure,new throws std::bad_alloc exception 7 ... 8 }
这使得enqueue()的代码更短,也更安全,因为它自动进行初始化,无需程序员记住应该做什么。
这个例子在类声明中定义了构造函数。假设想在方法文件中定义构造函数,则定义必须指出Node类,
这是通过两次作用域运算符来完成的;
Queue::Node::Node(const Item & i):item(i),next(0) { }
嵌套类和访问权限
嵌套类的声明位置决定了嵌套类的作用域。即它决定了程序的那些部分可以创建这种类的对象。
其次,和其他类一样,嵌套类的公有部分、保护部分、私有部分控制了对类成员的访问。
在哪些地方可以使用嵌套类以及如何使用嵌套类,取决于作用域和访问控制。
1、作用域
如果嵌套类是在类的私有部分声明的:
则只有类的私有部分知道它,在前一个例子中,被嵌套在Queue声明中的Node类就属于这种情况。
Queue成员可以使用Node对象和Node对象的指针,但是程序的其他部分甚至不知道存在Node类。
而对于从Queue派生而来的类,Node也是不可见的。因为派生类不能直接访问积累的私有部分。
如果嵌套类是在类的保护部分声明的:
则该嵌套类对于后面这个类是可见的,但是对于外部世界则是不可见的。
在这种情况下,派生类知道嵌套类,病可以直接创建这种类型的对象。
如果嵌套类是在类的公有部分声明的:
则后者、后者的派生类以及外部世界使用它,因为它是公有的。
然而,由于嵌套类的作用域为包含它的类,因此在外部世界使用它时,必须使用类限定符。
嵌套结构和枚举的作用域如此相同。其实,很多程序员都使用公有枚举来提供可供客户程序员使用的类常数。
作用域就是指这个对象(类、枚举)可见的范围,有效的范围。
2、访问控制
类可见之后,起决定作用的就是访问控制。
类声明的位置决定了类的作用域或可见性。类可见后,访问控制规则(公有、保护、私有、友元)将决定程序对嵌套类成员的访问权限。
在Queue类声明中声明Node并没有赋予Queue类对Node类的访问特权。
因此Queue类对象只能显式地访问Node对象的公有成员。
由于这个原因,在Queue示例中,Node类的所有成员都被声明为公有的。
这样有悖于应将数据成员声明为私有的惯例,但Node类是Queue类内部实现的一项特性,对外部实际不可见,这是因为Queue类是在Queue类的私有部分声明的。
所以,虽然Queue的方法可以直接访问Node成员,但使用Queue类的客户不能这样做。
模板中的嵌套
模板很适合用于诸如实现Queue等容器类。
将Queue类定义转换为模板时,是否会由于它包含嵌套类而带来问题?答案是不会。
1 //queuetp.h -- queue template with a nested class 2 #ifndef QUEUETP_H_ 3 #define QUEUETP_H_ 4 5 template <class Item> 6 class QueueTP 7 { 8 private: 9 enum {Q_SIZE =10}; 10 //Node is a nested class definition 11 class Node 12 { 13 public: 14 Item item; 15 Node * next; 16 Node(const Item & i): item(i),next(0) {} 17 }; 18 Node * front; 19 Node * rear; 20 int item; //current number of items in Queue; 21 const int qsize; //maximum number of items in Queue; 22 QueueTP(const QueueTP & q): qsize(0) {} 23 QueueTP & operator=(const QueueTP & q) {return *this;} 24 public: 25 QueueTP(int qs= Q_SIZE); 26 ~QueueTP(); 27 bool isempty() const{ 28 return item==0; 29 }; 30 bool isfull() const{ 31 return item==qsize; 32 }; 33 int queuecount const{ 34 return item; 35 }; 36 bool enqueue(const Item & item); //add item to end 37 bool dequeue(Item & item); //remove item from front 38 }; 39 40 41 #endif
1 //QueueTP methods 2 QueueTP<Item>::QueueTP(int qs): qsize(qs) 3 { 4 front = reart = 0; 5 items = 0; 6 } 7 8 9 template <class Item> 10 QueueTP<Item>::~QueueTP() 11 { 12 Node * temp; 13 while() 14 { 15 temp=front; 16 front=front->next; 17 delete temp; 18 } 19 } 20 21 22 template <class Item> 23 bool QueueTP<Item>::enqueue(const Item & item) 24 { 25 if(isfull()) 26 return false; 27 Node * add = new Node(item); //create node 28 //on failure, new throws std::bad_alloc exception 29 items++; 30 if(front ==0) 31 front == add; 32 else 33 rear->next == add; 34 rear = add; 35 return true; 36 } 37 38 39 template <class Item> 40 bool QueueTP<Item>::dequeue(Item & item) 41 { 42 if(front == 0) 43 return false; 44 item = front->item; 45 items--; 46 Node * temp =front; 47 front = front->next; 48 delete temp; 49 if(items==0) 50 rear = 0; 51 return true; 52 }
在模板中Node是利用Item类型来定义的。
下面声明将导致Node被定义成用于储存double值:
QueueTP<double> dq;
而下面的声明将导致Node被定义成用于存储char的值:
QueueTP<char> dq;
这两个Node类将在两个独立的QueueTP类中定义,因此不会发生名称冲突。
即一个节点的类型为QueueTP<double>::Node
另一个节点的类型为QueueTP<char>::Node
接下来用一个小程序测试一下:
1 //nested.cpp -- using a queue that has a nested class 2 3 #include <iostream> 4 #include <string> 5 #include "queuetp.h" 6 7 int main() 8 { 9 using std::string; 10 using std::cin; 11 using std::cout; 12 13 QueueTP<string> cs(5); 14 string temp; 15 16 while(!cs.isfull()) 17 { 18 cout<<"Please enter your name.Your will be" 19 "served in the order of arrival. " 20 "name:"; 21 getline(cin,temp); 22 cs.enqueue(temp); 23 } 24 cout<<"The queue is full.Processing begins! "; 25 26 while(!cs.isempty()) 27 { 28 cs.dequeue(temp); 29 cout<<"Now processing "<<temp<<"... "; 30 } 31 return 0; 32 }