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++程序员,这点小玩意还真是勾不起你的兴致。于是你懒洋洋的摆了摆手:“都散了吧,该干什么干什么去。”