C++联合体union用法实例详解

本文实例讲述了C++联合体union用法。分享给大家供大家参考。具体如下:

我们应该按照C中的convention去使用union,这是我这篇文章要给出的观点。虽然C++使得我们可以扩展一些新的东西进去,但是,我建议你不要那样去做,看完这篇文章之后,我想你大概也是这么想的。

  C由于没有类的概念,所有类型其实都可以看作是基本类型的组合,因此在union中包含struct也就是一件很自然的事情了,到了C++之后,既然普遍认为C++中的struct与class基本等价,那么union中是否可以有类成员呢?先来看看如下的代码:

struct TestUnion
{
    TestUnion() {}
};

typedef union
{
    TestUnion obj;
} UT;

int main (void)
{
    return 0;
}

  编译该程序,我们将被告知:
  error C2620: union '__unnamed' : member 'obj' has user-defined constructor or non-trivial default constructor

  而如果去掉那个什么也没干的构造函数,则一切OK。

  为什么编译器不允许我们的union成员有构造函数呢?我无法找到关于这个问题的比较权威的解释,对这个问题,我的解释是:

   如果C++标准允许我们的union有构造函数,那么,在进行空间分配的时候要不要执行这个构造函数呢?如果答案是yes,那么如果TestUnion 的构造函数中包含了一些内存分配操作,或者其它对整个application状态的修改,那么,如果我今后要用到obj的话,事情可能还比较合理,但是如果我根本就不使用obj这个成员呢?由于obj的引入造成的对系统状态的修改显然是不合理的;反之,如果答案是no,那么一旦我们今后选中了obj来进行 操作,则所有信息都没有初始化(如果是普通的struct,没什么问题,但是,如果有虚函数呢?)。更进一步,假设现在我们的union不是只有一个 TestUnion obj,还有一个TestUnion2 obj2,二者均有构造函数,并且都在构造函数中执行了一些内存分配的工作(甚至干了很多其它事情),那么,如果先构造obj,后构造obj2,则执行的 结果几乎可以肯定会造成内存的泄漏。

  鉴于以上诸多麻烦(可能还有更多麻烦),在构造union时,编译器只负责分配空间,而不负责去执行附加的初始化工作,为了简化工作,只要我们提供了构造函数,就会收到上面的error。

同理,除了不能加构造函数,析构函数/拷贝构造函数/赋值运算符也是不可以加。

  此外,如果我们的类中包含了任何virtual函数,编译时,我们将收到如下的错误信息:
  error C2621: union '__unnamed' : member 'obj' has copy constructor

  所以,打消在union中包含有构造函数/析构函数/拷贝构造函数/赋值运算符/虚函数的类成员变量的念头,老老实实用你的C风格struct吧!
  不过,定义普通的成员函数是OK的,因为这不会使得class与C风格的struct有任何本质区别,你完全可以将这样的class理解为一个C风格的struct + n个全局函数。

  现在,再看看在类中包含内部union时会有什么不同。看看下面的程序,并请注意阅读程序提示:

class TestUnion
{
    union DataUnion
    {
      DataUnion(const char*);
      DataUnion(long);
      const char* ch_;
      long l_;
    } data_;

  public:
    TestUnion(const char* ch);
    TestUnion(long l);
};

TestUnion::TestUnion(const char* ch) : data_(ch) // if you want to use initialzing list to initiate a                                nested-union member, the union must not be anonymous and                              must have a constructor。
{}

TestUnion::TestUnion(long l) : data_(l)
{}

TestUnion::DataUnion::DataUnion(const char* ch) : ch_(ch)
{}

TestUnion::DataUnion::DataUnion(long l) : l_(l)
{}

int main (void)
{
    return 0;
}

  正如上面程序所示,C++中的union也可以包含构造函数,但是,这虽然被语言所支持,但实在是一种不佳的编程习惯,因此,        我不打算对上面的程序进行过多的说明。我更推荐如下的编程风格:

class TestUnion
{
    union DataUnion
    {
      const char* ch_;
      long l_;
    } data_;
  
  public:
    TestUnion(const char* ch);
    TestUnion(long l);
};

TestUnion::TestUnion(const char* ch)
{
    data_.ch_ = ch;
}

TestUnion::TestUnion(long l)
{
    data_.l_ = l;
}

int main (void)
{
    return 0;
}

它完全是C风格的。

所以,接受这个结论吧:

请按照C中的convention去使用union,尽量不要尝试使用任何C++附加特性。

union是个好东西,union是个struct,里面所有成员共享一块内存,大小由size最大的member决定,存取成员的时候会以成员的类型来解析这块内存;在gamedev中,union可以在这些方面有所作为:

1. 换名:

