c++高级---C++ 中的枚举类型----联合类型(总结)

来源:互联网 发布:java telnet服务 编辑:程序博客网 时间:2024/06/16 09:39

一、枚举类型

C++ 中的枚举类型继承于 C 语言。就像其他从 C 语言继承过来的很多特性一样,C++ 枚举也有缺点,这其中最显著的莫过于作用域问题——在枚举类型中定义的常量,属于定义枚举的作用域,而不属于这个枚举类型。即enum所定义的类型不具备名字空间限定能力(因为不属于类类型),其所定义的常量子具备和enum类型所在名字空间相同的可见性,由于自身没有名字限定能力,所以会出现名字冲突现象。
例如下面的示例:

enum FileAccess {
    Read = 0x1,
    Write = 0x2,
};

FileAccess access = ::Read; // 正确
FileAccess access = FileAccess::Read; // 错误

C++枚举的这个特点对于习惯面向对象和作用域概念的人来说是不可接受的。首先,FileAccess::Read 显然更加符合程序员的直觉,因为上面的枚举定义理应等价于如下的定义(实际上,.NET 中的枚举类型便是如此实现的):

class FileAccess {
    static const int Read = 0x1;
    static const int Write = 0x2;
};

其次,这导致我们无法在同一个作用域中定义两个同样名称的枚举值。也就是说,以下的代码是编译错误:

enum FileAccess {
    Read = 0x1,
    Write = 0x2,
};

enum FileShare {
    Read = 0x1, // 重定义
    Write = 0x2, // 重定义
};

如果这一点没有让你恼怒过的话,你可能还没写过多少 C++ 代码 :-)。实际上,在最新的 C++0x 标准草案中有关于枚举作用域问题的提案,但最终的解决方案会是怎样的就无法未卜先知了,毕竟对于象 C++ 这样使用广泛的语言来说,任何特性的增删和修改都必须十分小心谨慎。

当然,我们可以使用一些迂回的方法来解决这个问题(C++ 总是能给我们很多惊喜和意外)。例如,我们可以把枚举值放在一个结构里,并使用运算符重载来逼近枚举的特性:

struct FileAccess {
    enum __Enum {
        Read = 0x1,
        Write = 0x2
    };
    __Enum _value; // 枚举值

    FileAccess(int value = 0) : _value((__Enum)value) {}
    FileAccess& operator=(int value) {
        this->_value = (__Enum)value;
        return *this;
    }
    operator int() const {
        return this->_value;
    }
};

我们现在可以按照希望的方式使用这个枚举类型:

FileAccess access = FileAccess::Read;

并且,因为我们提供了到 int 类型的转换运算符,因此在需要 int 的地方都可以使用它,例如 switch 语句:

switch (access) {
    case FileAccess::Read:
        break;
    case FileAccess::Write:
        break;
}

当然我们不愿意每次都手工编写这样的结构。通过使用宏,我们可以很容易做到这一点:

#define DECLARE_ENUM(E) \
struct E \
{ \
public: \
    E(int value = 0) : _value((__Enum)value) { \
    } \
    E& operator=(int value) { \
        this->_value = (__Enum)value; \
        return *this; \
    } \
    operator int() const { \
        return this->_value; \
    } \
\
    enum __Enum {

#define END_ENUM() \
    }; \
\
private: \
    __Enum _value; \
};

我们现在可以按如下的方式定义前面的枚举,并且不比直接写 enum 复杂多少。

DECLARE_ENUM(FileAccess)
    Read = 0x1,
    Write = 0x2,
END_ENUM()

DECLARE_ENUM(FileShare)
    Read = 0x1,
    Write = 0x2,
END_ENUM()

枚举类型常用在什么地方?

1.枚举类型可以用于swith...case语句;

2.枚举类型不支持直接的cin>>和cout<<; 

       cin>>thisMonth;        //error,接受参数类型

       cout<<nextMonth;      //输出为其标号

3.枚举元素之间比较可以用一下6个操作符: <,>,<=,>=,==.!=

4.枚举类型可作为函数的返回类型。

