第九章 系 统 安 全 性
第九章 系 统 安 全 性
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
{ 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门课的平均分。
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>
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] );
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);
}
}
第九章 系 统 安 全 性
运行情况为
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
Do you want to continue?(Y/N)y
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的指针, 它将存放该类变量的首地址, 但并无具
体值 。
第九章 系 统 安 全 性
long int num
char name[ 20]
char sex
int age
p→
在程序中执行了语句 p = &stu1之后, p才真正指向了结构
体型变量 stu1,如图所示 。
第九章 系 统 安 全 性
注意以下表达式的含义 ( 指向运算符 ->的优先级高于 ++),
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()
{ 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
Li-Li part1 1000
第九章 系 统 安 全 性
9.3.2 指向结构体型数组的指针
例 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 指针与结构体型数组
W a n g _ L i
6 0 0
L i _ P i n g
7 0 0
L i u _ Y u a n
8 0 0
p
p'
w o r k e r
& w o r k e r [ 0 ]
& w o r k e r [ 1 ]
& w o r k e r [ 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 (分配失败,一般是没有空间)
第九章 系 统 安 全 性
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 改进上例,在使用后释放动态分配的空间。
# 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)
第九章 系 统 安 全 性
{ 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 链表概述
1 1 1 1
A
1 2 3 0
B
3 8 0 0
C
6 0 0 0
D
2 0 0 0
N
N U L L?
h e a d 1 1 1 1 1 2 3 0 3 8 0 0 6 0 0 0
图 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;
第九章 系 统 安 全 性
开辟一个新结点并使p 1 指向它
读入第一个工人的信息给p 1 所指结点
h e a d = N U L L,n = 0
当读入p 1 - > s a l a r y > 0 时
n = n + 1
n = = 1?
真 假
h e a d = p l ; 把p 1 所指结点作
为第一个结点
再开辟一个新结点并使p 1 指向它
读入下一个工人的信息给p 1 所指结点
链尾结点的指针成员置N U L L
返回链表头
p 2 - > n e x t = p 1 ; 把p 1 所指结
点连接到链尾
图 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
第九章 系 统 安 全 性
形如,
?
w o r k e r 1
8 8 0
n e x t
h e a d
p1
其中 worker1代表第一个工人的姓名;至于 head的具
体内容是什么, 即 p1的值是多少, 由系统决定, 我们无
需关心 。
第九章 系 统 安 全 性
(3) 移动 p2,使其指向最后一个结点,即执行 p2=p1。形如,
?
w o r k e r 1
8 8 0
n e x t
h e a d
p1p2
第九章 系 统 安 全 性
(4) 再开辟下一个结点的空间由 p1指向, 即再次执行,
p1=(struct staff*)malloc(LEN);读入有效数据后, 执
行 p2->next=p1;将其挂至链尾 。
?
w o r k e r 1
8 8 0
n e x t
h e a d
p2
w o r k e r 2
9 8 0
n e x t
p1
第九章 系 统 安 全 性
(5) 重复 3,4两步, 直至所读数据无效, 即 p2所指为真
正尾结点, 此时令 p2->next=NULL,建链结束 。 形如,
?
w o r k e r 1
8 8 0
n e x t
h e a d
w o r k e r 2
9 8 0
n e x t
?
w o r k e r n
1 1 8 0
N U L L
p2
0
p1
第九章 系 统 安 全 性
例 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.5所示, 建立之后的链表如图 9.6
( 其中代表工人姓名的 worker1,worker2等也代表了结点
输入的顺序 ), 程序清单如例 9.10所示 。
第九章 系 统 安 全 性
图 9.5 倒向建立链表流程图
开辟一个新结点并使p 1 指向它
读入第一个工人的信息给p 1 所指结点
h e a d = N U L L,n = 0
当读入p 1 - > s a l a r y > 0 时
n = n + 1
n = = 1?
真 假
h e a d = p l ;
p l, n e x t = N U L L
p l, n e x t = h e a d ;
h e a d = p l
再开辟一个新结点并使p 1 指向它
读入下一个工人的信息给p 1 所指结点
返回链表头
第九章 系 统 安 全 性
?
w o r k e r n
1 1 8 0
n e x t
h e a d
?
w o r k e r 2
9 8 0 8 8 0
p1
n e x t
w o r k e r 1
n e x t
图 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,遍历输出
?
A
n e x t
h e a d
p
B
n e x t
p
?
N
N U L L
′
图 9.7 遍历示意图
第九章 系 统 安 全 性
2,
查找思路类似遍历输出, 只是将输出换成比较, 在查
找到所要找的一个或全部项后结束子函数 。
3,
删除是将某一结点从链中摘除, 即将待删结点与其前
一结点解除联系 。 如图 9.8(a)(head=p1->next) 或图 9.8(b) (p2-
>next =p1->next)。 之后顺着链头就访问不到 p1结点了, 即
p1所指的结点被删除了, 不再是链表的一部分了 。
第九章 系 统 安 全 性
图 9.8 删除链表示意图
( b )
?
A
n e x t
h e a d
p2
B
n e x t
C
N U L L
p1
?
A
n e x t
h e a d
p1
B
n e x t
C
N U L L
( a )
第九章 系 统 安 全 性
4,插入
?
A
n e x t
h e a d
p1
B
n e x t
C
N U L L
( a )
p0
( b )
?
A
n e x t
h e a d
p2
B
n e x t
C
n e x t
p1
D
N U L L
p0
?
A
n e x t
h e a d
p
2
B
n e x t
C
N U L L
( c )
p0
p
1
图
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.5}是错误的 。 不能把共用体型变量
作为函数参数或函数的返回值, 但可以使用指向共用体型
变量的指针 ( 与结构体型变量的用法类似 ) 。
第九章 系 统 安 全 性
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);
}
switch(num.type -flag)
{ 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 位 段
位段是一种特殊的结构体类型, 其每个成员是以位为单
位来定义长度的, 不再是各种类型的变量 。 例如,
struct packed -data
{ unsigned x,1;
unsigned y,2;
unsigned z,3;
} bits;
第九章 系 统 安 全 性
图 9.11 位段示意图
z y x bits,
6 3 1 0
说明,
(1) 一个位段必须被说明成 int,unsigned或 signed中的任
一种 。 长度为 1的位段被认为是 unsigned类型, 因为单个位
不可能具有符号 。
第九章 系 统 安 全 性
(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为指向该结构体型数据的指针 )
第九章 系 统 安 全 性
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
{ 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门课的平均分。
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>
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] );
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);
}
}
第九章 系 统 安 全 性
运行情况为
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
Do you want to continue?(Y/N)y
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的指针, 它将存放该类变量的首地址, 但并无具
体值 。
第九章 系 统 安 全 性
long int num
char name[ 20]
char sex
int age
p→
在程序中执行了语句 p = &stu1之后, p才真正指向了结构
体型变量 stu1,如图所示 。
第九章 系 统 安 全 性
注意以下表达式的含义 ( 指向运算符 ->的优先级高于 ++),
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()
{ 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
Li-Li part1 1000
第九章 系 统 安 全 性
9.3.2 指向结构体型数组的指针
例 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 指针与结构体型数组
W a n g _ L i
6 0 0
L i _ P i n g
7 0 0
L i u _ Y u a n
8 0 0
p
p'
w o r k e r
& w o r k e r [ 0 ]
& w o r k e r [ 1 ]
& w o r k e r [ 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 (分配失败,一般是没有空间)
第九章 系 统 安 全 性
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 改进上例,在使用后释放动态分配的空间。
# 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)
第九章 系 统 安 全 性
{ 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 链表概述
1 1 1 1
A
1 2 3 0
B
3 8 0 0
C
6 0 0 0
D
2 0 0 0
N
N U L L?
h e a d 1 1 1 1 1 2 3 0 3 8 0 0 6 0 0 0
图 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;
第九章 系 统 安 全 性
开辟一个新结点并使p 1 指向它
读入第一个工人的信息给p 1 所指结点
h e a d = N U L L,n = 0
当读入p 1 - > s a l a r y > 0 时
n = n + 1
n = = 1?
真 假
h e a d = p l ; 把p 1 所指结点作
为第一个结点
再开辟一个新结点并使p 1 指向它
读入下一个工人的信息给p 1 所指结点
链尾结点的指针成员置N U L L
返回链表头
p 2 - > n e x t = p 1 ; 把p 1 所指结
点连接到链尾
图 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
第九章 系 统 安 全 性
形如,
?
w o r k e r 1
8 8 0
n e x t
h e a d
p1
其中 worker1代表第一个工人的姓名;至于 head的具
体内容是什么, 即 p1的值是多少, 由系统决定, 我们无
需关心 。
第九章 系 统 安 全 性
(3) 移动 p2,使其指向最后一个结点,即执行 p2=p1。形如,
?
w o r k e r 1
8 8 0
n e x t
h e a d
p1p2
第九章 系 统 安 全 性
(4) 再开辟下一个结点的空间由 p1指向, 即再次执行,
p1=(struct staff*)malloc(LEN);读入有效数据后, 执
行 p2->next=p1;将其挂至链尾 。
?
w o r k e r 1
8 8 0
n e x t
h e a d
p2
w o r k e r 2
9 8 0
n e x t
p1
第九章 系 统 安 全 性
(5) 重复 3,4两步, 直至所读数据无效, 即 p2所指为真
正尾结点, 此时令 p2->next=NULL,建链结束 。 形如,
?
w o r k e r 1
8 8 0
n e x t
h e a d
w o r k e r 2
9 8 0
n e x t
?
w o r k e r n
1 1 8 0
N U L L
p2
0
p1
第九章 系 统 安 全 性
例 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.5所示, 建立之后的链表如图 9.6
( 其中代表工人姓名的 worker1,worker2等也代表了结点
输入的顺序 ), 程序清单如例 9.10所示 。
第九章 系 统 安 全 性
图 9.5 倒向建立链表流程图
开辟一个新结点并使p 1 指向它
读入第一个工人的信息给p 1 所指结点
h e a d = N U L L,n = 0
当读入p 1 - > s a l a r y > 0 时
n = n + 1
n = = 1?
真 假
h e a d = p l ;
p l, n e x t = N U L L
p l, n e x t = h e a d ;
h e a d = p l
再开辟一个新结点并使p 1 指向它
读入下一个工人的信息给p 1 所指结点
返回链表头
第九章 系 统 安 全 性
?
w o r k e r n
1 1 8 0
n e x t
h e a d
?
w o r k e r 2
9 8 0 8 8 0
p1
n e x t
w o r k e r 1
n e x t
图 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,遍历输出
?
A
n e x t
h e a d
p
B
n e x t
p
?
N
N U L L
′
图 9.7 遍历示意图
第九章 系 统 安 全 性
2,
查找思路类似遍历输出, 只是将输出换成比较, 在查
找到所要找的一个或全部项后结束子函数 。
3,
删除是将某一结点从链中摘除, 即将待删结点与其前
一结点解除联系 。 如图 9.8(a)(head=p1->next) 或图 9.8(b) (p2-
>next =p1->next)。 之后顺着链头就访问不到 p1结点了, 即
p1所指的结点被删除了, 不再是链表的一部分了 。
第九章 系 统 安 全 性
图 9.8 删除链表示意图
( b )
?
A
n e x t
h e a d
p2
B
n e x t
C
N U L L
p1
?
A
n e x t
h e a d
p1
B
n e x t
C
N U L L
( a )
第九章 系 统 安 全 性
4,插入
?
A
n e x t
h e a d
p1
B
n e x t
C
N U L L
( a )
p0
( b )
?
A
n e x t
h e a d
p2
B
n e x t
C
n e x t
p1
D
N U L L
p0
?
A
n e x t
h e a d
p
2
B
n e x t
C
N U L L
( c )
p0
p
1
图
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.5}是错误的 。 不能把共用体型变量
作为函数参数或函数的返回值, 但可以使用指向共用体型
变量的指针 ( 与结构体型变量的用法类似 ) 。
第九章 系 统 安 全 性
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);
}
switch(num.type -flag)
{ 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 位 段
位段是一种特殊的结构体类型, 其每个成员是以位为单
位来定义长度的, 不再是各种类型的变量 。 例如,
struct packed -data
{ unsigned x,1;
unsigned y,2;
unsigned z,3;
} bits;
第九章 系 统 安 全 性
图 9.11 位段示意图
z y x bits,
6 3 1 0
说明,
(1) 一个位段必须被说明成 int,unsigned或 signed中的任
一种 。 长度为 1的位段被认为是 unsigned类型, 因为单个位
不可能具有符号 。
第九章 系 统 安 全 性
(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为指向该结构体型数据的指针 )