★引子
今天写程序的时候,又用到这个 idiom 了,于是顺便贴出来。这个 idiom 蛮简单的,估计很多人都用过。今天主要是贴出来给新手参考(老手们就甭费时看此帖了)。
为了说明这个手法具体该咋用,咱举一个简单的例子来说事儿。比方说要开发一个网络程序,其中需要统计各种网络协议的数据包数量。
★版本1
假设一开始只需要处理 HTTP 和 FTP 两种协议。有些同学不假思索,立即会声明如下两个整数用于统计:
int nCntHttp = 0; int nCntFtp = 0;
猛一看,似乎没啥问题。但是,如果需求发生变更,又要增加两种协议:SMTP 和 SSH。然后,该同学会继续扩展上述代码,变为如下:
int nCntHttp = 0; int nCntFtp = 0; int nCntSmtp = 0; int nCntSsh = 0;
这时候,问题开始显露出来了。比方说要打印上述4统计值,就得写4个 printf 语句;再假如要用断言确保所有统计值大于零,也得写4个 assert 断言。这都是挺烦人的事儿。(当然啦,有些同学会把4个变量的打印写在一个 printf 中,但还是一样烦人)
★版本2
这可咋办捏?有点小聪明的程序猿就灵机一动,把上述代码修改为数组形式,上述的4个统计值【依次】放入数组中。具体如下:
int nCntProto[4]; /* 第0个是HTTP,第1个是FTP,第2个是SMTP,第3个是SSH */
这样一来,无论是打印还是断言,用一个 for 循环就搞定,貌似挺方便滴。
但这么一来,引入了另一个问题:假设我在程序中要用到 SMTP 的统计数字,就得这么写代码:
nCntProto[2]
这就造成了很不雅观的“Magic Number”!要知道,Magic Number 可是代码的臭味之一啊(其弊端在“这篇博文”中曾经介绍过)。万一将来,数组中的存放顺序发生变化,那就完蛋了:好多用到 Magic Number 的代码都得跟着改。一旦漏改某处,引出 Bug 无数!
★版本3
为了消除 Magic Number,增加代码可读性和可维护性,有些同学开始打起 enum 的主意。在代码中增加了一组 enum,具体如下:
enum PROTO { PROTO_HTTP, PROTO_FTP, PROTO_SMTP, PROTO_SSH, }; int nCntProto[4];
这样,如果我需要用到 SMTP 的统计数字,我就不用写
nCntProto[2]
nCntProto[PROTO_SMTP]
★版本4
但是,还是有一个不爽的地方。定义数组的语句用到了“4”这个 Magic Number。万一将来需求继续变更,继续增加协议,那这个数字还得不断调整。还是有点不爽!
咋办捏?这时候,终极版本隆重登场。请看如下代码:
enum PROTO { PROTO_HTTP, PROTO_FTP, PROTO_SMTP, PROTO_SSH, PROTO_NUM /* 表示协议数量 */ }; int nCntProto[PROTO_NUM];
这种写法的好处在于,【没有任何一个】Magic Number 出现在代码中。不管是引用某个统计值还是循环遍历数组,都使用的是定义好的常量。
就算未来发生需求变更,要增加新的协议,只要往 enum 中增加相应的 enum 常量即可(但要记得保证 PROTO_NUM 位于 enum 定义的末尾)。由于 PROTO_NUM 会自动跟着增长,所以其它的代码几乎不会受到影响。
★C++ 的补充说明
上述代码同时适用于 C 和 C++。不过捏,某些 C++ 程序员或许看不惯原始数组,觉得 STL 的容器类看起来比较顺眼。那也没啥大关系:只要把上述代码中声明数组的那句修改为如下,其它的代码基本照旧。
std::vector<int> vctCntProto(PROTO_NUM);
版权声明
本博客所有的原创文章,作者皆保留版权。转载必须包含本声明,保持本文完整,并以超链接形式注明作者编程随想和本文原始地址:
https://program-think.blogspot.com/2009/04/c-cxx-enum-idiom.html?m=0
本博客所有的原创文章,作者皆保留版权。转载必须包含本声明,保持本文完整,并以超链接形式注明作者编程随想和本文原始地址:
https://program-think.blogspot.com/2009/04/c-cxx-enum-idiom.html?m=0
21 条评论
代码中出现很多magic number固然不好,但版本4的trick也未必全是优点。为了不引起误解,enum里应该放相同“类型”的元素,前面4个都是协议类型,突然冒出一个PROTO_NUM算什么事?
回复删除紧跟着enum后声明#define PROTO_NUM 4不就很清晰吗?这里的4并不算是magic number,因为不妨碍理解。
用枚举的好处是,编译器会帮你数出到底有多少个协议,这样就能自动确保一致性。
回复删除1楼的同学,
回复删除如果另外定义PROTO_NUM常量,代码会有冗余。一旦协议类型有变化但又忘记调整PROTO_NUM的值,无形中就引入了Bug。
不过PROTO_NUM确实和其它枚举常量的类型不一致,这算是美中不足 :(
版本2:下面
回复删除"第4个是SSH"应该为"第3个是SSH"
非常感谢楼上同学的认真仔细 :)
回复删除C++ 应该这样用才好:
回复删除tr1::array<size_t, PROTO_NUM> ar = { ... };
这样既有C的效率, 又有边界检查。
而且size_t比int有更好的移植性。
另外还需要定义初始值吧,如PROTO_HTTP=1,另外就是各个枚举的数值必须是连续的,才可以
回复删除楼上的同学,默认情况下(不设定初始值),枚举值从0开始依次递增。
回复删除哦,算错了,加上PROTO_NUM ,正好是4个,这个虽然是个很好的技巧,但是在也算有个小的问题,就是一般定义ENUM的时候,其他人的理解应该表示某个状态或者数值,但是这个PROTO_NUM却表示其他的意思,感觉有些不挨边的,不过任何事情没有完美的,也算是挺好的创意了。致敬。
回复删除:) 我觉得可不可以这样呢?
回复删除enum PROTO{ PROTO_HTTP, PROTO_FTP, PROTO_SMTP, PROTO_SSH};
int nCntProto[sizeof(PROTO)/sizeof(PROTO_HTTP)];
呵呵 不过 没有这样写过 不知道可不可行
楼上的同学,sizeof(PROTO)不是你所期望的效果。
回复删除对,他的数值是1,不是整个的长度,但是这个好像是由编译器来决定的。另外是否可以扩充一下,ENUM的原理呢?知其所以然,才可以呀,另外就是要和系统,编译器,操作系统关联最好了
回复删除楼上同学所说的“ENUM的原理”是指啥?
回复删除其实我的意思是系统是怎么处理 enum,编译器是怎么样处理的
回复删除楼上的同学,“编译器如何处理enum”这个话题貌似太底层了,关心的人不多。
回复删除喔~ :-)
回复删除那改成这样
enum PROTO{ PROTO_HTTP, PROTO_FTP, PROTO_SMTP, PROTO_SSH};
int nCntProto[sizeof(PROTO)];
の 还是不对 不好意思
回复删除直接把PROTO_NUM 改成size或者length 比较通用的长度表示就行了 应该不会有什么误解了
回复删除十分感谢楼主分享~
此评论已被作者删除。
删除换个名称就好了,这个解决方法蛮好的,enum适用于做标签
删除好囧哦 enum里面的元素名只能是唯一的标识~ 请楼主原谅这些小白贴
回复删除