420 likes | 627 Views
第 12 章 结 构 与 联 合. 九、联合 union 十、数据的引用类型转换 十一、位域或位字段. 九、联合 union 1. 联合的特性和定义 由关键字 struct 声明的数据描述称为结构类型而 union 或 class 声明的数据描述称为联合类型或类类型 , 分别简称为 结构和联合或类。 结构、联合、类是集合数据类型。 int 、 float 等是系统预先取好的可以直接使用的类名, 关键字 struct 和 union 或 class 后紧跟的标识符则是用户引入 的类名,需要事先加以声明。.
E N D
第12章 结 构 与 联 合 • 九、联合union • 十、数据的引用类型转换 • 十一、位域或位字段
九、联合union • 1.联合的特性和定义 • 由关键字struct声明的数据描述称为结构类型而union • 或class声明的数据描述称为联合类型或类类型,分别简称为 • 结构和联合或类。 • 结构、联合、类是集合数据类型。 • int、float等是系统预先取好的可以直接使用的类名, • 关键字struct和union 或class后紧跟的标识符则是用户引入 • 的类名,需要事先加以声明。
在程序设计中常出现非此即彼的数据,这些数据的类型在程序设计中常出现非此即彼的数据,这些数据的类型 • 可以不同,需要将其放置在同一片内存。 • 关键字union 建立的数据类型为用户引入的联合类型, • 简称联合。联合一方面是节省内存空间,另一方面是特殊的 • 编程考虑。 • 联合的特性和定义: • 联合类型定义的变量称为联合变量。指向联合变量的指 • 针简称为联合指针。联合类型的声明和联合变量的定义采用 • 与结构类似的语法,不同的是联合使用关键字union。
将前面关于结构声明复制过来略加变动并用union替代将前面关于结构声明复制过来略加变动并用union替代 • struct得到: • union UnionName 联合 联合名 • { type member; { 数据类型 成员名 ; • type2 member2; 数据类型2 成员名2; • typen membern; 数据类型n 成员名n; • }; }; • 关键字union用于声明同一片内存可以存放不同类型的 • 数据,但在一个时刻只有某一类型的数据处于有效的状态。
联合类型声明中的数据成员具有如下的性质: • a.不同的成员占有起始地址相同的内存区域,这些成 • 员以并排的方式重合在一起。 • b.该内存区域的长度确定为所有成员中占有内存空间 • 最长的成员所对应的长度。 • c.在一个具体的时刻仅包含一种数据成员有效虽然成 • 员可以是集合型的数据。 • d.初始化联合变量时默认对第一个成员进行赋值亦仅 • 对一个成员赋值,初始值的类型属性应与第一个成员的类型 • 属性一致,否则编译器至少给出警告。 • e. C++中联合不参入继承机制即联合类既不作基类也不 • 作派生类,不能有虚拟函数。
[例]联合的内存和结构的内存大小比较 • #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
联合一经声明就可以定义联合变量、联合数组和联合指联合一经声明就可以定义联合变量、联合数组和联合指 • 针。 箭头运算符“->”与圆点运算符“.”可以用来访问联合中 • 的成员,箭头运算符"->"的左侧是联合指针,圆点操作符".“ • 的左侧是联合变量,右侧是联合中的成员。 • 这和结构变量的使用规则一致。 • 不同的是结构变量拥有结构中各独立的成员所占内存之 • 和; • 联合变量则仅是最大成员所拥有的内存,这一片内存可 • 由若干类型属性不同的成员适当索引,它们齐头地占有同一 • 块内存。
联合遵循先声明后定义再使用的次序。如下所示:联合遵循先声明后定义再使用的次序。如下所示: • 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.这三个成员的内存起始地址是一样的。
[例]联合指针入口形参和联合引用返回 • #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++)
{ 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] • 联合变量、联合指针和联合引用可以作为形参,联合变 • 量可以相互赋值。 • 对联合变量的操作需要特别注意成员的类型属性,不同 • 类型属性的成员应由不同的分支处理。
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
k[0] k[1] K[2] K[3] • 该联合在32位编程模式下的内存布局如下: • int型数组k[4] • long型成员z 8 12 16 • float型成员y 4 联合的数据内存分布 • 在PC微机上数据的存放方式是低尾端形式的, 即short • 型16位字节的低8位存放在内存的低地址处, 高8bit存放在内 • 存的高地址处高尾端的存放方式则相反。 • 下页的例子说明数据在PC内存中的顺序是低尾端形式。
[例]强制类型转换显示低尾端的存储格式(8位二进制数的低[例]强制类型转换显示低尾端的存储格式(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++); • }
21 43 65 87 00100001 01000011 01100101 10000111 • 上例低尾端的PC计算上输出结果:21 43 65 87 • 这样32位整数的8个 4位二进制数的十六进制数数码表示 • 为: • m= h7h6h5h4h3h2h1h0 =87654321 • 该数以字节即8位bit为最小内存寻址单位的内存存储格 • 式为(低地址标注在左边): • 16进制表示: • 低地址 高地址 • 2进制表示: • 低地址 高地址
联合的同一片内存可以通过不同的名称索引。对一个成联合的同一片内存可以通过不同的名称索引。对一个成 • 员的改变直接影响联合中的其它成员的数据状态,对数据的 • 解释取决于数据的存储格式和模块转换。 • [例]联合的内存布局和内存数据的解释 • #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]); • }
d3 00000000 c[3] c[3] c[2] d2 00000000 c[2] c[1]0x61'a' d1 00000001 0x61 c[0]0x62'b' d0 000000011 0x62 0x67 0x68 c[1]=100000001 c[0]=300000011 • 说明:占4字节的整型数 (4个8位的十六进制数)存贮格 • 式在微机上是低尾端格式,具有如下的形式(低地址标注在 • 边,8位二进制数的低位在右边): • 高地址 低地址 • k= 0x0000|0103 1*16*16+3=259 • x.k= 0x67686162UL; • 高地址 低地址
[例]浮点数和整型数内存存储格式不同 • #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]
联合将不同类型的数据锁定在一块起始地址相同的内存, • 通过不同的成员名称或别名来索引内存,以多种方式解释同 • 一内存数据。 • 联合具有特殊的数据强制类型转换的能力。 • 注意: • union {float f ; long k;} a; 是联合类型直接定 • 义变量,此时联合是无名的。但这种格式不减少访问内层成 • 员的层次 。
3.无名联合 • 无名联合在声明时不带联合名,这种声明在C++语言 • 中具有特殊的含义。 • 在无名联合中定义的名称超出定界的一对花括号之外, • 不能跟同一作用范围其它的变量名冲突,不能有成员函数。 • 在全局范围定义的无名联合必须声明为静态的,结构中的无 • 名联合其成员访问控制属性是公共的,不允许存在私有的成 • 员。 • a.局部的和全局的无名联合 • 局部范围的无名联合实际上定义的是内存共享的变量, • 这些不同类型的变量拥有相同的起始内存地址,内存数据的 • 有效性取决于最新的二进制状态和对上下文环境的理解。
[例]无名联合直接定义局部共享的多个变量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]
[例]无名联合 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; • }
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); • }
//输出: • 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,只是其中的成员名称不同,可 • 以将其联合在一起。
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;
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;
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; • }
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"); • }
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]); • }
//输出结果: • {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原有的丰富内涵。 • 术语翻译应切近原意,而其确切含义则需详细解释。
十、数据的引用类型转换 • 基本变量之间存在类型转换关系。 • 例如: • float f; long l=7788; f=(float)l; l=(long)f; • 表达式语句[f=(float)l;]意味着将long型变量l的值在相 • 应的内存单元取出,经过类型转换模块,然后将结果送入f • 变量表示的存储单元中,以浮点格式存储。 • 对于同类型的结构变量或联合变量编译器允许赋值运 • 算。
设存在两个结构的声明为: • 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));
但不同类型的结构变量的赋值如 a=b1 则是不允许的。 • 因为编译器并未提供不同类型结构变量之间赋值的缺省 • 运算。 • 同样对于类型转换: a=(struct s)b1; • 系统也会提出错误警告。系统对于不同结构变量的数值 • 转换无论是隐含的或显式都没有提供缺省的保证。但C++允 • 许引用形式的类型转换。 • 引用形式类型转换的一般形式为: • (目标类型名&)源变量 • (type&)variable • 引用类型转换的结果为左值。
对于两个结构变量a,b,一个具体的引用类型转换的语对于两个结构变量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)个 • 元素进行了复制。引用形式类型转换表示了集合数据间的直 • 接映射,当然也可以对简单变量进行引用的类型转换。
[例]结构变量的类型转换与复制 • #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}
十一、位域或位字段 • 位字段(bit field)是特殊之至的数据结构,该结构将整 • 型变量占有的内存按位进行细分,细分的位数可以大小不 • 等,具体的bit位置通过若干成员来索引,成员的数据类型通 • 过char , int,short ,unsigned short,long等整型或梅举类界 • 定。 • 其中b1,b2,bn表示成员,count1, count2, countn表示 • 相应成员占有的bit位的个数。
方括号包括的项表示可以省略,格式如下: • struct bit_t • { int b1 : count1; • int [b2] : count2; • [int : 0;] • ... • int bn : countn; • } t; • b2不在时表示无名位域,无名位域成员的位长度用于 • 填充位域的特定位。 • 因其无名对应的bit位不可索引。
具体地考虑一个bitDate的结构: • struct bitDate • { unsigned int year: 9; • unsigned int month:4 ; • unsigned int day: 5 ; • unsigned int week: 3 ; • } d;
如上类型声明语句建立了奇怪的位域结构,结构名为如上类型声明语句建立了奇怪的位域结构,结构名为 • bitDate,同时定义该结构的实例d。这个结构实例d占有的 • 内存大小sizeof (d)=sizeof (bitDate)=4。 • 一般地sizeof (bit_t)并不简单是其中位域成员的位数之 • 和除8,而是同时满足每一单独的成员占有的位数连续地局 • 限于字节单元中。 • 一般不将一个位域成员从一个字节单元的中间跨字节边 • 界分布。
如下代码说明位域成员如何定位于字节的起始边界:如下代码说明位域成员如何定位于字节的起始边界: • 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
内存的寻址以字节为基本单元。 • 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;
11 week: 3 day: 5 month:4 year: 9 • 31 23 15 7 0 • 位域各成员的细节在字节单元上的分布取决于机器的具 • 体实现,依赖于这些情况的程序是不可移植的。 • 有的编译器位域成员的分布是从左往右,有的机器上则 • 相反。微软对位域成员的排列顺序是从低到高如: