枚举
c++线程池项目地址:现代C++线程池, 欢迎star, 欢迎issue和pr, 文档丰富, 代码结构清晰
正如cppreference上面提到的, 枚举是一种取值被限制的值类型。
我们常用的int类型, 取值受符合和机器限制, 而枚举类型的限制则更加明显, 如下面的代码片段:
enum Length
{
Min, // 不显式指定值的情况下, 默认为0
Mid,
Greater,
Max
}
那么这个Length类型的取值范围是多少呢?很遗憾, 只有四个也就是内部的min, mid, greater, max这四个值, 而没有像int那样可以取到任意值。
值得注意的是, 这些取值都是常量, 我们可以像如代码片一样使用枚举:
int main()
{
Length len = Min;
}
上面的代码片就构造了一个枚举类型的对象, 名为len, 然后把Length::Min赋值给len对象
也就是说, 在不强制类型转换的前提下, 只能将定义的枚举值赋值给枚举类型的对象, 言下之意就是可以通过强制类型转换将其他类型的值录转换为枚举类型
例如, 如下的代码片段:
int main()
{
auto len = Length(1); // ok! 等同于: Length len = Length::Mid;
auto len2 = Length(1000); // ub!
}
上面的代码片段就展示了试图把超出枚举值范围的值转换给枚举变量, 结果是未定义的
枚举类型的划分
枚举类型实际上是有一个区分, 从c++11开始, 枚举类型分为两种:
- 有作用域的枚举类型
- 无作用域的枚举类型
回到开头的代码片
enum Length
{
Min, // 不显式指定值的情况下, 默认为0
Mid,
Greater,
Max
}
int main()
{
Length len = Min;
}
这个Length::Min是被一个大括号括起来的, 而通常情况下大括号会限定一个作用域, 比如函数, 类的大括号等.
但好似对于这个代码片段, 我们的Min其实不属于Length这个作用域里, 比如上面我使用Min的时候, 并没有显式的给出Length这个作用域, 但是能够正常通过编译
而在c++11引入的有作用域的枚举类型, 其用法也和正常的枚举类型相似:
enum class Length
{
Min = 0,
Mid,
Greater,
Max
};
这个使用我们就需要显式的给出作用域了, 语法就是加上了class, 其实用struct也是可以的
分析
无作用域域的枚举类型源自于C语言, 但是在C++中, 会产生冲突, 因为枚举值类型属于全局作用域, 会产生名称污染, 所以在C++11以前, 使用枚举, 大多数都是放在类中, 这样可以用类产生的命名空间进行限制隔离, 避免产生冲突
而这些枚举项, 例如:Length::Min, 当你定义出一个枚举, 其中有若干枚举项的时候, 我们需要让系统保存一些信息, 然后我们就能定义出对应的枚举对象, 例如上面的Length len = Min, 保存长度信息
但是我们知道, C++代码在经过编译之后, 要编译成汇编语言, 最后是机器码, 那么, 枚举项是如何保存的呢?
显然, 就是利用整数值, cpprefrence原文如下:
声明一个无作用域枚举类型,它的底层类型不固定(此时底层类型是由实现定义的某个能表示所有枚举项值的整数类型;此类型不大于 int,除非枚举项的值不能放入 int 或 unsigned int。如果枚举项列表为空,那么底层类型是如同枚举拥有单个值为 0 的枚举项)
因此, 当我们定义出如下的枚举类型的时候:
enum Length
{
Min,
Mid,
Max
};
Min默认为0, 后续项依次递增1, 同时机器实际会把Min翻译成1
当然, 允许用户对中间值进行更改, 例如下面的代码片:
enum Length
{
Min, // 0
Mid = 2,
Max // 3
};
在上面的代码片中, 就把Mid修改为2, 其后的Max就自动变为3了
常量
上面提到无作用域的枚举, 可以囊括在一个类内部, 这样就可以避免命名冲突了, 在c++TR1和98的时候这样的代码很多, 但是其实, 这样做的目的不仅仅是避免名称污染, 还有一个更重要的就是因为枚举项是一个常量表达式
常量表达式: 第一,它是一个常量,第二,它是应该能够在编译期得出的
在现代C++要声明一个常量表达式, 很自然的会想到使用constexpr或者consteval
但是这两个关键字在C++11之前都没有, 所以就得借助枚举来实现
底层类型
先看一段代码片:
#include <iostream>
using namespace std;
enum Length
{
Min,
Mid,
Greater,
Max
};
int main()
{
Length len = Min;
cout << sizeof (len) << endl;
return 0;
}
这段代码片, 我在以下的环境里输出都是4
gcc version 11.4.0 (Ubuntu 11.4.0-1ubuntu1~22.04)
即使我追加了枚举项, 输出的结果也是4, 不同的C++标准都是4
现在我们可以为枚举指定使用哪个底层数据类型:
#include <iostream>
using namespace std;
enum Length : int
{
Min,
Mid,
Greater,
Max
};
int main()
{
Length len = Min;
cout << sizeof (len) << endl;
return 0;
}
当然, 输出的结果依然为4, 但是如果我不是指定为int类型, 而是:
enum Length : char
{
Min,
Mid,
Greater,
Max
};
此时输出的结果就是1, 因此就节省了许多空间, 这是声明底层数据类型的好处之一
其二:
enum Length
{
Min,
Mid,
Greater,
Max
};
int main()
{
Length len = 1;
return 0;
}
这段代码是无法通过编译的, 因为在无作用域的情况下, 允许枚举值转换为整数值, 但是整数值是无法隐式转换为枚举值
先说为啥允许枚举值转换为整型: 这个由来是从C语言继承过来的, 为了和C语言兼容
至于为啥不能由整数值隐式转换为枚举值, 如下所示:
#include <iostream>
using namespace std;
enum Length
{
Min,
Mid,
Greater,
Max
};
void func(Length len)
{
cout << "Length" << endl;
}
void func(int len)
{
cout << "int" << endl;
}
int main()
{
Length len = Min;
func(1);
func(Min);
return 0;
}
上面的代码片, 能不能发生重载呢?
答案是可以的, 那如果我这样呢?
#include <iostream>
using namespace std;
enum Length
{
Min,
Mid,
Greater,
Max
};
void func(Length len)
{
cout << "Length" << endl;
}
int main()
{
Length len = Min;
func(1);
func(Min);
return 0;
}
这样的话就无法通过编译, 上两个代码片段可以明确看到调用结果, 但是假如我是一个糊涂鬼, 我就是想用整型来调用参数为Length的函数, 编译器怎么知道我要调用的是哪个函数呢?显然是不知道的, 于是C++就不允许我们把整数作为枚举类型来使用
当然, 经过显示的类型转换之后是允许的
生命周期
enum Length;
如果我们声明了这样的枚举类型, 是无法通过编译的, 我这边的编译报错信息如下:
error: use of enum ‘Length’ without previous declaration
5 | enum Length;
这本质上是底层数据类型的问题, 我们都知道, 要定义一个类型的对象, 需要知道该类型的信息, 比如我们定义一个int类型的对象, 编译器是知道int的大小的, 但是上面的代码片, 编译器不知道Length这个类型的内存信息, 因此编译器就会报错, 但是现在我们在C++11可以这样声明一个枚举类型:
enum Length : int;
enum class Length2;
这样, 就能顺利通过编译
经过上面的讨论, 我认为已经对枚举做了比较详细的说明, 还是建议多看看cpprefrence的文档, 毕竟, 枚举的用法还是挺多的