【总结:枚举类型定义时就可以理解为像类那样定义了几个static const类型的静态常量,这些常量作为静态的当然可以不需要实例化枚举类型的实例就可以使用,只是和类不同的是枚举类型不具有命名空间限制。另外当定义了一个枚举类型的实例时要注意其内存分配的大小一般就为最大那个成员的内存大小,大多数编译器就等于int类型的大小。】

下面解释下枚举常量:

1、枚举常量的定义

enum 枚举类型名{常量1,常量2,常量3,.......};

例如定义一个星期的枚举常量:enum Week {Mon,Tue,Wed,.....};

这就定义了一个新的数据类型:Week。

Week数据类型来源于int类型(默认)。

Week类型的数据只能有7种取值,它们是:SUNDAY,MONDAY,TUESDAY……SATURDAY。

其中SUNDAY = 0,MONDAY = 1……SATURDAY = 6。也就是说,第1个枚举值代表0,第2个枚举值代表1,这样依次递增1。

不过,也可以在定义时,直接指定某个或某些枚举值的数值。比如,对于中国人,可能对于用0表示星期日不是很好接受,不如用7来表示星期天。这样我们需要的个值就是 1,2,3,4,5,6,7。可以这样定义:

enum Week {MONDAY = 1,TUESDAY,WEDNESDAY,THURSDAY,FRIDAY,SATURDAY,SUNDAY};

我们希望星期一仍然从1开始,枚举类型默认枚举值从0开始,所以我们直接指定MONDAY等于1,这样,TUESDAY就将等于2,直接到SUNDAY等于7。

枚举值,我们就称为枚举常量,因为它一经定义以后,就不可再改变,以下用法是错误的!

TUESDAY = 10; //错误!我们不能改变一个枚举值的数值。

用枚举常量来完成表达今天是星期三:

Week today = TUESDAY;

2、枚举常量和宏定义的区别与联系

宏和枚举的主要区别是作用的时间和存储形式不同。宏定义是在编译预处理阶段作用的,也就是在编译预处理时,就会进行宏替换,将程序中的所有宏名替换为所定义的常量名,而枚举则是在程序运行之后才起作用的。宏定义不分配内存空间,而枚举常量存储在数据的静态存储区中(这一点一定要注意)。宏只占用代码段的空间,而枚举除了占用代码段空间外,还耗费CPU资源。

但是不能说宏就一定比枚举好,因为你定义一大堆的宏总是非常的不方便的。

 

 

二、联合类型  转自:http://www.cnblogs.com/younes/archive/2009/11/11/1601223.html

联合(union)在C/C++里面见得并不多,但是在一些对内存要求特别严格的地方,联合又是频繁出现,那么究竟什么是联合、怎么去用、有什么需要注意的地方呢?就这些问题,我试着做一些简单的回答,里面肯定还有不当的地方,欢迎指出!

1、什么是联合?
   “联合”是一种特殊的类,也是一种构造类型的数据结构。 在一个“联合”内可以定义多种不同的数据类型, 一个被说明为该“联合”类型的变量中,允许装入该“联合”所定义的任何一种数据,这些数据共享同一段内存,已达到节省空间的目的(还有一个节省空间的类型:位域)。 这是一个非常特殊的地方,也是联合的特征。另外,同struct一样,联合默认访问权限也是公有的,并且,也具有成员函数。

2、联合与结构的区别?
   “联合”与“结构”有一些相似之处。但两者有本质上的不同。在结构中各成员有各自的内存空间, 一个结构变量的总长度是各成员长度之和(空结构除外,同时不考虑边界调整)。而在“联合”中,各成员共享一段内存空间, 一个联合变量的长度等于各成员中最长的长度。应该说明的是, 这里所谓的共享不是指把多个成员同时装入一个联合变量内, 而是指该联合变量可被赋予任一成员值,但每次只能赋一种值, 赋入新值则冲去旧值。

3、如何定义?
   例如:
    union test
    {
      test() { }
      int office;
      char teacher[5];
    };
    定义了一个名为test的联合类型,它含有两个成员,一个为整型,成员名为office;另一个为字符数组,数组名为teacher。联合定义之后,即可进行联合变量说明,被说明为test类型的变量,可以存放整型量office或存放字符数组teacher。

