1
九、联合 union
十、数据的引用类型转换十一、位域或位字段
2
九、联合 union
1,联合的特性和定义由关键字 struct声明的数据描述称为结构类型而 union
或 class声明的数据描述称为联合类型或类类型,分别简称为结构和联合或类。
结构、联合、类是集合数据类型。
int,float等是系统预先取好的可以直接使用的类名,
关键字 struct和 union 或 class后紧跟的标识符则是用户引入的类名,需要事先加以声明。
3
在程序设计中常出现非此即彼的数据,这些数据的类型可以不同,需要将其放置在同一片内存。
关键字 union 建立的数据类型为用户引入的联合类型,
简称联合。联合一方面是节省内存空间,另一方面是特殊的编程考虑。
联合的特性和定义,
联合类型定义的变量称为联合变量。指向联合变量的指针简称为联合指针。联合类型的声明和联合变量的定义采用与结构类似的语法,不同的是联合使用关键字 union。
4
将前面关于结构声明复制过来略加变动并用 union替代
struct得到:
union UnionName 联合 联合名
{ type member; { 数据类型 成员名 ;
type2 member2; 数据类型 2 成员名 2;
typen membern; 数据类型 n 成员名 n;
}; };
关键字 union用于声明同一片内存可以存放不同类型的数据,但在一个时刻只有某一类型的数据处于有效的状态。
5
联合类型声明中的数据成员具有如下的性质:
a,不同的成员占有起始地址相同的内存区域,这些成员以并排的方式重合在一起。
b,该内存区域的长度确定为所有成员中占有内存空间最长的成员所对应的长度。
c,在一个具体的时刻仅包含一种数据成员有效虽然成员可以是集合型的数据。
d,初始化联合变量时默认对第一个成员进行赋值亦仅对一个成员赋值,初始值的类型属性应与第一个成员的类型属性一致,否则编译器至少给出警告。
e,C++中联合不参入继承机制即联合类既不作基类也不作派生类,不能有虚拟函数。
6
[例 ] 联合的内存和结构的内存大小比较
#include<stdio.h>
typedef struct s_a { double d; char c[9]; } A;
typedef struct s_b { double d; long k[2];} B;
typedef union u_t { A a; B b; } U;
typedef struct s_t { A a; B b; } S;
void main (void)
{ printf ("sizeof(A)= %d,",sizeof (A));
printf ("sizeof(B)= %d,",sizeof (B));
printf ("sizeof(U)= %d,",sizeof (U));
printf ("sizeof(S)= %d",sizeof (S));
} //输出,sizeof (A)= 24,sizeof (B)= 16,16,
sizeof (U)= 24,24,sizeof (S)= 40
7
联合一经声明就可以定义联合变量、联合数组和联合指针。 箭头运算符,->”与圆点运算符,.”可以用来访问联合中的成员,箭头运算符 "->"的左侧是联合指针,圆点操作符 ".“
的左侧是联合变量,右侧是联合中的成员。
这和结构变量的使用规则一致。
不同的是结构变量拥有结构中各独立的成员所占内存之和;
联合变量则仅是最大成员所拥有的内存,这一片内存可由若干类型属性不同的成员适当索引,它们齐头地占有同一块内存。
8
联合遵循先声明后定义再使用的次序。如下所示:
typedef union u_t {int k[4]; long member; float y; }U;
U obj,*pobj;
obj.member //联合变量名,成员名
pobj->member //联合指针名 ->成员名三个步骤可以合为一体,再加上初始化格式为:
union u_t { int k[4]; long member; float y; }
b = {1,2,3,4};
初始化只对第一个联合成员进行。该凝练的格式声明一个名为 u_t的联合名,其拥有三个并置的成员,第一个是 int
型的数组成员 k[4]、第二个是 long型的成员 member,第三个是 float型的成员 y.这三个成员的内存起始地址是一样的。
9
[例 ] 联合指针入口形参和联合引用返回
#include<stdio.h>
inline int f(int k) {return k;}
typedef union u_t { char* s; int (*f)(int); int d; } Un ;
Un& initial (Un*p,int n,char * s)
{ switch (n)
{ case 1,p->s=s; break;
case 2,p->f=f; break;
default,p->d=n; break;
} //三个成员互斥地使用同一内存
return *p;
}
void main (void)
{ Un x;
for (int n=1;n<4;n++)
10
{ Un y= initial (&x,n,"abcd");
switch (n)
{ case 1,printf ("[%s,%s]\t",x.s,y.s); break;
case 2,printf ("[%d,%d]\t",x.f(1),y.f(2)); break;
default,printf ("[%d,%d]\n",x,d,y.d); break; }
}
} //输出,[abcd,abcd] [1,2] [3,3]
联合变量、联合指针和联合引用可以作为形参,联合变量可以相互赋值。
对联合变量的操作需要特别注意成员的类型属性,不同类型属性的成员应由不同的分支处理。
11
2,联合的内存映像考虑如下初始化于一体的联合声明和联合变量的定义:
union u_t { int k[4]; long z; float y; }
b={1,2,3,4},c;
声明了一个联合名为 u_t的联合类型,具有三个成员,
这三个成员是 int型数组 k[4],long型成员 z,float型成员 y。
第一个成员是数组成员 k。同时定义了两个联合变量 b
和 c,在定义联合变量 b的时候对其第一个成员进行了初始化处理。
该联合占有的内存空间是:
sizeof (b)=sizeof (union u)= sizeof (int[4])=
sizeof (b.k)=16
12
该联合在 32位编程模式下的内存布局如下:
int型数组 k[4]
long型成员 z 8 12 16
float型成员 y 4 联合的数据内存分布在 PC微机上数据的存放方式是低尾端形式的,即 short
型 16位字节的低 8位存放在内存的低地址处,高 8bit存放在内存的高地址处高尾端的存放方式则相反。
下页的例子说明数据在 PC内存中的顺序是低尾端形式。
k[0] k[1] K[2] K[3]
13
[例 ] 强制类型转换显示低尾端的存储格式 (8位二进制数的低位在右边,高位在左边 ).
#include<stdio.h>
void main()
{ unsigned long m = 0x87654321UL;
unsigned char * p=(unsigned char*)&m;
for (int i=0; i<sizeof (long);i++)
printf ("%x ",*p++);
}
14
上例低尾端的 PC计算上输出结果,21 43 65 87
这样 32位整数的 8个 4位二进制数的十六进制数数码表示为,
m= h7h6h5h4h3h2h1h0 =87654321
该数以字节即 8位 bit为最小内存寻址单位的内存存储格式为 (低地址标注在左边 ):
16进制表示:
低地址 高地址
2进制表示:
低地址 高地址
21 43 65 87
00100001 01000011 01100101 10000111
15
联合的同一片内存可以通过不同的名称索引。对一个成员的改变直接影响联合中的其它成员的数据状态,对数据的解释取决于数据的存储格式和模块转换。
[例 ] 联合的内存布局和内存数据的解释
#include<stdio.h>
typedef union u_t { char c[2]; unsigned long k; } U;
void main()
{ U x = {3,1}; //x.c[0]=3;x.c[1]=1; 潜在地导致 x.c[2]=0;x.c[3]=0;
printf ("%d,0x%08x\t",x.k,x.k);
x,k= 0x67686162UL;
printf ("%c,%d,%c,%d; ",x.c[0],x.c[0],x.c[1],x.c[1]);
printf ("%c,%d,%c,%d\n ",x.c[2],x.c[2],x.c[3],x.c[3]);
}
16
说明:占 4字节的整型数 (4个 8位的十六进制数 )存贮格式在微机上是低尾端格式,具有如下的形式(低地址标注在边,8位二进制数的低位在右边),
高地址 低地址
k= 0x0000|0103? 1*16*16+3=259
x.k= 0x67686162UL;
高地址 低地址
d3?00000000 d2?00000000 d1? 00000001 d0?000000011
0x67?0x68?0x61?0x62
c[0]=3?00000011c[1]=1?00000001c[2]c[3]
c[0]?0x62?'b'c[1]?0x61?'a'c[2]c[3]
17
[例 ]浮点数和整型数内存存储格式不同
#include<stdio.h>
typedef union u_fl { float f; long k; } Ua;
typedef union u_il { int f; long k; } Ub;
struct Sab { union {float f ; long k;} a; Ub b;} s={ 1.0,2 };
void main()
{ printf("[%1.0f,%d]\t",s.a.f,s.b.f);
Ua & x=(Ua &)s.a ; x.f=10;
printf ("[%d,%d]\t",(long)(x.f+s.a.f),x.k); x.k=20;
printf ("[%d,%d]\t",(long)x.f,x.k+s.a.k);
Ub & y=s.b ;
y.f=10; printf ("[%d,%d]\t",(long)y.f,s.b.k);
y.k=20; printf ("[%d,%d]\n",(long)s.b.f,y.k);
} //输出,[1,2] [20,1092616192] [0,40] [10,10] [20,20]
18
联合将不同类型的数据锁定在一块起始地址相同的内存,
通过不同的成员名称或别名来索引内存,以多种方式解释同一内存数据。
联合具有特殊的数据强制类型转换的能力。
注意:
union {float f ; long k;} a; 是联合类型直接定义变量,此时联合是无名的。但这种格式不减少访问内层成员的层次 。
19
3,无名联合无名联合在声明时不带联合名,这种声明在 C++语言中具有特殊的含义。
在无名联合中定义的名称超出定界的一对花括号之外,
不能跟同一作用范围其它的变量名冲突,不能有成员函数。
在全局范围定义的无名联合必须声明为静态的,结构中的无名联合其成员访问控制属性是公共的,不允许存在私有的成员。
a,局部的和全局的无名联合局部范围的无名联合实际上定义的是内存共享的变量,
这些不同类型的变量拥有相同的起始内存地址,内存数据的有效性取决于最新的二进制状态和对上下文环境的理解。
20
[例 ] 无名联合直接定义局部共享的多个变量 n,m,x,y。
#include<stdio.h>
void main (void)
{ union { int n; long m; double x; double y; } ;
printf ("[%p,%p]\t",&n,&y);
n=3; printf ("[%d,%d]\t",n,m);
x=6; printf ("[%f,%f]\n",x,y);
}
//输出:
[0065FDF0,0065FDF0] [3,3] [6.000000,6.000000]
21
[例 ] 无名联合 static union{ point_t z; point3d b;};定义静态全局结构变量 b和 z
# include <stdio.h>
typedef struct { double v[3]; } point_t;
typedef struct { double x,y,z; } point3d;
typedef union { point_t z; point3d b;} Ua ;
point3d* CrossProduct ( point3d in1,point3d& a)
{ const point3d in2=a;
a.x = in1.y * in2.z - in1.z * in2.y;
a.y = in1.z * in2.x - in1.x * in2.z;
a.z = in1.x * in2.y - in1.y * in2.x;
return &a;
}
22
static union { point_t z; point3d b; };
extern point_t * q;
const point3d x={1,1,0};
point_t* q;
void main(void)
{ b.x=0; b.y=2; b.z=1;
q= (point_t*)CrossProduct (x,b);
if ( b.x==z.v[0] && b.y==z.v[1] && b.z==z.v[2])
printf ("b={%4.1f,%4.1f,%4.1f}\t",b.x,b.y,b.z);
printf ("q={%4.1f,%4.1f,%4.1f}\t",q->v[0],
q->v[1],q->v[2]);
Ua a={1,2,3};
printf ("a={%4.1f,%4.1f,%4.1f}\n",a.b.x,a.b.y,a.b.z);
}
23
//输出,
b={ 1.0,-1.0,2.0} q={ 1.0,-1.0,2.0} a={ 1.0,2.0,3.0}
上面的无名联合定义静态全局结构变量 b和 z,b和 z 是同一片集合内存的两个别名,相应的结构成员也一对一的彼此相配。
两个结构本身描述的是空间点的坐标,只是成员名称不同 ;将这样的结构变量联合在一起达到内存数据充分共享,
静态的全局变量可以通过外部连接属性的全局指针在不同模块中传递信息。
联合可用于接口设计,两个课题组建立了相同的数据结构例如,point_t和 point3d,只是其中的成员名称不同,可以将其联合在一起。
24
b,结构范围中的无名联合可以在结构的声明中引入无名联合,无名联合包含的数据成员在内存中共享一片内存空间,无名联合中单独的成员直接作为结构成员的相对独立部分。联合变量可以作为结构的成员,这相当于嵌入的对象,对于嵌入对象需要层层访问。无名联合减少访问成员的层次,可以直接访问无名联合中的成员。联合提供一种课题组之间相同数据结构 (即成员个数、类型、次序相同只是名称不同 )的接口技术。设课题组 A和 B的结构类型为:
typedef struct person_a
{ int n; float f; char *s; } A;
typedef struct person_b
{ int number; float income; char * name; } B;
25
A结构和 B结构是一致的。并在相关的结构上相应地开发一套算法。将这两个结构通过无名联合并置在一起,则可以发挥各自的特点。下面的例子说明无名联合的这一用法。
[例 ] 结构范围中的无名联合实现不同课题组之间的数据接口
# include<stdio.h>
# include<string.h>
typedef struct a_t
{ union { int n; int number; };
union { float f; float income; };
union { char *s; char *name; };
} A,B;
26
int SeqFind(const B s[ ],int n,float key)
{ for(int i=0;i<n;i++)
if (s[i].income==key) return i;
return -1;
}
int SeqFind (const A s[ ],int n,const char* key)
{ for (int i=0;i<n;i++)
if (strcmp(s[i].s,key)==0) return i;
return -1;
}
27
void InitData (B b[ ],int n[ ],float f[ ],
char (*s)[20],int num=5)
{ for(int i=0;i<num;i++)
{ b[i].number=n[i];
b[i].income=f[i];
b[i].name =s[i];
}
}
void show (B& r)
{ printf ("{%d,%f,%s}\t",r.number,r.income,r.name); }
void show (A* s,int n)
{ for(A*p=s; p<s+n; p++)
printf ("{%d,%4.1f,%s},",p->n,p->f,p->s);
printf ("\n");
}
28
const int N=5;
static char
ca[N][20]={"Hisen","Boir","Rose","Bush","Kelin"};
void main()
{ static int na[N]={11,22,33,44,55};
float xa[N]={88,90,70,80,60}; A sa [N];
InitData(sa,na,xa,ca,N); show (sa,N);
int k=SeqFind (sa,N,"Rose"); if (k!=-1)
show (sa[k]);
k=SeqFind (sa,N,60);
if (k!=-1)
show (sa[k]);
}
29
//输出结果,
{11,88.0,Hisen},{22,90.0,Boir},
{33,70.0,Rose},{44,80.0,Bush},
{55,60.0,Kelin},{33,70.000000,Rose}
{55,60.000000,Kelin}
联合可实现内存的共用与数据的共用,更可实现不同类型数据互相排斥地占用同一内存或相同内存段互异数据的不共用。
union在一些介绍 C语言的中文书中被译为共用体,这多少有失 union原有的丰富内涵。
术语翻译应切近原意,而其确切含义则需详细解释。
30
十、数据的引用类型转换基本变量之间存在类型转换关系。
例如:
float f; long l=7788; f=(float)l; l=(long)f;
表达式语句 [f=(float)l;]意味着将 long型变量 l的值在相应的内存单元取出,经过类型转换模块,然后将结果送入 f
变量表示的存储单元中,以浮点格式存储。
对于同类型的结构变量或联合变量编译器允许赋值运算。
31
设存在两个结构的声明为:
struct sb {sb结构数据成员列表; } ;
struct sa {sa结构数据成员列表; } ;
定义结构变量 a,a1,b,b1分别如下,
struct sa a,a1; struct sb b,b1; a=a1; b=b1;
赋值表达式语句 a=a1表示结构变量 a1的数据状态赋给对应的结构变量 a,相当于系统进行了函数调用:
memcpy (&a,&a1,sizeof(a));
32
但不同类型的结构变量的赋值如 a=b1 则是不允许的。
因为编译器并未提供不同类型结构变量之间赋值的缺省运算。
同样对于类型转换,a=(struct s)b1;
系统也会提出错误警告。系统对于不同结构变量的数值转换无论是隐含的或显式都没有提供缺省的保证。但 C++允许引用形式的类型转换。
引用形式类型转换的一般形式为:
(目标类型名 &)源变量
(type&)variable
引用类型转换的结果为左值。
33
对于两个结构变量 a,b,一个具体的引用类型转换的语法格式如下:
a = (struct sa&)b; (struct sa&)b = a;
上面两个类型转换赋值表达式可分别理解为:
memcpy (&a,&b,sizeof (struct sa));
memcpy (&b,&a,sizeof (struct sa));
赋值拷贝映射的原则是将源数据内存的状态根据目标集合的长度复制给目标所占用的内存。如果 sizeof(sb)大于
sizeof(sa),那么转换 b = (sb&)a将导致对集合数据 a的越界,反之如 a = (sa&)b则对长的源数据 b前面的 sizeof(sa)个元素进行了复制。引用形式类型转换表示了集合数据间的直接映射,当然也可以对简单变量进行引用的类型转换。
34
[例 ] 结构变量的类型转换与复制
#include <stdio.h>
typedef struct sa { int m; } A;
typedef struct sb { int x; int y; } B;
void show (B& b) { printf ("b={%d,%d}\t",b.x,b.y); }
void main()
{ A a={1}; B b={3,4};
(struct sa&)b=a; show(b);
a.m=8; b=(B &)a; show(b);
}
//程序输出,b={1,4} b={8,6684216}
35
十一、位域或位字段位字段 (bit field)是特殊之至的数据结构,该结构将整型变量占有的内存按位进行细分,细分的位数可以大小不等,具体的 bit位置通过若干成员来索引,成员的数据类型通过 char,int,short,unsigned short,long等整型或梅举类界定。
其中 b1,b2,bn表示成员,count1,count2,countn表示相应成员占有的 bit位的个数。
36
方括号包括的项表示可以省略,格式如下:
struct bit_t
{ int b1,count1;
int [b2],count2;
[int,0;]
...
int bn,countn;
} t;
b2不在时表示无名位域,无名位域成员的位长度用于填充位域的特定位。
因其无名对应的 bit位不可索引。
37
具体地考虑一个 bitDate的结构,
struct bitDate
{ unsigned int year,9;
unsigned int month:4 ;
unsigned int day,5 ;
unsigned int week,3 ;
} d;
38
如上类型声明语句建立了奇怪的位域结构,结构名为
bitDate,同时定义该结构的实例 d。这个结构实例 d占有的内存大小 sizeof (d)=sizeof (bitDate)=4。
一般地 sizeof (bit_t)并不简单是其中位域成员的位数之和除 8,而是同时满足每一单独的成员占有的位数连续地局限于字节单元中。
一般不将一个位域成员从一个字节单元的中间跨字节边界分布。
39
如下代码说明位域成员如何定位于字节的起始边界,
struct c{ char a:8; char b:3; char e:5; char d:7; } e;
printf ("%d,%d\n",sizeof (c),sizeof (e));
//输出 3,3
struct r { char b:3; char a:8; char e:5; char d:7; } s[6];
printf ("%d,%d\n",sizeof (r),sizeof (s));
//输出 4,24
40
内存的寻址以字节为基本单元。
C/C++规定不取位域成员的长度也不取位域成员地址即 sizeof(d.year)和 & d.year是不允许的。
成员名称 year占有 9个 bit位,其合理的取值范围为
0~511。位域成员的性质类似于由其后位长度界定的整数,
保证位域成员不要超出其各自的范围,同其它整数一样可出现在算术表达式中,因此可以将 d.year等当作一个微小的整数看待,作为左值或右值。
如,d.year=52; d.month=11;
d.week=3; d.day=18;
int k=d.day;
41
位域各成员的细节在字节单元上的分布取决于机器的具体实现,依赖于这些情况的程序是不可移植的。
有的编译器位域成员的分布是从左往右,有的机器上则相反。微软对位域成员的排列顺序是从低到高如:
11 week,3 day,5 month:4 year,9
31 23 15 7 0
42