C++包含两种枚举类型:
- 限定作用域的枚举类型
- 不限定作用域的枚举型别
带限定作用域的枚举型别通过enum class声明,不限定作用域的枚举型别通过enum声明.
不限定作用域枚举类型说明
- 枚举元素是常量,不能对他们赋值
例如enum Weekday{SUN,MON,TUE,SAT};
,不能写赋值表达式:SUN=0;
- 枚举元素有默认值,依次为:0,1,2,…
- 也可以在声明时另行指定枚举元素的值
如:enum Weekday{SUN=7,MON=1,TUE,SAT};
- 枚举值可以进行关系运算
不能直接用一个整数给枚举值赋值,需要进行强制类型转化。
例子,某次比赛结果四种可能:
胜win,负lose,平局tie,比赛取消cancel。编写程序输出四种情况。
使用的时候,两种方式都是可以的,即 带或者不带上enum关键字都可以。
#include<iostream>
using namespace std;
enum GameResult{WIN,LOSE,TIE,CANCEL};
int main(){
GameResult result;
enum GameResult omit=CANCEL;
// 上述两种方式都是可以的。带或者不带上enum关键字都可以。
for(int count=WIN;count<=CANCEL;count++){
// 这里是不限定作用域的枚举类型。所以可知直接比较(隐式类型转换),
// 不需要显式类型转换
result = static_cast<GameResult>(count);
//不能直接用一个整数给枚举值赋值,需要进行强制类型转化。
if (result==omit){
cout<<"The game was cancelled"<<endl;
}
else{
cout<<"The game was played ";
if(result==WIN){
cout<<"and we won!";
}
if(result==LOSE){
cout<<"and we lost...";
}
cout<<endl;
}
}
return 0;
}
1、不限定作用域的枚举型别可能导致枚举量泄漏到所在的作用域空间
namespace TestSpace {
enum Color {
red = 0,
green,
blue,
};
auto red = true; // 错误
}; // namespace TestSpace
在同一个作用域中,定义了不限定作用域的枚举类Color,然后定义了red变量。由于没限定作用域,所以外部也可以使用red。auto red重新定义了red所以报错。
对一个变量,只能声明一次,多次声名,就算声明类型相同,也是错误的。 而函数中,可以直接在 “函数原型” 中声明。
而限定作用域的枚举型别
namespace TestSpace {
enum class Color {
red = 0,
green,
blue,
};
auto red = true; // 没问题
}; // namespace TestSpace
2.不限定作用域的枚举型别存在一些隐式转换
这个我不能说是不好的语法,我的意思是你知道自己代码在干嘛。
比如这段代码
#include <iostream>
namespace TestSpace {
enum Color {
red = 0,
green,
blue,
};
}; // namespace TestSpace
int main() {
using namespace TestSpace;
for (int i = 0; i < static_cast<int>(Color::blue); i++) {
std::cout << i << std::endl;
}
// or
Color color = Color::green;
if (1 < color ) {
std::cout << color << std::endl;
}
return 0;
}
这段代码运行是没问题的。
但是将在num后加上class,就不存在任何隐式转换了。
如果你能控制不限定作用域的枚举型别做什么,那么我觉得enum和enum class你喜欢就好。
#include <iostream>
using namespace std;
namespace TestSpace {
enum class Color {
red = 0,
green,
blue
};
}; // namespace TestSpace
int main() {
using namespace TestSpace;
for (int i = 0; i < static_cast<int>(Color::blue); i++) {
std::cout << i << std::endl;
}
// or
cout<<"Color::green is "<<static_cast<int>(Color::green)<<endl;
if (2>static_cast<int>(Color::green)){
cout<<"2>Color::green"<<endl;
}
return 0;
}
反而这里如果在enum
后加上class
做判断的时候还要加上强制类型转换。那么就不自然了。
3. 再举个不带限定作用域的优点吧
using UserInfo = std::tuple<std::string, // 姓名
std::string, // 电子邮件
std::size_t>; // 积分
auto val = std::get<1>(uInfo); // 这里我们用1来获取get uInfo的中的电子邮件地址
但是实际上可能代码迭代过很久后,你也不知道UserInfo中1就是代表电子邮件地址。
这时候我们就可添加一个不带限定作用域的枚举类型来辅助我们记住这个tuple。
using UserInfo = std::tuple<std::string, // 姓名
std::string, // 电子邮件
std::size_t>; // 积分
enum UserInfoFields {uiName, uiEmail, uiReputation}; // 关联UserInfo,这个有点map的味道
auto val = std::get<uiEmail>(uInfo);
这就是利用不带限定作用域的枚举型别可以进行隐式转换。
如果使用带限定作用域的枚举型别就需要进行强制类型转换。
auto val = std::get<std::static_cast<std::size_t>(UserInfoFields::uiEmail)>(uInfo);
这样写出代码就不太好看。
https://en.cppreference.com/w/cpp/utility/tuple/get
如果不这么写也是可以的,简单的说就是希望通过传入一个枚举变量得到一个std::size_t
,其实就是得到尖括号中的值。
那么我就需要编译时期确定。那么我们就写一个模板函数吧。
template<typename E>
constexpr typename std::underlying_type<E>::type
toUType(E enumberator) noexcept {
return static_cast<typename std::underly_type<E>::type>(enumberator);
}
在c++14中就可以写成这样
template<typename E>
constexpr std::underlying_type_t<E>
toUType(E enumberator) noexcept {
return static_cast<std::underly_type_t<E>::type>(enumberator);
}
或者加个auto
template<typename E>
constexpr auto
toUType(E enumberator) noexcept {
return static_cast<std::underly_type_t<E>::type>(enumberator);
}
那么我们客户段代码就可以写成
auto val = std::get<toUType(UserInfoFields::uiEmail)>(uIndo);
本文全部来自《effect modern c++》