struct Rename
{
  public:
    union
    {
      struct
      {
        float x,y,z,w;
      };
      struct
      {
        float vec[4];
      };
    };
};

这样我们既可以根据具体的含义来访问变量,也可以象数组一样的loop;

2 .压缩:

struct Compression
{
 public:
   bool operator==(const Compression& arg) const { return value == arg.value; }
   union
   {
     struct
     {
       char a,b,c,d,e,f,g;
     };
     struct
     {
       long long value;
     };
   };
};

这样对于集中处理的情况,比如==,就会大幅度提高效率,象在64位机上,只要一次,或者传输数据的情况,压缩解压缩都非常方便;

3. 危险:

匿名的union用法,不是standard,所以在compiler上要确认==>编译器移植性不好;
不同的机器操作系统上数据的size都是不一样,表示不一样,那么在用union的时候,尤其是在移植的时候,都是危险的情况;
但是如果系统,compiler都是一样的话,在合适的地方使用union还是可以的。

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

1、什么是联合?

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

2、联合与结构的区别?

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

下面举一个例了来加对深联合的理解。

例4:

#include <stdio.h>
void main()
{
  union number
  { /*定义一个联合*/
   int i;
   struct
   { /*在联合中定义一个结构*/
    char first;
    char second;
   }half;
  }num;
  num.i=0x4241; /*联合成员赋值*/
  printf("%c%c\n", num.half.first, num.half.second);
  num.half.first='a'; /*联合中结构成员赋值*/
  num.half.second='b';
  printf("%x\n", num.i);
  getchar();
}

输出结果为:

AB
6261

从上例结果可以看出: 当给i赋值后, 其低八位也就是first和second的值; 当给first和second赋字符后, 这两个字符的ASCII码也将作为i 的低八位和高八位。

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;

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

3)又是匿名惹的祸??
我们先看下一段代码:

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!

4)如何有效的防止访问出错?

使用联合可以节省内存空间,但是也有一定的风险:通过一个不适当的数据成员获取当前对象的值!例如上面的ch、i交错访问。

为了防止这样的错误,我们必须定义一个额外的对象,来跟踪当前被存储在联合中的值得类型,我们称这个额外的对象为:union的判别式。

一个比较好的经验是,在处理作为类成员的union对象时,为所有union数据类型提供一组访问函数。

希望本文所述对大家的C++程序设计有所帮助。

(0)