4、如何说明?
   联合变量的说明有三种形式:先定义再说明、定义同时说明和直接说明。
   以test类型为例,说明如下:
    1) union test
       {
         int office;
         char teacher[5];
       };
       union test a,b;    /*说明a,b为test类型*/
    2) union test
       {
         int office;
         char teacher[5];
       } a,b;
    3) union
       {
         int office;
         char teacher[5];
       } a,b;
       经说明后的a,b变量均为test类型。
    a,b变量的长度应等于test的成员中最长的长度,即等于teacher数组的长度,共5个字节。a,b变量如赋予整型值时,只使用了4个字节,而赋予字符数组时,可用5个字节。

5、如何使用?
   对联合变量的赋值,使用都只能是对变量的成员进行。
   联合变量的成员表示为:联合变量名.成员名
   例如,a被说明为test类型的变量之后,可使用a.class、a.office
   不允许只用联合变量名作赋值或其它操作,也不允许对联合变量作初始化赋值【即不能定义联合类型时对成员初始化】,赋值只能在程序中进行。
   还要再强调说明的是,一个联合变量,每次只能赋予一个成员值。换句话说,一个联合变量的值就是联合变员的某一个成员值。

6、匿名联合 【使用形式上类似枚举类型,不具有命名空间限制,但本质不同,枚举本质是存在静态内存中的】
   匿名联合仅仅通知编译器它的成员变量共同享一个地址,而变量本身是直接引用的,不使用通常的点号运算符语法,例如:
     #include <iostream>
     void main()
     {
         union{
                int test;
                char c;
               };         
        test=5;
        c=''a'';
        std::cout<<i<<" "<<c;
     }
    正如所见到的,联合成分象声明的普通局部变量那样被引用,事实上对于程序而言,这也正是使用这些变量的方式。另外,尽管被定义在一个联合声明中,他们与同一个程序快那的任何其他局部变量具有相同的作用域级别,这意味这匿名联合内的成员的名称不能与同一个作用域内的其他一直标志符冲突.。
    对匿名联合还存在如下限制:
    因为匿名联合不使用点运算符,所以包含在匿名联合内的元素必须是数据,不允许有成员函数,也不能包含私有或受保护的成员。还有,全局匿名联合必须是静态(static)的,否则就必须放在匿名名字空间中。

7、几点需要讨论的地方:
   1、联合里面那些东西不能存放?
      我们知道,联合里面的东西共享内存,所以静态、引用都不能用,因为他们不可能共享内存。
   2、类可以放入联合吗?
      我们先看一个例子:
      class Test
      {
      public:
    Test():data(0) { }
      private:
          int data;
      };

     typedef union _test
     {
              Test test;
     }UI;  
     编译通不过,为什么呢?
     因为联合里不允许存放带有构造函数、析够函数、复制拷贝操作符等的类,因为他们共享内存,编译器无法保证这些对象不被破坏,也无法保证离开时调用析够函数。


    8、又是匿名惹的祸??
       我们先看下一段代码:
class test
{
        public:
             test(const char* p);
             test(int in);
             const operator char*() const {return data.ch;}
             operator long() const {return data.l;}
        private:
     enum type {Int, String };
       union
     {
            const char* ch;
            int i;
      }datatype;
      type stype;
      test(test&);
      test& operator=(const test&);
        };
       test::test(const char *p):stype(String),datatype.ch(p)     { }
       test::test(int in):stype(Int),datatype.l(i)     { }
     看出什么问题了吗?呵呵,编译通不过。为什么呢?难道datatype.ch(p)和datatype.l(i)有问题吗?
     哈哈,问题在哪呢?让我们来看看构造test对象时发生了什么,当创建test对象时,自然要调用其相应的构造函数,在构造函数中当然要调用其成员的构造函数,所以其要去调用datatype成员的构造函数,但是他没有构造函数可调用,所以出错。
     注意了,这里可并不是匿名联合!因为它后面紧跟了个data!


    9、如何有效的防止访问出错?
       使用联合可以节省内存空间,但是也有一定的风险:通过一个不适当的数据成员获取当前对象的值!例如上面的ch、i交错访问。
       为了防止这样的错误,我们必须定义一个额外的对象,来跟踪当前被存储在联合中的值得类型,我们称这个额外的对象为:union的判别式。
       一个比较好的经验是,在处理作为类成员的union对象时,为所有union数据类型提供一组访问函数。