返回介绍

15.2 嵌套类

发布于 2024-10-08 23:14:11 字数 5025 浏览 0 评论 0 收藏 0

在 C++中,可以将类声明放在另一个类中。在另一个类中声明的类被称为嵌套类(nested class),它通过提供新的类型类作用域来避免名称混乱。包含类的成员函数可以创建和使用被嵌套类的对象;而仅当声明位于公有部分,才能在包含类的外面使用嵌套类,而且必须使用作用域解析运算符(然而,旧版本的 C++不允许嵌套类或无法完全实现这种概念)。

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

对类进行嵌套通常是为了帮助实现另一个类,并避免名称冲突。Queue 类示例(第 12 章的程序清单 12.8)嵌套了结构定义,从而实现了一种变相的嵌套类:

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

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

上述代码创建 Node 后,显式地给 Node 成员赋值,这种工作更适合由构造函数来完成。

知道应在什么地方以及如何使用构造函数后,便可以提供一个适当的构造函数定义:

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

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

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

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

15.2.1 嵌套类和访问权限

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

1.作用域

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

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

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

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

嵌套结构和枚举的作用域与此相同。其实,很多程序员都使用公有枚举来提供可供客户程序员使用的类常数。例如,很多类实现都被定义为支持 iostream 使用这种技术来提供不同的格式选项,前面已经介绍过这方面的内容,第 17 章将更加全面地进行介绍。表 15.1 总结了嵌套类、结构和枚举的作用域特征。

表 15.1 嵌套类、结构和枚举的作用域特征

声明位置

包含它的类是否可以使用它

从包含它的类派生而来的类是否可以使用它

在外部是否可以使用

私有部分

保护部分

公有部分

是,通过类限定符来使用

2.访问控制

类可见后,起决定作用的将是访问控制。对嵌套类访问权的控制规则与对常规类相同。在 Queue 类声明中声明 Node 类并没有赋予 Queue 类任何对 Node 类的访问特权,也没有赋予 Node 类任何对 Queue 类的访问特权。因此,Queue 类对象只能显示地访问 Node 对象的公有成员。由于这个原因,在 Queue 示例中,Node 类的所有成员都被声明为公有的。这样有悖于应将数据成员声明为私有的这一惯例,但 Node 类是 Queue 类内部实现的一项特性,对外部世界是不可见的。这是因为 Node 类是在 Queue 类的私有部分声明的。所以,虽然 Queue 的方法可直接访问 Node 的成员,但使用 Queue 类的客户不能这样做。

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

15.2.2 模板中的嵌套

您知道,模板很适合用于实现诸如 Queue 等容器类。您可能会问,将 Queue 类定义转换为模板时,是否会由于它包含嵌套类而带来问题?答案是不会。程序清单 15.5 演示了如何进行这种转换。和类模板一样,该头文件也包含类模板和方法函数模板。

程序清单 15.5 queuetp.h

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

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

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

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

程序清单 15.6 nested.cpp

程序清单 15.5 和程序清单 15.6 组成的程序的运行情况如下:

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。
列表为空,暂无数据
    我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
    原文