嵌套类 局部类

简介: 嵌套类 局部类 《c++ primer 3th》 C++嵌套类 1、   嵌套类的名字只在外围类可见。 2、   类的私有成员只有类的成员和友元可以访问,因此外围类不可以访问嵌套类的私有成员。嵌套类可以访问外围类的成员(通过对象、指针或者引用)。

嵌套类 局部类

《c++ primer 3th》

C++嵌套类

1、   嵌套类的名字只在外围类可见。

2、   类的私有成员只有类的成员和友元可以访问,因此外围类不可以访问嵌套类的私有成员。嵌套类可以访问外围类的成员(通过对象、指针或者引用)。

3、   一个好的嵌套类设计:嵌套类应该设成私有。嵌套类的成员和方法可以设为 public 。

4、   嵌套类可以直接访问外围类的静态成员、类型名( typedef )、枚举值。

嵌套类

一个类可以在另一个类中定义,这样的类被称为嵌套类嵌套类是其外围类的一个成员。嵌套类的定义可以出现在其外围类的公有、私有或保护区中。

嵌套类的名字在其外围类域中是可见的,但是在其他类域或名字空间中是不可见的,这意味着,嵌套类的名字不会与外围域中声明的相同名字冲突。例如:

class Node { /* ... */ };

class Tree {

public:

// Node 被封装在 Tree 的域中

// 在类域中 Tree::Node 隐藏了 ::Node

class Node {...};

 

// ok: 被解析为嵌套类: Tree::Node

Node *tree;

};

 

// Tree::Node 在全局域中不可见

// Node 被解析为全局的 Node 声明

Node *pnode;

class List {

public:

// Node 被封装在 List 的域中

// 在类域 List::Node 中隐藏了 ::Node

class Node {...};

 

// ok: 解析为: List::Node

Node *list;

};

与非嵌套类一样,嵌套类可以有与自身同样类型的成员:

// Not ideal configuration: evolving class definition

class List {

public:

class ListItem {

friend class List; // 友元声明

ListItem( int val = 0 ); // 构造函数

ListItem *next; // 指向自己类的指针

int value;

};

// ...

private:

ListItem *list;

ListItem *at_end;

};

私有成员是指这样的成员,它只能在该类的成员或友元定义中被访问。除非外围类被声明为嵌套类的友元,否则它没有权利访问嵌套类的私有成员。这就是为什么ListItem 把List声明为友元的原因:为了允许类List的成员定义访问ListItem 的私有成员。嵌套类也没有任何特权访问其外围类的私有成员。如果我们想授权ListItem 允许它访问类List 的私有成员,那么该外围类List 必须把嵌套类ListItem 声明为友元。在前面的例子中ListItem 不是List 的友元,所以它不能引用List 的私有成员。

把类ListItem 声明为List类的公有成员意味着,该嵌套类可以在整个程序中(在类List的友元和成员定义之外)用作类型。例如:

// ok: 全局域中的声明

List::ListItem *headptr;

这超出了我们的本意。嵌套类ListItem 支持List类的抽象,我们不希望让ListItem 类型在整个程序中都可以被访问。那么,比较好的设计是把ListItem 嵌套类定义为类List 的私有成员:

// 不理想的配置: 要改进的类定义

class List {

public:

// ...

private:

class ListItem {

// ...

};

ListItem *list;

ListItem *at_end;

};

现在,只有List 的成员和友元的定义可以访问类型ListItem 。把类ListItem 的所有成员都声明为公有的也不再有任何坏处。因为ListItem 类是List 的私有成员,所以只有List 类的友元和成员可以访问ListItem 的成员。有了这个新的设计,我们就不再需要友元声明了。下面是类List 的新定义:

// 较好的设计!

class List {

public:

// ...

private:

// 现在 ListItem 是一个私有的嵌套类型

class ListItem {

// 它的成员都是公有的

public:

ListItem( int val = 0 );

ListItem *next;

int value;

};

ListItem *list;

ListItem *at_end;

};

