您的位置:首页 > 其它

union联合的使用详解

2013-05-17 11:26 218 查看
注意: 经本人测试,在linux下, gcc默认并不支持匿名联合; 而g++是支持匿名联合的;猜测c语言不支持匿名联合,c++支持。如有问题可以留言
http://visionsky.blog.51cto.com/733317/151760
联合(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、匿名联合

匿名联合仅仅通知编译器它的成员变量共同享一个地址,而变量本身是直接引用的,不使用通常的点号运算符语法.例如:

#i nclude <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数据类型提供一组访问函数。

******************************************************************************
http://blog.csdn.net/jiangnanyouzi/article/details/3158702?reload
以前在学校学习C语言的时候一直搞不懂那个共用体union有什么用的。工作之后才发现它的一些妙用,现举例如下:

1. 为了方便看懂代码。

比如说想写一个3 * 3的矩阵,可以这样写:

[注:下面用红色部分标记的地方是后来添加上去的,谢谢yrqing718的提醒!]

struct Matrix
{
union
{
struct
{
float _f11, _f12, _f13, _f21, _f22, _f23, _f31, _f32, _f33;
};
float f[3][3];
}_matrix;
};

struct Matrix m;

这两个东西共同使用相同的空间,所以没有空间浪费,在需要整体用矩阵的时候可以用

m._matrix.f (比如说传参,或者是整体赋值等);需要用其中的几个元素的时候可以用m._matrix._f11那样可以避免用m.f[0][0](这样不大直观,而且容易出错)。

2. 用在强制类型转换上(比强制类型转换更加容易看懂)

下面举几个例子:

(1). 判断系统用的是big endian 还是 little endian(其定义大家可以到网上查相关资料,此略)

#define TRUE 1
#define FALSE 0
#define BOOL int

BOOL isBigEndian()
{
int i = 1; /* i = 0x00000001*/
char c = *(char *)&i; /* 注意不能写成 char c = (char)i; */
return (int)c != i;
}

如果是little endian字节序的话,那个i = 1;的内存从小到大依次放的是:0x01 0x00 0x00 0x00,如是,按照i的起始地址变成按照char *方式(1字节)存取,即得c = 0x01;

反之亦然

也许看起来不是很清晰,下面来看一下这个:

BOOL isBigEndian()
{
union
{
int i;
char c;
}test;

test.c = 2;

return test.i != 2;
}

这里用的是union来控制这个共享布局,有个知识点就是union里面的成员c和i都是从低地址开始对齐的。同样可以得到如此结果,而且不用转换,清晰一些。

什么,不觉得清晰??那再看下面的例子:

(2). 将little endian下的long long类型的值换成 big endian类型的值。已经知道系统提供了下面的api:long htonl(long lg);作用是把所有的字节序换成大端字节序。因此得出下面做法:

long long htonLL(long long lg)
{
union
{
struct
{
long low;
long high;
}val_1;
long long val_2;
}val_arg, val_ret;

if( isBigEndian() )
return lg;
val_arg.val_2 = lg;

val_ret.val_1.low = htonl( val_arg.val_1.high );
val_ret.val_1.high = htonl( val_arg.val_1.low );

return val_ret.val_2;
}

只要把内存结构的草图画出来就比较容易明白了。

(3).为了理解c++类的布局,再看下面一个例子。有如下类:

class Test
{
public:
float getFVal(){ return f;}
private:
int i;
char c;
float f;
};
Test t;

不能在类Test中增加代码,给对象中的f赋值7.0f.

class Test_Cpy
{
public:
float getVal(){ return f;}
float setVal(float f){ this->f = f;}
private:
int i;
char c;
float f;
};

....

int main()
{
Test t;
union
{
Test t1,
Test_Cpy t2;
}test;

test.t2.setVal(7.0f);
t = test.t1;
assert( t.getVal() == 7.0f );

return 0;
}

说明:因为在增加类的成员函数时候,那个类的对象的布局基本不变。因此可以写一个与Test类一样结构的类Test_Cpy,而多了一个成员函数setVal,再用uinon结构对齐,就可以给私有变量赋值了。(这种方法在有虚机类和虚函数机制时可能失灵,故不可移植)至于详细的讨论,网上有,这个例子在实际中没有用途,只是用来考察这个内存布局的使用而已.

union在操作系统底层的代码中用的比较多,因为它在内存共赏布局上方便且直观。所以网络编程,协议分析,内核代码上有一些用到union都比较好懂,简化了设计。

*************************************************************
http://www.189works.com/article-69958-1.html
3.利用union进行类型转换

前面已经说过,union具有多变的特性,可以在不同成员中任意切换,而无需借助强制类型转换,下面举例说明这一点(其实1已经很好地说明了这一点):

#i nclude

using namespace std;

struct DATA

{

char c1;

char c2;

};

int main()

{

union {

int i;

DATA data;

} _ut;

_ut.i = 0x6162;

cout << "_ut.data.c1 = " << _ut.data.c1 << endl

<< "_ut.data.c2 = " << _ut.data.c2 << endl;

return 0;

}

需要提醒你的是,数据类型的转换,并非union的专长,只是一个可资利用的特性而已.因为,采用union进行类型间转换极易受平台影响,如上面的程序采用Intel x86 + Windows 2000 + VC6时输出为:

_ut.data.c1 = b

_ut.data.c2 = a

(注:因为Intel CPU的架构是Little Endian)

而在Sun的Sparc上,你得到的结果却是:

_ut.data.c1 =

_ut.data.c2 =

(注:因为采用Big Endian时,前两个字节为0x0000)

而即便是在同一平台上,在integer类型与real类型间进行转换时也不要采用union,否则,你会得到令你莫名其妙的结论(这是由于CPU对 real类型的处理方式引起的,该方式在各平台上有极大区别,同时,根据C++ Standard,这种作法会引起"undefined behavior").
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: