C的时代
枚举类型
在代码中直接写出来的常量,我们称之为“魔数”。例如:
int week = 1;
你说这个week是星期一,也有人说这个week应该是星期日。这玩意太容易引起歧义,让人摸不着头脑,如同莫名其妙的魔术,所以我们称之为“魔数”。
问题的关键是我们应该给这个常量一个确切的名字:
int monday = 1;
这看起来好多了,不会引起歧义了,于是你愉快地继续写道:
int monday = 1; int tuesday = 2; int wednesday = 3; int thursday = 4; int friday = 5; int saturday = 6; int sunday = 7;
没错,你搞出来了一堆的全局变量。不过别担心,这在C语言中是常规操作,没人会说什么。
但是这里还有一个问题,我们从这些全局变量中完全看不出它们应该是一起的?现在它们就是一帮散兵游勇,而你迫切需要把它们组个朋友圈。于是,C语言说,好吧,那我们就创造一个枚举的概念吧:
enum Week { Monday, Tuesday, Wednesday, Thursday, Friday, Saturday, Sunday, };
现在枚举是一种类型了,尽管这个改进比直接用全局变量强不了多少,但无论如何,还是勉强可以用的:
Week week = Monday; week = Tuesday;
整型提升
很快,新的问题又冒出来了,枚举该怎么打印呢?
Week week = Monday; week = Tuesday; std::cout << week << Wednesday;
你用IDE尝试了一下,然后看到输出了一个1,一个2。“就这?我想输出它的名称啊,而不是一个数字。”,你有些无语。
C编译器面对你的质问有些羞愧:“我确实不知道该怎么输出枚举,实际上,为了输出这些数字,我已经不得不扩展了整型提升的规则。”
你有些惊讶:“”整型提升?就像下面这个?“
char c = 'a'; int cc = c;
C编译器涨红的脸变得更红了,“对,就是这个,你也知道,我会的不多。”
你心头一惊,有种不好的预感,“那岂不是说,这枚举还能运算?”
你飞快的敲下了下面的代码:
#include <iostream> enum Week { Monday, Tuesday, Wednesday, Thursday, Friday, Saturday, Sunday, }; enum Season { spring, // 春 summer, // 夏 autumn, // 秋 winter, // 冬 }; int main() { Week week = Monday; week = Tuesday; int m = week + 1; bool is = week > spring; std::cout << week << Wednesday; }
果然,这枚举居然会算术运算,还会和其它的枚举进行比较,这是什么狗屁的类型?一个怪物吗?说好的强类型语言呢?
总结
好吧,无论你喜欢不喜欢,C语言的枚举就是这个鬼样子。
优点:
是个类型,勉强能用
缺点:
作用域有问题,没有局部化,会污染当前的名称空间。
整型提升,确实不得不用,但是也真的不严谨啊
C++98的时代
早期的C++直接继承了C的全部遗产,所以C的枚举也同时继承过来了。
“真是丑陋的玩意。”,老B大叔嘟囔了一声,然后就走了,啥也没干。所以C++98的枚举就是C的枚举。
老B大叔是有这个底气跳过这个小问题的,因为他知道C++程序员自己肯定会解决这个问题的。“不就是一个设计实现一个类似于枚举的新类型嘛,这个太简单了。”。于是我们就看到了下面的代码:
#include "pch.h" #include "CppUnitTest.h" using namespace Microsoft::VisualStudio::CppUnitTestFramework; #include <utility> using namespace std::rel_ops; class WeekType { public: enum Week { _Monday, _Tuesday, _Wednesday, _Thursday, _Friday, _Saturday, _Sunday, }; Week _self; public: WeekType(Week week) :_self(week) { } bool operator<(const WeekType& other) const { return _self < other._self; } bool operator>(const WeekType& other) const { return _self > other._self; } const static WeekType Monday; const static WeekType Tuesday; const static WeekType Wednesday; const static WeekType Thursday; const static WeekType Friday; const static WeekType Saturday; const static WeekType Sunday; }; const WeekType WeekType::Monday(WeekType::_Monday); const WeekType WeekType::Tuesday(WeekType::_Tuesday); const WeekType WeekType::Wednesday(WeekType::_Wednesday); const WeekType WeekType::Thursday(WeekType::_Thursday); const WeekType WeekType::Friday(WeekType::_Friday); const WeekType WeekType::Saturday(WeekType::_Saturday); const WeekType WeekType::Sunday(WeekType::_Sunday); namespace UnitTestHello { TEST_CLASS(UnitTestEnum) { public: TEST_METHOD(TestWeekType) { WeekType week = WeekType::Monday; Assert::IsTrue(week < WeekType::Sunday); Assert::IsTrue(week <= WeekType::Sunday); } }; }
作用域问题搞定;整型提升的问题搞定。就这?分分钟的事情嘛。
在C++98的时代,关于枚举,C++程序员有两个选择:
如果追求效率,那就直接用C的枚举
如果追求严谨,那就自定义一个类
C++11的时代
转眼来到了2011年,那些追求严谨同时又被Java、C#等后起之秀惯坏了的C++程序员再也忍受不了为了个破枚举就要写这么多代码的现状了。他们强烈要求,改进C++的枚举语法。
“都21世纪了,再不变法,大C++就要亡了啊。”
新的枚举类型很快被敲定,发布。实际上,这几乎就是将C++自定义类的工作内置到语言而已。还是上面的例子,现在被简化为:
enum class WeekClass { Monday, Tuesday, Wednesday, Thursday, Friday, Saturday, Sunday, }; TEST_METHOD(TestWeekClass) { WeekClass week = WeekClass::Monday; Assert::IsTrue(week < WeekClass::Sunday); Assert::IsTrue(week <= WeekClass::Sunday); }
就这?多了一个class关键字而已。对,就这,现在它被称为“强枚举类型”。
“现在我们甚至可以指定枚举类型底层的数据类型”,新的C++编译器向你献宝似的说。
enum class WeekByte : unsigned char { Monday, Tuesday, Wednesday, Thursday, Friday, Saturday, Sunday, }; TEST_METHOD(TestWeekByte) { WeekByte week = WeekByte::Monday; Assert::AreEqual(size_t(1), sizeof(WeekByte)); Assert::AreEqual(size_t(4), sizeof(Week)); }
就这?还凑合吧。作为一位老C++程序员,这点小玩意还真是勾不起你的兴致。于是你懒洋洋的摆了摆手:“都散了吧,该干什么干什么去。”