目录:
一、union的定义/声明/使用
1、union的定义
2、union的声明
3、union的使用
二、联合体union的基本特性
三、双刃剑-多种访问内存途径共存
四、联合体union和大小端
五、联合体union占内存空间
六、联合体union适用场合
七、union本质与进阶
八、联合与结构
九、实现多字节数据类型转换
一、union的定义/声明/使用
1、union的定义
union test
{
test() { }
int office;
char teacher[5];
};
定义一个名为 test 的联合类型,它含有两个成员,一个为整型,成员名 office;另一个字符数组,数组名为 teacher。联合定义之后,即可进行联合变量说明,被说明为 test 类型的变量,可以存放整型量 office 或存放字符数组 teacher。
2、union的声明
联合变量的声明有三种形式:先定义再声明、定义同时声明和直接声明。以 test 类型为例,说明如下:
union test
{
int office;
char teacher[5];
};
union test a,b;
union test
{
int office;
char teacher[5];
} a,b;
union
{
int office;
char teacher[5];
} a,b;
经声明后的 a,b 变量均为 test 类型。a,b 变量的长度应等于 test 的成员中最长的长度,即等于 teacher 数组的长度,共 5 个字节。a,b 变量如赋予整型值时,只使用了 4 个字节,而赋予字符数组时,可用 5 个字节。
3、union的使用
对联合变量的赋值,使用都只能是对变量的成员进行。联合变量的成员表示为:
联合变量名.成员名
例如,a 被声明为 test 类型的变量之后,可使用 、。
不允许只用联合变量名作赋值或其它操作,也不允许对联合变量作初始化赋值,赋值只能在程序中进行。
还要再强调声明的是,一个联合变量,每次只能赋予一个成员值。换句话说,一个联合变量的值就是联合变员的某一个成员值。
二、联合体union的基本特性
union 中文名“联合体、共用体”,在某种程度上类似结构体 struct 的一种数据结构,共用体 (union) 和结构体 (struct) 同样可以包含很多种数据类型和变量。不过区别也挺明显:
①结构体 (struct) 中所有变量是“共存”的——优点是“有容乃大”,全面;缺点是 struct 内存空间的分配是粗放的,不管用不用,全分配。
②而联合体 (union) 中是各变量是“互斥”的——缺点就是不够“包容”;但优点是内存使用更为精细灵活,也节省了内存空间。
三、双刃剑-多种访问内存途径共存
#include ""
union var
{
long int l;
int i;
};
int main(void)
{
union var v;
= 5;
printf(" is %d\n",);
= 6;
printf("now is %ld! the address is %p\n",,&);
printf("now is %d! the address is %p\n",,&);
}
结果:
is 5
now is 6! the address is 0xbfad1e2c
now is 6! the address is 0xbfad1e2c
所以说,管 union 的叫共用体还真是贴切——完全就是共用一个内存首地址,并且各种变量名都可以同时使用,操作也是共同生效。如此多的 access 内存手段,确实好用,不过这些“手段”之间却没法互相屏蔽——就好像数组+下标和指针+偏移一样。
上例中我改了 的值,结果 也能读取,那么也许我还以为是我想要的值呢,因为上边提到了 union 的内存首地址肯定是相同的,那么还有一种情况和上边类似:
一个int数组变量 a,一个 long int(32位机中,long int 占 4 字节,与 int 相同)变量 b,我即使没给 int 变量 b 赋值,因为数据类型相同,我使用 int 变量 b 也完全会拿出int数组 a 中的 a[0] 来,一些时候一不小心用上,还以为用的就是变量 b 呢。这种逻辑上的错误是很难找出来的(只有当数据类型相去甚远的时候稍好,出个乱码什么的很容易发现错误)。
四、联合体union和大小端
下边示范了一种用途,代表四个含义的四个变量,但是可以用一个 int 来操作,直接 int 赋值,无论内存访问(指针大小的整数倍,访问才有效率),还有时间复杂度(一次和四次的区别,而且这四次有三次都是不整齐的地址),都会低一些。
#include ""
union var
{
char c[4];
int i;
};
int main(void)
{
union var data;
[0] = 0x04;//因为是char类型,数字不要太大,算算ascii的范围~
[1] = 0x03;//写成16进制为了方便直接打印内存中的值对比
[2] = 0x02;
[3] = 0x11;
//数组中下标低的,地址也低,按地址从低到高,内存内容依次为:04,03,02,11。总共四字节!
//而把四个字节作为一个整体(不分类型,直接打印十六进制),应该从内存高地址到低地址看,0x11020304,低位04放在低地址上。
printf("%x\n",);
}
结果:11020304
证明我的 32 位 Win7 是小端 (Little-endian),大端就是 big-endian。
五、联合体union占内存空间
前边说了,首先 union 的首地址是固定的,那么 union 到底总共有多大?
根据:分配栈空间的时候内存地址基本上连续,至少同类型能保证在一起,连续就说明,如果弄三个结构体出来,它们三个地址应该连着,看一下三个地址的间隔就知道了。
#include ""
union sizeTest
{
int a;
double b;
};
int main(void)
{
union sizeTest unionA;
union sizeTest unionB;
union sizeTest unionC;
printf("the initial address of unionA is %p\n",&unionA);
printf("the initial address of unionB is %p\n",&unionB);
printf("the initial address of unionC is %p\n",&unionC);
}
打印,可以看到结果:
the initial address of unionA is 0xbf9b8df8
the initial address of unionB is 0xbf9b8e00
the initial address of unionC is 0xbf9b8e08
很容易看出,8,0,8,这间隔是 8 字节,按 double 走的。
怕不保险,再改一下,把 int 改成数组,其他不变:
union sizeTest
{
int a[10]; //4*10=40
double b;
};
打印:
the initial address of unionA is 0xbfbb7738
the initial address of unionB is 0xbfbb7760
the initial address of unionC is 0xbfbb7788
88-60 = 0x28
60-38 = 0x28
那么 0x28 就是 40 个字节,正好是数组 a(4*10=40) 的大小。
忘了提一个功能——sizeof( )
用 sizeof 直接看,就知道 union 的大小了
printf("the sizeof of unionA is %d\n",sizeof(unionA));
printf("the sizeof of unionB is %d\n",sizeof(unionB));
printf("the sizeof of unionC is %d\n",sizeof(unionC));
printf("the sizeof of union is %d\n",sizeof(union sizeTest));
上边说的地址规律,没有特定规则,也可能和编译器有关。另外,那只是栈空间,还可以主动申请堆空间,当然,堆空间就没有连续不连续一说了。
六、联合体union适用场合
有了前边那个验证,基本可以确认,union 的内存是照着里边占地儿最大的那个变量分的。
也就可以大胆的推测一下,这种 union 的使用场合,是各数据类型各变量占用空间差不多并且对各变量同时使用要求不高的场合。
像上边做的第二个测试,一个数组(或者更大的数组 int a[100]),和一个或者几个小变量写在一个 union 里,实在没什么必要,节省的空间太有限了,还增加了一些风险(最少有前边提到的逻辑上的风险)。所以,从内存占用分析,这种情况不如直接 struct。
不过话说回来,某些情况下虽然不是很节约内存空间,但是 union 的复用性优势依然存在啊,比如方便多命名,这种“二义性”,从某些方面也可能是优势。这种方法还有个好处,就是某些寄存器或通道大小有限制的情况下,可以分多次搬运。
七、union本质与进阶
根据 union 固定首地址和 union 按最大需求开辟一段内存空间两个特征,可以发现,所有表面的定义都是虚的,所谓联合体 union,就是在内存给你划了一个足够用的空间,至于你怎么玩~它不管~!(何止是 union 和 struct,C 不就是玩地址,所以使用 C 灵活,也容易犯错)。
union 的成员变量是相当于开辟了几个访问途径(即 union 包含的变量)!但是没开辟的访问方式就不能用了?当然也能用!写个小测试:
#include ""
union u
{
int i;
double d; //这个union有8字节大小
};
int main(void)
{
union u uu;
= 10;
printf("%d\n",);
char * c;
c = (char *)&uu; //把union的首地址赋值、强转成char类型
c[0] = 'a';
c[1] = 'b';
c[2] = 'c';
c[3] = '\0';
c[4] = 'd';
c[5] = 'e';
//最多能到c[7]
printf("%s\n",c);//利用结束符'\0'打印字符串"abc"
printf("%c %c %c %c %c %c\n",c[0],c[1],c[2],c[3],c[4],c[5]);
}
结构体只定义了 int 和 double “接口”,只要获得地址,往里边扔什么数据谁管得到?这就是 C 语言(不止 union)的本质——只管开辟一段空间。
但是获取地址并访问和存取的数据,最好确定是合法(语法)合理(用途符合)的地址,不然虽然能操作,后患无穷,C 的头疼之处,可能出了问题都找不到。
八、联合与结构
#include ""
union number
{//定义一个联合
int i;
struct
{//在联合中定义一个结构
char first;
char second;
}half;
}num;
int main(void)
{
=0x4241; //联合成员赋值
printf("%c%c\n", , );
='a'; //联合中结构成员赋值
='b';
printf("%x\n", );
getchar( );
}
输出结果为:
AB
6261
从上例结果可以看出:当给i赋值后,其低八位也就是 first 和 second 的值;当给 first 和 second 赋字符后,这两个字符的 ASCII 码也将作为 i 的低八位和高八位。
九、实现多字节数据类型转换
1、音视频编解码算法
经常会涉及一些数据压缩、声音解码、图象的缩放等问题。
这里通过一个例子来推荐一种 union 绝妙用法(这种方法由 Equator 公司提供)。在该例子中,利用 union 结构 n64u 实现占8个字节 n64 类型与单字节的 c0~c7 的相互转换,从而达到数据压缩和分解之目的。
#include #include ""
#define IN
#define OUT
#define INOUT
typedef unsigned long long n64;
typedef unsigned int n32;
typedef unsigned short n16;
typedef unsigned char n8;
typedef struct _s8t
{
unsigned char c0, c1, c2, c3, c4, c5, c6, c7;
}s8t;
typedef union
{
n64 n64;
struct {
n32 l0, l1;
} u32;
struct {
long l0, l1;
} s32;
struct {
unsigned short s0, s1, s2, s3;
} u16;
struct {
short s0, s1, s2, s3;
} s16;
struct {
unsigned char c0, c1, c2, c3, c4, c5, c6, c7;
} u8;
struct {
char c0, c1, c2, c3, c4, c5, c6, c7;
} s8;
} n64u;
#define MAX_DATA_COMPRESSED_NUM 8
int compress64_8(IN const n64* src,IN n16 n,OUT n8* dst);
int uncompress8_64(IN const n8* src,IN n16 n,OUT n64* dst);
int compress64_8(IN const n64* src,IN n16 n,OUT n8* dst)
{
n64u n64u_data;
register n64* n64ptr=(n64*)src;
register n8* n8ptr=dst;
n16 i=0,num=n;
if(NULL==n64ptr || NULL==n8ptr || n<1)
{
printf("invalid param,src 0x%x,dst 0x%x,n %d\n",n64ptr,n8ptr,n);
return 0;
}
for(i=0;i小于num;i++)
{
n64u_data.n64 = *n64ptr++;
*n8ptr++ = (n64u_data.u8.c0+n64u_data.u8.c1+n64u_data.u8.c2+\
n64u_data.u8.c3+n64u_data.u8.c4+n64u_data.u8.c5+\
n64u_data.u8.c6+n64u_data.u8.c7)/(sizeof(n64)/sizeof(n8));
}
return 1;
}
int uncompress8_64(IN const n8* src,IN n16 n,OUT n64* dst)
{
n64u n64u_data;
register n64* n64ptr=dst;
register n8* n8ptr=(n8*)src;
register n8 n8data;
n16 i=0,num=n;
if(NULL==n64ptr || NULL==n8ptr || n<1)
{
printf("invalid param,src 0x%x,dst 0x%x,n %d\n",n64ptr,n8ptr,n);
return 0;
}
for(i=0;i小于num;i++)
{
n8data=*n8ptr++;
n64u_data.u8.c0 = n8data;
n64u_data.u8.c1 = n8data;
n64u_data.u8.c2 = n8data;
n64u_data.u8.c3 = n8data;
n64u_data.u8.c4 = n8data;
n64u_data.u8.c5 = n8data;
n64u_data.u8.c6 = n8data;
n64u_data.u8.c7 = n8data;
*n64ptr++ = n64u_data.n64;
}
}
int main(int argc, char *argv[])
{
n64 n64data[MAX_DATA_COMPRESSED_NUM];
n8 n8data[MAX_DATA_COMPRESSED_NUM];
s8t s8t_data[MAX_DATA_COMPRESSED_NUM]=
{
{1,2,3,4,5,6,7,8},
{2,2,3,4,5,6,7,7},
{3,2,3,4,5,6,7,6},
{4,2,3,4,5,6,7,5},
{5,2,3,4,5,6,7,4},
{6,2,3,4,5,6,7,3},
{7,2,3,4,5,6,7,2},
{8,7,6,5,4,3,2,1}
};
n16 i,n=MAX_DATA_COMPRESSED_NUM;
printf("data:\n");
for(i=0;i小于n;i++)
{
n64data[i] = *(n64*)&s8t_data[i];
printf("%3u %3u %3u %3u %3u %3u %3u %3u\n",
s8t_data[i].c0,s8t_data[i].c1,s8t_data[i].c2,
s8t_data[i].c3,s8t_data[i].c4,s8t_data[i].c5,
s8t_data[i].c6,s8t_data[i].c7);
}
printf("\n");
compress64_8(n64data,n,n8data);
printf("compressed to:\n");
for(i=0;i小于n;i++)
{
printf("%3u ",n8data[i]);
}
printf("\n\n");
uncompress8_64(n8data,n,n64data);
printf("uncompressed to:\n");
for(i=0;i小于n;i++)
{
*(n64*)&s8t_data[i] = n64data[i];
printf("%3u %3u %3u %3u %3u %3u %3u %3u\n",
s8t_data[i].c0,s8t_data[i].c1,s8t_data[i].c2,
s8t_data[i].c3,s8t_data[i].c4,s8t_data[i].c5,
s8t_data[i].c6,s8t_data[i].c7);
}
printf("\n");
}
2、使不同数据包兼容
union的用法如下:
struct _my_struct
{
unsigned int struct_id
typedef union _my_union
{
struct my_struct_1;
struct my_struct_2;
struct my_struct_3;
}my_union;
}my_struct;
在处理音频视频数据流方面,为了区分音频和视频数据以及来自网络和编解码的数据并减少内存占用使用了下面的数据结构。这种 union 使用方法在网络应用中特别常见。在数据结构 TFrameBufferInfo 中,用 bufferType 标识数据来源。
typedef struct
{
int codecId;
int packetNum;
int actualNum;
int packetLen;
int leftPacketLen;
int frameSample;
int timeStamp;
int dataLen;
int ready;
unsigned char* buffer;
int reserve1;
} TABufferInfoFromCodec;
typedef struct
{
int codecId;
int bKeyFrame;
int packetNum;
int actualNum;
int packetLen;
int leftPacketLen;
int timeStamp;
int dataLen;
int ready;
unsigned char* buffer;
int reserve1;
} TVBufferInfoFromCodec;
typedef struct
{
int codecId;
int bKeyFrame;
int packetNum;
int actualNum;
int packetLen;
int leftPacketLen;
int rtpLen;
int timeStamp;
int firstSquence;
int dataLen;
int ready;
unsigned char* buffer;
} TVBufferInfoToCodec;
typedef struct
{
int codecId;
int packetNum;
int actualNum;
int packetLen;
int leftPacketLen;
int rtpLen;
int timeStamp;
int firstSquence;
int dataLen;
int ready;
unsigned char* buffer;
int reserve1;
} TABufferInfoToCodec;
typedef struct
{
int bufferType;
union
{
TVBufferInfoFromCodec bufferInfoFromVCoder;
TABufferInfoFromCodec bufferInfoFromACoder;
TVBufferInfoToCodec bufferInfoFromVNetWork;
TABufferInfoToCodec bufferInfoFromANetWork;
} buffer_info;
} TFrameBufferInfo;
int send_to(void* stream);
int send_to(void* stream)
{
}
int main(int argc, char *argv[])
{
TFrameBufferInfo tFrameBufferInfo;
TVBufferInfoFromCodec* pVBufferInfoFromCodec;
unsigned char buffer[1200*5];
=4;
pVBufferInfoFromCodec=&tFrameBufferInfo.buffer_info.bufferInfoFromVCoder;
pVBufferInfoFromCodec->bKeyFrame = 1;
pVBufferInfoFromCodec->packetNum = 2;
pVBufferInfoFromCodec->actualNum = 2;
pVBufferInfoFromCodec->leftPacketLen = 123;
pVBufferInfoFromCodec->dataLen = 1323;
pVBufferInfoFromCodec->ready = 1;
pVBufferInfoFromCodec->buffer = buffer;
send_to((void*)&tFrameBufferInfo);
}
青春时代是一个短暂的美梦,当你醒来时,它早已消失得无影无踪。觉得不错,动动发财的小手点个赞哦!