一、用法介绍
如果一个变量你需要几种可能存在的值,那么就可以被定义成为枚举类型。之所以叫枚举就是说将变量或者叫对象可能存在的情况也可以说是可能的值一一例举出来。
举个例子来说明一吧,为了让大家更明白一点,比如一个铅笔盒中有一支笔,但在没有打开之前你并不知道它是什么笔,可能是铅笔也可能是钢笔,这里有两种可能,那么你就可以定义一个枚举类型来表示它!
enum box{pencil,pen};//这里你就定义了一个枚举类型的变量叫box,这个枚举变量内含有两个元素也称枚举元素在这里是pencil和pen,分别表示铅笔和钢笔。
这里要说一下,如果你想定义两个具有同样特性枚举类型的变量那么你可以用如下的两种方式进行定义!
enum box{pencil,pen};
enum box box2;//或者简写成box box2;
再有一种就是在声明的时候同时定义。
enum {pencil,pen}box,box2; //在声明的同时进行定义!
枚举变量中的枚举元素系统是按照常量来处理的,故叫枚举常量,他们是不能进行普通的算术赋值的,(pencil=1;)这样的写发是错误的,但是你可以在声明的时候进行赋值操作!
enum box{pencil=1,pen=2};
但是这里要特别注意的一点是,如果你不进行元素赋值操作那么元素将会被系统自动从0开始自动递增的进行赋值操作,说到自动赋值,如果你只定义了第一个那么系统将对下一个元素进行前一个元素的值加1操作,例如
enum box{pencil=3,pen};//这里pen就是4系统将自动进行pen=4的定义赋值操作!
前面说了那么多,下面给出一个完整的例子大家可以通过以下的代码的学习进行更完整的学习!
#include <iostream>
using namespace std;
void main(void)
{
enum egg {a,b,c};
enum egg test; //在这里你可以简写成egg test;
test = c; //对枚举变量test进行赋予元素操作,这里之所以叫赋元素操作不叫赋值操作就是为了让大家明白枚举变量是不能直接赋予算数值的,例如(test=1;)这样的操作都是不被编译器所接受的,正确的方式是先进行强制类型转换例如(test = (enum egg) 0;)!
if (test==c)
{
cout <<"枚举变量判断:test枚举对应的枚举元素是c" << endl;
}
if (test==2)
{
cout <<"枚举变量判断:test枚举元素的值是2" << endl;
}
cout << a << "|" << b << "|" << test <<endl;
test = (enum egg) 0; //强制类型转换
cout << "枚举变量test值改变为:" << test <<endl;
cin.get();
}
看到这里要最后说一个问题,就是枚举变量中的枚举元素(或者叫枚举常量)在特殊情况下是会被 自动提升为算术类型 的!
#include <iostream>
using namespace std;
void main(void)
{
enum test {a,b};
int c=1+b; //自动提升为算术类型
cout << c <<endl;
cin.get();
}
enum是用户自定义类型,他有数据成员,还有成员函数!
For example:
enum e{a=1 , b=2 , c=4};
enum e e1; //enum e不是对象,它是类型,e1才是类型enum的对象!
e e1; //e是类型enum e的简写哦!
e1 = 1; //绝对的错误!int怎能赋值给一个用户自定义类型
e1 = e(); //e()? 对呀,你没看错,默认构造函数
e1 = e(1) ; //e(int)? 嘿嘿,这才是从int构造enum e类型对象的构造函数
e1 = c; //哈哈,默认调用“拷贝构造函数”···虽然有点不完备
enum类型和int类型具有隐示(自动)转换的规则, 那么是否真的在任何地方都可以使用enum类型的变量来代替int类型的变量呢?下面会逐一 回答这些问题。
1. 到底enum所定义出来的类型是一个什么样的类型呢?
在C++中大家都知道仅仅有两种大的类型分类:POD类型和类类型。enum所定义的类型其实属于POD类型,也就是说它会参与到POD类型的隐示转换规则当中去,所以才会出现enum类型与int类型之间的隐示转换现象。那么也就是说enum所定义的类型不具备名字空间限定能力(因为不属于类类型),其所定义的常量子具备和enum类型所在名字空间相同的可见性,由于自身没有名字限定能力,所以会出现名字冲突现象。如:
struct CEType
{
enum EType1 { e1, e2 };
enum EType2 { e1, e2 };
};
上面的例子会出现e1、e2名字冲突编译时错误,原因就在于枚举子(e1、e2)是 CEType名字空间中的名字,同样在引用该CEType中的枚举子时必须采用CEType::e1 这样的方式进行,而不是CEType::EType1::e1来进行引用。
二、内存分配
作为一个用户自定义的类型其所占用的内存空间是多少呢?
1.理论上说
For example:
enum e1{ a=2, b=4 };
首先找到其绝对值的最大值,但为了容易理解,我先不谈负数,也就是先找到其最大值,这里的最大值是4。4 用二进制表示就是 100,也就是需要3bits才能最小的容纳下4这个值,而3bits所能表示的范围是 0-7,于是e1的取值范围为[0,7]。
现在来看看负数,
enum e2{ a=-2, b=4 };
其中绝对值最大的是4,需要3bits才能容纳下,但因为可以取负值(而最大元素b=4不是负值),也就是说需要增加一个符号位,那么就需要4bits。
4bits的取值范围是 1000 - 0111(二进制表示),也就是 -8 到 7(十进制表示)。
enum e3{ a=-4, b=2 } 就只需要3bits,取值范围是[-4,3]。
简单的说就是找到最少的能容纳下所有的元素的位数
为什么要获取enum的取值范围?因为C++标准规定超出枚举类型表示范围的赋值结果是undefined的。
也就是说 e2 x = (e2)6 是肯定正确的,而 e2 y = (e2)8 行为是未定义的。
enum的内存分配呢?
比如 e2 需要3bits,那么C++规定e2的尺寸只要容得下3bits就行,到底是取1个byte,还是4个byte,还是...,那由编译器自己决定。但是,C++标准在这里有个限制:1<= sizeof(enmu)<=sizeof(int)。
2.一般情况下
该问题就是sizeof( EType1 )等于多少的问题,是不是每一个用户自定义的枚举类型都具有相同的尺寸呢?在大多数的32位编译器下(如:VC++、gcc等)一个枚举类型的尺寸其实就是一个sizeof( int )的大小,难道枚举类型的尺寸真的就应该是int类型的尺寸吗?其实不是这样的,在C++标准文档(ISO14882)中并没有这样来定义,标准中是这样说明的:“枚举类型的尺寸是以能够容纳最大枚举子的值的整数的尺寸”,同时标准中也说名了:“枚举类型中的枚举子的值必须要能够用一个int类型表述”,也就是说,枚举类型的尺寸不能够超过int类型的尺寸,但是是不是必须和int类型具有相同的尺寸呢?上面的标准已经说得很清楚了,只要能够容纳最大的枚举子的值的整数就可以了,那么就是说可以是char、short和int。例如:
enum EType1 { e1 = CHAR_MAX };
enum EType2 { e2 = SHRT_MAX };
enum EType3 { e3 = INT_MAX };
上面的三个枚举类型分别可以用char、short、int的内存空间进行表示,也就是:
sizeof( EType1 ) == sizeof( char );
sizeof( EType2 ) == sizeof( short );
sizeof( EType3 ) == sizeof( int );
那为什么在32位的编译器下都会将上面三个枚举类型的尺寸编译成int类型的尺寸呢?主要是从32位数据内存对其方面的要求进行考虑的,在某些计算机硬件环境下具有对齐的强制性要求(如:sun SPARC),有些则是因为采用一个完整的32位字长CPU处理效率非常高的原因(如:IA32)。所以不可以简单的假设枚举类型的尺寸就是int类型的尺寸,说不定会遇到一个编译器为了节约内存而采用上面的处理策略。
3. 使用enum类型是否真的能够起到有限集合常量的边界约束呢?
首先看一下下面这个例子:
enum EType { e1 = 0, e2 };
void func1( EType e )
{
if ( e == e1 )
{
// do something
}
// do something because e != e1 must e == e2
}
void func2( EType e )
{
if ( e == e1 )
{
// do something
}
else if ( e == e2 )
{
// do something
}
}
func1( static_cast<EType>( 2 ) );
func2( static_cast<EType>( -1 ) ); //并不出错,只是不执行if条件里的代码
上面的代码应该很清楚的说明了这样一种异常的情况了,在使用一个操出范围的整 型值调用func1函数时会导致函数采取不该采取的行为,而第二个函数可能会好一些 他仅仅是忽略了超出范围的值。这就说明枚举所定义的类型并不是一个真正强类型 的有限常量集合,这样一种条件下和将上述的两个函数参数声明成为整数类型没有 任何差异。所以以后要注意标准定义中枚举类型的陷阱。(其实只有类类型才是真 正的强类型)
4. 是否真的在任何地方都可以使用enum类型的变量来代替int类型的变量呢?
通过上面的讨论,其实枚举类型的变量和整型变量具有了太多的一致性和可互换性,那么是不是在每一个可以使用int类型的地方都可以很好的用枚举类型来替代呢?其实也不是这样的,毕竟枚举类型是一个在编译时可区分的类型,同时第2点的分析枚举类型不一定和int类型具有相同的尺寸,这两个差异就决定了在某些场合是不可以使用枚举类型来代替int类型的。如:
//第一种情况:
enum EType { e1 = 0, e2, e3 };
EType val;
std::cin >> val;
//第二种情况:
enum EType { e1 = 0, e2, e3 };
EType val;
std::scanf( "%d", &val );
上面的两种情况看是基本上属于同一种类型的问题,其实不然。第一种情况会导致
编译时错误,会因为std::cin没有定义对应的枚举类型的重载>>运算符而出错,这
就说明枚举类型是一种独立和鉴别的类型;而第二种情况不会有任何编译时问题,
但是可能会导致scanf函数栈被破坏而使得程序运行非法,为什么会这样呢?上面
已经分析过了枚举类型变量的尺寸不一定和int类型相同,这样一来我们采用%d就
是说将枚举类型变量val当作4字节的int变量来看待并进行参数压栈,而在某些编
译器下sizeof( val )等于1字节,这样scanf函数就会将val变量地址中的后续的三
字节地址也压入栈中,并对其进行赋值,也许val变量后续的三个字节的地址没有
特殊含义可以被改写(比如是字节对齐的空地址空间),可能会认为他不会出现错
误,其实不然,在scanf函数调用结束后会进行栈清理,这样一来会导致scanf函数
清理了过多的地址空间,从而破坏了外围函数的栈指针的指向,从而必然会导致程
序运行时错误。
由上面的说明枚举类型有那么多的缺点,那我们怎样才能够有一个类型安全的枚举类型 呢?其实可以采用类类型来模拟枚举类型的有限常量集合的概念,同时得到类型安全的好处, 具体参见后续的文章。
转自http://hi.baidu.com/wy_51131/item/c63507be3166ca402aebe3bd#0
http://www.cppblog.com/chemz/archive/2007/06/05/25578.html