10.6 联合
和结构相比,联合(union)可以说是另一种“动物”了。联合的声明和结构类似,但它的行为方式却和结构不同。联合的所有成员引用的是内存中的相同位置。如果想在不同的时刻把不同的东西存储于同一个位置,就可以使用联合。
首先,如下例所示:
union {
float f;
int i;
} fi;
在一个浮点数和整型都是32位的机器上,变量fi只占据内存中一个32位的字。如果成员f被使用,这个字就作为浮点值访问;如果成员i被使用,这个字就作为整型值访问。所以,下面这段代码
fi.f = 3.14159;
printf( "%d\n", fi.i );
/*
** 联合。
*/
union {
float f;
int i;
double d;
} fi;
#include <stdio.h>
#include <stdlib.h>
int main( void ){
fi.f = 3.14159;
/*
** test every member value of union.
*/
printf( "fi.f = %f, fi.i = %d, fi.d = %lf\n", fi.f, fi.i, fi.d );
/*
** test the size of union.
*/
printf( "sizeof(fi) = %zd, sizeof(float) = %zd, sizeof(int) = %zd, sizeof(double) = %zd\n",
sizeof(fi), sizeof(float), sizeof(int), sizeof(double) );
return EXIT_SUCCESS;
}
/* 输出:
*/
首先把π的浮点数表示形式存储于fi,然后把这些相同位当做一个整型值打印输出。注意,这两个成员所引用的位相同,仅有的区别在于每个成员的类型决定了这些位如何解释。
为什么人们有时想使用类似此例的形式呢?如果你想看看浮点数是如何存储在一种特定的机器中,但又对其他内容不感兴趣,联合就可能有所帮助。这里有一个更为现实的例子。BASIC解释器的任务之一就是记住程序所使用的的变量的值。BASIC提供了几种不同类型的变量,所以每个变量的类型必须和它的值一起存储。下面这个结构用于保存这个信息,但它的效率不高。
struct VARIABLE {
enum { INT, FLOAT, STRING } type;
int int_value;
float float_value;
char *string_value;
};
当BASIC程序中的一个变量被创建时,解释器就创建一个这样的结构并记录变量的类型。然后,根据变量的类型,把变量的值存储在这3个值字段的其中一个。
这个结构的低效之处在于它所占用的内存---每个VARIABLE结构存在两个未使用的值字段。联合就可以减少这种浪费,它把这3个值字段的每一个都存储于同一个内存位置。这3个字段并不会冲突,因为每个变量只可能具有一种类型,这样在某一时刻,联合的这几个字段只有一个被使用。
struct VARIABLE {
enum { INT, FLOAT, STRING } type;
union {
int i;
float f;
char *s;
} value;
};
现在,对于整型变量,你将把type字段位置设置为INT,并把整型值存储于value.i字段。对于浮点值,你将使用value.f字段。当以后得到这个变量的值时,对type字段进行检查可以决定使用哪个值字段。这个选择决定内存位置如何被访问,所以同一个位置可以用于存储这3种不同类型的值。注意,编译器并不对type字段进行检查,以证实程序使用的是正确的联合成员。维护并检查type字段是程序员的责任。如果联合的各个成员具有不同的长度,联合的长度就是它最长成员的长度。
/*
** 联合。
*/
struct VARIABLE {
enum { INT, FLOAT, STRING } type;
int int_value;
float float_value;
char *string_value;
};
/*
** use union can save more memory.
*/
struct VARIABLE2 {
enum { INT, FLOAT, STRING } type;
union {
int i;
float f;
char *s;
} value;
};
#include <stdio.h>
#include <stdlib.h>
int main( void ){
printf( "sizeof(struct VARIABLE) = %zd, sizeof(struct VARIABLE2) = %zd\n",
sizeof(struct VARIABLE), sizeof(struct VARIABLE2) );
return EXIT_SUCCESS;
}
/* 输出:
*/
10.6.1 变体记录(variant record)
从概念上说,这就是我们刚刚讨论过的那个情况---内存中某个特定的区域将在不同的时刻存储不同类型的值。但是,在现在这个情况下,这些值比简单的整型或浮点型更为复杂,它们的每一个都是完整的结构。
下面这里例子取自一个存货系统,它记录了两种不同的实体:零件(part)和装配件(subassembly)。零件就是一种小配件,从其他生产厂家购得。它具有不同的属性,如购买来源,购买价格等。装配件是我们制造的东西,它由一些零件及其装配件组成。
下面两个结构指定每个零件和装配件的内容。
struct PARTINFO {
int cost;
int supplier;
...
};
struct SUBASSYINFO {
int n_parts;
struct {
char partno[10];
short quan;
} parts[MAXPARTS];
};
接下来的存货(inventory)记录包含了每个项目的一般信息,并包括了一个联合:或者用于存储零件信息,或者用于存储装配件信息。
struct INVREC {
char partno[10];
int quan;
enum { PART, SUBASSY } type;
union {
struct PARTINFO part;
struct SUBASSYINFO subassy;
} info;
};
这里有一些语句,用于操作名叫rec的INVREC结构变量
if( rec.type == PART ){
y = rec.info.cost;
z = rec.info.part.supplier;
}else{
y = rec.info.subassy.nparts;
z = rec.info.subassy.parts[0].quan;
}
尽管并非真实,但这段代码说明了如何访问联合的每个成员。
/*
** 变体记录。
*/
#include <stdio.h>
#include <stdlib.h>
struct PARTINFO {
int cost;
int supplier;
/*...*/
};
#define MAXPARTS 10
struct SUBASSYINFO {
int n_parts;
struct {
char partno[10];
short quan;
} parts[MAXPARTS];
};
struct INVREC {
char partno[10];
int quan;
enum { PART, SUBASSY } type;
union {
struct PARTINFO part;
struct SUBASSYINFO subassy;
} info;
};
int main( void ){
struct INVREC rec;
int y;
int z;
if( rec.type == INVREC::PART ){
y = rec.info.part.cost;
z = rec.info.part.supplier;
}else{
y = rec.info.subassy.n_parts;
z = rec.info.subassy.parts[0].quan;
}
return EXIT_SUCCESS;
}
/* 输出:
*/
在一个成员长度不同的联合里,分配给联合的内存数量取决于它的最长成员的长度。这样,联合的长度总是足以容纳它最大的成员。如果这些成员的长度相差悬殊,当存储长度较短的成员时,浪费的空间是相当客观的。在这种情况下,更好的方法是在联合中存储指向不同成员的指针而不是直接存储成员本身。所有指针的长度都是相同的,这样就解决了内存浪费的问题。当它决定需要哪个成员时,就分配正确数量的内存来存储它。动态内存分配包含了一个用于说明这种技巧的例子。
/* 后面有这个例子,这里就不再书写例子了*/
10.6.2 联合的初始化
联合变量可以被初始化,但这个初始值必须是联合第一个成员的类型,而且它必须位于一对花括号里面。例如,
union {
int a;
float b;
char c[4];
}x = { 5 };
把x.a初始化为5。
我们不能把这个类量初始化一个浮点值或字符值。如果给出的初始值是其他任何类型,它就会转换(如果可能的话)为一个整型并赋值给x.a。
/*
** 联合的初始化。
*/
#include <stdio.h>
#include <stdlib.h>
union {
int a;
float b;
char c[4];
}x = { 5 };
int main( void ){
printf( "x.a = %d, x.b = %f, x.c[0] = %c, x.c[1] = %c, x.c[2] = %c, x.c[3] = %c\n",
x.a, x.b, x.c[0], x.c[1], x.c[2], x.c[3] );
return EXIT_SUCCESS;
}
/* 输出:
*/