在类ListItem 的定义中没有把构造函数定义为inline(内联的),构造的数必须在类定义之外被定义。在哪儿可以定义它呢?ListItem 的构造函数不是类List 的成员,所以我们不能在类List 的体内定义。ListItem 的构造函数必须被定义在全局域中——该域含有其外围类的定义。当我们没有在嵌套类体内以inline 形式定义嵌套类的成员函数时,我们就必须在最外围的类之外定义这些成员函数。下面是ListItem 构造函数的一种可能的定义。但是,对于全局域定义的语法来说这是不正确的:

class List {

public:

// ...

private:

class ListItem {

public:

ListItem( int val = 0 );

// ...

};

};

// 错误: ListItem 不在全局域中

ListItem::ListItem( int val ) { ... }

问题在于,名字ListItem 在全局域中是不可见的。在全局域中使用ListItem必须指明ListItem是类List 中嵌套的类。可以通过用其外围类名List 限定修饰类名ListItem 来做到这一点。下面是正确的语法:

// 用外围类名限定修饰嵌套类名

List::ListItem::ListItem( int val ) {

value = val;

next = 0;

}

注意,只有嵌套类名是限定修饰的。第一个限定修饰符List::指外围类,它限定修饰其后的名字——嵌套类ListItem。第二个ListItem 是指构造函数而不是嵌套类。下列定义中的成员名字是不正确的:

// 错误: 构造函数名是 ListItem 而不是 List::ListItem

List::ListItem::List::ListItem( int val ) {

value = val;

next = 0;

}

如果ListItem 已经声明了一个静态成员,那么它的定义也要放在全局域中。在这样的定义中,静态成员名看起来如下所示:

int List::ListItem::static_mem = 1024;

注意,对于成员函数和静态数据成员而言,不一定只有嵌套类的公有成员,才能在类定义之外被定义。类ListItem 的私有成员也可以被定义在全局域中。

嵌套类也可以被定义在其外围类之外。例如,Lisiltem 的定义也可以在全局域中被给出,如下:

class List {

public:

// ...

private:

// 这个声明是必需的

class ListItem;

ListItem *list;

ListItem *at_end;

};

// 用外围类名限定修饰嵌套类名

class List::ListItem {

public:

ListItem( int val = 0 );

ListItem *next;

int value;

};

在全局定义中,嵌套类ListItem 的名字必须由其外围类List 的名字限定修饰。注意,在类List体内的ListItem 的声明不能省略。如果嵌套类没有先被声明为其外围类的一个成员,则全局域中的定义不能被指定为嵌套类。在全局域中定义的嵌套类不一定是其外围类的公有成员。

在嵌套类的定义被看到之前,我们只能声明嵌套类的指针和引用。即使类ListItem 是在全局域中被定义的,List 的数据成员list 和at_end 仍然是有效的,因为这两个成员都是指针。如果这两个成员中有一个是对象而不是指针,那么类List 的成员声明将会引发一个编译错误。例如:

class List {

public:

// ...

private:

// 这个声明是必需的

class ListItem;

ListItem *list;

ListItem at_end; // 错误: 未定义嵌套类 ListItem

};

为什么会希望在类定义之外定义嵌套类呢?或许嵌套类支持外围类的实现细节,我们不想让List类的用户看到ListItem 的细节。因此,我们不愿把嵌套类的定义放在含有List 类接口的头文件中。于是,我们只能在含有List 类及其成员实现的文本文件中给出嵌套类ListItem的定义。

嵌套类可以先被声明,然后再在外围类体中被定义。这允许多个嵌套类具有互相引用的成员,例如:

class List {

public:

// ...

private:

// List::ListItem 的声明

class ListItem;

class Ref {

ListItem *pli; // pli 类型为: List::ListItem*

};

// List::ListItem 的定义

class ListItem {

Ref *pref; // pref 的类型为: List::Ref*

};

};

如果类ListItem 没有在类Ref 之前先被声明,那么成员pli 的声明就是错的,因为名字ListItem 没有被声明。

嵌套类不能直接访问其外围类的非静态成员,即使这些成员是公有的,任何对外围类的非静态成员的访问都要求通过外围类的指针、引用或对象来完成。例如:

class List {

public:

int init( int );

private:

class ListItem {

public:

ListItem( int val = 0 );

void mf( const List & );

int value;

int memb;

};

};

