enum的本质

原创 2013年12月02日 14:35:16
至从C语言开始enum类型就被作为用户自定义分类有限集合常量的方法被引入到了语言
当中,而且一度成为C++中定义编译期常量的唯一方法(后来在类中引入了静态整型常量)。
  根据上面对enum类型的描述,到底enum所定义出来的类型是一个什么样的类型呢?作为
一个用户自定义的类型其所占用的内存空间是多少呢?使用enum类型是否真的能够起到有限
集合常量的边界约束呢?大家可能都知道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来进行引用。
  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( 2 ) );
  func2( static_cast( -1 ) );
  上面的代码应该很清楚的说明了这样一种异常的情况了,在使用一个操出范围的整
  型值调用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://www.cppblog.com/chemz/archive/2007/06/05/25578.aspx

 

在另外一篇文章中,有解决enum不支持命名空间的特性的办法:

 当然,c++这样设计也有一些好处,我们也使用一个例子说明:

  class File

{

public:

  enum OpenMode{ READ, WRITE};

  void open( OpenMode , const char* filename );

};

int main()

{

  File file;

  file.open(OpenMode::READ,"c://1.txt");

  return 0;

}

代码 3 C++枚举不使用限定名适合的例子

我们注意到,这里的代码可读性非常好.但是这段代码的特点是:枚举类型嵌套的定义在父类型里面.但是在很多的情况下,枚举类型具有独立的意义,不必嵌套在任何的类型里面,例如上面的代码 1.

要解决这个问题,传统的做法有两种:

1 仍然使用枚举声明,但是增加前缀,例如 enum Days{Day_Sat=1, Day_Sun, Day_Mon, Day_Tue, Day_Wed, Day_Thu, Day_Fri}; enum Planets{ Planet_Moon, Planet_Earth, Planet_Sun };

2 不再使用枚举声明,使用int替代,并且嵌套在类型之中,例如

struct Days

{

  const static int Sat = 1;

  const static int Sun = 2;

  const static int Mon = 3;

  const static int Tue = 4;

  const static int Wed = 5;

  const static int Thu = 6;

  const static int Fri = 7;

};  

struct Planets

{

  const static int Moon = 0;

  const static int Earth = 1;

  const static int Sun = 1;

};

  第一个方法显得累赘,第二个方法则失去了枚举类型的固有优点;我们希望提供一种把两者结合起来的方法.

  好了,啰嗦了这么多,该拿出我们的干货了.下面是我们的方法:

  namespace Days

{

  enum Days_ {Sat=1, Sun, Mon, Tue, Wed, Thu, Fri};

};

typedef Days::Days_ Days;

 

int main()

{

  Days d = Days::Sun;//1

  int x = d;  

  printf("Sun = {%d}", x);

  return 0;

 

}

代码 4 C++ enum的用法

  在代码 4的1中,Days出现在两次,第一次是用作类型,实际上指向Days::Days_,第二次是用作命名空间,指向命名空间Days.那么这个魔法是怎么实现的呢?显然,编译器提供了智能化,为我们完成了这个工作.

  枚举类型Days_为什么要有一个下划线? 我们的目的是提醒用户不要使用Days::Days_,而是使用我们定义的类型别名Days.

  这个方法稍微繁琐一点,但是满足了我们的要求:使用枚举类型(带来枚举类型固有的优点);使得枚举类型具有命名空间的特点(虽然起这个作用的并不是枚举类型本身).

相关文章推荐

enum类型的本质

enum类型的本质     至从C语言开始enum类型就被作为用户自定义分类有限集合常量的方法被引入到了语言 当中,而且一度成为C++中定义编译期常量的唯一方法(后来在类中引入了静态整型常量)。 ...

Enum Device

  • 2016年03月08日 16:15
  • 1.11MB
  • 下载

c# enum の値を対応

enum の値を対応する任意の文字列に変換する画面に表示や印刷するときなど、enum値を文字列に変換したいときがあります。そういうとき、C#ではenumに対しても拡張メソッドを定義することが可能なので...

HDD enum tool

  • 2014年03月13日 21:20
  • 355KB
  • 下载

C# C++ enum 枚举 例子.rar

  • 2013年03月27日 15:31
  • 2KB
  • 下载

Java enum的用法详解

用法一:常量 在JDK1.5 之前,我们定义常量都是: public static fianl.... 。现在好了,有了枚举,可以把相关的常量分组到一个枚举类型里,而且枚举提供了比常量更多的方法...

Mybatis基于enum的开发

mybatis有着强大的类型处理器,可以使用enum来定义对象属性,完成映射。
内容举报
返回顶部
收藏助手
不良信息举报
您举报文章:enum的本质
举报原因:
原因补充:

(最多只允许输入30个字)