the c programming language
DESCRIPTION
THE C PROGRAMMING LANGUAGE. 计算中心 - NEU Computer Center 高克宁 -gaokening E_mail:[email protected]. 0. 本章内容. 结构的概念 定义 使用 结构与数组 结构与指针 链表 公用体的基本概念. 1. 结构概念. C 语言允许将一组逻辑上联系的不同类型的数据组织起来作为一个整体使用 保证了数据之间的内在联系 用同一个名字引用的相关变量的集合 提供了将相关信息组合在一起的一种手段 C语言提供了一种新的称为结构的构造型数据类型 概念 - PowerPoint PPT PresentationTRANSCRIPT
THE C THE C PROGRAMMING PROGRAMMING LANGUAGELANGUAGE
计算中心 - NEU Computer Center高克宁 -gaokening E_mail:[email protected]
G_kn2007@ 东北大学计算机基础教研室 2
0.0. 本章内容
结构的概念 定义 使用 结构与数组 结构与指针
链表公用体的基本概念
G_kn2007@ 东北大学计算机基础教研室 3
1. 结构概念 C 语言允许将一组逻辑上联系的不同类型
的数据组织起来作为一个整体使用保证了数据之间的内在联系
用同一个名字引用的相关变量的集合 提供了将相关信息组合在一起的一种手段
C语言提供了一种新的称为结构的构造型数据类型
概念 结构是一组相关的不同类型的数据的集合
结构类型为处理复杂的数据提供了便利的手段 结构体类型的变量可以拥有不同数据类型的成员
是不同数据类型成员的集合
G_kn2007@ 东北大学计算机基础教研室 4
例如:学生成绩表由下面的项目组成: 班级 学号 姓名 操作系统 数据结构 C 程序设计 网络工程(字符串) (长整) (字符串) (实型) (实型) (实型) (实型)
通讯录有下列数据项组成: 姓名 工作单位 家庭住址 邮编 电话号码 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. 结构概念
G_kn2007@ 东北大学计算机基础教研室 5
1. 结构概念 结构体与数组
组成方式 都是由若干分量组成的
数组是由相同类型的数组元素组成 结构的分量可以是不同类型的
结构中的分量称为结构的成员访问方式
数组中的分量(元素)是通过数组的下标 访问结构中的成员是通过成员的名字
结构体的成员可以分别引用 利用结构体可以组织复杂的紧凑的数据结构
如:链表、队列、堆栈和数等
G_kn2007@ 东北大学计算机基础教研室 6
2. 结构的定义 在程序中使用结构之前,必须做的工作
定义结构体类型 建立一个可用于定义结构类型变量的模型
其组成的各个要素称为结构体的成员 结构的定义说明了该结构的组成成员,以及每个成员的数据类
型 变量在计算机中的存在格式
定义结构体变量 要使用该结构就必须说明结构类型的变量
根据结构体类型为所定义的变量分配内存空间 提示
不同的问题有不同数据成员,即不同描述的结构体类型 可以理解为结构体类型根据所针对的问题不同而使得其结构体的
成员是不同的 可以有任意多的结构体类型描述 使得 C 语言可以解决的问题范围扩大
G_kn2007@ 东北大学计算机基础教研室 7
2. 结构的定义 结构定义的形式
说明 struct 为关键字
结构的标识符 结构类型名称是所定义的结构的类型标识
由用户自己定义 { } 中包围的是组成该结构的成员项
每个成员的数据类型既可以是简单的数据类型,也可以是复杂的数据类型
整个定义作为一个完整的语句用分号结束提示
结构体类型的说明只是列出了该结构的组成情况 标志这种类型的结构模式已存在 编译系统并没有因此而分配存储空间
struct 结构类型名称 { 数据类型 成员名 1; 数据类型 成员名 2; …… 数据类型 成员名 n; };
G_kn2007@ 东北大学计算机基础教研室 8
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] ; };
G_kn2007@ 东北大学计算机基础教研室 9
2. 结构的定义 结构定义的形式
提示 结构体必须有 struct开始 结构体的成员可以是基本数据类型,也可以是数组
和其它结构类型的变量 结构体不能递归定义
在结构体类型说明中不能有该结构体变量 允许有指向该结构体的指针成员(自引用结构)
结构的定义可以在一个函数的内部,也可以在所有函数的外部
在函数内部定义的结构,仅在该函数内部有效 定义在外部的结构,在所有函数中都可以使用
定义结构体类型后可以定义该结构体类型的变量 通过变量使用该结构,以对不同变量的成员进行引用
G_kn2007@ 东北大学计算机基础教研室 10
2. 结构的定义 结构体变量
定义的形式先定义结构体类型再定义结构体变量 定义结构体类型和结构体变量直接定义结构体变量而不需要定义结构体类型名
G_kn2007@ 东北大学计算机基础教研室 11
2. 结构的定义 结构体变量
定义的形式( 1 )
struct 结构类型名称 结构变量名; 说明
说明结构变量的作用类似于说明一个类型变量一样 系统为所说明的结构变量按照结构定义时组成分配存储数据
的实际内存单元 结构变量的成员在内存中占用连续存储区域,所占内存大小
为结构中每个成员的长度之和 例: struct date { int year ; int mouth ; int day ; }; struct date today , days[20] , *day ;
struct 结构类型名 { 成员 1 类型标识 成员 1 名; 成员 2 类型标识 成员 2 名; … 成员 n 类型标识 成员 n 名; }; struct 结构体类型名 结构体变量列表;
G_kn2007@ 东北大学计算机基础教研室 12
2. 结构的定义 结构体变量
提示 在程序中,结构的定义要先于结构变量的说明
不能用尚未定义的结构类型对变量进行说明 结构的定义和说明可以同时进行
被说明的结构变量可直接在结构定义的“ }”后给出 例如说明结构变量 today 可以使用下面的语句 struct date
{ int year, month, day; } today;
使用 sizeof 计算一个结构变量占用内存的实际大小 使用的一般形式为: sizeof( 变量或类型说明符 )
G_kn2007@ 东北大学计算机基础教研室 13
2. 结构的定义 结构体变量
提示 结构体类型与结构体变量是两个不同的概念
编译系统不为结构体类型分配内存空间,只为结构体变量分配内存空间
内存的大小依据结构体类型的定义(结构体类型的变量占内存长度不定长)
对结构体变量来说,在定义时一般先定义一个结构体类型,然后定义变量为该类型。只能对变量赋值、存取或运算,而不能对一个类型赋值、存取或运算。
struct teacher { char name[20] ; int age ; char sex ; char depart[20] ; struct teacher *next ; };
G_kn2007@ 东北大学计算机基础教研室 14
2. 结构的定义 结构体变量
提示 结构体中的成员名可以和程序中的其他变量同名,互
不影响,也可以与结构变量名相同但应尽量避免在不同的结构中使用相同的结构名虽然允许,但会造成混淆
结构体变量的成员(域)可以单独使用(相当于基本变量)
成员即可以是一个结构体变量,也可以是指向本结构体类型的指针
struct teacher { char name[20] ; int age ; char sex ; char depart[20] ; struct teacher *next ; };
G_kn2007@ 东北大学计算机基础教研室 15
3. 使用结构体 结构成员的引用
结构作为若干成员的集合是一个整体 可对结构整体进行操作 可访问结构中的每个成员
使用结构中成员的方法 结构变量名 . 成员名称
运算符“ .” 的含义是访问结构中的成员 “.” 操作的优先级最高 结合性为从左到右
指针变量名 -> 成员名 在结构体指针变量情况下使用运算符“ ->”
G_kn2007@ 东北大学计算机基础教研室 16
3. 使用结构体 结构成员的引用
例
对于结构体变量 today 有效的结构体成员是 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 ;
G_kn2007@ 东北大学计算机基础教研室 17
3. 使用结构体 结构成员的引用
提示 结构的成员可以象一般变量一样参与各种操作和运算
进行结构变量的整体操作就有很多限制 由于结构中各个成员的逻辑意义不同,类型不同,对结构
变量整体的操作的物理意义不是十分明显 C 语言中能够对结构进行整体操作的运算只有赋值“ =”
和取地址“ &” 操作 例如: struct date day1={ 1999,10,12};
struct date day2 ; day2= day1 ; /* 将 day1 的所有成员值赋值给 day2 ; */
可以引用结构体成员的地址也可以引用结构体变量的地址
主要作用于函数的参数传递(传递结构体的地址)
G_kn2007@ 东北大学计算机基础教研室 18
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” );
G_kn2007@ 东北大学计算机基础教研室 19
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 ;
3. 使用结构体 结构成员的引用
提示 对于嵌套定义的结构体成员的引用,要逐级找到最后一
级的结构体成员,只能对这个成员进行引用 用成员运算符“ .” 自左向右的将内外层连接起来 对中间过程所涉及的成员不能引用
合法引用一个学生的生日: stud1.birthday.day; stud1.birthday.month ; stud1.birthday.year ;
G_kn2007@ 东北大学计算机基础教研室 20
3. 使用结构体 结构的初始化
在结构说明的同时,可以对每个成员置初值,称为结构的初始化
结构初始化的一般形式
说明初始化数据的个数与结构成员的个数应相同
它们是按成员的先后顺序一一对应赋值的 每个初始化数据必须符合与其对应的成员的数据类型
例如 struct date nextday={ 2000 , 12 , 27} ;
struct 结构类型名 结构变量名 ={成员 1 的值,成员 2 的值,…}
G_kn2007@ 东北大学计算机基础教研室 21
【例】调式程序 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
3. 使用结构体
G_kn2007@ 东北大学计算机基础教研室 22
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] ;
( 3 )struct { char name[20] ; int age ; char sex ; char depart[20] ; } teach[40] ;
G_kn2007@ 东北大学计算机基础教研室 23
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]
name ( 20 )age ( 2 )sex ( 1 )
depart ( 20 )
G_kn2007@ 东北大学计算机基础教研室 24
4. 结构体与数组 结构数组引用
指对结构体数组元素的引用 由于结构体数组元素相对于结构体变量,因此对结
构体变量的引用方法也同样适用与结构体数组元素引用形式
提示 必须带有下标,以表明要访问结构数组中某个元素对应的
结构中的成员
结构体数组名 [ 下标 ]. 成员名
例如: struct { char name[20] ; int age ; char sex ; char depart[20] ; } teach[40] ; 则 :teach[2].age 表示 teach 数组的第 3 个元素的 age 成员项。
G_kn2007@ 东北大学计算机基础教研室 25
4. 结构体与数组 结构体数组元素的赋值
ANSI C 标准允许将一个结构体数组元素赋值给同一结构体数组的其它元素,或者赋值给同一类型的变量
例如:前面的 teach 结构体数组定义后,可以进行这样的赋值: teach[3]=teach[0] ;
结构体数组的输入输出不能直接进行输入输出
只能对数组元素的单个成员进行输入输出 例如: scanf (“%d” , &teach[3].age );
printf (“%c” , teach[0].sex );
G_kn2007@ 东北大学计算机基础教研室 26
4. 结构体与数组 结构数组的初始化
C 语言规定只能对全局的或静态的结构体数组进行初始化
数组中的每个元素都是一个结构体类型 要将其成员的值依次放在一对{}中以便区分各个
元素例如: struct depart { int no ; char depname; } dp[3] ={{ 3, “人事处”}, { 6, “财务处”}, { 10,“计算中心” } };
可以看出:初始化的一般形式: struct 结构名 结构数组名 ={初始值};初值表内的{}的作用主要是区别不同数组元素的数据,系统会顺序地按照每对{}将数据赋给每一个数组元素。有关数组的规定对结构体数组都适用。
G_kn2007@ 东北大学计算机基础教研室 27
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 );}
G_kn2007@ 东北大学计算机基础教研室 28
问题 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]的位置?
4. 结构体与数组
G_kn2007@ 东北大学计算机基础教研室 29
5. 结构体与指针 结构体指针
是一个指针变量,指向一个结构体变量 指向该变量所分配的存储区域的起始地址 一个结构体变量的指针就是该变量所占据的内存空间
的起始地址 结构体指针变量还可以用来指向结构体数组中的元素
对结构体变量的访问 即可以通过结构体变量名实现(成员访问运算符),也可以通过结构体指针实现
指针 p teach地址
G_kn2007@ 东北大学计算机基础教研室 30
5. 结构体与指针 结构体指针
结构体指针定义方式
提示 结构体指针在特性和使用方法上与指针完全相同 结构体指针的运算也按照 C 语言的地址计算规则进行
结构体指针自身地址值的增加量取决于它所指向的结构体的数据长度(可以采用 sizeof函数获取)
struct 结构体类型名 *结构体指针名;
pt 是 teacher 结构体变量的指针变量,它将指向 teacher 结构体类型的变量 (不能指向其它结构体类型)。若令 pt= &teach ,则 pt 将指向相同结构体类型的变量 teach 。
struct teacher { char name[20] ; int age ; char sex ; char depart[20] ; }; struct teacher teach , *pt ;
G_kn2007@ 东北大学计算机基础教研室 31
5. 结构体与指针 结构体指针
提示 pt 不是结构体变量 ( 是指向结构体变量的指针 )
不能写成成员引用方式 pt.age 的形式 如需要则应写成( *pt ) .age 的形式(“显示法”)
struct teacher { char name[20] ; int age ; char sex ; char depart[20] ; }; struct teacher teach , *pt ;
指针 ptteach 结构体类型
单元长度
&teach.name
&teach.age
&teach.sex
&teach.depart
G_kn2007@ 东北大学计算机基础教研室 32
5. 结构体与指针 结构体指针
提示 C 语言中引用了一个指向运算符“ ->”
用于连接指针变量与其指向的结构体成员 如: ( *pt ) .age 可写成 pt ->age 的形式 下面三种引用方式是等价的
struct teacher { char name[20] ; int age ; char sex ; char depart[20] ; }; struct teacher teach , *pt ;
结构体变量名 . 成员名
( *结构体类型指针) . 成员名
结构体类型指针 ->成员名
例如: teach.age<==> ( *pt ) .age< == >pt->age
G_kn2007@ 东北大学计算机基础教研室 33
5. 结构体与指针 结构体指针
提示 pt只能指向一个结构体变量
如: pt=&teach ; 不能指向结构体变量的一个成员
如: pt=&teach.age ;是非法的。虽然 &teach.age也有地址,但这个地址存放的数据类型
为整型数据而非结构体类型数据 pt只能指向一个结构体数组的一个元素(相当于变
量) 用 -> 指向运算符取其成员的值 而不能直接指向一个数组元素的成员
struct teacher { char name[20] ; int age ; char sex ; char depart[20] ; }; struct teacher teach , *pt ;
G_kn2007@ 东北大学计算机基础教研室 34
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。
G_kn2007@ 东北大学计算机基础教研室 35
5. 结构体与指针 结构体指针
例题:对结构体变量的使用——输入一个结构体变量成员并输出
提示 程序中使用结构体类型指针引用结构体类型变量的
成员 通过 C 语言提供的库函数 malloc ()来为指针分
配安全的地址函数 sizeof ()返回值是计算给定数据类型所占内
存的字节数 指针所指向各个成员的形式为
studptr->name studptr->num studptr->birthday
G_kn2007@ 东北大学计算机基础教研室 36
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
#include<stdib.h> struct data { int day,month,year;}; struct stud { char name[20]; long num; struct data birthday; };
5. 结构体与指针
G_kn2007@ 东北大学计算机基础教研室 37
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] 的意义相同 .
G_kn2007@ 东北大学计算机基础教研室 38
5. 结构体与指针 指向结构体数组的指针的应用
对数组元素的引用方法有三种 指针法
若 studptr 指向某个元素 ,则 studptr ++ 就是指向其后续元素 指针的数组表示法
当指针 studptr 指向了数组 student, studptr[i] 表示数组的第 i 个元素
其效果与 student [i] 相同 对数组成员的引用描述为: studptr[i].name 、 studptr[i].num
等 提示
在结构指针运算符-和 > 之间插入空格 试图只用成员名引用结构的成员。(必须给出结构名) 在用指针和结构成员运算符引用结构成员时没有()
就是说 *p.name 是一种语法错误。
G_kn2007@ 东北大学计算机基础教研室 39
【例】利用指向结构体的指针编制一程序,实现输入三个学生的学号、 数学期中和期末成绩,然后计算平均成绩并输出成绩表。 #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 ;} for ( p=s ; p<s+3 ; p++) printf(“%d %d %d %d\n”, p->num , p->mid , p->end , p->aver ); } jgt4.c
5. 结构体与指针
G_kn2007@ 东北大学计算机基础教研室 40
5. 结构体与函数 结构体变量定义范围
与普通变量一样,结构变量在函数内部定义时为局部的
其值只在本函数范围内有效 不会影响其它函数
结构体作为函数参数将结构传递给函数的方式有三种
传递单个成员传递整个结构传递指向结构的指针
G_kn2007@ 东北大学计算机基础教研室 41
5. 结构体与函数 结构体作为函数参数
提示传递结构变量的地址可以实现结构的传递 结构变量作为参数传递时,其实参与形参的结构类
型必须一致传递时实参只需指定其结构变量名即可当实参为数组时,其形参可以定义为同类型结构的结构数
组或结构指针
G_kn2007@ 东北大学计算机基础教研室 42
#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. 结构体与函数
G_kn2007@ 东北大学计算机基础教研室 43
【例】按学生姓名查询其排名,查询可连续进行,直到键入 0时结束。 #include <stdio.h> #include <string.h> define NUM 4 struct student { int rank ; char *name ; float score ; }; struct student 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
5. 结构体与函数
G_kn2007@ 东北大学计算机基础教研室 44
【例】设有下面的定义, struct aa { int i ; char c ;} t ; struct cc{ int m , struct aa *n ;}; struct cc w, *p=&w;要建立右图的结构应如何操作?(用指针实现)
0
p
w
m n1A
i
c
t
解: p->m=0 ; p->n=&t ; p->n->i=1 ; p->n->c=‘A’ ; (成员 n 指向一个嵌套的结构体)
使 p 指向的结构体变量w中的成员 m 的值为 0
将 t 的起始地址放到 p 所指向的结构体变量w中的成员 n 中
p->n 是地址,它指向 t ,因此 p->n->i相当于 t.i
p->n->c 是 p->n 所指向的结构体变量 t 的成员 c
6. 链表
G_kn2007@ 东北大学计算机基础教研室 45
【例】设有下面的定义 struct aa { int i ; struct aa *n }w, t , *p=&w;要建立如图的结构应如何用指针完成?
p
0
w i
n1
\0
t
i
n
解: p->i=0 ; p->n=&t ; p->n->i=1 ; p->n->n=‘\0’ ; 成员 n 指向了本结构体类型的变量。这种结构就是链表中的节点。
6. 链表
G_kn2007@ 东北大学计算机基础教研室 46
6. 链表什么是链表?
链接方式存储的线性表简称为链表( Linked List )
是常见的数据结构链表是指若干个数据项按一定的原则连接起来
每个数据项称为一个“结点” 每个数据项都包含有若干个数据和一个指向下一个
数据项的指针,依靠这些指针将所有的数据项连接成一个链表
G_kn2007@ 东北大学计算机基础教研室 47
6. 链表什么是链表?
存储方式特点 用一组任意的存储单元来存放线性表的结点
这组存储单元既可以是连续的,也可以是不连续的 链表中结点的逻辑次序和物理次序不一定相同
为了能正确表示结点间的逻辑关系,在存储每个结点值的同时,必须存储指示其后继结点的地址(或位置)信息(称为指针( pointer )或链 (link) )
头指针 head
姓 名 1
学 号 1
成 绩 1
指 针 1
数据项 1 数据项 3数据项 2姓 名 2
学 号 2
成 绩 2
指 针 2
姓 名 3
学 号 3
成 绩 3
指 针 3
G_kn2007@ 东北大学计算机基础教研室 48
6. 链表什么是链表?
域 data域
存放结点值的数据域 next域
存放结点的直接后继的地址(位置)的指针域(链域)
struct node { int data; struct node *next; };
G_kn2007@ 东北大学计算机基础教研室 49
6. 链表 链表与数组的主要区别
个数 数组的元素个数是固定的,链表的结点个数可按需要增减;
存贮单元 数组元素的存贮单元在数组定义时分配,链表结点的存贮单元在
程序执行时动态向系统申请 对于不是固定长度的链表,用可能最大长度的数组来描述,会浪费
内存空间 关系
数组中的元素顺序关系由元素在数组中的位置确定,链表中的结点顺序关系由结点所包含的指针来体现
操作(如果需要频繁处理元素 /结点的插入、删除操作)
用数组处理非常麻烦,程序结构不易清晰 用链表实现,则程序结构清晰,处理的方法也较为简便
G_kn2007@ 东北大学计算机基础教研室 50
6. 链表 内存管理库函数
分配存储空间函数 malloc( )分配存储空间函数 calloc( ) 重新分配空间函数 realloc ( )释放空间函数 free ( )
G_kn2007@ 东北大学计算机基础教研室 51
6. 链表 内存管理库函数 ---- 分配存储空间函数 mal
loc( )malloc ( ) 函数的原型
函数的作用 在内存自由空间开辟一块大小为 size 字节的空间,并将此存储空间的起始地址作为函数值带回。
例如, malloc ( 10 ) 的结果是分配了一个长度为 10 字节的内存空间。
void *malloc ( unsigned size ) ;
G_kn2007@ 东北大学计算机基础教研室 52
6. 链表 内存管理库函数 ----重新分配空间函数 rea
lloc ( )realloc ( ) 的原型
函数用于使已分配的空间改变大小,即重新分配。void *realloc ( void * ptr , unsigned newsize )
G_kn2007@ 东北大学计算机基础教研室 53
6. 链表 内存管理库函数
释放空间函数 free ( )free ( )函数的原型
将指针 ptr 指向的存储空间释放,交还给系统,系统可以另行分配作它用
必须指出, ptr 值不能是随意的地址,而是只能是程序在运行时通过动态申请分配到的存储空间的首地址
下面的做法是正确的
void free ( void * ptr ) ;
pt = ( long * ) malloc ( 10 ) ;…………free (pt ) ;
G_kn2007@ 东北大学计算机基础教研室 54
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; for(t=0;t<20;++t) printf(“\t%d”,*(p+t)); free(p); }
G_kn2007@ 东北大学计算机基础教研室 55
6. 链表 链表的基本操作
建立链表遍历链表删除链表中的结点插入结点输出和查找等
以单链表(每个结点只有一个链域的链表)为例
G_kn2007@ 东北大学计算机基础教研室 56
6. 链表 链表的基本操作
建立链表 指一个一个地输入各结点数据,并建立起各结点前后相链的关系
两种方式尾插法头插法
G_kn2007@ 东北大学计算机基础教研室 57
6. 链表 链表的基本操作
建立链表 (尾插法建表)
head
newNULL
G_kn2007@ 东北大学计算机基础教研室 58
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直到链表创建完成
G_kn2007@ 东北大学计算机基础教研室 59
6. 链表 链表的基本操作
插入链表 将一个结点插入一个已经存在的链表中
插入在链表尾部、头部或者插入在一个有序链表中步骤
找到插入点插入结点
注意 如果插入的位置为第一结点之前
要修改头结点( head )指针 如果要插到表尾之后
将插入结点的指针域赋 NULL值 如果插入的位置链表中间,则修改相关指针域
G_kn2007@ 东北大学计算机基础教研室 60
6. 链表 链表的基本操作
插入链表
head
new
NULL
head
new
NULL
NULL
(1)插入在头结点前面,则new->next=head;head=new;
插入在表尾,则 p->next=new;new->next=NULL
G_kn2007@ 东北大学计算机基础教研室 61
6. 链表 链表的基本操作
插入链表
head
new
NULL
向一个有序链表中插入新结点( new)step1 :找到要插入结点的位置( r 结点前, p 结点后) step2 : new->next=r;
p->next=new;
p r
G_kn2007@ 东北大学计算机基础教研室 62
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); }
G_kn2007@ 东北大学计算机基础教研室 63
6. 链表 链表的基本操作
删除链表从链表中删除一个结点,只要把该节点从链表中分离开来,即改变链表中的链接关系即可
不一定从内存中真正把它删除掉 可使用 free函数真正释放
head
NULL
delnode找到要删除的结点,用 delnode 指向该结点, p 指向要删除结点的前趋结点。
如果要删除的结点为头结点,则: head= delnode ->next;如果要删除的结点不是头结点,则: p->next= delnode ->next
释放已经删除的结点 free(current);
G_kn2007@ 东北大学计算机基础教研室 64
6. 链表 链表的基本操作
删除链表 处理过程
令 p 指向第一个结点 从 p开始检查该结点中的 data值是否等于指定 data值
如果相等,则删除该结点 如果不等,则 p后移一个结点
如此进行下去,直到遇到表尾为止
G_kn2007@ 东北大学计算机基础教研室 65
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);
}
G_kn2007@ 东北大学计算机基础教研室 66
7. 公用体的基本概念 公用体类型
几个不同类型的变量共占一段内存 相互覆盖属于构造数据类型 类型定义不分配内存
定义形式union 公用体类型名{ 类型标识符 成员名; 类型标识符 成员名; …………….} ;
union data { int i; char ch; float f; };
G_kn2007@ 东北大学计算机基础教研室 67
7. 公用体的基本概念 公用体类型变量的定义
三种形式先定义公用体类型再定义公用体变量 同时定义公用体类型和公用体变量直接定义结构体数组而不需要定义结构体类型名
union data { int i; char ch; float f; }a ,*pt,arry[10];
union data { int i; char ch; float f; };union data a,*pt,arry[10];
union { int i; char ch; float f; } a,*pt,arry[10];
G_kn2007@ 东北大学计算机基础教研室 68
7. 公用体的基本概念 公用体类型变量的定义
提示 公用体变量定义分配内存
公用体变量任何时刻只有一个成员存在 引用方式如结构体变量 公用体变量的引用规则
不能在定义公用体变量时初始化 不能引用公用体变量,只能引用其成员 变量中起作用的成员是最后一次存放的成员 可以用一个公用体变量为另一个变量赋值
长度 =最长成员所占字节数
THE THE C C PROGRAMMING PROGRAMMING LANGUAGELANGUAGE
计算中心 - NEU Computer Center高克宁 -gaokening E_mail:[email protected]
G_kn2007@ 东北大学计算机基础教研室 70
0. 本章内容预编译处理命令概述宏定义“文件包含”处理条件编译
G_kn2007@ 东北大学计算机基础教研室 71
1. 预编译处理命令概述 概念
预编译 是在编译前对源程序进行的一些预加工 预编译由编译系统中的预处理程序 , 按源程序中的预处理命令进行
预编译处理命令 C 语言的预编译处理命令均以 “ # ”打头 , 末尾不加分号
预处理命令可以出现在程序的任何位置 其作用域是从出现点到所在源程序的末尾
优点 能改善程序设计的环境 , 助于编写易移植 , 易调试的程
序
G_kn2007@ 东北大学计算机基础教研室 72
2.#define 命令进行宏定义 不带参数的宏定义
格式
说明 标识符也称宏名 , 一般用大写字母表示预处理时将程序中所有的宏名用宏体替换 , 该过程
称“宏展开” ; 但在程序中用 “ ” 括起来的字符串中 , 即使有的字符串与宏名相同 , 也不进行替换
#define 标识符 替换文本称为宏体
称为宏名
#define SIZE 20void main ( ){ int x ; x = SIZE+15 ; printf( “ SIZE=%d \n”, SIZE ) ; }
输出结果 : SIZE=35
G_kn2007@ 东北大学计算机基础教研室 73
2.#define 命令进行宏定义 不带参数的宏定义
说明宏定义只是一种简单的字符替代 , 不进行语法检查
若将 #define SIZE 20 的零写成英文字母‘ o’ , 程序中的 x = SIZE+15 ; 会替换为 x = 2o+15;这时才会发现错误
宏定义不是 C 语句 , 行末不加分号 , 每条宏命令要单独占一行
#define命令出现在函数的外部 , 宏名的有效范围为定义命令之后到本文件结束
可以用 #undef 命令终止宏定义的作用域
#define 标识符 替换文本
G_kn2007@ 东北大学计算机基础教研室 74
2.#define 命令进行宏定义 不带参数的宏定义
说明宏定义可以嵌套使用
例宏定义与变量定义不同 , 它只作字符替换 , 不分配
内存空间使用宏替换的优点
提高程序的可读性 , 易于修改
#define 标识符 替换文本
#define L 10#define W 20 #define S L*W
G_kn2007@ 东北大学计算机基础教研室 75
2.#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 ;
G_kn2007@ 东北大学计算机基础教研室 76
2.#define 命令进行宏定义带参数的宏定义
格式说明
#define 宏名 ( 形参表 ) 替换文本
#define PI 3.14159#define S(r) PI* r * r
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 ;
#define PI 3.14159
#define S(r) PI*(r)*(r) 宏替换 :
area = 3.14159*(a+b)*(a+b) ;
G_kn2007@ 东北大学计算机基础教研室 77
2.#define 命令进行宏定义带参数的宏定义
格式说明
#define 宏名 ( 形参表 ) 替换文本
① #define SQUARE(x) x*x
② #define SQUARE(x) (x)*(x)
③ #define SQUARE(x) ((x)*(x))
若 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))
若 a=SQUARE(n+1)
宏展开 ① a=n+1*n+1
② a=(n+1)*(n+1)
③ a=((n+1)*(n+1))
出错出错
G_kn2007@ 东北大学计算机基础教研室 78
2.#define 命令进行宏定义带参数的宏定义
格式带参数的宏与函数的区别
参数处理函数调用时 ,先求出实参表达式的值 ,再代入形参带参数的宏定义只是进行简单的字符替换
内存分配函数调用是在程序运行时处理 , 分配临时的内存单元宏展开是在编译时进行的 , 在展开时不分配内存单元
类型定义 对函数的形参和实参都要定义类型 , 且要求一致宏不存在类型问题 , 宏名无类型 , 其参数也无类型
#define 宏名 ( 形参表 ) 替换文本
G_kn2007@ 东北大学计算机基础教研室 79
2.#define 命令进行宏定义带参数的宏定义
格式带参数的宏与函数的区别
返回值调用函数只可得到一个返回值 使用宏可以设法得到几个结果
长度变化函数调用不会使源程序变长宏展开会使源程序增长
运行时间函数调用占用运行时间 , 宏展开不占运行时间 , 只占编译时间
#define 宏名 ( 形参表 ) 替换文本
G_kn2007@ 东北大学计算机基础教研室 80
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*Rmain() { 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); }宏展开后:main() { float 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); }
G_kn2007@ 东北大学计算机基础教研室 81
3. “文件包含”处理命令 include文件包含的概念
用 #include命令把另一个文件的整个内容嵌入进来
文件包含的两种格式格式 #include “文件标识”
文件标识可以包含有文件路径 ( 即文件在哪个目录下 )
使用方式 系统先在引用被包含文件的源文件所在的目录下寻找被包含文件,如果找不到 , 系统再按指定的标准方式检索其他目录 , 直到找到为止。
通常使用用户自己编写的文件时用 “ ”
G_kn2007@ 东北大学计算机基础教研室 82
3. “文件包含”处理命令 include 文件包含的两种格式
格式 #include <文件标识 > 使用方式
系统只按规定的标准方式检索文件目录 通常使用系统提供的标准头文件时用 < >
说明 文件包含的作用就是在编译预处理时将被包含文件的全部内容复制并插入到 #include命令处
一个 include命令只能指定一个被包含文件 . 如果有 n 个被包含文件则需要用 n 个 include命令 , 且一个命令占一行
使用文件包含时 , 在被包含文件中绝对不能有 main函数 文件包含可以嵌套使用 被包含文件中的全局变量在其所在的文件中有效
G_kn2007@ 东北大学计算机基础教研室 83
注 : 文件 file1.c, file2.c, file3.c 都在 tc 目录下的 include 目录中
3. “文件包含”处理命令 include 例文件 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) ; }
文件 file2.cint max (int a , int b){ return(a>b? a : b ) ; }
文件 file3.cint min (int a , int b){ return(a<b? a : b ) ; }
文件 stdio.h 的内容
int min (int a , int b) { return(a<b? a : b ) ; }
int max (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) ; }
G_kn2007@ 东北大学计算机基础教研室 84
3. “文件包含”处理命令 include 例文件 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) ; }
文件 file2.cint 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 ) ; }
文件 stdio.h 的内容
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) ; }
int max (int a , int b){ return(a>b? a : b ) ; }
G_kn2007@ 东北大学计算机基础教研室 85
4.条件编译条件编译概念
对源程序中的一部分内容在满足一定条件时才进行编译
即对一部分内容指定编译的条件形式
#ifdef 标识符 程序段 1 #else 程序段 2#endif
作用: 指定的标识符已经被 #define命令定义过,则在程序编译阶段只编译程序段 1 ,否则只编译程序段 2。特别地: #else 部分可以没有
G_kn2007@ 东北大学计算机基础教研室 86
4.条件编译条件编译概念
形式#ifndef 标识符 程序段 1 #else 程序段 2 #endif
作用: 若指定的标识符没有被 #define命令定义过 , 则编译程序段 1,否则编译程序段 2。
#if 表达式 程序段 1#else 程序段 2 #endif
作用: 当表达式的值为真时编译程序段 1 ,否则编译程序段 2。
G_kn2007@ 东北大学计算机基础教研室 87
4.条件编译条件编译概念
例 #define LETTER 1main(){ 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); }}