5.10 union
简介
联合union
是一种特殊的类,一个union
可以有多个数据成员,但是在任意时刻只有一个数据成员有值。当我们给union
的某个成员赋值之后,该union
的其他成员就变成未定义的状态了。
Tips:分配给一个
union
对象的存储空间至少要能容纳它的最大的数据成员。
定义union
union
提供了一种有效的途径使得我们可以方便地表示一组类型不同的互斥值。举个例子,假设我们需要处理一些不同种类的数字数据和字符数据,则可以定义一个union
来保存这些值:
// Token类型的对象只有一个成员, 该成员的类型可能是下列类型中的任意一个
union Token {
// 默认情况下成员是公有的
char cval;
int ival;
double dval;
};
使用union类型
和其他内置类型一样,默认情况下union
是未初始化的,我们可以像显式地初始化聚合类一样用一对花括号内的初始值显式地初始化一个union
:
Token first_token = {'a'}; // 初始化cval成员, 如果提供初始值则用于初始化第一个成员
Token last_token; // 未初始化的Token对象
Token *pt = new Token; // 指向一个未初始化的Token对象的指针
匿名union
// 定义一个匿名union, 我们可以直接访问它的成员
union {
char cval;
int ival;
double dval;
};
cval = 'a'; // 为匿名union赋一个新值
ival = 42; // 该对象当前保存的值是42
5.11 嵌套类
简介
一个类可以定义在另一个类的内部,前者被定义为嵌套类。嵌套类的名字在外层类作用域中是可见的,在外层作用域之外不可见。
声明一个嵌套类
我们为TextQuery
类定义了一个名为QueryResult
的配套类。QueryResult
类的主要作用是表示TextQuery
对象上query
操作的结果,显然将QueryResult
用作其他目的没有任何意义。
class TextQuery {
public:
// 嵌套类稍后定义
class QueryResult;
using line_no = std::vector<std::string>::size_type;
TextQuery(std::ifstream&);
QueryResult query(const std::string&) const;
private:
// 输入文件
std::shared_ptr<std::vector<std::string>> file;
// 每个单词到它所在行号的集合的映射
std::map<std::string, std::shared_ptr<std::set<line_no>>> wm;
};
定义一个嵌套类
// QueryResult是TextQuery的成员
class TextQuery::QueryResult {
// 位于类的作用域内, 因此我们不必对QueryResult形参进行限定
friend std::ostream& print(std::ostream&, const QueryResult&);
public:
// 嵌套类可以直接使用外层类的成员line_no, 无须对该名字进行限定
QueryResult(std::string, std::shared_ptr<std::set<line_no>>,
std::shared_ptr<std::vector<std::string>>);
private:
// 查询单词
std::string sought;
// 出现的行号
std::shared_ptr<std::set<line_no>> lines;
// 输入文件
std::shared_ptr<std::vector<std::string>> file;
};
定义嵌套类的成员
前面我们并没有在QueryResult
类中定义其构造函数,为其定义构造函数时必须指明QueryResult
是嵌套在TestQuery
的作用域之内的:
// QueryResult类嵌套在TestQuery类中
TextQuery::QueryResult::QueryResult(string s, shared_ptr<set<line_no>> p,
std::shared_ptr<std::vector<std::string>> f) :
sought(s), lines(p), file(f) { }
定义嵌套类的静态成员
如果QueryResult
声明了一个静态成员,则该成员的定义将位于TestQuery
的作用域之外。例如QueryResult
有一个静态成员,则该成员的定义如下:
int TestQuery::QueryResult::static_mem = 1024;
嵌套类和外层类是互相独立的
尽管嵌套类定义在其外层类的作用域中,但是外层类的对象和嵌套类的对象没有任何关系。嵌套类的对象只包含嵌套类定义的成员,同样外层类的对象只包含外层类定义的成员,在外层类对象中不会有任何嵌套类的成员。
5.12 局部类
简介
类可以定义在某个函数的内部,我们称这样的类为局部类local class
。
- 局部类的成员必须完整定义在类的内部,所以成员函数的复杂性不能太高,一般只有几行代码
- 在局部类中不允许声明静态数据成员
局部类不能使用函数作用域中的变量
局部类只能访问外层作用域定义的类型名、静态变量以及枚举成员。如果局部类定义在某个函数内部,则该函数的普通局部变量不能被该局部类使用:
int a, val;
void foo(int val) {
static int si;
enum Loc { a = 1024, b };
// Bar是foo的局部类
struct Bar {
Loc locVal; // 正确: 使用一个局部类型名
int barVal;
void fooBar(Loc l = a) {
barVal = val; // 错误: val是foo的局部变量
barVal = ::val; // 正确: 使用一个全局变量
barVal = si; // 正确: 使用一个静态局部对象
locVal = b; // 正确: 使用一个美剧成员
}
};
// ...
}
常规的访问保护规则对局部类同样适用
外层函数对局部类的私有成员没有任何访问特权。当然,局部类可以将外层函数声明为友元;或者更常见的是局部类将其成员声明成公有的。在程序中有权访问局部类的代码非常有限,局部类已经封装在函数作用域中,通过信息隐藏进一步封装就显得没有必要。
局部类中的名字查找
局部类内部的名字查找次序与其他类相似。在声明类的成员时,必须先确保用到的名字位于作用域中,然后再使用该名字。定义成员时用到的名字可以出现在类的任意位置,如果某个名字不是局部类的成员,则继续在外层函数函数作用域中查找。如果还是没有找到,则在外层函数所在的作用域中查找。
嵌套的局部类
可以在局部类的内部再嵌套一个类。此时嵌套类的定义可以出现在局部类之外,不过嵌套类必须定义在于局部类相同的作用域中。
void foo() {
class Bar {
public:
// ...
class Nested;
};
class Bar::Nested {
// ...
};
}
局部类内的嵌套类也是一个局部类,必须遵循局部类的各种规定。嵌套类的所有成员都必须定义在嵌套类内部。