List::ListItem::ListItem( int val )

{

// List::init() 是类 List 的非静态成员

// 必须通过 List 类型的对象或指针来使用

value = init( val ); // 错误: 非法使用 init

}

使用类的非静态成员时,编译器必须能够识别出非静态成员属于哪个对象。在类ListItem的成员函数中,this 指针只能被隐式地应用在类ListItem 的成员上,而不是外围类的成员上。由于隐式的this 指针,我们知道数据成员value 指向被凋用构造函数的对象。在ListItem 的构造函数中的this 指针的类型是ListItem*。 而要访问成员init()所需的是List 类型的对象或List*类型的指针。

下面是成员函数mf()通用引用参数引用init()。从这里我们能够知道,成员init()是针对函数实参指定的对象而被调用的:

void List::ListItem::mf( const List &il ) {

memb = il.init(); // ok: 通过引用调用 init()

}

尽管访问外围类的非静态数据成员需要通过对象、指针或引用才能完成,但是嵌套类可以直接访问外围类的静态成员、类型名、枚举值(假定这些成员是公有的)。类型名是一个typedef 名字、枚举类型名、或是一个类名。例如:

class List {

public:

typedef int (*pFunc)();

enum ListStatus { Good, Empty, Corrupted };

// ...

private:

class ListItem {

public:

void check_status();

ListStatus status; // ok

pFunc action; // ok

// ...

};

// ...

};

pFunc、ListStatus 和ListItem 都是外围类List的域内部的嵌套类型名。这三个名字以及ListStatus 的枚举值都可以被用在ListItem 的域中,这些成员可以不加限定修饰地被引用:

void List::ListItem::check_status()

{

ListStatus s = status;

switch ( s ) {

case Empty: ...

case Corrupted: ...

case Good: ...

}

}

在ListItem 的域之外,以及在外围类List 域之外引用外围类的静态成员、类型名和枚举名都要求域解析操作符,例如:

List::pFunc myAction; // ok

List::ListStatus stat = List::Empty; // ok

当引用一个枚举值时,我们不能写:

List::ListStatus::Empty

这是因为枚举值可以在定义枚举的域内被直接访问。为什么?因为枚举定义并不像类定义一样维护了自己相关的域。

 

在嵌套类域中的名字解析

让我们来看看在嵌套类的定义,及其成员定义中的名字解析是怎样进行的。被用在嵌套类的定义中的名字(除了inline 成员函数定义中的名字和缺省实参的名字之外)其解析过程如下:

1、考虑出现在名字使用点之前的嵌套类的成员声明。

2、如果第1 步没有成功,则考虑出现在名字使用点之前的外围类的成员声明。

3、如果第2 步没有成功,则考虑出现在嵌套类定义之前的名字空间域中的声明。

例如:

enum ListStatus { Good, Empty, Corrupted };

class List {

public:

// ...

private:

class ListItem {

public:

// 查找:

// 1) 在 List::ListItem 中

// 2) 在 List 中

// 3) 在全局域中

ListStatus status; // 引用全局枚举

// ...

};

// ...

};

编译器首先在类ListItem 的域中查找ListStatus 的声明。因为没有找到成员声明,所以编译器接着在类List 的域中查找ListStatus 的声明。因为在List 类中也没有找到声明。于是编译器在全局域中查找ListStatus 的声明。在这三个域中,只有位于ListStatus使用点之前的声明才会被编译器考虑。编译器找到了全局枚举ListStatus 的声明,它是被用在Status 声明中的类型。

如果在全局域中,在外围域List 之外定义嵌套类ListItem,则List类的所有成员都已经被声明完毕,因而编译器将考虑其所有声明:

class List {

private:

class ListItem;

// ...

public:

enum ListStatus { Good, Empty, Corrupted };

// ...

};

class List::ListItem {

public:

// 查找:

// 1) 在 List::ListItem 中

// 2) 在 List 中

// 3) 在全局域中

ListStatus status; // List::ListStatus

// ...

};

