枚举类型
枚举类型使我们可以将一组整型常量组织在一起。
和类一样,每个枚举类型定义了一种新的类型。
枚举属于字面值常量类型。
C++包含两种枚举:限定作用域的和不限定作用域的。
限定作用域的枚举类型
C++11新标准引入了限定作用域的枚举类型。
定义限定作用域的枚举类型的一般形式是:首先是关键字enum class(或者等价地使用 enum struct),随后是枚举类型名字以及用花括号括起来的以逗号分隔的枚举成员列表,最后是一个分号:
enum class open_modes (input, output, append);
我们定义了一个名为open_modes的枚举类型,它包含三个枚举成员:input、output和append。
不限定作用域的枚举类型
定义不限定作用域的枚举类型时省略掉关键字 class(或struct),枚举类型的名字是可选的:
enum color(red,yellow, green); // 不限定作用域的枚举类型
//未命名的、不限定作用域的枚举类型
enum(floatPrec=6, doublePrec = 10, double_doublePrec = 10};
如果enum是未命名的,则我们只能在定义该enum时定义它的对象。和类的定义类似,我们需要在 enum 定义的右侧花括号和最后的分号之间提供逗号分隔的声明列表
枚举成员
- 在限定作用域的枚举类型中,枚举成员的名字遵循常规的作用域准则,并且在枚举类型的作用域外是不可访问的。
- 与之相反,在不限定作用域的枚举类型中,枚举成员的作用域与枚举类型本身的作用域相同:
我们来看看容易用错的几个点子
易错点1:重复定义枚举成员
不限定作用域的枚举类型
enum A { a, b, c };
enum B { a, b, c };//重复定义枚举成员
这是因为在不限定作用域的枚举类型中,枚举成员的作用域与枚举类型本身的作用域相同
限定作用域的枚举类型
相反,在限定作用域的枚举类型中就不会出现这种问题
enum class A { a, b, c };
enum class B { a, b, c };//没有问题
这是因为枚举成员的作用域仅限于其对应的枚举类型,对外界是不可见的
所以我们可以混用不限定作用域的枚举类型和限定作用域的枚举类型
enum A { a, b, c };
enum class B { a, b, c };//没有问题
易错点2:给枚举类型赋值
限定作用域的枚举类型
enum class B { a, b, c };
B b1 = a;//会报错
B b2 = B::a;//没有问题
我们不能在枚举类型定义外直接使用限定作用域的枚举类型的枚举成员,必须通过::来显式访问
不限定作用域的枚举类型
enum A{a,b,c};
A a1 = a;//正确
A a2 = A::a;//正确
我们可以通过两种方式来使用不限定作用域的枚举类型的枚举成员。
混用两种枚举类型
enum A { a, b, c };
enum class B { a, b, c };//没有问题
A a1 = a;//没有问题,因为
B b1 = a;//有问题,因为B类型的b成员的作用域被限定在枚举类型中,
//所以此时赋给b1的是A::a,而不是B::a
A a2 = A::a;//没有问题
B b2 = B::a;//没有问题
就自己看吧
枚举成员的值
默认情况下,枚举值从0开始,依次加1。
enum A{a,b,c};
//默认a=0,b=1,c=2
不过我们也能为一个或几个枚举成员指定专门的值:
enum class_intTypes{
charTyp = 8, shortTyp = 16, intTyp = 16,
longTyp = 32, long_longTyp = 64
}
由枚举成员 intTyp和shortTyp可知,枚举值不一定唯一。
如果我们没有显式地提供初始值,则当前枚举成员的值等于之前枚举成员的值加1。
enum A{a=9,b=3,c};//默认c的值是它前面的那个成员的值加一,也就是4
枚举成员是const
枚举成员是const,因此在初始化枚举成员时提供的初始值必须是常量表达式。
也就是说,每个枚举成员本身就是一条常量表达式,我们可以在任何需要常量表达式的地方使用枚举成员。
例如,我们可以定义枚举类型的constexpr变量:
enum A{a,b,c};
constexpr A a3= A::a;
类似的,我们也可以将一个enum作为switch语句的表达式,而将枚举值作为case标签。
enum A{a,b,c};
A a3= A::a;
switch (a3)
{
case a:cout << "a" << endl; break;
case b:cout << "b" << endl; break;
case c:cout << "c" << endl; break;
}
出于同样的原因,我们还能将枚举类型作为一个非类型模板形参使用;
enum Color {
RED,
GREEN,
BLUE
};
template<Color color>
class MyClass {
public:
void printColor() {
switch (color) {
case RED:
std::cout << "Red" << std::endl;
break;
case GREEN:
std::cout << "Green" << std::endl;
break;
case BLUE:
std::cout << "Blue" << std::endl;
break;
}
}
};
int main() {
MyClass<RED> redObj;
redObj.printColor(); // 输出 "Red"
MyClass<GREEN> greenObj;
greenObj.printColor(); // 输出 "Green"
return 0;
}
或者在类的定义中初始化枚举类型的静态数据成员。
class C
{
enum A { a, b, c };
static int d[a] ;
static const int e = b;
};
和类一样,枚举也定义新的类型
只要enum有名字,我们就能定义并初始化该类型的成员。
要想初始化enum对象或者为enum对象赋值,必须使用该类型的一个枚举成员或者该类型的另一个对象,
enum A{a,b,c};
A a1 = A::a;//正确
一个不限定作用城的枚举类型的对象或枚举成员自动地转换成整型,限定作用域的枚举类型不会进行隐式转换
因此,我们可以在任何需要整型值的地方使用它们:
enum A{a,b,c};
enum class B{d,e};
int a1 = a;//可以
int b1 = B::d;//不可以,限定作用域的枚举类型不会进行隐式转换
指定enum的大小
尽管每个enum都定义了唯一的类型,但实际上enum是由某种整数类型表示的。
在C++11新标准中,我们可以在enum的名字后加上冒号以及我们想在该enum中使用的类型:
enum intValues : unsigned long long {
charTyp = 255, shortTyp = 65535, intTyp = 65535,
longTyp = 4294967295UL,
long_longTyp = 18446744073709551615ULL
};
如果我们没有指定enum的潜在类型,则默认情况下限定作用域的enum成员类型是int。
对于不限定作用域的枚举类型来说,其枚举成员不存在默认类型,我们只知道成员的潜在类型足够大,肯定能够容纳枚举值。
如果我们指定了枚举成员的潜在类型(包括对限定作用域的enum的隐式指定),则一旦某个枚举成员的值超出了该类型所能容纳的范围,将引发程序错误。
指定enum潜在类型的能力使得我们可以控制不同实现环境中使用的类型,我们将可以确保在一种实现环境中编译通过的程序所生成的代码与其他实现环境中生成的代码一致。
枚举类型的前置声明
在C++11 新标准中,我们可以提前声明 enum。
enum的前置声明(无论隐式地还是显示地)必须指定其成员的大小:
//不限定作用域的枚举类型intValues的前置声明
enum A : unsigned long long;// 不限定作用域的,必须指定成员类型
enum class B; //限定作用域的枚举类型可以使用默认成员类型int
- 因为不限定作用域的enum未指定成员的默认大小,因此每个声明必须指定成员的大小,这里被指明为unsigned long long
- 对于限定作用域的enum来说,我们可以不指定其成员的大小,这个值被隐式地定义成int。
和其他声明语句一样,enum的声明和定义必须匹配,这意味着在该enum的所有声明和定义中成员的大小必须一致。
enum A : unsigned long long;
enum A:long {a,b,c};//这是错误的
而且,我们不能在同一个上下文中先声明一个不限定作用域的enum名字,然后再声明一个同名的限定作用域的enum:
enum class intValues;// 错误:所有的声明和定义必须对该enum是限定作用域的还是不限定作用域的保持一致
enum intValues;// 错误:intValues已经被声明成限定作用域的
enum intValues : long; //错误:intValues 已经被声明成int
形参匹配与枚举类型
要想初始化一个enum对象,必须使用该enum类型的另一个对象或者它的一个枚举
成员。
因此,即使某个整型值恰好与枚举成员的值相等,它也不能作为函数的enum实参使用:
//不限定作用域的枚举类型,潜在类型因机器而异
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或更大的整型,实际提升的结果由枚举类型的潜在类型决定:
void newf (unsigned char);
void newf(int);
unsigned char uc = VIRTUAL;
newf(VIRTUAL); // 调用 newf(int)
newf(uc); // 调用 newf (unsigned char)
枚举类型 Tokens只有两个枚举成员,其中较大的那个值是129。该枚举类型可以用unsigned char来表示,因此很多编译器使用unsigned char作为Tokens的潜在类型。
不管Tokens的潜在类型到底是什么,它的对象和枚举成员都提升成int。
尤其是,枚举成员永远不会提升成unsigned char,即使枚举值可以用unsigned char存储是如此。