- 枚举类型使我们可以将一组整型常量组织在一起。和类一样,每个枚举类型定义了一种新的类型。枚举属于字面值常量类型
一、枚举的分类
- C++包含两种枚举:
- 限定作用域的(C++11标准引入)
- 不限定作用域的
限定作用域的
- 限定作用域的一般形式是:
- 首先是关键字enum class(或者等价地调用enum struct)
- 随后是枚举类型名字以及花括号括起来的以逗号分隔的枚举成员列表
- 最后包含一个分号
- 例如,下面定义一个名为open_modes的枚举类型,包含三个枚举成员:
enum class open_modes { input, output, append };
不限定作用域的
- 不限定作用域的枚举类型省略了关键字class(或struct),枚举类型的名字是可选的
//命名的
enum color { red, yellow, green };
//未命名的
enum { floatPrec = 6, doublePrec = 10, double_doublePrec = 10 };
- 如果enum是未命名的,则我们只能在定义该enum时定义它的对象。和类的定义类似,我们需要在enum定义的右侧花括号和最后的分号之间提供逗号分隔的声明列表
二、枚举成员
枚举成员的作用域
- 两种枚举类型成员的作用域不同:
- 限定作用域的枚举类型中:枚举成员的名字遵循常规的作用域准则,并且在枚举类型的作用域外是不可访问的
- 不限定作用域的枚举类型中:枚举成员的作用域与枚举类型本身的作用域相同
- 演示案例:
enum color { red, yellow, green }; //不限定作用域的枚举类型
//enum stoplight { red, yellow, green }; //错误,与color中的成员冲突
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
- 我们可以为枚举成员指定专门的值,例如:
enum class intTypes {
charTyp = 8, shortTyp = 16, intTyp = 16,
longTyp = 32, long_longTyp = 64
};
- 枚举成员是const的,因此在初始化枚举成员时提供的初始值必须是常量表达式。也就是说,每个枚举成员本身就是一条常量表达式,我们可以在任何需要常量表达式的地方使用枚举成员。例如,我们可以定义枚举类型的constexpr变量:
enum class intTypes {
charTyp = 8, shortTyp = 16, intTyp = 16,
longTyp = 32, long_longTyp = 64
};
int main()
{
constexpr intTypes charbits = intTypes::charTyp;
return 0;
}
- 枚举的其它使用场景:
- 我们可以将一个enum作为switch语句的表达式,而将枚举值作为case标签
- 我们还能将枚举类型作为一个非类型模板形参使用
- 在类型的定义中初始化枚举类型的静态数据成员
三、和类一样,枚举也定义新的类型
- 只要enum有名字,我们就能定义并初始化该类型的成员
- 想要初始化enum对象或者为enum对象赋值,必须使用该类型的一个枚举成员或者该类型的另一个对象。例如:
enum class open_modes { input, output, append };
int main()
{
//open_modes om = 2; //错误,2不属于类型open_modes
open_modes om;
om = open_modes::input; //正确,input是open_modes的一个枚举成员
return 0;
}
- 一个不限定作用域的枚举类型的对象或枚举成员自动地转换成整型。因此,我们可以在任何需要整型值的地方使用它们。例如:
enum color { red, yellow, green };
enum class peppers { red, yellow, green };
int main()
{
int i = color::red; //正确,不限定作用域的枚举类型的枚举成员隐式地转换成int
int j = peppers::red; //错误,限定作用域的枚举类型不会进行隐式转换
return 0;
}
四、指定enum的大小
- 尽管每个enum都定义了唯一的类型,但实际上enum是由整数类型表示的
- 在C++11标准中,我们可以在enum的名字后加上冒号以及我们想在该enum中使用的类型
- 例如:
enum intValues :unsigned long long
{
charTyp = 255, shortTyp = 65535, intTyp = 65535,
longTyp = 4294967285UL,
long_longTyp = 18446744073709551615ULL
};
- 默认的情况下:
- 限定作用域的enum成员类型是int
- 不限定作用域的枚举类型来说,其枚举成员不存在默认类型,我们只知道成员的潜在类型足够大,肯定能够容纳枚举值
- 如果我们指定了枚举成员的潜在类型(包括对限定作用域的enum的隐式指定),则一旦某个枚举成员的值超出了该类型所能容纳的范围,将引发程序错误
- 指定enum潜在类型的能力使得我们可以控制不同实现环境中使用的类型,我们将可以确保在一种实现环境中编译通过的程序所生成的代码与其他环境中生成的代码一致
五、枚举类型的前置声明
- 在C++11标准中,我们可以提前声明enum。enum的前置声明(无论隐式地还是显式地)必须指定其成员大小
- 格式如下:
- 对于不限定作用域的enum来说:因为其未指定成员的默认大小,因此每个声明必须指定成员的大小
- 对于限定作用域的enum来说:可以不指定其成员的大小,这个值被隐式地定义成int
- 例如:
enum intValues :unsigned long long; //前置声明,指定了成员类型
enum intValues //定义
{
charTyp = 255, shortTyp = 65535, intTyp = 65535,
longTyp = 4294967285UL,
long_longTyp = 18446744073709551615ULL
};
enum class open_modes; //前置声明,使用默认成员类型int
enum class open_modes { input, output, append }; //定义
- 前置声明的一些注意事项:
- 和其他声明语句一样,enum的声明和定义必须匹配,这意味着在该enum的所有声明和定义中成员的大小必须一致
- 而且,我们不能在同一个上下文中先声明一个不限定作用域的enum名字,然后再声明一个同名的限定作用域的enum
- 下面是一些演示案例
enum class intValues;
enum intValues; //错误,intValues已经被声明限定作用域的enum
enum intVales :long; //错误,intValues已经被声明为int
六、形参匹配与枚举类型
- 要想初始化一个enum对象,必须使用该enum类型的另一个对象或者它的一个枚举成员
- 因此,即使某个整型值恰好与枚举成员的值相等,它也不能作为函数的enum实参使用
enum Tokens { INLINE = 128, VIRTUAL = 129 };
void ff(Tokens)
{
std::cout << "Tokens" << std::endl;
}
void ff(int)
{
std::cout << "int" << std::endl;
}
int main()
{
Tokens curTok = INLINE;
ff(128);
ff(INLINE);
ff(curTok);
return 0;
}
- 尽管我们不能直接将整型值传递给enum形参,但是可以将一个不限定作用域的枚举类型的对象或枚举成员传给整型形参。此时,enum的值提升成int或更大的整型,实际提升的结果由枚举类型的潜在类型决定:
enum Tokens { INLINE = 128, VIRTUAL = 129 };
void newf(unsigned char)
{
std::cout << "unsigned char" << std::endl;
}
void newf(int)
{
std::cout << "int" << std::endl;
}
int main()
{
unsigned char uc = VIRTUAL;
newf(VIRTUAL);
newf(uc);
return 0;
}
- 枚举类型Tokends只有两个枚举成员,其中较大的值是129。该枚举类型可以用unsigned char表示,因此很多编译器使用unsigned char作为Tokens的潜在类型。不管Tokens的潜在类型是什么,它的对象和枚举成员都提升成int。尤其是,枚举成员永远不会提升成unsigned char,即使枚举值可以用unsigned char存储也是如此