相关推荐

  • C++基础入门教程(四):枚举和指针

    我已经把<C++ Primer>一书准备好了,如果这本<C++ Primer Plus>继续这么瞎闹的话,我就换主角~! 没错,这书连if while都还没介绍呢,就开始把指针搬出来了,虽然只是简单介绍.. 这目录编排我也是醒了. 那么,按照书上的进度,今天来初步说说指针. 但,今天周五,你懂的,就随便水一下,因为我怕到周一又忘了. 1.枚举 那么,枚举是什么呢?(小若:等等~!说好的指针呢?) 大部分高级语言里应该都有枚举了,所以也没什么好介绍的. 使用是这样的: 复制代码 代码

  • 讲解C++中的枚举类型以及声明新类型的方法

    C++枚举类型 如果一个变量只有几种可能的值,可以定义为枚举(enumeration)类型.所谓"枚举"是指将变量的值一一列举出来,变量的值只能在列举出来的值的范围内.声明枚举类型用enum开头.例如: enum weekday{sun, mon, tue, wed, thu, fri, sat}; 上面声明了一个枚举类型weekday,花括号中sun, mon, -, sat等称为枚举元素或枚举常量.表示这个类型的变量的值只能是以上7个值之一.它们是用户自己定义的标识符. 声明枚举类

  • 结合C++11的新特性来解析C++中的枚举与联合

    枚举 枚举是用户定义的类型,其中包含一组称为枚举器的命名的整型常数. 语法 // unscoped enum: enum [identifier] [: type] {enum-list}; // scoped enum: enum [class|struct] [identifier] [: type] {enum-list}; // Forward declaration of enumerations (C++11): enum A : int; // non-scoped enum mu

  • C++联合体union用法实例详解

    本文实例讲述了C++联合体union用法.分享给大家供大家参考.具体如下: 我们应该按照C中的convention去使用union,这是我这篇文章要给出的观点.虽然C++使得我们可以扩展一些新的东西进去,但是,我建议你不要那样去做,看完这篇文章之后,我想你大概也是这么想的. C由于没有类的概念,所有类型其实都可以看作是基本类型的组合,因此在union中包含struct也就是一件很自然的事情了,到了C++之后,既然普遍认为C++中的struct与class基本等价,那么union中是否可以有类成员

  • C语言中联合体union的实例详解

     C语言中联合体union的实例详解 1.定义: union(int i, short s, char c) un; un.i = 3; printf("i=%d",un.i); printf("length = %d\n",sizeof(un);//==4,有最大的变量来决定 2.相当与java里的List T类型 3.数据交换 void swap(int *p , int *q){ int temp = *p; *p = *q; *q = temp; } 4.打

  • C语言 联合(union)用法案例详解

    联合(union)的声明和结构与结构体类似,但是本质不同.    联合的所有成员引用的是内存中的相同位置.当你想在不同时刻把不同的东西存储于同一位置时,就可以使用联合.   构体(struct)中所有变量是"共存"的--优点是"有容乃大",全面:缺点是struct内存空间的分配是粗放的,不管用不用,全分配.   而联合体(union)中是各变量是"互斥"的--缺点就是不够"包容":但优点是内存使用更为精细灵活,也节省了内存空间

  • Angular中$cacheFactory的作用和用法实例详解

    先说下缓存: 一个缓存就是一个组件,它可以透明地储存数据,以便以后可以更快地服务于请求.多次重复地获取资源可能会导致数据重复,消耗时间.因此缓存适用于变化性不大的一些数据,缓存能够服务的请求越多,整体系统性能就能提升越多. $cacheFactory介绍: $cacheFactory是一个为Angular服务生产缓存对象的服务.要创建一个缓存对象,可以使用$cacheFactory通过一个ID和capacity.其中,ID是一个缓存对象的名称,capacity则是描述缓存键值对的最大数量. 1.

  • MySQL数据类型中DECIMAL的用法实例详解

    MySQL数据类型中DECIMAL的用法实例详解 在MySQL数据类型中,例如INT,FLOAT,DOUBLE,CHAR,DECIMAL等,它们都有各自的作用,下面我们就主要来介绍一下MySQL数据类型中的DECIMAL类型的作用和用法. 一般赋予浮点列的值被四舍五入到这个列所指定的十进制数.如果在一个FLOAT(8, 1)的列中存储1. 2 3 4 5 6,则结果为1. 2.如果将相同的值存入FLOAT(8, 4) 的列中,则结果为1. 2 3 4 6. 这表示应该定义具有足够位数的浮点列以便

  • jQuery siblings()用法实例详解

    siblings() 获得匹配集合中每个元素的同胞,通过选择器进行筛选是可选的. jQuery 的遍历方法siblings() $("给定元素").siblings(".selected") 其作用是筛选给定的同胞同类元素(不包括给定元素本身) 例子:网页选项栏 当点击任意一个选项卡是,其他2个选项卡就会改变样式,其内容也会隐藏. 下面是html代码. <body> <ul id="menu"> <li class=

  • Oracle addBatch()用法实例详解

    Oracle addBatch()用法实例详解 PreparedStatement.addbatch()的使用 Statement和PreparedStatement的区别就不多废话了,直接说PreparedStatement最重要的addbatch()结构的使用. 1.建立链接     Connection connection =getConnection(); 2.不自动 Commit connection.setAutoCommit(false); 3.预编译SQL语句,只编译一回哦,效

  • Bootstrap 折叠(Collapse)插件用法实例详解

    Bootstrap,来自 Twitter,是目前最受欢迎的前端框架.Bootstrap 是基于 HTML.CSS.JAVASCRIPT 的,它简洁灵活,使得 Web 开发更加快捷.下面通过本文给大家介绍Bootstrap 折叠(Collapse)插件用法实例,一起看看吧! 折叠(Collapse)插件可以很容易地让页面区域折叠起来.无论您用它来创建折叠导航还是内容面板,它都允许很多内容选项. 如果您想要单独引用该插件的功能,那么您需要引用 collapse.js.同时,也需要在您的 Bootst

  • jQuery stop()用法实例详解

    近期查看前辈的代码,发现在使用animate()的时候前面需要加上stop(),来防止移进移出的闪动问题,但却不知道stop()里面参数的真正意思,今天查了下stop()中参数的意义和具体使用方法,分享给大家. stop(true)等价于stop(true,false): 停止被选元素的所有加入队列的动画. stop(true,true):停止被选元素的所有加入队列的动画,但允许完成当前动画. stop()等价于stop(false,false):停止被选元素当前的动画,但允许完成以后队列的所有

  • ES6中Array.copyWithin()函数的用法实例详解

    ES6为Array增加了copyWithin函数,用于操作当前数组自身,用来把某些个位置的元素复制并覆盖到其他位置上去. Array.prototype.copyWithin(target, start = 0, end = this.length) 该函数有三个参数. target:目的起始位置. start:复制源的起始位置,可以省略,可以是负数. end:复制源的结束位置,可以省略,可以是负数,实际结束位置是end-1. 例: 把第3个元素(从0开始)到第5个元素,复制并覆盖到以第1个位置

随机推荐