二、用户定义类型
❶
我们通常希望数据表示对用户不可访问,从而避免被使用, 确保该类型数据的使用一致性,这还让我们后续能够改进数据表示。 要达成这个目的,必须区分类型的(供任何人使用的)接口和(可对数据排他性访问的)实现。 这个语言机制叫做类(class)。 类拥有一组成员(member),成员可以是数据、函数或者类型成员。 接口由类的public成员定义,而private成员仅允许通过接口访问。例如:
class Vector{
public:
Vector(int s) :elem{new double[s]}, sz{s} { }
double& operator[](int i) { return elem[i]; }
int size() { return sz; }
private:
double* elem; // 指向元素的指针
int sz; // 元素的数量
};
有了这些,就可以定义新的Vector类型的变量了:
Vector v(6); // 具有6个元素的Vector
Vector对象可图示如下:
![](https://img.haomeiwen.com/i6368826/1d37cf8e9b0733d4.png)
大体上,Vector对象就是个“把手”, 其中装载着指向元素的指针(elem)和元素数量(sz)。 元素数量(例中是6)对不同的Vector对象是可变的,而同一个Vector对象, 在不同时刻,其元素数量也可以不同。但是Vector对象自身的大小始终不变。 在C++中,这是处理可变数量信息的基本技巧:以固定大小的把手操控数量可变的数据, 这些数据被放在“别处”(比如用new分配在自由存储上)。
在这里,Vector的数据(成员elem及sz)只能通过接口访问, 这些接口都是public成员:Vector()、operator及size()。
❷
联合(union)就是结构体(struct),只不过联合的所有成员都分配在相同的地址上, 因此联合所占据的空间,仅跟其容量最大的那个成员相同。 自然而然,任何时候联合都只能持有其某一个成员的值。
举例来说,有个符号表条目,它包含一个名称和一个值。其值可以是Node*或int:
enum Type { ptr, num }; // 一个 Type 可以是ptr和num
struct Entry {// 结构体
string name; // string是个标准库里的类型
Type t;
Node* p; // 如果t==ptr,用p
int i; // 如果t==num,用i
};
void f(Entry* pe) {
if (pe->t == num)
cout << pe->i;
// ...
}
成员p和i永远不会同时使用,但这样空间就被浪费了。 可以指定它们都是某个union的成员,这样空间就轻而易举地节省下来了,像这样:
union Value {// 使用联合
Node* p;
int i;
};
/******然后这样使用:******/
struct Entry {
string name;
Type t;
Value v; // 如果t==ptr,用v.p;如果t==num,用v.i
};
void f(Entry* pe) {
if (pe->t == num)
cout << pe->v.i;
// ...
}
标准库有个类型叫variant
,使用它就可以避免绝大多数针对 联合 的直接应用。 variant存储一个值,该值的类型可以从一组类型中任选一个。 举个例子,variant<Node,int>的值,可以是Node或者int。
借助variant,可以这样写:
struct Entry {
string name;
variant<Node*,int> v;
};
void f(Entry* pe) {
if (holds_alternative<int>(pe->v)) // *pe的值是int类型吗?
cout << get<int>(pe->v); // 取(get)int值
}
很多情况下,使用variant都比union更简单也更安全。
❸
枚举用于表示一小撮整数值。 使用它们,可以让代码更具有可读性,也更不易出错。
enum class Color { red, blue, green };
enum class Traffic_light { green, yellow, red };
Color col = Color::red;
Traffic_light light = Traffic_light::red;
enum
后的class
指明了这是个强类型的枚举,并且限定了这些枚举值的作用域。 作为独立的类型,enum class有助于防止常量的误用。 比方说,Traffic_light和Color的值无法混用:
Color x = red; // 错误:哪个颜色?
Color y = Traffic_light::red; // 错误:此red并非Color类型
Color z = Color::red; // OK
同样,Color的值也不能和整数值混用:
int i = Color::red; // 错误:Color::red不是int类型
Color c = 2; // 初始化错误:2不是Color类型
默认情况下,enum class仅定义了赋值、初始化和比较(也就是==和<)。不过,既然枚举是用户定义类型,我们就可以给它定义运算符:
Traffic_light& operator++(Traffic_light& t) { // 前置自增:++
switch (t) {
case Traffic_light::green: return t=Traffic_light::yellow;
case Traffic_light::yellow: return t=Traffic_light::red;
case Traffic_light::red: return t=Traffic_light::green;
}
}
Traffic_light next = ++light; // next 将是 Traffic_light::green
默认情况下,枚举值的整数值从0开始,每增加一个新枚举值就递增一。
如果你的枚举值不需要独立的作用域,并希望把它们作为int使用(无需显式类型转换), 可以省掉enum class中的class,以获得一个“普通”enum。 “普通”enum中的枚举值的作用域跟这个enum相同,还能隐式转换成整数值。
忠告
- [1] 如果某个内置类型过于底层,请使用定义良好的用户定义类型代替它;
- [2] 把有关联的数据组织到结构里(成
struct
或class
); - [3] 借助
class
区分接口和实现; - [4]
struct
就是其成员默认为public
的class
; - [5] 为
class
定义构造函数,以确保执行初始化操作并简化它; - [6] 别用“裸”
union
,把它们和类型字段凑一起放进类里面; - [7] 使用枚举表示具名常量的集合;
- [8] 用
enum class
替代“普通”enum
以避免事故; - [9] 给枚举定义运算,可以获得安全性和便利性。