ListItem的名字解析过程首先在类ListItem 的域中开始查找。因为没有找到成员声明,所以编译器在类List 的域内查找ListStatus 的声明。因为类List 的完整定义都已经能够看得到,所以这一步查找考虑List 的所有成员。于是找到List 中嵌套的enumListStatus, 尽管它是在ListItem 之后被声明的。status 是List 的ListStatus 类型的一个枚举对象。如果List没有名为ListStatus 的成员,则名字查找过程会在全局域中。在嵌套类ListItem 定义之前查找声明。

被用在嵌套类的成员函数定义中的名字其解析过程如下:

1、首先考虑在成员函数局部域中的声明。

2、如果第1 步没有成功,则考虑所有嵌套类成员的声明。

3、如果第2 步没有成功,则考虑所有外围类成员的声明。

4、如果第3 步没有成功,则考虑在成员函数定义之前的名字空间域中出现的声明。

在下面的代码段中,成员函数check_status()定义中的list 引用了哪个声明?

class List {

public:

enum ListStatus { Good, Empty, Corrupted };

// ...

private:

class ListItem {

public:

void check_status();

ListStatus status; // ok

// ...

};

ListItem *list;

// ...

};

int list = 0;

void List::ListItem::check_status()

{

int value = list; // 哪个 list?

}

很有可能程序员想让check_status()中的List 引用全局对象:

1、value 和全局对象List 的类型都是int。List::list 成员是指针类型,在没有显式转换的情况它不能被赋给value。

2、不允许ListItem 访问其外围类的私有数据成员,如List。

3、list 是一个非静态数据成员,在ListItem 的成员函数中必须通过对象、指针或引用来访问它。

但是,尽管有这些原因,在成员check_status()中用到的名字List 仍被解析为类List 的数据成员list。记住,如果在嵌套类ListItem 的域中没有找到该名字;则在查找全局域之前,下一个要查找的是其外围类的域。外围类List 的成员list 隐藏了全局域中的对象。于是产生一个错误消息,因为在check_status()中使用指针list 是无效的。

只有在名字解析成功之后,编译器才会检查访问许可和类型兼容性。如果名字的用法本身就是错误的,则名字解析过程将不会再去查找更适合于该名字用法的声明,而是产生一个错误消息。

为了访问全局对象list, 必须使用全局域解析操作符:

void List::ListItem:: check_status() {

value = ::list; // ok

}

如果成员函数check_status()被定义成位于ListItem 类体中的内联函数,则上面所讲到的最后一步修改会使编译器产生一个错误消息,报告说全局域中的list 没有被声明。

class List {

public:

// ...

private:

class ListItem {

public:

// 错误: 没有可见的 ::list 声明

void check_status() { int value = ::list; }

// ...

};

ListItem *list;

// ...

};

int list = 0;

全局对象list 是在类List 定义之后被声明的,对于在类体中内联定义的成员函数,只考虑在外围类定义之前可见的全局声明。如果check_status()的定义出现在List 的定义之后,则编译器考虑在check_status()定义之前可见的全局声明,于是找到对象list 的全局声明。

 

 

局部类

类也可以定义在函数体内,这样的类被称为局部类局部类只在定义它的局部域内可见。与嵌套类不同的是,在定义该类的局部域外没有语法能够引用局部类的成员。因此,局部类的成员函数必须被定义在类定义中。在实际中,这就把局部类的成员函数的复杂性限制在几行代码中。否则,对读者来说,代码将变得很难理解。

因为没有语法能够在名字空间域内定义局部类的成员,所以也不允许局部类声明静态数据成员。

在局部类中嵌套的类可以在其类定义之外被定义。但是,该定义必须出现在包含外围局部类定义的局部域内。在局部域定义中的嵌套类的名字必须由其外围类名限定修饰。在外围类中,该嵌套类的声明不能被省略,例如:

void foo( int val )

{

class Bar {

public:

int barVal;

class nested; // 嵌套类的声明是必需的

};

// 嵌套类定义

class Bar::nested {

// ...

};

}

外围函数没有特权访问局部类的私有成员。当然,这可以通过使外围函数成为局部类的友元来实现。但是,看起来,局部类几乎从不需要私有成员。能够访问局部类的程序部分只有很少的一部分。局部类被封装在它的局部域中,通过信息隐藏进一步封装好像有点太过了。在实际中,很难找到一个理由不把局部类的所有成员都声明为公有的。

