一、前言
枚举是一个很有意思的东西,并且实际上它完全是可以被宏给替代的。
其本质上来说,就是给数字取一个有意义的名字而已,方便我们程序员使用。
二、基本使用
它的使用方法很简单:
enum Weekday {
MONDAY=0,
TUESDAY=1,
WEDNESDAY=2,
THURSDAY=3,
FRIDAY=4,
SATURDAY=5,
SUNDAY=6
};
比如上面这样,用关键字enum
定义一个枚举,其后紧跟这个枚举的名字。
最后在大括号中让一个有意义的名字等于一个数字即可。
比如我们这里就是定义的一个星期枚举,从星期一到星期日,相比于用数字0-6,这种使用单词的方式明显更好看:
int main() {
int d;
scanf_s("%d", &d);
switch (d) {
case MONDAY:
std::cout << "星期一" << std::endl;
break;
case TUESDAY:
std::cout << "星期二" << std::endl;
break;
case WEDNESDAY:
std::cout << "星期三" << std::endl;
break;
case THURSDAY:
std::cout << "星期四" << std::endl;
break;
case FRIDAY:
std::cout << "星期五" << std::endl;
break;
case SATURDAY:
std::cout << "星期六" << std::endl;
break;
case SUNDAY:
std::cout << "星期日" << std::endl;
break;
default:
std::cout << "未知的星期" << std::endl;
break;
}
}
比如上面使用switch
语句来判断用户输入的是哪个数字从而输出当前是星期几。
从这里你也能看出来,枚举在C/C++中真的仅仅就是给数字取个别名而已。
这就是它最基本的用法了。
三、进阶
上面这种写法虽然可以用,但却很累赘,因为有时候我是用枚举仅仅只是为了区分用户选择了不同的类型而已,至于它到底等于几,很多时候我们是完全不关心的。
举个例子:
void test(Weekday d) {
switch (d) {
case MONDAY:
std::cout << "星期一" << std::endl;
break;
case TUESDAY:
std::cout << "星期二" << std::endl;
break;
/*省略后面的代码*/
}
}
这里我以这个枚举的名字作为参数类型(但其实依旧可以传入数字,因为枚举就是给数字取的个别名),让用户选择传入哪个枚举量,这个函数就打印出来对应的字符串。
那么使用的时候就可以像这样:
int main() {
test(MONDAY);
}
这个时候你就会发现,我们对于这些枚举量到底等于数字几是完全不关心的(比如我这里无论是调用、还是判断都用的枚举量,而没有使用数字)。
所以就有简化的写法:
enum Weekday {
MONDAY,
TUESDAY,
WEDNESDAY,
THURSDAY,
FRIDAY,
SATURDAY,
SUNDAY
};
这个写法等价于前面的写法,它默认就是从0开始递增的。
甚至你也可以两种方式都用:
enum Weekday {
MONDAY,
TUESDAY,
WEDNESDAY,
THURSDAY,
FRIDAY=100,
SATURDAY,
SUNDAY
};
这个时候,上面的代码就等价于:
enum Weekday {
MONDAY=0,
TUESDAY=1,
WEDNESDAY=2,
THURSDAY=3,
FRIDAY=100,
SATURDAY=101,
SUNDAY=102
};
也就是说,如果某一个枚举量没有被赋值,那它默认就是在上一个的基础上进行加一的。
甚至你也可以不用写枚举的名字:
enum{
MONDAY,
TUESDAY,
//省略...
};
这样就只有一个enum
关键字,但这同样是可以正常使用的,只不过此时你就无法像前面那样将其作为一个函数的参数类型了而已(毕竟你都把名字给省略了)。
这种写法也很常见的,并不是没有。
四、枚举类
上面的枚举用起来很简单,就是给数字取个别名。
但问题就在于,它是全局可用的,举个例子:
#include<iostream>
enum Test1 {
a,
b,
c
};
enum Test2 {
a,
b,
c
};
int main() {
printf("%d",a);
}
这里我定义了两个枚举类型:Test1
,Test2
。
然后我想使用a
这个枚举量,那么编译器怎么知道我想使用哪一个?
所以这个时候编译器就会报错。
而这是在一个文件中的情况下,你很容易看出来,可我们平时编程的时候,大多都在使用别人的库,如果有两个库都使用全局枚举,并且还刚好枚举量的名字相同,这怎么办?
你不可能知道别人代码是怎么写的,所以这种情况就是有可能发生的,一旦你同时包含了这两个库,就会直接报错。
所以很多第三方库为了尽量避免这种情况,就会采取两种方式。
第一种就是,在枚举量名字前面添加一个包名,比如我用过的一个FLTK
库,它的很多枚举量就是这么写的:
enum{
FLTK_A,
FLTK_B,
FLTK_C
}
因为别人写的东西一般不会在前面添加这么一个莫名奇妙的头字符串,出名的第三方包也不会和其它包取相同的名字。
所以这种方式大部分情况下也是很适用的,包括win api编程中,也会看到大量的参数枚举值都是全局变量,为了区分不同枚举量,都是在前面添加一个前缀。
而另一种方式就是这里要介绍的枚举类,它是C++中才有的东西,和普通类区别很大,相当于就是给枚举添加了一个类的属性检测而已。
#include<iostream>
enum class Test1 {
a,
b,
c
};
enum class Test2 {
a,
b,
c
};
int main() {
printf("%d",Test1::a);
}
就像上面这样,在关键字enum
后面再添加一个关键字class
即可,其它没有任何变化。
但这个时候它就不再是全局的,而是限定在了其内部,如果你想要使用,就必须通过Test1::a
这种方式。
此时的含义就变成了Test1
中的a
,Test2
中的a
,这时两个枚举中的枚举量不就可以区分了吗?
这样写的另一个好处就是,IDE可以提供更加友好的类型提示,比如将其作为函数参数时:
当你发现这个函数的参数为某个枚举类型,那么你只需要将这个枚举类名字写上,添加符号::
,之后IDE就会给出这个枚举类中有哪些可选值,非常的方便!
但使用枚举类也要注意,它虽然本质上还是数字,但多了一层类的范围限定,所以如果你想要将其作为数字使用时,还需要强制转换才行:
int t=(int)Test1::a;
五、枚举组合用法
在不使用枚举类的情况下,枚举仅仅只是给数字取了一个名字,也就是说,此时枚举仅仅只是一个数字。
依旧这个特性,很多开源框架都会像下面这样写枚举量:
enum Test{
a= 1 << 0, // 0001
b= 1 << 1, //0010
c= 1 << 2, //0100
d= 1 << 3 //1000
};
通过左移运算符,将数值1的二进制整体左移一位,此时各个枚举量中的值就都是2的次方,将其转换为二进制就成为注释中的数值。
这些写的好处就在于,此时我们可以组合枚举量了。
因为按位或运算符的逻辑如下,只要有一位为1,结果就为1:
0001 | 0010 = 0011
而按位与运算符的逻辑如下,只有同时为1的位结果才为1:
0011 & 0001 = 0001
0011 & 0010 = 0010
0011 & 0100 = 0000
根据这两种运算符的特性,我们就可以组合枚举量传入函数:
void fun(int t){
if( (t&a) && (t&b) ){
printf("同时传入了a、b枚举");
}
}
fun(a|b);
比如最基本的C++文件打开类函数:
ifstream f("1.txt", ios::binary | ios::in);
其第二个参数就是组合枚举。