THE C PROGRAMMING LANGUAGE 计算中心- NEU Computer Center 高克宁-gaokening E_mail:chengxusheji01@necmail.neu.edu.cn
0.本章内容 结构的概念 定义 使用 结构与数组 结构与指针 链表 公用体的基本概念
1.结构概念 C语言允许将一组逻辑上联系的不同类型的数据组织起来作为一个整体使用 保证了数据之间的内在联系 用同一个名字引用的相关变量的集合 提供了将相关信息组合在一起的一种手段 C语言提供了一种新的称为结构的构造型数据类型 概念 结构是一组相关的不同类型的数据的集合 结构类型为处理复杂的数据提供了便利的手段 结构体类型的变量可以拥有不同数据类型的成员 是不同数据类型成员的集合
1.结构概念 例如:学生成绩表由下面的项目组成: 通讯录有下列数据项组成: 成绩表: 通讯录表: struct score (字符串) (长整) (字符串) (实型) (实型) (实型) (实型) 通讯录有下列数据项组成: 姓名 工作单位 家庭住址 邮编 电话号码 E_mail (字符串) (字符串) (字符串) (长整) (字符串或长整) (字符串) 成绩表: struct score {char grade[20];/*班级*/ long number ; /*学号*/ char name[20];/*姓名*/ float os; /*操作系统*/ float datastru; /*数据结构*/ float cprog; /*C语言程序设计*/ float compnet; /*网络工程*/ }; 通讯录表: struct addr {char name[20]; /*姓名*/ char department[30];/*部门*/ char f_address[50];/*家庭住址*/ long box; /*邮编*/ long phone; /*电话*/ char email[20];/*Email*/ } ;
1.结构概念 结构体与数组 组成方式 访问方式 都是由若干分量组成的 数组中的分量(元素)是通过数组的下标 访问结构中的成员是通过成员的名字 数组是由相同类型的数组元素组成 结构的分量可以是不同类型的 结构中的分量称为结构的成员 访问方式 数组中的分量(元素)是通过数组的下标 访问结构中的成员是通过成员的名字 结构体的成员可以分别引用 利用结构体可以组织复杂的紧凑的数据结构 如:链表、队列、堆栈和数等
2.结构的定义 在程序中使用结构之前,必须做的工作 定义结构体类型 定义结构体变量 提示 建立一个可用于定义结构类型变量的模型 其组成的各个要素称为结构体的成员 结构的定义说明了该结构的组成成员,以及每个成员的数据类型 变量在计算机中的存在格式 定义结构体变量 要使用该结构就必须说明结构类型的变量 根据结构体类型为所定义的变量分配内存空间 提示 不同的问题有不同数据成员,即不同描述的结构体类型 可以理解为结构体类型根据所针对的问题不同而使得其结构体的成员是不同的 可以有任意多的结构体类型描述 使得C语言可以解决的问题范围扩大
2.结构的定义 结构定义的形式 说明 提示 struct为关键字 结构类型名称是所定义的结构的类型标识 { }中包围的是组成该结构的成员项 struct 结构类型名称 { 数据类型 成员名1; 数据类型 成员名2; …… 数据类型 成员名n; }; 结构定义的形式 说明 struct为关键字 结构的标识符 结构类型名称是所定义的结构的类型标识 由用户自己定义 { }中包围的是组成该结构的成员项 每个成员的数据类型既可以是简单的数据类型,也可以是复杂的数据类型 整个定义作为一个完整的语句用分号结束 提示 结构体类型的说明只是列出了该结构的组成情况 标志这种类型的结构模式已存在 编译系统并没有因此而分配存储空间
2.结构的定义 结构定义的形式 例 常见的错误是忘记终止结构定义的“;” 当一个成员项是一个结构体时,就形成了结构体的嵌套 例:定义一个日期结构体类型 struct date { int year; int mouth; int day;}; 结构定义的形式 例 当一个成员项是一个结构体时,就形成了结构体的嵌套 在数据处理时有时要用到结构体嵌套处理组织复杂的数据集合 常见的错误是忘记终止结构定义的“;” 例:定义一个teacher 类型的结构体 struct date { int year; int mouth; int day; }; strcut teacher { char name[20] ; struct date birthday; char depart[20] ; };
2.结构的定义 结构定义的形式 提示 结构体必须有struct开始 结构体的成员可以是基本数据类型,也可以是数组和其它结构类型的变量 结构体不能递归定义 在结构体类型说明中不能有该结构体变量 允许有指向该结构体的指针成员(自引用结构) 结构的定义可以在一个函数的内部,也可以在所有函数的外部 在函数内部定义的结构,仅在该函数内部有效 定义在外部的结构,在所有函数中都可以使用 定义结构体类型后可以定义该结构体类型的变量 通过变量使用该结构,以对不同变量的成员进行引用
2.结构的定义 结构体变量 定义的形式 先定义结构体类型再定义结构体变量 定义结构体类型和结构体变量 直接定义结构体变量而不需要定义结构体类型名
2.结构的定义 结构体变量 定义的形式(1) struct 结构类型名 { 成员1类型标识 成员1名; 成员2类型标识 成员2名; … { 成员1类型标识 成员1名; 成员2类型标识 成员2名; … 成员n类型标识 成员n名; }; struct 结构体类型名 结构体变量列表; 结构体变量 定义的形式(1) struct 结构类型名称 结构变量名; 说明 说明结构变量的作用类似于说明一个类型变量一样 系统为所说明的结构变量按照结构定义时组成分配存储数据的实际内存单元 结构变量的成员在内存中占用连续存储区域,所占内存大小为结构中每个成员的长度之和 例:struct date { int year; int mouth; int day; }; struct date today,days[20],*day;
2.结构的定义 结构体变量 提示 在程序中,结构的定义要先于结构变量的说明 不能用尚未定义的结构类型对变量进行说明 结构的定义和说明可以同时进行 被说明的结构变量可直接在结构定义的“ }”后给出 例如说明结构变量today可以使用下面的语句 struct date { int year, month, day; } today; 使用sizeof计算一个结构变量占用内存的实际大小 使用的一般形式为:sizeof(变量或类型说明符)
2.结构的定义 结构体变量 提示 结构体类型与结构体变量是两个不同的概念 编译系统不为结构体类型分配内存空间,只为结构体变量分配内存空间 struct teacher { char name[20] ; int age; char sex; char depart[20] ; struct teacher *next; }; 结构体变量 提示 结构体类型与结构体变量是两个不同的概念 编译系统不为结构体类型分配内存空间,只为结构体变量分配内存空间 内存的大小依据结构体类型的定义(结构体类型的变量占内存长度不定长) 对结构体变量来说,在定义时一般先定义一个结构体类型,然后定义变量为该类型。只能对变量赋值、存取或运算,而不能对一个类型赋值、存取或运算。
2.结构的定义 结构体变量 提示 结构体中的成员名可以和程序中的其他变量同名,互不影响,也可以与结构变量名相同 struct teacher { char name[20] ; int age; char sex; char depart[20] ; struct teacher *next; }; 结构体变量 提示 结构体中的成员名可以和程序中的其他变量同名,互不影响,也可以与结构变量名相同 但应尽量避免在不同的结构中使用相同的结构名 虽然允许,但会造成混淆 结构体变量的成员(域)可以单独使用(相当于基本变量) 成员即可以是一个结构体变量,也可以是指向本结构体类型的指针
3.使用结构体 结构成员的引用 结构作为若干成员的集合是一个整体 使用结构中成员的方法 可对结构整体进行操作 可访问结构中的每个成员 结构变量名.成员名称 运算符“.”的含义是访问结构中的成员 “.”操作的优先级最高 结合性为从左到右 指针变量名->成员名 在结构体指针变量情况下使用运算符“->”
3.使用结构体 结构成员的引用 例 对于结构体变量today struct date { int year; int month; 有效的结构体成员是year,month,day today.year,today.mouth,today.day是对结构体变量today的合法引用 打印today的成员year printf(“%d” ,today.year); 将today的成员 month置为12 today.month=12; struct date { int year; int month; int day; } today,tomorrow ;
3.使用结构体 结构成员的引用 提示 结构的成员可以象一般变量一样参与各种操作和运算 可以引用结构体成员的地址也可以引用结构体变量的地址 进行结构变量的整体操作就有很多限制 由于结构中各个成员的逻辑意义不同,类型不同,对结构变量整体的操作的物理意义不是十分明显 C语言中能够对结构进行整体操作的运算只有赋值“=”和取地址“&”操作 例如: struct date day1={1999,10,12}; struct date day2; day2= day1;/*将day1的所有成员值赋值给 day2;*/ 可以引用结构体成员的地址也可以引用结构体变量的地址 主要作用于函数的参数传递(传递结构体的地址)
3.使用结构体 结构成员的引用 提示 C语言不允许将两个结构体变量整体比较,只能是逐个成员之间进行比较 例如 struct date d1={1994,11,20},d2; d2=d1; d2.day=7; if (( d1.year= =d2.year )&&( d1.month= =d2.month ) &&( d1.day= =d2.day )) printf(“same day”); else printf(“diffierent day”);
3.使用结构体 结构成员的引用 提示 对于嵌套定义的结构体成员的引用,要逐级找到最后一级的结构体成员,只能对这个成员进行引用 用成员运算符“.”自左向右的将内外层连接起来 对中间过程所涉及的成员不能引用 struct date { int year; int mouth; int day;} struct student { int num; char name[20]; char sex; int age; struct date birthday; char addr[30] ;} stud1,stud2; 合法引用一个学生的生日: stud1.birthday.day; stud1.birthday.month; stud1.birthday.year;
3.使用结构体 结构的初始化 在结构说明的同时,可以对每个成员置初值,称为结构的初始化 结构初始化的一般形式 说明 例如 初始化数据的个数与结构成员的个数应相同 它们是按成员的先后顺序一一对应赋值的 每个初始化数据必须符合与其对应的成员的数据类型 例如 struct date nextday={2000,12,27} ; struct 结构类型名 结构变量名={成员1的值,成员2的值,…}
3.使用结构体 【例】调式程序 struct person { char name[20] ; int age; char sex;} ; void main() { struct person a={“liming” ,23,‘m’}; struct person b; b=a; b.age=21; b.sex=‘f’ ; printf(“%s,%d year old.\n” ,b.name,b.age); if (b.sex= = ‘m’) printf(“%s is a man.\n” ,b.name); else printf(“%s is a woman.\n” ,b.name); } jgt1.c
4. 结构体与数组 结构数组 定义 方式 结构体数组中的所有元素都具有相同的结构体类型 先定义结构体类型再定义结构体数组 (3) struct { char name[20] ; int age; char sex; char depart[20] ; } teach[40] ; 4. 结构体与数组 结构数组 定义 结构体数组中的所有元素都具有相同的结构体类型 方式 先定义结构体类型再定义结构体数组 同时定义结构体类型和结构体数组 直接定义结构体数组而不需要定义结构体类型名 (1) struct teacher { char name[20] ; int age; char sex; char depart[20] ; }; struct teacher teach[40] ; (2) struct teacher { char name[20] ; int age; char sex; char depart[20] ; } teach[40] ;
4. 结构体与数组 结构数组 三种方法都实现了一个结构体数组的定义 例 结构体数组中有40个元素,每个元素的类型为结构体类型struct teacher 可用teach[0] 、teach[1] 、…teach[39]表示数组元素 在内存中的存放方式: teach 43个 字节 name(20) age(2) sex(1) depart(20) teach[0] teach[1] … teach[39]
4. 结构体与数组 结构数组引用 指对结构体数组元素的引用 引用形式 由于结构体数组元素相对于结构体变量,因此对结构体变量的引用方法也同样适用与结构体数组元素 引用形式 提示 必须带有下标,以表明要访问结构数组中某个元素对应的结构中的成员 结构体数组名[下标].成员名 例如:struct {char name[20] ; int age; char sex; char depart[20] ; } teach[40] ; 则:teach[2].age表示teach数组的第3个元素的age成员项。
4. 结构体与数组 结构体数组元素的赋值 结构体数组的输入输出 ANSI C标准允许将一个结构体数组元素赋值给同一结构体数组的其它元素,或者赋值给同一类型的变量 例如:前面的teach结构体数组定义后,可以进行这样的赋值: teach[3]=teach[0] ; 结构体数组的输入输出 不能直接进行输入输出 只能对数组元素的单个成员进行输入输出 例如:scanf(“%d” ,&teach[3].age); printf(“%c” ,teach[0].sex);
4. 结构体与数组 结构数组的初始化 C语言规定只能对全局的或静态的结构体数组进行初始化 数组中的每个元素都是一个结构体类型 要将其成员的值依次放在一对{}中以便区分各个元素 可以看出: 初始化的一般形式: struct 结构名 结构数组名={初始值}; 初值表内的{}的作用主要是区别 不同数组元素的数据,系统会顺序 地按照每对{}将数据赋给每一个 数组元素。有关数组的规定对结构 体数组都适用。 例如: struct depart { int no; char depname; }dp[3] ={{3, “人事处”}, {6, “财务处”}, { 10,“计算中心” } };
4. 结构体与数组 例题:对4个学生(包括考号、成绩、姓名),求出成绩最好的学生的姓名和成绩。 struct syudent { int num; char name[10] ; floart score ; }stud[4]={{1,“ny” ,89}, {2,“tz” ,91}, {3,“lm” ,75},{4,“sn” ,89} }; main() { float max;int i,k; max=stud[0].score; k=0; for (i=1;i<=4;i++) if (max<stud[i].score ) { max=stud[i].score ;k=i;} printf(“\n name result\n ”); printf(“%6s%8.0f” ,stud[k].name,stud[k].score);}
4. 结构体与数组 问题1:有如下定义: struct aa { long num ; char name[20] ;}; struct bb { int age ; struct aa first ;}stu[2] ; 问:(1)如何给stu[1]输入18,001011,zhang hua? (2)如何将 ‘z’ 、‘h’改写为大写? (3)如何将stu[1]的名字复制到stu[0]的位置?
5. 结构体与指针 结构体指针 是一个指针变量,指向一个结构体变量 对结构体变量的访问 指向该变量所分配的存储区域的起始地址 一个结构体变量的指针就是该变量所占据的内存空间的起始地址 结构体指针变量还可以用来指向结构体数组中的元素 对结构体变量的访问 即可以通过结构体变量名实现(成员访问运算符),也可以通过结构体指针实现 指针p teach 地址
5. 结构体与指针 结构体指针 结构体指针定义方式 提示 结构体指针在特性和使用方法上与指针完全相同 结构体指针的运算也按照 C语言的地址计算规则进行 结构体指针自身地址值的增加量取决于它所指向的结构体的数据长度(可以采用 sizeof函数获取) struct 结构体类型名 *结构体指针名; struct teacher {char name[20] ; int age; char sex; char depart[20] ;}; struct teacher teach,*pt; pt是teacher结构体变量的指针变 量,它将指向teacher结构体类型的 变量(不能指向其它结构体类型)。 若令pt= &teach ,则pt将指向相同 结构体类型的变量teach 。
5. 结构体与指针 结构体指针 提示 pt不是结构体变量(是指向结构体变量的指针) struct teacher {char name[20] ; int age; char sex; char depart[20] ;}; struct teacher teach,*pt; 5. 结构体与指针 结构体指针 提示 pt不是结构体变量(是指向结构体变量的指针) 不能写成成员引用方式pt.age的形式 如需要则应写成( *pt ).age的形式(“显示法”) 指针pt 结构体类型 单元长度 &teach.name &teach.age &teach.sex &teach.depart teach
5. 结构体与指针 结构体指针 提示 C语言中引用了一个指向运算符“->” struct teacher {char name[20] ; int age; char sex; char depart[20] ;}; struct teacher teach,*pt; 5. 结构体与指针 结构体指针 提示 C语言中引用了一个指向运算符“->” 用于连接指针变量与其指向的结构体成员 如: ( *pt ).age可写成pt ->age的形式 下面三种引用方式是等价的 结构体变量名.成员名 (*结构体类型指针).成员名 结构体类型指针->成员名 例如:teach.age<==>(*pt).age< == >pt->age
5. 结构体与指针 结构体指针 提示 pt只能指向一个结构体变量 pt只能指向一个结构体数组的一个元素(相当于变量) struct teacher {char name[20] ; int age; char sex; char depart[20] ;}; struct teacher teach,*pt; 5. 结构体与指针 结构体指针 提示 pt只能指向一个结构体变量 如:pt=&teach; 不能指向结构体变量的一个成员 如:pt=&teach.age;是非法的。 虽然&teach.age也有地址,但这个地址存放的数据类型为整型数据而非结构体类型数据 pt只能指向一个结构体数组的一个元素(相当于变量) 用->指向运算符取其成员的值 而不能直接指向一个数组元素的成员
5. 结构体与指针 结构体指针 提示 ->运算符的优先级最高 例如:struct { int numb; char *name[]; } *p; 则有表达式: ++p->numb:表示numb的值增加1。而不是p指针向下移动。 等价于++(p->numb)。 (++p)->numb:表示p指针增加操作后,再访问其成员numb。 (p++)->numb:先对numb操作,再对指针p加1。 同理: *p->name:是取 name所指向的值; *p->name++:先取name所指向的值,在将name加1。 (*p->name)++:是将name所指向的值加1。 *p++->name:先取name所指向的值,再将指针p加1。
5. 结构体与指针 结构体指针 例题:对结构体变量的使用——输入一个结构体变量成员并输出 提示 程序中使用结构体类型指针引用结构体类型变量的成员 通过C语言提供的库函数 malloc()来为指针分配安全的地址 函数sizeof()返回值是计算给定数据类型所占内存的字节数 指针所指向各个成员的形式为 studptr->name studptr->num studptr->birthday
5. 结构体与指针 #include<stdib.h> struct data { int day,month,year;}; struct stud { char name[20]; long num; struct data birthday; }; 5. 结构体与指针 main() { struct stud *studptr; studptr=malloc(sizeof(struct stud)); printf(“input name,number,year,month,day:\n”); scanf(“%s”,studptr->name); scanf(“%ld”,&studptr->num); scanf(“%d%d%d”,&studptr-> birthday.year, &studptr-> birthday.month, &studptr->birthday.day); printf(“\noutput name,number,birthday:\n”); printf(“%20s%10ld%10d//%d//%d”, studptr->name,studptr->num, studptr->birthday.year, studptr->birthday.month, studptr->birthday.day); } jgt3.c
5. 结构体与指针 指向结构体数组的指针的应用 定义结构体类型的指针 对数组元素的引用方法有三种 例如:对上例定义成结构体数组以及指向结构体类型的指针 struct stu student[40],*studptr; 当 studptr=student时,指针studptr指向了结构体数组student 对数组元素的引用方法有三种 地址法 student+i和studptr +i 均表示数组第i个元素的地址,数组元素 各成员的引用形式为: ( student+i )->name、 ( student+i )->num和 ( studptr +i ) ->name、 ( studptr +i ) ->num等。 student+i和p+i 与&student[i]的意义相同.
5. 结构体与指针 指向结构体数组的指针的应用 对数组元素的引用方法有三种 提示 指针法 指针的数组表示法 若studptr指向某个元素,则studptr ++就是指向其后续元素 指针的数组表示法 当指针studptr指向了数组student, studptr[i]表示数组的第i个元素 其效果与student [i]相同 对数组成员的引用描述为: studptr[i].name、 studptr[i].num等 提示 在结构指针运算符-和>之间插入空格 试图只用成员名引用结构的成员。(必须给出结构名) 在用指针和结构成员运算符引用结构成员时没有() 就是说*p.name是一种语法错误。
5. 结构体与指针 【例】利用指向结构体的指针编制一程序,实现输入三个学生的学号、 数学期中和期末成绩,然后计算平均成绩并输出成绩表。 #include <stdio.h> struct student { int num;int mid;int end;int aver;}s[3] ; main() { int i; struct student *p; for (p=s;p<s+3;p++) {scanf(“%d%d%d” ,&(p->num),&(p->mid), &(p->end)); p->aver=(p->mid+p->end)/2;} printf(“%d %d %d %d\n”,p->num,p->mid, p->end,p->aver); } jgt4.c
5. 结构体与函数 结构体变量定义范围 结构体作为函数参数 与普通变量一样,结构变量在函数内部定义时为局部的 将结构传递给函数的方式有三种 其值只在本函数范围内有效 不会影响其它函数 结构体作为函数参数 将结构传递给函数的方式有三种 传递单个成员 传递整个结构 传递指向结构的指针
5. 结构体与函数 结构体作为函数参数 提示 传递结构变量的地址可以实现结构的传递 结构变量作为参数传递时,其实参与形参的结构类型必须一致 传递时实参只需指定其结构变量名即可 当实参为数组时,其形参可以定义为同类型结构的结构数组或结构指针
5. 结构体与函数 #define FORMAT “%d\n%s\n%6.2f\n” struct student { int num; char name[20]; float score; }; main() { void print (); struct student stud; stud.num=1001; strcopy(stud.name,”michell”); stud.score=90.9; print(&stud); } void print(p) struct student *p; { printf(FORMAT,p->num,p->name,p->score); printf(“\n”); } jgt5.c 在调用print时, &stud 做实参,将stud的地址 传递给函数的形参p, 这时p指向了结构变量 stud的成员值。
5. 结构体与函数 【例】按学生姓名查询其排名, 查询可连续进行,直到键入0时 结束。 #include <stdio.h> #include <string.h> define NUM 4 struct student { int rank; char *name; float score; }; stud[]={ 3,“T”,89.0, 2,“L”,72.6, 4,“W”,90.1, 1,“Y”,79.5 }; main() { char str[10] ; int i; pritf(“Enter a name:\n”); scanf(“%s” ,str); while(strcmp(str,“0”)!= 0) { for (i=0;i<=NUM;i++) if (strcmp(str,stud[i].name )==0 ) { printf(“name :%3s\n ”,stud[i].name); printf(“rank :%3s\n ”,stud[i].rank); printf(“aver :%5.1f\n”,stud[i].score); break; } if (i>=NUM) printf(“Not found\n”); scanf(“%s” ,str); } printf(”\nEnd\n”) } jgt7.c
6.链表 p 【例】设有下面的定义, t struct aa { w i int i; 1 char c;}t; c A p w m n 1 A i c t 【例】设有下面的定义, struct aa { int i; char c;}t; struct cc{ int m, struct aa *n;}; struct cc w,*p=&w; 要建立右图的结构应如何操作?(用指针实现) 将t的起始地址放到p所指向的结构体变量w中的成员n中 解: p->m=0; p->n=&t; p->n->i=1; p->n->c=‘A’ ; (成员n指向一个嵌套的结构体) 使p指向的结构体变量w中的成员m的值为0 p->n->c是 p->n所指向的结构体变量t的成员c p->n是地址,它指向t,因此p->n->i相当于t.i
6.链表 【例】设有下面的定义 struct aa { int i; struct aa *n w }w,t,*p=&w; w i n 1 \0 t 【例】设有下面的定义 struct aa { int i; struct aa *n }w,t,*p=&w; 要建立如图的结构应如何用指针完成? 解: p->i=0; p->n=&t; p->n->i=1; p->n->n=‘\0’ ; 成员n指向了本结构体类型的变量。这种结构就是链表中的节点。
6.链表 什么是链表? 链接方式存储的线性表简称为链表(Linked List) 链表是指若干个数据项按一定的原则连接起来 是常见的数据结构 每个数据项称为一个“结点” 每个数据项都包含有若干个数据和一个指向下一个数据项的指针,依靠这些指针将所有的数据项连接成一个链表
6.链表 什么是链表? 存储方式特点 用一组任意的存储单元来存放线性表的结点 链表中结点的逻辑次序和物理次序不一定相同 这组存储单元既可以是连续的,也可以是不连续的 链表中结点的逻辑次序和物理次序不一定相同 为了能正确表示结点间的逻辑关系,在存储每个结点值的同时,必须存储指示其后继结点的地址(或位置)信息(称为指针(pointer)或链(link)) 数据项1 数据项2 数据项3 姓 名2 学 号2 成 绩2 指 针2 姓 名3 学 号3 成 绩3 指 针3 姓 名1 学 号1 成 绩1 指 针1 头指针head
6.链表 什么是链表? 域 data域 next域 struct node { int data; struct node *next; 存放结点值的数据域 next域 存放结点的直接后继的地址(位置)的指针域(链域) struct node { int data; struct node *next; };
6.链表 链表与数组的主要区别 个数 存贮单元 关系 操作(如果需要频繁处理元素/结点的插入、删除操作) 数组的元素个数是固定的,链表的结点个数可按需要增减; 存贮单元 数组元素的存贮单元在数组定义时分配,链表结点的存贮单元在程序执行时动态向系统申请 对于不是固定长度的链表,用可能最大长度的数组来描述,会浪费内存空间 关系 数组中的元素顺序关系由元素在数组中的位置确定,链表中的结点顺序关系由结点所包含的指针来体现 操作(如果需要频繁处理元素/结点的插入、删除操作) 用数组处理非常麻烦,程序结构不易清晰 用链表实现,则程序结构清晰,处理的方法也较为简便
6.链表 内存管理库函数 分配存储空间函数malloc( ) 分配存储空间函数calloc( ) 重新分配空间函数 realloc ( ) 释放空间函数 free ( )
6.链表 内存管理库函数----分配存储空间函数malloc( ) malloc ( ) 函数的原型 函数的作用 在内存自由空间开辟一块大小为 size 字节的空间,并将此存储空间的起始地址作为函数值带回。 例如,malloc ( 10 ) 的结果是分配了一个长度为10字节的内存空间。 void *malloc ( unsigned size ) ;
6.链表 内存管理库函数----重新分配空间函数 realloc ( ) realloc ( )的原型 函数用于使已分配的空间改变大小,即重新分配。 void *realloc ( void * ptr , unsigned newsize )
6.链表 内存管理库函数 释放空间函数 free ( ) free ( )函数的原型 将指针 ptr 指向的存储空间释放,交还给系统,系统可以另行分配作它用 必须指出, ptr 值不能是随意的地址,而是只能是程序在运行时通过动态申请分配到的存储空间的首地址 下面的做法是正确的 void free ( void * ptr ) ; pt = ( long * ) malloc ( 10 ) ; …… free (pt ) ;
6.链表 内存管理库函数 malloc( )和free( )函数的使用实例 #include “stdlib.h” #include “stdio.h” main( ) { int *p,t; p=(int *)malloc(10*sizeof(int)); if (!p) { printf(“\t内存已用完!\t”); exit(0); } for(t=0;t<20;++t) *(p+t)=t; printf(“\t%d”,*(p+t)); free(p);
6.链表 链表的基本操作 建立链表 遍历链表 删除链表中的结点 插入结点 输出和查找等 以单链表(每个结点只有 一个链域的链表)为例
6.链表 链表的基本操作 建立链表 指一个一个地输入各结点数据,并建立起各结点前后相链的关系 两种方式 尾插法 头插法
6.链表 链表的基本操作 建立链表(尾插法建表) head new NULL
6.链表 链表的基本操作 建立链表(尾插法建表) struct node { int data; struct node *next; /* 指向本结点类型的指针 */ }; *new,*head,*tail; Step1:/*头指针置空*/ head=NULL; Step2:/*创建新结点*/ new=(struct node*)malloc(sizeof(struct node)); new->next=NULL; /* 指针值为空,表示最后一个结点 */ Step3:/*新结点连入链表*/ if (head==NULL) { head=new; tail=new; } else tail->next=new; 重复step2、step3直到链表创建完成
6.链表 链表的基本操作 插入链表 将一个结点插入一个已经存在的链表中 步骤 注意 插入在链表尾部、头部或者插入在一个有序链表中 找到插入点 插入结点 注意 如果插入的位置为第一结点之前 要修改头结点(head)指针 如果要插到表尾之后 将插入结点的指针域赋NULL值 如果插入的位置链表中间,则修改相关指针域
6.链表 链表的基本操作 插入链表 head new head new (1)插入在头结点前面,则 new->next=head; NULL new head NULL 插入在表尾,则 p->next=new; new->next=NULL new NULL
6.链表 链表的基本操作 插入链表 head new p r 向一个有序链表中插入新结点(new) NULL new 向一个有序链表中插入新结点(new) step1:找到要插入结点的位置(r结点前,p结点后) step2: new->next=r; p->next=new;
6.链表 链表的基本操作 插入链表 插入结点的函数insert #include <stdio.h> struct node{ int data; struct node *next;}; struct node * insertsert (struct node *head, int n) { struct node *new,*p,*q; new=(struct node *)malloc(sizeof(struct node)); /*产生插入结点*/ new->data=n; p=head; /*p指向第一个结点*/ while(p!=NULL &&p->data<n) /*找插入位置*/ { q=p; p=p->next; } if(p==head) { new->next=head; head=new; } else /*new插入q之后 */ { q->next=new; new->next=p; } return (head); }
6.链表 链表的基本操作 删除链表 从链表中删除一个结点,只要把该节点从链表中分离开来,即改变链表中的链接关系即可 不一定从内存中真正把它删除掉 可使用free函数真正释放 找到要删除的结点,用delnode指向该 结点,p指向要删除结点的前趋结点。 delnode head 如果要删除的结点为头结点, 则:head= delnode ->next; 如果要删除的结点不是头结点, 则:p->next= delnode ->next NULL 释放已经删除的结点 free(current);
6.链表 链表的基本操作 删除链表 处理过程 令p指向第一个结点 从p开始检查该结点中的data值是否等于指定data值 如果相等,则删除该结点 如果不等,则p后移一个结点 如此进行下去,直到遇到表尾为止
6.链表 链表的基本操作 删除链表 #include <stdio.h> #include <stdlib.h> struct node{ int data; struct node *next; }; struct node * deletelink (struct node *head,int n) { struct node *p,*q; p=head; while(p->data!=n&&p->next!=NULL) { q=p; p=p->next; } if(p->data==n) /*找到*/ { if(p==head) /*被删除的结点是链头*/ head=p->next; else /*被删除的结点不是链头*/ q->next=p->next; free(p); return (head); 链表的基本操作 删除链表
7.公用体的基本概念 公用体类型 几个不同类型的变量共占一段内存 定义形式 相互覆盖 属于构造数据类型 类型定义不分配内存 union data { int i; char ch; float f; }; union 公用体类型名 { 类型标识符 成员名; ……………. };
7.公用体的基本概念 公用体类型变量的定义 三种形式 union data { int i; char ch; float f; }; union data a,*pt,arry[10]; 7.公用体的基本概念 公用体类型变量的定义 三种形式 先定义公用体类型再定义公用体变量 同时定义公用体类型和公用体变量 直接定义结构体数组而不需要定义结构体类型名 union { int i; char ch; float f; } a,*pt,arry[10]; union data { int i; char ch; float f; }a ,*pt,arry[10];
7.公用体的基本概念 公用体类型变量的定义 提示 公用体变量定义分配内存 公用体变量任何时刻只有一个成员存在 引用方式如结构体变量 公用体变量的引用规则 不能在定义公用体变量时初始化 不能引用公用体变量,只能引用其成员 变量中起作用的成员是最后一次存放的成员 可以用一个公用体变量为另一个变量赋值 长度=最长成员所占字节数
THE C PROGRAMMING LANGUAGE 计算中心- NEU Computer Center 高克宁-gaokening E_mail:chengxusheji01@necmail.neu.edu.cn
0.本章内容 预编译处理命令概述 宏定义 “文件包含”处理 条件编译
1.预编译处理命令概述 概念 预编译 预编译处理命令 是在编译前对源程序进行的一些预加工 预编译由编译系统中的预处理程序, 按源程序中的预处理命令进行 预编译处理命令 C语言的预编译处理命令均以 “ # ”打头, 末尾不加分号 预处理命令可以出现在程序的任何位置 其作用域是从出现点到所在源程序的末尾 优点 能改善程序设计的环境, 助于编写易移植, 易调试的程序
2.#define命令进行宏定义 不带参数的宏定义 格式 说明 称为宏名 #define 标识符 替换文本 标识符也称宏名, 一般用大写字母表示 预处理时将程序中所有的宏名用宏体替换, 该过程称“宏展开”; 但在程序中用 “ ” 括起来的字符串中, 即使有的字符串与宏名相同, 也不进行替换 称为宏名 #define 标识符 替换文本 称为宏体 #define SIZE 20 void main ( ) { int x ; x = SIZE+15 ; printf( “ SIZE=%d \n”, SIZE ) ; } 输出结果: SIZE=35
2.#define命令进行宏定义 不带参数的宏定义 说明 #define 标识符 替换文本 宏定义只是一种简单的字符替代, 不进行语法检查 若将#define SIZE 20 的零写成英文字母‘o’ , 程序中的 x = SIZE+15 ; 会替换为 x = 2o+15;这时才会发现错误 宏定义不是C语句, 行末不加分号, 每条宏命令要单独占一行 #define命令出现在函数的外部, 宏名的有效范围为定义命令之后到本文件结束 可以用#undef 命令终止宏定义的作用域 #define 标识符 替换文本
2.#define命令进行宏定义 不带参数的宏定义 说明 使用宏替换的优点 #define 标识符 替换文本 宏定义可以嵌套使用 例 宏定义与变量定义不同, 它只作字符替换, 不分配内存空间 使用宏替换的优点 提高程序的可读性, 易于修改 #define 标识符 替换文本 #define L 10 #define W 20 #define S L*W
2.#define命令进行宏定义 带参数的宏定义 格式 说明 #define 宏名( 形参表) 替换文本 宏定义时宏名与括号之间没有空格, 若有空格则会把空格后的所有字符都看成是宏体 带参数的宏在替换时,不仅宏名被宏体替换, 同时形参被实参替换 建议带运算符的宏体和形参要用 ( ) 括起来 #define 宏名( 形参表) 替换文本 #define PI 3.14159 #define S(r) PI*r*r void main ( ) { float a , area ; a = 3.6 ; area = S(a); printf( “ %f \n”, area) ; } 宏替换: area = 3.14159*a*a ;
2.#define命令进行宏定义 带参数的宏定义 格式 说明 #define 宏名( 形参表) 替换文本 宏替换: #define PI 3.14159 #define S(r) PI*(r)*(r) #define PI 3.14159 #define S(r) PI* r * r 宏替换: area = 3.14159*(a+b)*(a+b) ; void main ( ) { float a , b , area ; a = 3.6 ; b = 1.2 ; area = S(a+b); printf( “ %f \n”, area) ; } 宏替换: area = 3.14159*a+b*a+b ;
2.#define命令进行宏定义 带参数的宏定义 格式 说明 #define 宏名( 形参表) 替换文本 ① #define SQUARE(x) x*x ② #define SQUARE(x) (x)*(x) ③ #define SQUARE(x) ((x)*(x)) 若 a=SQUARE(n+1) 宏展开 ① a=n+1*n+1 ② a=(n+1)*(n+1) ③ a=((n+1)*(n+1)) 若 a=2.7/SQUARE(3.0) 宏展开 ① a=2.7/3.0*3.0 ② a=2.7/(3.0)*(3.0) ③ a=2.7/((3.0)*(3.0)) 出错 出错
2.#define命令进行宏定义 带参数的宏定义 格式 带参数的宏与函数的区别 #define 宏名( 形参表) 替换文本 参数处理 函数调用时,先求出实参表达式的值,再代入形参 带参数的宏定义只是进行简单的字符替换 内存分配 函数调用是在程序运行时处理, 分配临时的内存单元 宏展开是在编译时进行的, 在展开时不分配内存单元 类型定义 对函数的形参和实参都要定义类型, 且要求一致 宏不存在类型问题, 宏名无类型, 其参数也无类型 #define 宏名( 形参表) 替换文本
2.#define命令进行宏定义 带参数的宏定义 格式 带参数的宏与函数的区别 #define 宏名( 形参表) 替换文本 返回值 长度变化 调用函数只可得到一个返回值 使用宏可以设法得到几个结果 长度变化 函数调用不会使源程序变长 宏展开会使源程序增长 运行时间 函数调用占用运行时间, 宏展开不占运行时间, 只占编译时间 #define 宏名( 形参表) 替换文本
2.#define命令进行宏定义 带参数的宏定义 例题 #define PI 3.14 #define CIR(R,L,S,V) L=2*PI*R;S=PI*R*R;V=4.0/3.0*PI*R*R*R main() { float r,l,s,v; scanf("%f"&r); CIR(r,l,s,v); printf("r=%6.2f,l=%6.2f, S=%6.2f,v=%6.2f\n",r,l,s,v); } 宏展开后: scanf("%f",&r); l=2*3.14*r; s=3.14*r*r; v=4.0/3.0*3.14*r*r*r; printf("r=%6.2f,l=%6.2f,s=%6.2f,v=%6.2f\n",r,l,s,v); }
3. “文件包含”处理命令include 文件包含的概念 文件包含的两种格式 用#include命令把另一个文件的整个内容嵌入进来 文件标识可以包含有文件路径(即文件在哪个目录下) 使用方式 系统先在引用被包含文件的源文件所在的目录下寻找被包含文件,如果找不到, 系统再按指定的标准方式检索其他目录, 直到找到为止。 通常使用用户自己编写的文件时用 “ ”
3. “文件包含”处理命令include 文件包含的两种格式 格式 #include <文件标识> 说明 使用方式 系统只按规定的标准方式检索文件目录 通常使用系统提供的标准头文件时用 < > 说明 文件包含的作用就是在编译预处理时将被包含文件的全部内容复制并插入到#include命令处 一个include命令只能指定一个被包含文件. 如果有n个被包含文件则需要用n个include命令, 且一个命令占一行 使用文件包含时, 在被包含文件中绝对不能有main函数 文件包含可以嵌套使用 被包含文件中的全局变量在其所在的文件中有效
{ return(a>b? a : b ) ; } { return(a<b? a : b ) ; } 3. “文件包含”处理命令include 例 文件stdio.h的内容 文件 file1.c #include <stdio.h> #include “file2.c” #include “file3.c” void main ( ) { int x , y, s1 , s2 ; scanf(“%d%d”, &x, &y) ; s1 = max ( x , y ) ; s2 = min ( x , y ) ; printf(“max=%d\n”, s1) ; printf( “min=%d\n”, s2) ; } int max (int a , int b) { return(a>b? a : b ) ; } 文件 file2.c int max (int a , int b) { return(a>b? a : b ) ; } int min (int a , int b) { return(a<b? a : b ) ; } void main ( ) { int x , y, s1 , s2 ; scanf(“%d%d”, &x , &y ) ; s1 = max ( x , y ) ; s2 = min ( x , y ) ; printf(“max=%d\n”, s1) ; printf( “min=%d\n”, s2) ; } 文件 file3.c int min (int a , int b) { return(a<b? a : b ) ; } 注: 文件file1.c, file2.c, file3.c都在tc目录下的include目录中
{ return(a<b? a : b ) ; } { return(a>b? a : b ) ; } 3. “文件包含”处理命令include 例 文件stdio.h的内容 文件 file1.c #include <stdio.h> #include “file3.c” void main ( ) { int x , y, s1 , s2 ; scanf(“%d%d”, &x, &y) ; s1 = max ( x , y ) ; s2 = min ( x , y ) ; printf(“max=%d\n”, s1) ; printf( “min=%d\n”, s2) ; } int min (int a , int b) { return(a<b? a : b ) ; } int max (int a , int b) { return(a>b? a : b ) ; } 文件 file2.c int max (int a , int b) { return(a>b? a : b ) ; } 文件 file3.c #include “fill2.c” int min (int a , int b) { return(a<b? a : b ) ; } void main ( ) { int x , y, s1 , s2 ; scanf(“%d%d”, &x , &y ) ; s1 = max ( x , y ) ; s2 = min ( x , y ) ; printf(“max=%d\n”, s1) ; printf( “min=%d\n”, s2) ; }
4.条件编译 条件编译概念 对源程序中的一部分内容在满足一定条件时才进行编译 形式 即对一部分内容指定编译的条件 #ifdef 标识符 程序段1 #else 程序段2 #endif 作用: 指定的标识符已经被#define命令 定义过,则在程序编译阶段只编译程 序段1,否则只编译程序段2。 特别地: #else部分可以没有
4.条件编译 条件编译概念 形式 作用: 若指定的标识符没有被#define命令定 义过, 则编译程序段1,否则编译程序段2。 作用: #ifndef 标识符 程序段1 #else 程序段2 #endif 作用: 若指定的标识符没有被#define命令定 义过, 则编译程序段1,否则编译程序段2。 #if 表达式 程序段1 #else 程序段2 #endif 作用: 当表达式的值为真时编译程序 段1,否则编译程序段2。
4.条件编译 条件编译概念 例 #define LETTER 1 main() { char str[20]="C Language",c; int i; i=0; while((c=str[i])!='\0') { i++; #if LETTER if(c>='a'&&c<='z') c=c-32; #else if(c>='A'&&c<='Z') c=c+32; #endif printf("%c",c); } }