C++98
风格的枚举,现在称作为unscoped enum
。scoped enum
的枚举元素只在enum
内可见, 元素只能强制转换成其他类型。scoped enum
和unscoped enum
都可以指定潜在类型。scoped enum
默认是int
,unscoped enum
没有默认类型。scoped enum
总是能前置声明,unscoped enum
只有指定了潜在类型, 才可以前置声明。
枚举(enumeration)是独立的类型,它的值被限制在一个取值范围内,它可以包含数个明确命名的常量(“枚举项(enumerator)”)。各常量的值是某个整型类型(称为该枚举的底层类型(underlying type))的值。
有两种截然不同的枚举:unscoped enum
(以 枚举关键词 enum 声明)和scoped enum
(以 枚举关键词 enum class 或 enum struct 声明)。
unscoped enum
enum 名字(可选) { 枚举项 = 常量表达式 , 枚举项 = 常量表达式 , … }
enum Color { red, green, blue };
enum 名字(可选) : 类型 { 枚举项 = 常量表达式 , 枚举项 = 常量表达式 , … } (C++11 起)
enum Color:int { red, green, blue };
enum 名字 : 类型 ; (C++11 起) 前置声明
enum Color:int;
下面是两个具有相似值的enum元素的示例。
#include <iostream>
using namespace std;
enum Cars{Jeep = 1, BMW = 0, Mercedes_Benz = 0};
int main(){
cout<<"Jeep= "<<Jeep<<" BMW= "<<BMW<<" Benz= "<<Mercedes_Benz;
return 0;
}
如果不给enum元素赋自定义值,编译器会给它们赋从0开始的默认值。
#include <iostream>
using namespace std;
enum Months{January, February, March, April, May, June, July, August, September, October, November, December};
int main(){
enum Months m = May;
cout<< "以月份为单位的五月的值为 "<< m;
return 0;
}
可以以任何顺序为enum中的任何元素提供值。所有未赋值的元素将获得先前的+ 1值。
#include <iostream>
using namespace std;
enum weekdays {
Sunday,
Monday = 2,
Tuesday,
Wednesday = 6,
Thursday,
Friday = 9,
Saturday = 12
};
int main(){
cout << "Sunday 值=" << Sunday << " Tuesday的值 = " << Tuesday;//Sunday 值=0 Tuesday的值 = 3
return 0;
}
赋给enum元素的所有值都必须是整型常量。例如,它们应该在最小和最大可能整数的范围内。
所有枚举元素或常量都应该具有惟一的作用域。这意味着一个元素不能成为同一程序中两个不同枚举的一部分,因为它将在编译期间失败。
enum Cars{Mahindra, Jeep, BMW};
enum Luxury_Cars{BMW, Ferrari, Mercedes_Benz};
int main(){
return 0;
}
编译失败
<source>:2:18: error: 'BMW' conflicts with a previous declaration
2 | enum Luxury_Cars{BMW, Ferrari, Mercedes_Benz};
| ^~~
<source>:1:27: note: previous declaration 'Cars BMW'
1 | enum Cars{Mahindra, Jeep, BMW};
| ^~~
scoped enum
enum struct|class 名字 { 枚举项 = 常量表达式 , 枚举项 = 常量表达式 , ... }
声明底层类型为 int 的scoped enum
类型(关键词 class 与 struct 完全等价)
示例
#include <iostream>
using namespace std;
enum class Color { red, green = 20, blue };
int main() {
Color r = Color::blue;
switch (r) {
case Color::red:
std::cout << "红\n";
break;
case Color::green:
std::cout << "绿\n";
break;
case Color::blue:
std::cout << "蓝\n";
break;
}
return 0;
}
enum struct|class 名字 : 类型 { 枚举项 = 常量表达式 , 枚举项 = 常量表达式 , ... }
声明底层类型为 类型 的scoped enum
类型
enum class Color:int { red=0, green = 20, blue=1 };
enum struct|class 名字 ;
前置声明
底层类型为 int 的scoped enum
类型的不可见枚举声明
enum struct|class 名字 : 类型
; 前置声明
底层类型为 类型 的scoped enum
类型的不可见枚举声明
enum classes
(new enums
, strong enums
) 解决了传统c++枚举的三个问题:
- 常规枚举隐式转换为int,当有人不希望枚举充当整数时,会导致错误。
unscoped enum
类型中的枚举量可以隐式的转换为整型类别(并能够转换为浮点类型),scoped enum
类型中枚举量不能转换为其他类别。
enum color{
Red,
Green,
Yellow
};
enum class NewColor{
Red_1,
Green_1,
Yellow_1
};
int main()
{
//! 隐式转换是可能的
int i = Red;
//! 需要enum类名后跟访问说明符。 Ex: NewColor::Red_1
// int j = Red_1; // error C2065: 'Red_1': undeclared identifier
//! 隐式转换是不可能的。Solution Ex: int k = (int)NewColor::Red_1;
// int k = NewColor::Red_1; // error C2440: 'initializing': cannot convert from 'NewColor' to 'int'
return 0;
}
- 传统枚举将枚举器超出到周围的作用域,导致名称冲突。
enum vehicle{
Car,
Bus,
Bike,
Autorickshow
};
enum FourWheeler{
Car, // car 已经在范围内被声明过,error C2365: 'Car': redefinition; previous definition was 'enumerator'
SmallBus
};
enum class Editor{
vim,
eclipes,
VisualStudio
};
enum class CppEditor{
eclipes, // No error of redefinitions
VisualStudio, // No error of redefinitions
QtCreator
};
编译错误
<source>:11:5: error: 'Car' conflicts with a previous declaration
11 | Car, // error C2365: 'Car': redefinition; previous definition was 'enumerator'
| ^~~
<source>:3:5: note: previous declaration 'vehicle Car'
3 | Car,
| ^~~
- 不能指定枚举的基础类型,这会导致混淆、兼容性问题,并导致无法前置声明。
注:C++11后传统的枚举也可以被前置声明
#include <iostream>
using namespace std;
enum class Port : unsigned char; // 前置声明
class MyClass{
public:
void PrintPort(enum class Port p);
};
void MyClass::PrintPort(enum class Port p){
cout << (int)p << endl;
}
int main(){
MyClass a;
a.PrintPort(static_cast<Port>(1));
return 0;
}
每个枚举类型都拥有底层类型,它可以是
- 显式指定(
scoped enum
和unscoped enum
均可)- 省略,该情况下对于有作用域枚举是
int
,或(对于无作用域枚举)是足以表示枚举所有值的实现定义的整数类型
#include <iostream>
#include <type_traits>
using namespace std;
enum class Color : char { red, blue };
enum class Color2 {
red,
yellow,
green
};
enum Color3 {
red,
yellow,
green
};
enum E {
E1 = 1,
E2 = 2,
Ebig = 0xFFFFFFF0U
};
enum EE : unsigned long {
EE1 = 1,
EE2 = 2,
EEbig = 0xFFFFFFF0U
};
int main() {
constexpr bool e1 = std::is_same_v< std::underlying_type_t<Color>, char >;
constexpr bool e2 = std::is_same_v< std::underlying_type_t<Color2>, int >;
constexpr bool e3 = std::is_same_v< std::underlying_type_t<Color3>, int >;
constexpr bool e4 = std::is_same_v< std::underlying_type_t<E>, int >;
constexpr bool e5 = std::is_same_v< std::underlying_type_t<EE>, unsigned long >;
std::cout
<< " color 底层类型 " << (e1 ? "char" : "non-char") << '\n'
<< " color2 底层类型 " << (e2 ? "int" : "non-int") << '\n'
<< " color3 底层类型 " << (e3 ? "int" : "non-int") << '\n'
<< " E 底层类型 " << (e4 ? "int" : "non-int") << '\n'
<< " EE 底层类型 " << (e5 ? "unsigned long" : "unsigned long") << '\n'
;
return 0;
}
运行结果如下:
color 底层类型 char
color2 底层类型 int
color3 底层类型 non-int
E 底层类型 non-int
EE 底层类型 unsigned long
scope enum
的三个理由scope enum
可以进行前置声明,而unscope enum
需要进行简单处理才能进行前置声明。在标准库中,使用enum类
<system_error>
,如下:enum class errc {
address_family_not_supported, // EAFNOSUPPORT
address_in_use, // EADDRINUSE
address_not_available, // EADDRNOTAVAIL
already_connected, // EISCONN
...
wrong_protocol_type, // EPROTOTYPE
};
<memory>
中enum class pointer_safety {
relaxed,
preferred,
strict
};
- pointer_safety::strict 只有安全派生的指针(指向以 new 分配对象或其子对象的指针)可解引用或解分配。垃圾收集器可能活跃。
- pointer_safety::preferred 认为所有指针均合法且可以解引用或解分配。基于可达性的泄露检测器可能活跃。
- pointer_safety::relaxed 认为所有指针均合法且可以解引用或解分配。
<iosfwd>
enum class io_errc;(C++11 起)
带作用域枚举
std::io_errc
定义I/O
流报告于std::ios_base::failure
异常对象的错误码。只要求一个错误码(std::io_errc::stream
),虽然实现可以定义额外的错误码。因为提供了std::is_error_code_enum
的适当特化,故std::io_errc
类型的值能隐式转换成std::error_code
。
<future>
中enum class future_errc {
broken_promise = /* implementation-defined */,
future_already_retrieved = /* implementation-defined */,
promise_already_satisfied = /* implementation-defined */,
no_state = /* implementation-defined */
};
[1] Enum in C: Understanding The Concept of Enumeration
[2] Why is enum class preferred over plain enum?
[3] Enumeration declaration
[4] enum class – scoped and strongly typed enums
[5] Why am I getting this ‘enum’ is not a class or a namespace error?
[6] How to use enums in C++
[7] std::pointer_safety