同嵌套类一样,局部类可以访问的外围域中的名字也是有限的。局部类只能访问在外围局部域中定义的类型名、静态变量以及枚举值,例如:

int a, val;

void foo( int val )

{

static int si;

enum Loc { a = 1024, b };

class Bar {

public:

Loc locVal; // ok;

int barVal;

void fooBar( Loc l = a ) { // ok: Loc::a

barVal = val; // 错误: 局部对象

barVal = ::val; // OK: 全局对象

barVal = si; // ok: 静态局部对象

locVal = b; // ok: 枚举值

}

};

// ...

}

在局部类体内(不包括成员函数定义中的)的名字解析过程是:在外围域中查找出现在局部类定义之前的声明。在局部类的成员函数体内的名字的解析过程是:在查找外围域之前,首先直找该类的完整域。

还是一样,如果先找到的声明使该名字的用法无效,则不考虑其他声明。即使在fooBar()中使用val 是错的,编译器也不会找到全局变量val,除非用全局域解析操作符限定修饰val。

相关文章
|
5月前
|
存储 编译器 程序员
【C++】类与对象(一)类的定义 访问限定符 类的实例化 this指针
【C++】类与对象(一)类的定义 访问限定符 类的实例化 this指针
|
8天前
|
存储 编译器 程序员
【C++】类和对象①(什么是面向对象 | 类的定义 | 类的访问限定符及封装 | 类的作用域和实例化 | 类对象的存储方式 | this指针)
【C++】类和对象①(什么是面向对象 | 类的定义 | 类的访问限定符及封装 | 类的作用域和实例化 | 类对象的存储方式 | this指针)
|
1月前
|
Java
30、根据官方教程详解嵌套类、内部类、静态嵌套类、局部类、匿名类 ...
30、根据官方教程详解嵌套类、内部类、静态嵌套类、局部类、匿名类 ...
33 1
|
2月前
|
存储 C语言 C++
【c++】类和对象 - 类的访问限定符及封装/作用域和实例化
【c++】类和对象 - 类的访问限定符及封装/作用域和实例化
【c++】类和对象 - 类的访问限定符及封装/作用域和实例化
|
5月前
|
存储 Java
Java内部类 - 局部/匿名/成员/静态内部类
Java内部类 - 局部/匿名/成员/静态内部类
62 0
|
5月前
内部类的概念与分类(成员内部类,局部内部类,匿名内部类)
内部类,就是一个类内部包含另一个类,即一个事物的内部包含着另一个事物。例如:身体和心脏 、汽车与发动机之间的关系。 可以看见在out下的内部类文件命名规则是 外部类$内部类.class类名称 对象名 = new 类名称();外部类名称.内部类名称 对象名 = new 外部类名称().new 外部类名称(); 把这条公式插入到demo07InnerClass 中 使用heart.调用内部类方法 如果一个类是定义在一个方法内部的,那么这是一个局部内
17 2
|
7月前
|
存储 安全 编译器
【C++基础】类与对象(上):访问限定符、类作用域、类实例化、类对象模型、this指针
【C++基础】类与对象(上):访问限定符、类作用域、类实例化、类对象模型、this指针
76 0
|
8月前
|
安全 搜索推荐 Java
6.1 使用局部内部类优化代码:局部内部类的定义与用法
6.1 使用局部内部类优化代码:局部内部类的定义与用法
64 0
|
10月前
|
编译器 C++
【C++知识点】嵌套类和局部类
【C++知识点】嵌套类和局部类
70 0
|
存储 Java 编译器
【C++要笑着学】类和对象 | 初识封装 | 访问限定符 | 类的作用域和实例化 | 类对象模型 | this指针(一)
本章将正式开始学习C++中的面向对象,本篇博客涵盖讲解 访问限定符、封装的基础知识、类的作用域和实例化、探究类对象的存储和对于this指针由浅入深地讲解。
117 0
【C++要笑着学】类和对象 | 初识封装 | 访问限定符 | 类的作用域和实例化 | 类对象模型 | this指针(一)