第九章 系 统 安 全 性 9.1 结构体 9.2 结构体型数组 9.3 结构体型指针 9.4 内存的动态分配 9.5 共用体 第九章 系 统 安 全 性 9.1 结构体 9.2 结构体型数组 9.3 结构体型指针 9.4 内存的动态分配 9.5 共用体 9.6 位段 9.7 用typedef定义类型
9.1 结 构 体 9.1.1 结构体类型 数组将若干具有共同类型特征的数据组合在了一起。然而,在实际处理中,待处理的信息往往是由多种类型组成的,如有关学生的数据,不仅有学习成绩, 还应包括诸如学号(长整型)、姓名(字符串类型)、性别(字符型)、出生日期(字符串型)等。 再如编写工人管理程序时,所处理对象——工人的信息类似于学生,只是将学习成绩换成工资。就目前所学知识,我们只能将各个项定义成互相独立的简单变量或数组,无法反映它们之间的内在联系。应该有一种新的类型, 就像数组将多个同类型数据组合在一起一样, 能将这些具有内在联系的不同类型的数据组合在一起,C语言提供了“结构体”类型来完成这一任务。
9.1.2 结构体类型的定义 结构体类型的定义形式如下: struct 结构体类型名 {成员列表}; 例如: struct student {long int num; char name[20]; char sex; int age; };
说明: (1) 关键字struct和结构体类型名student组合成一种类型标识符, 其地位如同通常的int ,char 等,其用途是用来定义该结构体型变量,定义了变量之后,该变量就可以像其它变量一样的使用了,类型名便不应再在程序中出现(求长度运算除外,一般程序只对变量操作)。 类型名的起名规则遵从标识符。
(2) 成员列表为本结构体类型所包含的若干个成员的列表,必需用{ }括起来,并以分号结束。每个成员的形式为 类型标识符 成员名; 如例中的 long int num; char name[20]; 等 成员(如num)又可称为成员变量,也是一种标识符, 成员的类型可以是除该结构体类型自身外,C语言允许的任何数据类型,结构体类型struct student中学号num是长整型 姓名name是字符数组、性别sex是字符型等等。成员之一还可以是其它结构体类型,此时称为结构体类型嵌套,如用生日代替上例中的年龄。可以定义结构体类型如下:
struct date { int year; int month; int day; }; struct student1 { long int num; char name[20]; char sex; struct date birthday; struct studentl *ps; };
9.1.3 结构体型变量的定义 形式一, 类型、 变量分别定义: struct staff 9.1.3 结构体型变量的定义 形式一, 类型、 变量分别定义: struct staff { char name[20]; /* 姓名 */ char department[20]; /* 部门 */ int salary; /* 工资 */ int cost; /* 扣款 */ int realsum; /* 实发工资 */ }; struct staff worker1, worker2;
形式二, 类型、 变量一起定义: struct staff { char name[20]; char department[20]; int salary; int cost; int realsum; } worker1, worker2;
形式三是形式二的简化, 省略类型名: struct { char name[20]; char department[20]; int salary; int cost; int realsum; } worker1, worker2;
图 9.1 结构体型变量在内存中的存贮形式
9.1.4 结构体型变量及其成员的引用 (1) 变量成员的引用方法(成员运算符 “.”): 如前例结构体类型struct staff下定义的两个变量worker1, worker2, 二变量中的每个成员均可引用,且所引用的成员变量与其所属类型的普通变量一样可进行该类型所允许的任何运算。例如: worker1.realsum=worker1.salary-worker1.cost ; worker2.salary=worker1.salary; scanf(″%s″, worker1.name); scanf(″%d″, &worker1.cost); 又如: struct student stu1, stu2; 之后, 变量stu1, stu2成员的引用可以是: stu2.num=stu1.num + 1 ; stu1.age ++ ; scanf(″%ld″, &stu1.num);
在C语言的运算符中,取成员运算符“.”优先级最高, 故以上语句均为对引用之后的成员变量进行操作。 若结构体定义是嵌套的,则只能引用最低级的成员(用若干“.”运算符,逐级引用到最低级)。 例如:在上一节定义了类型student1之后,若有 struct student1 stu3; 结构体型变量stu3的成员引用为: stu3.birthday.year stu3.birthday.month 不可对stu3.birthday进行操作, 因其不是最低级。
(2) 结构体型变量可以整体引用来赋值:如worker2=worker1;即将变量worker1的所有成员的值一一赋给变量worker2的各成员。但不可进行整体的输入输出, 如: printf (″%s″, worker1); 是错误的。结构体型变量只能对逐个成员进行输入或输出。
(3) 结构体型变量占据的一片存贮单元的首地址称为该结构体型变量的地址,其每个成员占据的若干单元的首地址称为该成员的地址, 两个地址均可引用。 如: scanf(″%d″, &worker1.salary); 输入worker1.salary—— 一个成员的值。 printf(″0x%x″, &worker1); 输出worker1—— 一个结构体型变量的首地址。
9.1.5 结构体型变量的初始化 例如:已定义结构体类型如前, 则: stu1的初始化:struct student stu1={99001, ″Wang-Wei″, ′f′, 21}; stu3的初始化:struct student1 stu3={99010, ″Liu-Ping″, ′m′, {1987, 10, 2}}; 所有结构体型变量,不管是全局变量还是局部变量,自动变量还是静态变量均可如此初始化。
例 9.1 利用结构体类型,编程计算一名同学5门课的平均分。 例 9.1 利用结构体类型,编程计算一名同学5门课的平均分。 main() { struct stuscore {char name[20]; float score[5]; float average; }; struct stuscore x={"Wang-Wei", 90.5, 85, 70, 90, 98.5}; int i; float sum=0; for(i=0; i<5; i++) sum+=x.score[i]; x.average=sum/5; printf("The average score of %s is %4.1f\n", x.name, x.average); }
9.1.6 应用举例 例 9.2 将上例改为键入任意多人的5门课的成绩,打印平均分。 #include <con.0.h> 9.1.6 应用举例 例 9.2 将上例改为键入任意多人的5门课的成绩,打印平均分。 #include <con.0.h> main() { struct stuscore {char name[20]; float score[5]; float average; } x; int i; float sum; char rep; while(1)
{ printf("\nDo you want to continue?(Y/N)"); rep=getche(); if(rep==′N′ || rep==′n′) break; sum=0; printf("\nInput name(as Xu-jun) and 5 scores(all depart by ): \n"); scanf("%s", x.name); for(i=0; i<5; i++) scanf("%f", &x.score[i]); sum+=x.score[i]; x.average=sum/5; printf("The average score of %s is %4.1f\n", x.name, x.average); } }
运行情况为 Do you want to continue?(Y/N)y Input name(as Xu-jun) and 5 scores(all depart by): (提示) Guo-Yong 80 89.5 99 87.5 66 (输入) The average score of Guo-Yong is 84.4 (输出) Input name(as Xu-jun) and 5 scores(all depart by): (提示) Liu-Ying 87 88 89 99 98 (输入) The average score of Liu-Ying is 92.2 (输出) Do you want to continue?(Y/N)N
9.2 结构体型数组 9.2.1 结构体型数组的定义 相似于整型数组 int a[3]。 结构体型数组的定义不同之处是要先定义好结构体类型。例如:struct student 定义如前, 然后struct student stu[3]; 就定义了一个struct student结构体型数组stu,数组元素stu[0],stu[1],stu[2]分别是三个struct student结构体型变量。又如:struct staff worker[100]; 定义了另一个结构体类型struct staff的数组worker 数组元素worker[0],worker[1],…,worker[99]分别是100个 struct staff结构体型变量。
9.2.2 结构体型数组的初始化 结构体型数组和普通数组一样可以初始化,只是每个元素的初值为由{}括起来的一组数据,初始化形式是定义数组时, 后面加上={初值列表},如: struct student stu[2] = { {99010,″Wang-Yan″,′f′,20}, {99011,″Li-Li″,′m′,19}};
例 9.3 用结构体型数组初始化建立一工资登记表。然后键入其中一人的姓名,查询其工资情况。 #include <string.h> main() { struct staff { char name[20]; char department[20]; int salary; int cost; } worker[3]={{"Xu-Guo", "part1", 800, 200}, {"Wu-Xia", "part2", 1000, 300}, {"Li-Jun", "part3", 1200, 350} };
int i; char xname[20]; printf("\nInput the worker\′s name: "); scanf("%s", xname); for(i=0; i<3; i++) if(strcmp(xname, worker[i].name)==0) { printf("**** %s ****", xname); printf("\n salary: %6d", worker[i].salary); printf("\n cost : %6d", worker[i].cost); printf("\n payed : %6d\n", worker[i].salary-worker[i].cost); } }
程序运行: Input the worker′s name: Wu-Xia (提示 输入) * * * * Wu-Xia **** (输出) salary: 1000 cost: 300 payed: 700
9.3 结构体型指针 9.3.1 指向结构体型变量的指针 指向结构体型变量的指针的定义格式是:在定义结构体型变量的同时,定义一指针。 如 struct student stu1,*p;此时编译程序只知p为一结构体类型 struct student的指针,它将存放该类变量的首地址,但并无具体值。
在程序中执行了语句 p = &stu1之后,p才真正指向了结构体型变量stu1,如图所示。 long int num char name[20] char sex int age p→
注意以下表达式的含义(指向运算符->的优先级高于++): p->num: 得到p指向的结构体型变量中的成员num的值, 假设其值为990120。 p->num++(即(p->num)++): 得到p指向的结构体型变量中的成员num的值, 用完该值后使它加1。 ++p->num(即++(p->num)): 得到p指向的结构体型变量中的成员num的值, 使之先加1, 再使用。 从以下用三条连续的输出语句的执行结果可清楚地看到其含义: printf(″%d\n″, p->num); 输出 990120 printf(″%d\n″, p->num++); 输出 990120 printf(″%d\n″, ++p->num); 输出 990122其中的输出即为使用。
例 9.4 显示某人工资信息的程序如下: #include <string.h> main() 例 9.4 显示某人工资信息的程序如下: #include <string.h> main() { struct staff { char name[20]; char department[20]; int salary; }; struct staff w1, *p; p=&w1; strcpy(w1.name, "Li-Li"); /* 某人的信息*/ strcpy((*p).department, "part1"); p->salary=1000;
printf("%s %s %d\n", w1.name, w1.department, w1.salary); /*送显 */ printf("%s %s %d\n", (*p).name, (*p).department, (*p).salary); printf("%s %s %d\n", p->name, p->department, p->salary); } 显示结果: Li-Li part1 1000 Li-Li part1 1000
9.3.2 指向结构体型数组的指针 例 9.5 显示工资表。 struct staff { char name[20]; 例 9.5 显示工资表。 struct staff { char name[20]; int salary; }; main() { struct staff *p; struct staff worker[3] ={{"Wang-Li", 600}, {"Li-Ping", 700}, {"Liu-Yuan", 800}}; for(p=worker; p<worker+3; p++) printf("%s \′s salary is %d yuan\n", p->name, p->salary); }
程序的运行结果为 Wang-Li′s salary is 600 yuan Li-Ping′s salary is 700 yuan Liu-Yuan′s salary is 800 yuan
图 9.2 指针与结构体型数组
对于指向结构体型数组的指针, 请注意以下表达式的含义(在p=worker后): p->salary 、 p->salary++、 ++p->salary与前页的p->num的三种形式含义相同,均为p所指向的成员变量的值, 即对worker[0].salary的值(600)在使用前还是后加1。 对于指向数组的指针,用的更多的是指针值本身的加1移动,所以要特别注意: (++p)->salary:先使p自加1,指向下一元素,得到worker[1].salary的值,即700。 p++->salary:先得到worker[0].salary的值,即600, 然后使p加1,指为worker[1]。 (p++)->salary: 完全同上。 括号不改变++操作符在后的操作性质。
如在例9.5的worker数组初始化,且p=worker之后,连续执行以下四条语句,输出结果为 printf(″%d\n″, p->salary); 输出 600 printf(″%d\n″, (++p)->salary); 输出 700 printf(″%d\n″, p++->salary); 输出 700 printf(″%d\n″, (p++)->salary); 输出 800
9.3.3 用结构体型指针作函数的参数 例 9.6 用子函数求出worker数组中每个工人的实发工资。 #define NUM 3 struct staff { char name[20]; char department[20]; int salary; int cost; int realsum; }; main() { void getreal(struct staff *p, int n); struct staff worker[NUM], *pm; int i; printf("Input %d worker\′s name department salary cost: \n",NUM);
for(i=0, pm=worker; i<NUM; i++, pm++) scanf("%s%s%d%d", pm->name, pm->department, &pm->salary, & pm->cost); pm=worker; /* 注1 */ getreal(pm, NUM); /* 注2 */ for(pm=worker; pm<worker+NUM; pm++) printf("%s of %s should be payed %d yuan\n", /* 注3 */ pm->name, pm->department, pm->realsum); } void getreal( struct staff *p, int n) { int i; for(i=0; i<n; i++, p++) p->realsum=p->salary - p->cost; }
运行情况: Input 3 worker′s name department salary cost: (提示) Wang part1 1000 200 (输入) Liu part2 1500 300 Zhang part3 2000 600 Wang of part1 should be payed 800 yuan (输出) Liu of part2 should be payed 1200 yuan Zhang of part3 should be payed 1400 yuan
9.4 内存的动态分配 9.4.1 动态分配内存的意义 9.4.2 开辟和释放内存区的函数 1. malloc()函数 它的函数原型为 void *malloc(unsigned size); 其功能是在内存的动态存贮区中分配长度为size个字节的连续空间。 其返回值= 分配空间的起始地址 (分配成功) 空指针NULL (分配失败, 一般是没有空间)
例 9.7 分配一个能放置双精度数的空间。 2. free(p):释放由p指向的内存区,使这部分内存可以分配给其它变量 例 9.7 分配一个能放置双精度数的空间。 #include <stdlib.h> main() {double *p; p=(double *)malloc(sizeof(double)); /* 注1 */ if(p==0) { printf("malloc error\n"); exit(0); } *p=78.786; printf("*p=%f\n", *p); } 运行结果: *p=78.786000
另外, 对注1行有两点说明: (1) 从malloc函数原形可以得知,其返回值为void *型, 现在是对双精度型分配空间,所以要将其返回值强行转换为double * 型。 (2) 程序中出于易于移植的考虑,使用了sizeof(double)作为malloc函数的实参。因为不同机器上的双精度所占的字节数可能不同,用这种方法不论在哪种机器上都能为双精度型数据分配大小正确的空间。
例 9.8 改进上例,在使用后释放动态分配的空间。 例 9.8 改进上例,在使用后释放动态分配的空间。 #include <stdlib.h> main() { double *p, *q; p=(double *)malloc(sizeof(double)); if(p==0) { printf("malloc error\n"); exit(0); } printf("p=0x%x *p=%4.1f\n", p, *p=100); free(p); q=(double *)malloc(sizeof(double)); if (q==0)
程序的运行结果为 p=0X4E7 *p=100.0 q=0X4E7 *q=10.0 p=0X4E7 *p=10.0 { printf("malloc error\n"); exit(0); } *q=10.; printf("q=0x%x *q=%4.1f p=0x%x *p=%4.1f\n", q, *q, p, *p); } 程序的运行结果为 p=0X4E7 *p=100.0 q=0X4E7 *q=10.0 p=0X4E7 *p=10.0
指针p、q均为相同的地址值(具体值可能不是0X4E7),表明已经释放的由指针p所指的空间又重新分配给了指针q。 由于指针p的内容没变,故而指针p、q都指向同一空间。从第二行的结果可验证之。 在多次调用malloc函数开辟内存空间的过程中, 可能有另一种动态变化的数据也要在此分配空间,或者前边已分配的某个空间已被释放,又可重新被分配。因此多次开辟的内存空间的地址是不连续的。 这一点与数组完全不同。
另外两个相关函数是calloc()及realloc() 其原型为 void *calloc(unsigned n, unsigned size); void *realloc(void *p, unsigned size); calloc()的功能是分配n个大小为size个字节的连续空间, 它实际上是用于动态数组的分配。 realloc()的功能是将p所指出的已分配的内存空间重新分配成大小为size个字节的空间。它用于改变已分配的空间的大小,可以增减单元数。
9.4.3 链表概述 图 9.3 单向链表示意图
9.4.4 建立链表 所谓建立链表即是从无到有的形成一个链表。 建立链表的思想很简单:逐个地输入各结点的数据,同时建立起各结点的关系。这种建立关系可能是正挂、倒挂或插入,下面介绍前两种: 建立链表方法一:正挂——即先建立链头, 让链头指向首先开辟并输入数据的第一个结点;然后开辟并输入第二个结点数据,将其“挂”到第一个结点之后; 接着开辟第三个结点并输入实际数据,将其“挂”在第二个结点之后……,即按输入顺序将结点“挂”接在一起。 在实际编程时, 还有些细节要考虑, 如是否为空链等。
针对9.4.1节提出的问题, 我们建立一个职工工资链表, 现定义结点类型如下: struct staff {char name[20]; int salary; struct staff *next; }; (为了简化程序,减少了原有的数据成员项),在形成链表的过程中,首先要定义头指针,并且还需要两个工作指针p1、 p2 ,其定义如下: struct staff *head , *p1, *p2;
图 9.4 正向建立链表子函数流程图
具体步骤描述如下: (1)开始时先建一个空链表: head=NULL;形如:head (2) 开辟第一个结点空间并由p1指向,即p1=(struct staff*)(malloc(LEN)); LEN为结点结构体类型staff的一个变量所占字节数。之后, 执行语句: scanf("%s %d", p1->name, &p1->salary); 读入其有效数据,(以工资大于0为有效数据),执行head = p1; 将其挂到链头上,(如虚线所示,其后的链表图类似)。 NULL
形如: 其中worker1代表第一个工人的姓名;至于head的具体内容是什么,即p1的值是多少,由系统决定,我们无需关心。
(3) 移动p2,使其指向最后一个结点,即执行p2=p1。形如:
(4) 再开辟下一个结点的空间由p1指向,即再次执行: p1=(struct staff*)malloc(LEN);读入有效数据后,执行p2->next=p1;将其挂至链尾。
(5) 重复3、4两步,直至所读数据无效,即p2所指为真正尾结点, 此时令p2->next=NULL,建链结束。形如:
例 9.9 正向建立链表程序清单。 #include <stdlib.h> #define NULL 0 例 9.9 正向建立链表程序清单。 #include <stdlib.h> #define NULL 0 #define LEN sizeof(struct staff) struct staff { char name[20]; int salary; struct staff *next; }; int n; main()
{ struct staff *creat1(); /* 二函数声明 */ void print(struct staff *p ); struct staff *head; head=creat1(); /* 调子函数建立链表 */ print(head); /* 从头开始显示链表各结点的数据信息 */ } struct staff *creat1() { struct staff *head, *p1, *p2; n=0; p1=(struct staff *)malloc(LEN); /* 开辟第一结点 */ printf("Input the worker\′s name salary (salary=0 end): \n"); scanf("%s %d", p1->name, &p1->salary); /* 读入第一结点数据 */
head=NULL; /* 建空链 */ while(p1->salary>0) { n=n+1; /* 结点数加1 */ if(n==1) head=p1; /* “挂链” */ else p2->next=p1; p2=p1; /* 移动指针p2*/ p1=(struct staff *)malloc(LEN); /* 开辟下一结点空间 */ scanf("%s %d", p1->name, &p1->salary); /* 读入数据 */ }
p2->next=NULL; /* 数据无效置链尾 */ return (head); /* 返回链头 */ } void print(struct staff *head) { struct staff *p; p=head; /* p指向链头 */ while(p! =NULL) /* 未至链尾,则显示结点数据信息 */ { printf("%s\′s salary is %d\n", p->name, p->salary); p=p->next; /* p后移一结点 */ }
程序运行情况: Input the worker's name salary(salary=0 end): W1 1000 (输入) W2 2000 W3 3000 W 0 W1′s salary is 1000 (输出) W2′s salary is 2000 W3′s salary is 3000
建立链表方法二:“倒挂”——最先输入的结点作尾结点, 后输入的结点始终“挂”在链头上。此时只需要一个工作指针p1。其思路如图9 建立链表方法二:“倒挂”——最先输入的结点作尾结点, 后输入的结点始终“挂”在链头上。此时只需要一个工作指针p1。其思路如图9.5所示,建立之后的链表如图9.6(其中代表工人姓名的worker1、worker2等也代表了结点输入的顺序),程序清单如例9.10所示。
图 9.5 倒向建立链表流程图
图 9.6 “倒挂”形成的链表
例 9.10 倒向建立链表程序清单。 #include <stdlib.h> #define NULL 0 #define LEN sizeof(struct staff) struct staff { char name[20]; int salary; struct staff *next; }; int n; main() { struct staff *creat2(); void print(struct staff *p); struct staff *head;
head=creat2(); print(head); } struct staff *creat2() { struct staff *head, *p1; n=0; p1=(struct staff *)malloc(LEN); printf("Input the worker\′name salary(salary=0 end): \n"); scanf("%s %d", p1->name, &p1->salary); head=NULL; while(p1->salary>0) /* 建链开始 */ { n=n+1; if(n==1)
{ head=p1; /* 第一结点挂链 */ p1->next=NULL; } else { p1->next=head; /* 非第一结点挂链 */ head=p1; p1=(struct staff *)malloc(LEN); scanf("%s %d", p1->name, &p1->salary); return(head); /* 建链结束, 返回链头 */
void print(struct staff *head) { struct staff *p; p=head; while(p!=NULL) { printf("%s\′s salary is %d\n", p->name, p->salary); p=p->next; } }
程序运行情况: Input the worker's name salary(salary=0 end): W1 1000 (输入) W2 2000 W3 3000 W 0 W3′s salary is 3000 (输出) W2′s salary is 2000 W1′s salary is 1000
9.4.5 链表的其它操作 1. 遍历输出 图 9.7 遍历示意图
2. 查找 查找思路类似遍历输出,只是将输出换成比较, 在查找到所要找的一个或全部项后结束子函数。 3. 删除 删除是将某一结点从链中摘除,即将待删结点与其前一结点解除联系。如图9.8(a)(head=p1->next) 或图9.8(b) (p2->next =p1->next)。之后顺着链头就访问不到p1结点了,即p1所指的结点被删除了,不再是链表的一部分了。
图 9.8 删除链表示意图
4. 插入 图 9.9 链表插入示意图
9.5 共 用 体 9.5.1 共用体类型 共用体与结构体定义相类似, 只是定义时将关键词struct换成union。 如定义一个共用体类型 union data: union data {int i; char ch; float f; } datu;
图 9.10 共用体型变量datu在内存中的存贮情况
9.5.2 共用体型变量的引用方式 共用体型变量的引用方式与结构体类型的引用方式相同。 如共用体型变量datu的成员引用可以是 scanf(″%d″, &datu.i); printf(″%f″, datu.f); 但其整体引用, 如: printf(″%d″, datu); 或 datu=1; j=datu; 等都是错误的。
9.5.3 共用体型变量的特点 (1) 一个共用体型变量可以用来存放几种不同类型的成员,自然无法同时实现。即每一个瞬间只有一个成员起作用。因各成员共用一段内存,彼此互相覆盖, 故对于同一个共用体型变量,给一个新的成员赋值就“冲掉”了原有成员的值。因此在引用变量时应十分注意当前存放在共用体型变量中的究竟是哪个成员。 (2) 共用体型变量的地址和它的各成员的地址同值,如上述共用体型变量datu在内存中存放的情形如图9.10所示,所以&datu, &datu.i, &datu.ch, &datu.f 都是同一地址值。
(3) 不能在定义共用体型变量时对其初始化。 即union data datu={2, ′A′, 0
9.5.4 应用举例 例 9.11 根据类型标志type-flag可以处理任意类型的结构体成员变量。 9.5.4 应用举例 例 9.11 根据类型标志type-flag可以处理任意类型的结构体成员变量。 #include <conio.h> main() { struct { union { int i; char ch; float f; double d; } data; char type-flag; } num;
printf("Input the number\′s type(i, c, f, d): \n"); num.type-flag=getche(); printf("Input the number: \n"); switch(num.type-flag) { case ′i′: scanf("%d", &num.data.i); break; case ′c′: scanf("%c", &num.data.ch); break; case ′f′: scanf("%f", &num.data.f); break; case ′d′: scanf("%lf", &num.data.d); } { case ′i′: printf("%d", num.data.i); break; case ′c′: printf("%c", num.data.ch); break; case ′f′: printf("%f", num.data.f); break; case ′d′: printf("%lf", num.data.d); }
9.6 位 段 位段是一种特殊的结构体类型,其每个成员是以位为单位来定义长度的,不再是各种类型的变量。 例如: 9.6 位 段 位段是一种特殊的结构体类型,其每个成员是以位为单位来定义长度的,不再是各种类型的变量。 例如: struct packed-data { unsigned x: 1; unsigned y: 2; unsigned z: 3; } bits;
z y x bits: 6 3 1 0 图 9.11 位段示意图 说明: (1) 一个位段必须被说明成int,unsigned或signed中的任一种。 长度为1的位段被认为是unsigned类型,因为单个位不可能具有符号。
(2) 位段中成员的引用与结构体类型中成员的引用一样, 用“. ”运算符。比如:bits (2) 位段中成员的引用与结构体类型中成员的引用一样, 用“.”运算符。比如:bits.x表示引用位段变量bits中的第一位, 它的值只能是0或1,而bits.z的值可以是0~7中的任一值,而bits.z=8就错了,因为三位二进制码所能表示的数不能超过7。 若由指针来访问时,必须使用指向运算符。 (3) 可定义无名位段: struct {unsigned a: 1; unsigned b: 2; unsigned : 5; /* 此5位无名,不用 */ unsigned c: 8; };
(4) 一个位段必须存贮在同一存贮单元中,不能跨两个单元, 所以位段总长不能超过整型长度的边界。 (5) 位段可在表达式中被引用(按整型数),也可用整型格式符输出。 (6) 位段不能定义成数组。 (7) 不同的机器内部,位段是从右到左分配,还是从左到右分配是不定的,要事先试验。任何实际位段的代码都可能与机器的特征有关。
例 9.12 #include <stdio.h> main() { union { int i; struct { unsigned a: 1; unsigned b: 1; unsigned c: 2; } bits; }num; printf("\nInput an integer between 0 to 15: "); scanf("%d",&num.i); printf("i=%3d,bits=%3d%3d%3d\n",num.i,num.bits.c,num.bits.b, num.bits.a); }
程序运行情况: Input an integer between 0 to 15: 7 (提示 输入) i=7, bits= 1 1 1 (输出) 再次运行: Input an integer between 0 to 15: 4 (提示 输入) i=4, bits= 1 0 0 (输出) 注: Turbo C 是从右向左开始分配各成员的。
9.7 用typedef定义类型 typedef语句的的一般形式为 typedef 原数据类型 新的类型名; 如可以定义结构体类型如下: typedef struct {int year; int month; int day; }DATE;
新的类型名DATE 就代替了上面的结构体类型,此后就可以用DATE 来定义该结构体型变量: DATE birthday ; (不要写成 struct DATE birthday) DATE *p; (p为指向该结构体型数据的指针)