第 7章 结构类型数据描述
7.1 结构体
7.2 共用体
7.3 枚举类型
7.4 用 typedef 定义类型名
7.5 位段结构
第 7章 结构类型数据描述
这种多项组合又有内在联系的的数据称为 结构体
(structure)。它是可以由用户自己定义的。
7.1 结构体
在实际应用中,有时需要将一些有相互联系而类型
不同的数据组合成一个有机的整体,以便于引用。如学
生学籍档案中的学号、姓名、性别、年龄、成绩、地址
等数据,对每个学生来说,除了其各项的值不同外,但
表示形式是一样的。
num name sex age score Addr
10010 Li Fun F 18 87.5 Beijing
1,概述
2,结构体类型变量的定义
两者缺一不可
1) 结构体类型的定义形式
struct 结构体名
{ 分量表 };
其中, 分量表, 中的分量也应进行类型说明,
例如:
struct student
{ int num;
char name[20];
char sex;
int age;
float score;
char addr[30];
};
即,
类型标识符 分量名 ;
分量描述
由用户定义的“结构体类型”,可以同标准类型
一样作为定义变量的类型。相当于 PASCAL语言中的
记录 (record)。
2) 定义结构体类型变量的方法
?先定义结构体类型再定义变量
定义了结构体类型 struct student 后, 可以用它
定义变量 。
注,不能写成 struct st1,st2; 必须同时指定结构体名。
为了方便起见,可以在程序开头定义符号常量进行
简化。如:
如:
struct student st1,st2;
则在程序中可以直接写成,
STUDENT
{int num;
char name[20];
char sex;
int age;
float score;
char addr[30];
};
#define STUDENT struct student
STUDENT st1,st2;
?在定义类型的同时定义变量
如,struct student
{int num;
char name[20];
char sex;
int age;
float score;
char addr[30];
}st1,st2;
struct 结构体名
{
分量表;
} 变量表;
则一般定义形式为:
?直接定义结构类型变量
定义形式为:
struct
{
分量表;
}变量表;
在 struct 后不出现结构体名,因此也不能再以此定
义相同的结构体变量。
3.关于结构体类型的几点说明
?类型与变量是两个不同的概念。一般先定义结构体类
型,再定义变量为该类型。变量可以赋值、存取或运
算,而类型没有这些操作。在编译时,对变量分配空
间,对类型来说不存在分配空间。
?对结构体中的分量可以单独使用。
?分量也可以是一个结构体变量。如 student 中要增加
birthday,则可按如下方式进行定义:
struct date
{ int month;
int day;
int year;
};
struct student
{ ?
struct date birthday;
?
}st1,st2;
先定义一个日期结构
该分量也是一个结构体
分量名可以与程序中的变量名相同,两者之间不会
产生混淆。
4,结构体类型变量的引用
引用结构体变量应遵守如下规则:
1) 结构体变量中分量的引用方式
结构体变量名 ?分量名 [ ?二级分量名 ? ]
其中:,?”为分量运算符,在所有的运算符中 优先级最高 。
2) 结构体变量的分量本身又属于结构体类型时只能对最
低级分量进行操作。 如:
st1.num;
st1.name;
st1.birthday.day;
写成 st1.birthday 并不会访问 st1中的 birthday,只会
引起警告错误。
3) 不能将一个结构体变量直接进行输入输出,只能对结
构体变量的各分量进行输入输出。
如:
scanf(“%d,%s,%c,%d,%f,%s”,&st1); 错误
printf(“%d,%s,%c,%d,%f,%s”,st1); 错误
printf(“%s,%d”,st1.name,st1.birthday.day); 正确
4) 分量和结构体变量的地址均可以被引用
如:
scanf(“%d”,&st1.num); 输入 st1.num的值
printf(“%x”,&st1); 以十六进制输出 st1的首地址
5,结构体变量的初始化
1) 外部存储类的结构体变量初始化
例 7.1
struct student
{long int num;
char name[20];
char sex;
char addr[30];
}a={89031,“Li Lin”,?M?,“123 Beijing Road”};
main( )
{ printf(“%ld,%s,%c,%s\n”,a.num,a.name,a.sex,a.addr);
}
输出结果:
89031,Li Lin,M,123 Beijing Road
定义结构体的同时定义
变量 a 并进行初始化。
2) 静态存储类的结构体变量初始化
main( )
{struct student
{ long int num;
char name[20];
char sex;
char addr[30];
}a={89031,“Li Lin”,?M?,“123 Beijing Road”};
printf(“%ld,%s,%c,%s\n”,a.num,a.name,a.sex,a.addr);
}
可以将定义部分放在 main函数中
6,结构体数组
结构体数组与普通数组的不同之处在于每个数组元
素都是一个结构体类型的数据,且这些数据又分别包括
各个分量。结构体数组的定义、初始化等操作和内存中
的存放方式与普通数组相类似。
7.2 共用体
1,共用体的概念
二个以上不同类型的变量采用, 覆盖技术, 占用同
一段内存单元的结构称为共用体 。
共用体类型变量的定义形式如下:
union 共用体名
{
分量表 ;
}变量表;
例如,union data
{ int i;
char ch;
float f;
}a,b,c;
union data
{ int i;
char ch;
float f;
}
union data a,b,c;
union
{ int i;
char ch;
float f;
}a,b,c;
说明,虽然“共用体”与“结构体”的定义形式相似,但是:
? 一个结构体变量所需的存储容量为每个分量所需存储容
量之和,而 一个共用体变量所需的存储容量为各个分量
中占用存储容量最多的分量所需的存储容量 。
? 一个结构体变量的各个分量在任何时刻都同时存在,且
可同时引用。而一个共用体变量的各个分量在同一时刻
只存在其中一个,也只能引用其中的一个分量。即起作
用的只是最后一次存放的分量,在存入一个新的分量后,
原有分量的值被覆盖而失去作用。
? 一个结构体变量的各个分量的地址各不相同,分别拥有
各自的存储空间。而一个共用体变量的各个分量的地址
相同,共同拥有同一存储空间。
? 共用体变量可作为参数传递给函数,也可以作函数的返
回值。同样,可以使用地址传送方式将共用体变量的地
址作为参数或返回值在函数间传递。
?共用体类型可以出现在结构体类型定义中,也可以定
义共用体类型数组,数组也可以作为共用体的分量。
同样,结构体类型也可以出现在共用体类型定义中。
?不能在定义共用体变量时对其初始化,也不能对共用
体变量名赋值,更不能企图引用共用体变量名去得到
分量的值。
2,共用体变量的引用
不能引用共用体变量,只能采用分量运算符, ?” 引用
共用体变量的分量。与引用结构体变量的方法是一致的。
通常,在定义嵌套有共用体变量的结构体变量时,
在其中附加一个类型标志,以方便对共用体分量的操作。
如:
struct
{ ?
union
{ int i;
char ch;
float f;
double d;
}data;
int type;
}a;
?
switch(a.type)
{case 0,/* int */
printf(“%d\n”,a.data.i); break;
case 1,/* char */
printf(“%d\n”,a.data.ch); break;
case 2,/* float */
printf(“%d\n”,a.data.f ); break;
case 3,/* double */
printf(“%d\n”,a.data.d); break;
7.3 枚举类型
所谓“枚举”是指变量的取值只限于所列举出来的值
的范围内。
枚举类型的定义以 enum开头。 如:
enum weekday{sun,mon,tue,wed,thu,fri,sat};
enum weekday workday,week_end;
enum weekday{sun,mon,tue,wed,thu,fri,sat}workday;
说明:
? { }中的枚举元素是常量而不是变量,也不代表什么实
际的含义。
?枚举型变量 workday,week_end 的取值只限于 { }中
列举的元素范围内。
? { }中枚举元素的值按其排列顺序为 0,1,2,?,可
用于输出。
?枚举值可按其定义时的顺序号用作判断比较。
?不得直接将一个整数赋给一个枚举变量 。 如:
Workday = 2 ;
是不对的, 因为它们不属于同一数据类型 。
但可以进行强制类型转换赋值 。 如:
workday=(enum weekday)2;
甚至可以是表达式, 如:
workday=(enum weekday)(5-3);
?可用如下定义改变枚举元素中的序号值:
enum weekday {sun,mon,tue,wed,thu=7,fri,sat};
则枚举元素的序号值依次为:
0,1,2,3,7,8,9。
例 7.2 口袋中有若干个红、黄、蓝、白、黑五种颜色
的球,试编程求出每次从口袋中取出三个不同
颜色的球的可能取法,并输出每种组合的三种
颜色。
60
2
1 2 0
)!35(
!5
)!kn(
!n
B l l
)1kn()2n)(1n(nA
k
n
??
?
?
?
?
????? ?
main( )
{enum color {red,yellow,blue,white,black};
enum color i,j,k,pri;
int n,loop;
n = 0;
for (i = red; i<=black; i++)
for (j = red; j<=black; j++)
if (i != j)
{ for (k = red; k<=black; k++)
if ( ( k != i ) && ( k != j ) )
{ n = n+1; printf(“%?4d”,n);
for ( loop=1; loop<=3; loop++)
{switch(loop)
{ case 1,pri = i; break;
case 2,pri = j; break;
case 3,pri = k; break;
default,break;
}
switch( pri )
{ case red, printf(“%?10s”,“red”); break;
case yellow, printf(“%?10s”,“yellow”); break;
case blue, printf(“%?10s”,“blue”); break;
case white, printf(“%?10s”,“white”); break;
case black, printf(“%?10s”,“black”); break;
default,break ;
}
}
printf(,\n” );
}
}
printf(,\ntotal, %5d\n”,n );
}
使用关键字 typedef说明一个新的类型名,往往可以
在程序中简化变量的类型定义。
例如:
typedef struct student
{ int num;
?
}REC;
REC x,y,*p ;
语句:
p=(struct student *)malloc(sizeof(struct student));
可以写成,p=(REC *) malloc(sizeof(REC) )
7.4 用 typedef 定义类型名
相当于 struct student x,y,*p;
说明:
?用 typedef 不是也不能建立新的数据类型, 也不能
用来定义变量, 只是以一个新的类型名 (通常用大写
字母表示 )代替已存在的类型名, 以此简化程序中变
量的类型定义 。
?使用 typedef 有利于程序的通用性和可移植性 。
例如:程序中有,int a,b,c;
要修改为,long a,b,c;
则可用 typedef 定义:
typedef int INTEGER;
在程序中用 INTEGER定义变量, 当修改程序时再用
typedef 定义即可:
typedef long INTEGER;
课外练习:习题册中习题九全部
7.5 位段结构
1,位运算
C 既具有高级语言的特点,又具有低级语言的功能,
位运算能力就是其特色之一。所谓位运算就是指进行二
进制位的运算。
C提供的位运算符有:
运算符 含义
& 按位与
| 按位或
? 按位异或
~ 取反
<< 左移
>> 右移
说明:
?位运算符中除, ~”外, 其余均为二目运算符, 即要求
两侧各有一个运算量 。
?运算量只能是整型或字符型的数据,不能为实型数据 。
1),按位与” 运算符 &
参加运算的两个运算量之 对应位都为 1,则该位的结
果为 1,否则为 0。
例,3&5=1
3的补码,00000011
5的补码,00000101
& 00000001
即:
0&0=0 0&1=0 1&0=0 1&1=1
& 运算符的用途:
?清零
如果想将一个单元清零 (全部二进位为 ?),则只要找
一个数的补码的对应位 0与被清零数的对应位 1刚好对应,
然后使两者进行 & 运算 。
如,00101011
& 10010100
00000000
?取一个数中的某些指定位
如,a,0010110010101100
b,0000000011111111 (377)8
& 0000000010101100 得到 a 的低 8 位
?保留一个数的某一位
如,01010100 (84)10
& 00111011 (59)10
00010000 (16)10
2),按位或”运算符 |
参加运算的两个运算量之对应位 只要有一位为 1,则
该位的结果为 1。 即:
0 |0 = 0 0 |1 = 1 1 |0 = 1 1 |1 = 1
如,00110000 (060)8
| 00001111 (017)8
00111111 (077)8
即一个数与 017进行按位或运算,就可将该数的低 4
位全置为 1; 与 0377进行按位或运算,就可将该数的低 8
位全置为 1。
3),异或”运算符
^ 参加运算的两个运算量的 对应位相同, 则该位的结
果为 0。 否则为 1。 即:
0^0 = 0 0^1 = 1 1^0 = 1 1^1 = 0
如,00111001 (57)10, (071)8
^ 00101010 (42)10, (052)8
00010011 (19)10, (023)8
^ 运算符的用途,
使指定的位翻转
如,01111010
^ 00001111 对应原数的低 4位均置为 1
01110101 原数的低 4位被翻转
若 a=3,b=4。
有 a=a^b; b=b^a; a=a^b;
则 a,b 的值多少?
4),取反” 运算符 ~
~ 运算是对一个二进制数 按位取反,即将 0变为 1,
1变为 0。
8位全 1用 377;
16位全 1用 177777;
32位全 1用 37777777777。
5) 左移运算符 <<
<< 用来将一个数的各二进制位 全部左移若干位,并在
右边补若干个 0。高位左移后溢出,舍弃 不起作用。
<< 运算的最大用途是做乘法运算 。 将乘以 2n的幂
运算处理为左移 n 位 。
a的值 a的补码 a<<1 a<<2
64 01000000 0 10000000 01 00000000
127 01111111 0 11111110 01 11111100
如,
6) 右移运算符 >>
>>用来将一个数的各二进制位全部右移若干位,移
到右边的低位被舍弃,对无符号数,高位补 0。
如:
a 为 00001111,则 a>>2为 00000011 11
右移一位相当于除以 2,右移 n 位相当于除以 2n。
(此 2位被舍弃 )
注意,在右移时,需要注意符号位问题。若为无符号数,
右移时左边高位移入 0。若为有符号数,如果原来符号位
为 0 (正数 ),则左边移入 0 ;如果原来符号位为 1,左边
移入 0的称为,逻辑右移,,移入 1的称为,算术右移,。大
多数 C语言均采用算术移位。
a,1001011111101101 (113755)8
a>>1,0100101111110110 逻辑右移,得 (045766)8
a>>1,1100101111110110 算术右移,得 (145766)8
如:
2,位运算举例
例 5.23 取一个整数 a 从右端开始的 4 ~ 7 位。
main( )
{unsigned a,b,c,d ;
scanf(“%o”,&a);
b = a>>4;
c = ~(~0<<4);
d = b&c;
printf(“%o\n%o\n”,a,d );
}
b=a>>(m-n+1)
c=~(~0<<n)
运行情况:
331 ?
331
15
例 5.24 循环移位。将 a 右循环移 4 位。
main( )
{unsigned a,b,c; int n;
scanf(“a=%o,n=%d”,&a,&n);
b = a<<(16–n);
c = a>>n;
c = c|b;
printf(“%o\n%o\n”,a,c);
}
运行情况:
a=157653,n=3 ?
a,1101111110101011
1151171160151031
b,0110000000000000
c,0001101111110101|
0111101111110101
7115117016105
157653
75765
此题为无符号数逻辑右移,若为有符号数则最高位移入 1
3,位段
有时存储一个信息不必用一个或多个字节,可以在
一个字节中存放几个信息。例如,“真”或“假”用 1或 0表
示,只需要 1 位就够了。
1) 在一个字节中存放几个数据
data,a b c d
151413 8 7 4 3 0
图中,a,b,c,d分别占 2位,6位,4位,4位。假定 c
原来的值为 0,现要变为 12,则:
?将数 12 左移 4 位,使 1100 成为右边起第 4~7 位。
?将 data 与 12<<4 按位或即可 (设 data 中第 4~7 位
原来为 0 )。
若 c 的原值不为 0,则应先使 c 为 0。方法如下:
data = data & 0177417 0177417 称为,屏蔽字,
当然也可以用,data=data & ~(15<<4) 来实现, 而
不必计算屏蔽码 。 从而可以得到,
data=data & ~(15<<4)|(n&15)<<4 n 为赋给 c 的值
2) 位段
位段是以位为单位定义长度的结构体类型中的成员。
struct packed_data
{unsigned a,2;
unsigned b,6;
unsigned c,4;
unsigned d,4;
int i;
}data;
a,b,c,d 共占 2 个字节
i 占 2个字节
几点说明:
?位段也是一种结构体分量, 其引用方法与结构体相同 。
如,
data.a = 2;
data.b = 3;
必须注意位段分量允许的最大取值范围 。
?长度为 ?的位段可以使其后的位段从下一个存储单元
开始存放 。 如,
unsigned a,1;
unsigned b,2;
unsigned, 0;
unsigned c,3;
在同一存储单元
在另一存储单元
允许不允许
?一个位段不得跨 2 个存储单元。如,
unsigned a, 6;
unsigned b, 6;
unsigned c, 5;
?
unsigned a, 6;
unsigned b, 6;
unsigned, 0;
unsigned c, 5;
?一个位段的长度不得大于一个存储单元的长度,也不能定
义位段数组。
?位段可以用整型格式符输出 。 如:
printf(“%d,%d,%d”,data.a,data.b,data.c);
也可以用 %u,%o,%x 等格式符输出 。
?位段可以在数值表达式中引用, 它会被系统自动地转换成
整型数 。 如,data.a + data.b 是合法的 。
?可以定义无名位段 。 如,
unsigned a, 1;
unsigned, 2;
unsigned b, 3;
无名位段