【C++基础】第一百零八课:[特殊工具与技术]枚举类型

enum,enum class

Posted by x-jeff on August 31, 2024

【C++基础】系列博客为参考《C++ Primer中文版(第5版)》C++11标准)一书,自己所做的读书笔记。
本文为原创文章,未经本人允许,禁止转载。转载请注明出处。

1.枚举类型

枚举类型(enumeration)使我们可以将一组整型常量组织在一起。和类一样,每个枚举类型定义了一种新的类型。枚举属于字面值常量类型

C++包含两种枚举:限定作用域的和不限定作用域的。C++11新标准引入了限定作用域的枚举类型(scoped enumeration)。定义限定作用域的枚举类型的一般形式是:首先是关键字enum class(或者等价地使用enum struct),随后是枚举类型名字以及用花括号括起来的以逗号分隔的枚举成员(enumerator)列表,最后是一个分号:

1
enum class open_modes {input, output, append};

定义不限定作用域的枚举类型(unscoped enumeration)时省略掉关键字class(或struct),枚举类型的名字是可选的:

1
2
3
enum color {red, yellow, green}; //不限定作用域的枚举类型
//未命名的、不限定作用域的枚举类型
enum {floatPrec = 6, doublePrec = 10, double_doublePrec = 10};

如果enum是未命名的,则我们只能在定义该enum时定义它的对象。和类的定义类似,我们需要在enum定义的右侧花括号和最后的分号之间提供逗号分隔的声明列表(参见:定义Sales_data类型)。

1.1.枚举成员

在限定作用域的枚举类型中,枚举成员的名字遵循常规的作用域准则,并且在枚举类型的作用域外是不可访问的。与之相反,在不限定作用域的枚举类型中,枚举成员的作用域与枚举类型本身的作用域相同:

1
2
3
4
5
6
7
8
enum color {red, yellow, green}; //不限定作用域的枚举类型
enum stoplight {red, yellow, green}; //错误:重复定义了枚举成员
enum class peppers {red, yellow, green}; //正确:枚举成员被隐藏了
color eyes = green; //正确:不限定作用域的枚举类型的枚举成员位于有效的作用域中
peppers p = green; //错误:peppers的枚举成员不在有效的作用域中
                   //color::green在有效的作用域中,但是类型错误
color hair = color::red; //正确:允许显式地访问枚举成员
peppers p2 = peppers::red; //正确:使用peppers的red

默认情况下,枚举值从0开始,依次加1。不过我们也能为一个或几个枚举成员指定专门的值:

1
2
3
4
enum class intTypes {
    charTyp = 8, shortTyp = 16, intTyp = 16,
    longTyp =32, long_longTyp = 64
};

由枚举成员intTyp和shortTyp可知,枚举值不一定唯一。如果我们没有显式地提供初始值,则当前枚举成员的值等于之前枚举成员的值加1。

枚举成员是const,因此在初始化枚举成员时提供的初始值必须是常量表达式。也就是说,每个枚举成员本身就是一条常量表达式,我们可以在任何需要常量表达式的地方使用枚举成员。例如,我们可以定义枚举类型的constexpr变量:

1
constexpr intTypes charbits = intTypes::charTyp;

类似的,我们也可以将一个enum作为switch语句的表达式,而将枚举值作为case标签。出于同样的原因,我们还能将枚举类型作为一个非类型模板形参使用(参见:函数模板);或者在类的定义中初始化枚举类型的静态数据成员(参见:类的静态成员)。

1.2.和类一样,枚举也定义新的类型

只要enum有名字,我们就能定义并初始化该类型的成员。要想初始化enum对象或者为enum对象赋值,必须使用该类型的一个枚举成员或者该类型的另一个对象:

1
2
open_modes om = 2; //错误:2不属于类型open_modes
om = open_modes::input; //正确:input是open_modes的一个枚举成员

一个不限定作用域的枚举类型的对象或枚举成员自动地转换成整型。因此,我们可以在任何需要整型值的地方使用它们:

1
2
int i = color::red; //正确:不限定作用域的枚举类型的枚举成员隐式地转换成int
int j = peppers::red; //错误:限定作用域的枚举类型不会进行隐式转换

1.3.指定enum的大小

尽管每个enum都定义了唯一的类型,但实际上enum是由某种整数类型表示的。在C++11新标准中,我们可以在enum的名字后加上冒号以及我们想在该enum中使用的类型:

1
2
3
4
5
enum intValues : unsigned long long {
    charTyp = 255, shortTyp = 65535, intTyp = 65535,
    longTyp = 4294967295UL,
    long_longTyp = 18446744073709551615ULL
};

如果我们没有指定enum的潜在类型,则默认情况下限定作用域的enum成员类型是int。对于不限定作用域的枚举类型来说,其枚举成员不存在默认类型,我们只知道成员的潜在类型足够大,肯定能够容纳枚举值。如果我们指定了枚举成员的潜在类型(包括对限定作用域的enum的隐式指定),则一旦某个枚举成员的值超出了该类型所能容纳的范围,将引发程序错误。

1.4.枚举类型的前置声明

在C++11新标准中,我们可以提前声明enum。enum的前置声明(无论隐式地还是显式地)必须指定其成员的大小:

1
2
3
//不限定作用域的枚举类型intValues的前置声明
enum intValues : unsigned long long; //不限定作用域的,必须指定成员类型
enum class open_modes; //限定作用域的枚举类型可以使用默认成员类型int

和其他声明语句一样,enum的声明和定义必须匹配,这意味着在该enum的所有声明和定义中成员的大小必须一致。而且,我们不能在同一个上下文中先声明一个不限定作用域的enum名字,然后再声明一个同名的限定作用域的enum:

1
2
3
4
//错误:所有的声明和定义必须对该enum是限定作用域的还是不限定作用域的保持一致
enum class intValues;
enum intValues; //错误:intValues已经被声明成限定作用域的enum
enum intValues : long; //错误:intValues已经被声明成int

1.5.形参匹配与枚举类型

要想初始化一个enum对象,必须使用该enum类型的另一个对象或者它的一个枚举成员。因此,即使某个整型值恰好与枚举成员的值相等,它也不能作为函数的enum实参使用:

1
2
3
4
5
6
7
8
9
10
11
//不限定作用域的枚举类型,潜在类型因机器而异
enum Tokens {INLINE = 128, VIRTUAL = 129};
void ff(Tokens);
void ff(int);
int main() {
    Tokens curTok = INLINE;
    ff(128); //精确匹配ff(int)
    ff(INLINE); //精确匹配ff(Tokens)
    ff(curTok); //精确匹配ff(Tokens)
    return 0;
}

尽管我们不能直接将整型值传给enum形参,但是可以将一个不限定作用域的枚举类型的对象或枚举成员传给整型形参。此时,enum的值提升成int或更大的整型,实际提升的结果由枚举类型的潜在类型决定:

1
2
3
4
5
void newf(unsigned char);
void newf(int);
unsigned char uc = VIRTUAL;
newf(VIRTUAL); //调用newf(int)
newf(uc); //调用newf(unsigned char)