文档详情

谭浩强-C语言教程顶级版结构体课件

汽***
实名认证
店铺
PPT
1.35MB
约162页
文档ID:571998171
谭浩强-C语言教程顶级版结构体课件_第1页
1/162

第第1111章章 结构体与共用体结构体与共用体11.1 概述11.2 定义结构体类型变量的方法11.3 结构体变量的引用11.4 结构体变量的初始化11.5 结构体数组11.6 指向结构体类型数据的指针11.7 用指针处理链表11.8 共用体11.9 枚举类型11.10 用typedef定义类型习题 11.1 概述 概述迄今为止,已介绍了基本类型迄今为止,已介绍了基本类型(或称简单类型或称简单类型)的变量的变量(如整型、实型、字符型变量等如整型、实型、字符型变量等),也介绍了一种构造,也介绍了一种构造类型数据类型数据——数组,数组中的各元素是属于同一个类数组,数组中的各元素是属于同一个类型的但是只有这些数据类型是不够的有时需要将型的但是只有这些数据类型是不够的有时需要将不同类型的数据组合成一个有机的整体,以便于引用不同类型的数据组合成一个有机的整体,以便于引用这些组合在一个整体中的数据是互相联系的例如,这些组合在一个整体中的数据是互相联系的例如,一个学生的学号、姓名、性别、年龄、成绩、家庭地一个学生的学号、姓名、性别、年龄、成绩、家庭地址等项这些项都与某一学生相联系见图址等项这些项都与某一学生相联系。

见图11.1可以看到性别以看到性别(sex)、年龄、年龄(age)、成绩、成绩(score)、地址、地址(addr)是属于学号为是属于学号为10010和名为和名为“Li Fun”的学生的的学生的如果将如果将num、、name、、sex、、age、、score、、addr分别定义分别定义为互相独立的简单变量,难以反映它们之间的内在联为互相独立的简单变量,难以反映它们之间的内在联系 应当把它们组织成一个组合项,在一个组合项中包应当把它们组织成一个组合项,在一个组合项中包含若干个类型不同含若干个类型不同(当然也可以相同当然也可以相同)的数据项的数据项C语言允许用户自己指定这样一种数据结构,它称语言允许用户自己指定这样一种数据结构,它称为结构体为结构体(structure)它相当于其他高级语言中它相当于其他高级语言中的的“记录记录”图图11.1 假设程序中要用到图假设程序中要用到图11.1所表示的数据结构,但是所表示的数据结构,但是C语言没有提供这种现成的数据类型,因此用户必语言没有提供这种现成的数据类型,因此用户必须要在程序中建立所需的结构体类型例如:须要在程序中建立所需的结构体类型例如:struct  student {int num;; char  name[20];; char  sex;; int  age;; float  score;; char  addr[30];; };; 注意不要忽略最后的分号。

上面由程序设计者指定注意不要忽略最后的分号上面由程序设计者指定了一个新的结构体类型了一个新的结构体类型struct student(struct是声明是声明结构体类型时所必须使用的关键字,不能省略结构体类型时所必须使用的关键字,不能省略),,它向编译系统声明这是一个它向编译系统声明这是一个“结构体类型结构体类型”,它,它包括包括num、、name、、sex、、age、、score、、addr等不同等不同类型的数据项应当说明类型的数据项应当说明struct student是一个类是一个类型名,它和系统提供的标准类型型名,它和系统提供的标准类型(如如int、、char、、float、、double等等)一样具有同样的地位和作用,都一样具有同样的地位和作用,都可以用来定义变量的类型,只不过结构体类型需可以用来定义变量的类型,只不过结构体类型需要由用户自己指定而已要由用户自己指定而已声明一个结构体类型的一般形式为声明一个结构体类型的一般形式为struct结构体名结构体名{成员表列成员表列};; “结构体名结构体名” 用作结构体类型的标志,它又称用作结构体类型的标志,它又称“结构体标记结构体标记”(structure tag) 。

上面的结构体声明中上面的结构体声明中student就是结构就是结构体名体名(结构体标记结构体标记)大括弧内是该结构体中的各个成员,大括弧内是该结构体中的各个成员,由它们组成一个结构体例如,上例中的由它们组成一个结构体例如,上例中的num、、name、、sex等都是成员对各成员都应进行类型声明,即等都是成员对各成员都应进行类型声明,即类型名成员名类型名成员名也可以把也可以把“成员表列成员表列”称为称为“域表域表”每一个成员也称为结每一个成员也称为结构体中的一个域成员名定名规与变量名同构体中的一个域成员名定名规与变量名同结构体结构体”这个词是根据英文单词这个词是根据英文单词structure译出的有些译出的有些C语言书把语言书把structure直译为直译为“结构结构”作者认为译作作者认为译作“结构结构”会与一般含义上的会与一般含义上的“结构结构”混淆混淆(例如,数据结构、程例如,数据结构、程序结构、控制结构等序结构、控制结构等)日本把structure译作译作“结构体结构体”或或“构造体构造体”,作者认为译作,作者认为译作“结构体结构体”比译作比译作“结构结构”更更确切一些,不致与一般含义上的确切一些,不致与一般含义上的“结构结构”混淆。

混淆 11.2 定义结构体类型变量的方法 定义结构体类型变量的方法前面只是指定了一个结构体类型,它相当于一个模型,但其前面只是指定了一个结构体类型,它相当于一个模型,但其中并无具体数据,系统对之也不分配实际内存单元为了中并无具体数据,系统对之也不分配实际内存单元为了能在程序中使用结构体类型的数据,应当定义结构体类型能在程序中使用结构体类型的数据,应当定义结构体类型的变量,并在其中存放具体的数据可以采取以下三种方的变量,并在其中存放具体的数据可以采取以下三种方法定义结构体类型变量法定义结构体类型变量1.  先声明结构体类型再定义变量名 先声明结构体类型再定义变量名如上面已定义了一个结构体类型如上面已定义了一个结构体类型struct student,可以用它来,可以用它来定义变量如定义变量如:struct student student1, student2结构体类型名结构体类型名 结构体变量名结构体变量名;定义了定义了student1和和student2为为struct student类型的变量,即它类型的变量,即它们具有们具有struct student类型的结构。

如图类型的结构如图11.2所示 图图11.2在定义了结构体变量后,系统会为之分配内存单元在定义了结构体变量后,系统会为之分配内存单元例如例如student1和和student2在内存中各占在内存中各占59个字节个字节(2+20+1+2+4+30=59)应当注意,将一个变量定义为标准类型应当注意,将一个变量定义为标准类型(基本数据类基本数据类型型)与定义为结构体类型不同之处在于后者不仅要求与定义为结构体类型不同之处在于后者不仅要求指定变量为结构体类型,而且要求指定为某一特定指定变量为结构体类型,而且要求指定为某一特定的结构体类型的结构体类型(例如例如struct student类型类型)因为可以因为可以定义出许许多多种具体的结构体类型而在定义变定义出许许多多种具体的结构体类型而在定义变量为整型时,只需指定为量为整型时,只需指定为int型即可 如果程序规模比较大,往往将对结构体类型的声明如果程序规模比较大,往往将对结构体类型的声明集中放到一个文件集中放到一个文件(以.以.h为后缀的为后缀的“头文件头文件”)中哪个源文件需用到此结构体类型则可用哪个源文件需用到此结构体类型则可用#include命令将该头文件包含到本文件中。

这样做便于装命令将该头文件包含到本文件中这样做便于装配,便于修改,便于使用配,便于修改,便于使用2. 在声明类型的同时定义变量 在声明类型的同时定义变量  例如:  例如:     struct  student       {int  num;; char  name[20];; char  sex;; int  age;; float score;; char  addr[30];;       }student1,,student2;;它的作用与第一种方法相同,即定义了两个它的作用与第一种方法相同,即定义了两个struct student类型的变量类型的变量student1、、student2这种形式这种形式的定义的一般形式为的定义的一般形式为struct 结构体名 结构体名            {          成员表列       成员表列          }变量名表列;变量名表列; 3.  直接定义结构类型变量 直接定义结构类型变量  其一般形式为  其一般形式为struct{        成员表列 成员表列       }变量名表列;即不出现结构体名。

变量名表列;即不出现结构体名  关于结构体类型,有几点要说明:  关于结构体类型,有几点要说明:(1) 类型与变量是不同的概念,不要混同只能对类型与变量是不同的概念,不要混同只能对变量赋值、存取或运算,而不能对一个类型赋值、变量赋值、存取或运算,而不能对一个类型赋值、存取或运算在编译时,对类型是不分配空间的,存取或运算在编译时,对类型是不分配空间的,只对变量分配空间只对变量分配空间 (2) 对结构体中的成员对结构体中的成员(即即“域域”),可以单独使用,,可以单独使用,它的作用与地位相当于普通变量关于对成员的它的作用与地位相当于普通变量关于对成员的引用方法见引用方法见11.3节3) 成员也可以是一个结构体变量成员也可以是一个结构体变量如:如:struct  date /*声明一个结构体类型声明一个结构体类型*/      {int  month;; int  day;; int  year;;       };;      struct  student       {int  num;; char  name[20];; char  sex;; int  age;; struct date birthday;; /*birthday是是struct date类型类型*/ char  addr[30];;       }student1,,student2;;先声明一个先声明一个struct date类型,它代表类型,它代表“日期日期”,包括,包括3个成员:个成员:month(月月)、、day(日日)、、year(年年)。

然后然后在声明在声明struct student类型时,将成员类型时,将成员birthday  指定为 指定为struct date类型struct student的结构见图的结构见图11.3所示已声明的类型所示已声明的类型struct date与其他类型与其他类型(如如int,,char)一样可以用来定义成员的类型一样可以用来定义成员的类型4)成员名可以与程序中的变量名相同成员名可以与程序中的变量名相同,二者不代表二者不代表同一对象例如同一对象例如,程序中可以另定义一个变量程序中可以另定义一个变量num,它与它与struct student中的中的num是两回事是两回事,互不干扰互不干扰图图11.3 11.3 结构体变量的引用 结构体变量的引用在定义了结构体变量以后在定义了结构体变量以后,当然可以引用这个变量当然可以引用这个变量但应遵守以下规则但应遵守以下规则:(1) 不能将一个结构体变量作为一个整体进行输入不能将一个结构体变量作为一个整体进行输入和输出例如和输出例如,已定义已定义student1和和student2为结构为结构体变量并且它们已有值不能这样引用体变量并且它们已有值不能这样引用:printf ("%d,%s,%c,%d,%f,%s\n",student1);只能对结构体变量中的各个成员分别进行输入和输只能对结构体变量中的各个成员分别进行输入和输出。

引用结构体变量中成员的方式为出引用结构体变量中成员的方式为结构体变量名结构体变量名.成员名成员名例如例如: student1.num表示表示student1变量中的变量中的num成员成员,即即student1的的num(学号学号)项可以对变量的成员项可以对变量的成员 赋值赋值,例如例如:student1.num=10010;“.”是成员是成员(分量分量)运算符运算符,它在所有的运算符中优它在所有的运算符中优先级最高先级最高,因此可以把因此可以把student 1.num作为一个整体作为一个整体来看待上面赋值语句的作用是将整数来看待上面赋值语句的作用是将整数10010赋给赋给student 1变量中的成员变量中的成员num2) 如果成员本身又属一个结构体类型如果成员本身又属一个结构体类型,则要用若干则要用若干个成员运算符个成员运算符,一级一级地找到最低的一级的成员一级一级地找到最低的一级的成员只能对最低级的成员进行赋值或存取以及运算只能对最低级的成员进行赋值或存取以及运算例如例如,对上面定义的结构体变量对上面定义的结构体变量student1, 可以这样可以这样访问各成员访问各成员:student1.num student1.birthday.month注意注意:不能用不能用student1.birthday来访问来访问student1变量中的成员变量中的成员birthday,因为因为birthday本身是一个结构体变量。

本身是一个结构体变量3) 对结构体变量的成员可以像普通变量一样进行各种运算对结构体变量的成员可以像普通变量一样进行各种运算(根据其类型决定可以进行的运算根据其类型决定可以进行的运算)例如:student2..score=student1..score;;    sum=student1..score+student2..score;;    student1..age++;;    ++student1..age;;由于由于“..”运算符的优先级最高,因此运算符的优先级最高,因此student1..age++是对是对student1..age进行自加运算,而不是先对进行自加运算,而不是先对age进行自加运进行自加运算4) 可以引用结构体变量成员的地址,也可以引用结可以引用结构体变量成员的地址,也可以引用结构体变量的地址如:构体变量的地址如:scanf("%d",,&student1.num); (输入输入student1..num的值的值)printf("%o",,&student1);; (输出输出   student1的首地址的首地址)但不能用以下语句整体读入结构体变量,但不能用以下语句整体读入结构体变量,如:如:scanf("%d,,%s,,%c,,%d,,%f,,%s",,&student1);;结构体变量的地址主要用于作函数参数,传递结构体的地址。

结构体变量的地址主要用于作函数参数,传递结构体的地址11.4 结构体变量的初始化 结构体变量的初始化和其他类型变量一样,对结构体变量可以在定义时和其他类型变量一样,对结构体变量可以在定义时指定初始值指定初始值例例11.1对结构体变量初始化对结构体变量初始化 main() {struct student     {long int num;; char name[20];; char sex;; char addr[20];; }a={89031,,"Li Lin",,'M',,"123 Beijing Road"};;     printf("NO.:%ld\nname:%s\nsex:%c\naddress:%s\n",,a.nu,,a.sex,,a.addr);     }   运行结果如下:  运行结果如下:No.::89031    name::Li Lin    sex::M    address::123 Beijing Road11.5 结构体数组 结构体数组一个结构体变量中可以存放一组数据一个结构体变量中可以存放一组数据(如一个学生的如一个学生的学号、姓名、成绩等数据学号、姓名、成绩等数据)。

如果有如果有10个学生的数个学生的数据需要参加运算,显然应该用数组,这就是结构体据需要参加运算,显然应该用数组,这就是结构体数组结构体数组与以前介绍过的数值型数组不同数组结构体数组与以前介绍过的数值型数组不同之处在于每个数组元素都是一个结构体类型的数之处在于每个数组元素都是一个结构体类型的数  据,它们都分别包括各个成员 据,它们都分别包括各个成员(分量分量)项11.5.1 定义结构体数组 定义结构体数组和定义结构体变量的方法相仿,只需说明其为数组和定义结构体变量的方法相仿,只需说明其为数组即可如:即可如:struct student      {int num;; char name[20];; char sex;; int age;; float score;; char addr[30];;      };;        struct student  stu[3];;以上定义了一个数组以上定义了一个数组stu,其元素为,其元素为struct student类类型数据,数组有型数据,数组有3个元素也可以直接定义一个结个元素。

也可以直接定义一个结构体数组,如:构体数组,如:    struct student {int num;; … }stu[3];;或或 struct {int num;;… }stu[3];;图图11.4 数组各元素在内存中连续存数组各元素在内存中连续存放,见图放,见图11.5示意11.5.2结构体数组的初始化结构体数组的初始化与其他类型的数组一样,与其他类型的数组一样,对结构体数组可以初始化对结构体数组可以初始化如:如:     struct  student        {int  num;; char  name[20];; char  sex;; int  age;;图图11.5 float  score;; char  add[30];;        }stu[3]={{10101,,“Li Lin”,,‘M’,,18,,87..5,,“103 Beijing Road”},,{10102,,“Zhang Fun”,,‘M’,,19,,99,,“130 Shanghai Road”},,{10104,,“WWang Min”,,‘F’,,20,,78..5,,“1010 Zhongshan Road”}};;定义数组定义数组stu时,元素个数可以不指定,即写成以下时,元素个数可以不指定,即写成以下形式:形式:stu[  ]= {…},,{…},,{…} ;编译时,系统;编译时,系统会根据给出初值的结构体常量的个数来确定数组会根据给出初值的结构体常量的个数来确定数组元素的个数。

元素的个数 当然,数组的初始化也可以用以下形式:当然,数组的初始化也可以用以下形式:struct  student       {int  num;;… };;   struct  student  stu[  ]={{…},,{…},,{…}};;即先声明结构体类型,然后定义数组为该结构体类即先声明结构体类型,然后定义数组为该结构体类型,在定义数组时初始化从以上可以看到,结型,在定义数组时初始化从以上可以看到,结构体数组初始化的一般形式是在定义数组的后面构体数组初始化的一般形式是在定义数组的后面加上:加上:={初值表列初值表列};; 11.5.3 结构体数组应用举例 结构体数组应用举例下面举一个简单的例子来说明结构体数组的定义和下面举一个简单的例子来说明结构体数组的定义和引用例例11.2对候选人得票的统计程序设有对候选人得票的统计程序设有3个候选人,个候选人,每次输入一个得票的候选人的名字,要求最后输每次输入一个得票的候选人的名字,要求最后输出各人得票结果出各人得票结果程序如下:程序如下:#include struct person    {char name[20];; int count;;    } leader[3]={"Li",,0,,"Zhang",,0,,"Fun",,0};;    main()    {int  i,,j;; char  leader-name[20];; for  (i=1;;i<<=10;;i++) {scanf("%s",,leader-name);; for(j=0;;j<<3;;j++) if(strcmp(leader-name,,leader[j].name)==0) leader[j].count++: }   printf("\n");;   for(i=0;;i<<3;;i++)         printf("%5s:%d\n",,leader[i].name,,leader[i].count); }  运行情况如下:  运行情况如下: Li    Li    Fun    Zhang    Zhang    Fun     Li    Fun    Zhang    Li    Li∶ ∶4    Zhang∶ ∶3    Fun∶ ∶3程序定义一个全局的结构体数组程序定义一个全局的结构体数组leader,它有,它有3个元个元素,每一元素包含两个成员素,每一元素包含两个成员name(姓名姓名)和和count(票数票数)。

在定义数组时使之初始化,使在定义数组时使之初始化,使3位候选人位候选人的票数都先置零见图的票数都先置零见图11.6 图图11.6在主函数中定义字符数组在主函数中定义字符数组leader-name,它代表被选,它代表被选人的姓名,在人的姓名,在10次循环中每次先输入一个被选人次循环中每次先输入一个被选人的具体人名,然后把它与的具体人名,然后把它与3个候选人姓名相比,看个候选人姓名相比,看它和哪一个候选人的名字相同注意它和哪一个候选人的名字相同注意leader-name是和是和leader[j]..name相比,相比,leader[j]是数组是数组leader的第的第j个元素,它包含两个成员项,个元素,它包含两个成员项,leader-name应应该和该和leader数组第数组第j个元素的个元素的name成员相比成员相比 若若j为某一值时,输入的姓名与为某一值时,输入的姓名与leader[j]..name相等,相等,就执行就执行“leader[j]..count++”,由于成员运算符,由于成员运算符“·”优先于自增运算符优先于自增运算符“++”,因此它相当于,因此它相当于(leader[j]..count)++,使,使leader[j]的成员的成员count的值加的值加1。

在输入和统计结束之后,将在输入和统计结束之后,将3人的名字人的名字和得票数输出和得票数输出11.6 指向结构体类型数据的指针 指向结构体类型数据的指针一个结构体变量的指针就是该变量所占据的内存段一个结构体变量的指针就是该变量所占据的内存段的起始地址可以设一个指针变量,用来指向一的起始地址可以设一个指针变量,用来指向一个结构体变量,此时该指针变量的值是结构体变个结构体变量,此时该指针变量的值是结构体变量的起始地址指针变量也可以用来指向结构体量的起始地址指针变量也可以用来指向结构体数组中的元素数组中的元素 11.6.1 指向结构体变量的指针 指向结构体变量的指针下面通过一个简单例子来说明指向结构体变量的指下面通过一个简单例子来说明指向结构体变量的指针变量的应用针变量的应用例例11.3指向结构体变量的指针的应用指向结构体变量的指针的应用include main()   {struct  student {long  num;; char  name[20];; char sex;; float score;; };; struct  student  stu-1;; struct  student  * p;; p=&stu-1; stu-1..num=89101;; strcpy(stu-1..name,,"Li Lin");; stu-1..sex='M';; stu-1..score=89..5;; printf("No.:%ld\nname:%s\nsex:%c\nscore:%f\n",,stu-1.num,,stu-1.name,,stu-1.sex,,stu-1.score); printf("No.:%ld\nname:%s\nsex:%c\nscore:%f\n",,(*p).num,,(*p).name,,(*p).sex,, (*p).score); }在主函数中声明了在主函数中声明了struct student类型,然后定义一类型,然后定义一个个struct student类型的变量类型的变量stu-1。

同时又定义一同时又定义一个指针变量个指针变量p,它指向一个,它指向一个struct student类型的数类型的数据在函数的执行部分将结构体变量据在函数的执行部分将结构体变量stu-1的起始的起始地址赋给指针变量地址赋给指针变量p,也就是使,也就是使p指向指向stu-1(见图见图11.7),然后对,然后对stu-1的各成员赋值谝桓鰌的各成员赋值谝桓鰌rintf函函数是输出数是输出stu-1的各个成员的值用的各个成员的值用stu-1..num表表示示stu-1中的成员中的成员num,余类推第二个,余类推第二个printf函数函数也是用来输出也是用来输出stu-1各成员的值,但使用的是各成员的值,但使用的是   (*p)..num这样的形式这样的形式p)表示表示p指向的结构指向的结构体变量,体变量,(*p)..num是是p指向的结构体变量中指向的结构体变量中的成员的成员num注意*p两两侧的括弧不可省,因侧的括弧不可省,因稍痹怂惴稍痹怂惴“..”优优先于先于“*”运算符,运算符,*p..num就等价于就等价于*(p..num)了图图11.7 程序运行结果如下:程序运行结果如下:No.:.:89101                 name::Li Lin     sex::M score::89..500000No::89101name::Li Lin sex::Mscore::89..500000可见两个可见两个printf函数输出的结果是相同的。

函数输出的结果是相同的 在在C语言中,为了使用方便和使之直观,可以把语言中,为了使用方便和使之直观,可以把(*p)..num改用改用p—>>num来代替,它表示来代替,它表示*p所指所指向的结构体变量中的向的结构体变量中的num成员同样,成员同样,(*p).name等价于等价于p—>>name也就是说,以下三种形式等也就是说,以下三种形式等价:价:①① 结构体变量.成员名结构体变量.成员名②② (*p).成员名.成员名③③ p->成员名成员名上面程序中最后一个上面程序中最后一个printf函数中的输出项表列可以函数中的输出项表列可以改写为改写为p->num,,p->name,,p->sex,,p->score 其其中中->称为指向运算符称为指向运算符请分析以下几种运算:请分析以下几种运算: p->n得到得到p指向的结构体变量中的成员指向的结构体变量中的成员n的值p->n++ 得到 得到p指向的结构体变量中的成员指向的结构体变量中的成员n的值,的值,用完该值后使它加用完该值后使它加1    ++p->n 得到 得到p指向的结构体变量中的成员指向的结构体变量中的成员n的的值使之加值使之加1(先加先加)。

11.6.2 指向结构体数组的指针 指向结构体数组的指针以前已经介绍过,可以使用指向数组或数组元素的以前已经介绍过,可以使用指向数组或数组元素的指针和指针变量同样,对结构体数组及其元素指针和指针变量同样,对结构体数组及其元素也可以用指针或指针变量来指向例也可以用指针或指针变量来指向例11.4指向结构指向结构体数组的指针的应用体数组的指针的应用 struct student    {int num;; char name[20];; char sex;; int age;;     };;     struct student stu[3]={{10101,,“Li Lin”,,‘M’,,18},,{10102,,“Zhang Fun”,,‘M’,,19},,{10104,,“WWang Min”,,‘F’,,20}};;main()     {struct student*p;; printf(" No.Namesexage\n");; for  (p=stu;;p<<stu+3;;p++)printf("%5d %-20s %2c %4d\n",,p->num,, p->name,, p->sex,, p->age); }运行结果如下:运行结果如下: No. .  Name   sex age   10101   Li Lin   M   18 10102   Zhang Fun M   19   10104  W Wang Min F   20 p是指向是指向struct student结构结构体类型数据的指针变量。

体类型数据的指针变量在在for语句中先使语句中先使p的初值的初值为为stu,也就是数组,也就是数组stu的的起始地址,见图起始地址,见图11.8中中p的指向在第一次循环中的指向在第一次循环中输出输出stu[0]的各个成员值的各个成员值然后执行然后执行p++,使,使p自加自加1p加加1意味着意味着p所增加的值所增加的值为结构体数组为结构体数组stu的一个的一个元素所占的字节数元素所占的字节数(在本在本例中为例中为2+20+1+2=25字节字节)执行p++后后p的值等的值等图图11.811.8  于 于stu+1,,p指向指向stu[1]的起始地址,见图的起始地址,见图11.8中中p‘的指向在第二次循环中输出的指向在第二次循环中输出stu[1]的各成员值的各成员值在执行在执行p++后,后,p的值等于的值等于stu+2,它的指向见图,它的指向见图11.8中的中的p″再输出stu[2]的各成员值的各成员值在执行在执行p++后,后,p的值变为的值变为stu+3,, 已不再小于已不再小于stu+3了,不再执行循环了,不再执行循环注意以下两点:注意以下两点:(1) 如果如果p的初值为的初值为stu,即指向第一个元素,则,即指向第一个元素,则p加加1后后p就指向下一个元素的起始地址。

例如:就指向下一个元素的起始地址例如:(++p)->num 先使 先使p自加自加1,然后得到它指向的元素,然后得到它指向的元素中的中的num成员值成员值(即即10102) (p++)->num 先得到 先得到p->num的值的值(即即10101),然后,然后使使p自加自加1,指向,指向stu[1]请注意以上二者的不同请注意以上二者的不同2) 程序已定义了程序已定义了p是一个指向是一个指向struct student类型数类型数据的指针变量,它用来指向一个据的指针变量,它用来指向一个struct student型型的数据的数据(在例在例11.4中中p的值是的值是stu数组的一个元素数组的一个元素(如如stu[0],stu[1])的起始地址的起始地址),不应用来指向,不应用来指向stu数组数组元素中的某一成员例如,下面的用法是不对的:元素中的某一成员例如,下面的用法是不对的:p=stu[1]..name编译时将给出警告信息,表示地址的类型不匹配编译时将给出警告信息,表示地址的类型不匹配千万不要认为反正千万不要认为反正p是存放地址的,可以将任何地是存放地址的,可以将任何地  址赋给它如果地址类型不相同,可以用强制类 址赋给它。

如果地址类型不相同,可以用强制类型转换例如:型转换例如:p=(struct  student *)stu[0]..name;;此时,此时,p的值是的值是stu[0] 元素的元素的name成员的起始地址成员的起始地址可以用可以用“printf("%s",p);;”输出输出stu[0]中成员中成员name的值,但是,的值,但是,p仍保持原来的类型执行仍保持原来的类型执行“printf("%s",p+1);;”,则会输出,则会输出stu[1]中中name的值执行的值执行p+1时,时,p的值增加了结构体的值增加了结构体struct student的长度 11.6.3 用结构体变量和指向结构体的指针作函数参数 用结构体变量和指向结构体的指针作函数参数将一个结构体变量的值传递给另一个函数,有将一个结构体变量的值传递给另一个函数,有3种方种方法:法:(1) 用结构体变量的成员作参数例如,用用结构体变量的成员作参数例如,用stu[1]..num或或stu[2]..name作函数实参,将实参值传给形作函数实参,将实参值传给形参用法和用普通变量作实参是一样的,属于参用法和用普通变量作实参是一样的,属于“值值传递传递”方式。

应当注意实参与形参的类型保持一致应当注意实参与形参的类型保持一致2) 用结构体变量作实参老版本的用结构体变量作实参老版本的C系统不允许用结系统不允许用结构体变量作实参,构体变量作实参,ANSI C取消了这一限制但是用取消了这一限制但是用结构体变量作实参时,采取的是结构体变量作实参时,采取的是“值传递值传递”的方的方  式,将结构体变量所占的内存单元的内容全部顺序传 式,将结构体变量所占的内存单元的内容全部顺序传递给形参形参也必须是同类型的结构体变量在函递给形参形参也必须是同类型的结构体变量在函数调用期间形参也要占用内存单元这种传递方式在数调用期间形参也要占用内存单元这种传递方式在空间和时间上开销较大,如果结构体的规模很大时,空间和时间上开销较大,如果结构体的规模很大时,开销是很可观的此外,由于采用值传递方式,如果开销是很可观的此外,由于采用值传递方式,如果在执行被调用函数期间谋淞诵尾在执行被调用函数期间谋淞诵尾?也是结构体变量也是结构体变量)的的值,该值不能返回主调函数,这往往造成使用上的不值,该值不能返回主调函数,这往往造成使用上的不便因此一般较少用这种方法因此一般较少用这种方法3) 用指向结构体变量用指向结构体变量(或数组或数组)的指针作实参,将结构体的指针作实参,将结构体变量变量(或数组或数组)的地址传给形参。

的地址传给形参例例11.5有一个结构体变量有一个结构体变量stu,内含学生学号、姓名和,内含学生学号、姓名和3门门课的成绩要求在课的成绩要求在main函数中赋以值,在另一函数函数中赋以值,在另一函数print中将它们打印输出今用结构体变量作函数参数中将它们打印输出今用结构体变量作函数参数 #include #define FORMAT "%d\n%s\n%f\n%f\n%f\n"    struct student {int num;; char name[20];; float score[3];; };; main() {void print(struct student);; struct student stu;; stu..num=12345;; strcpy(stu..name,,"Li Li");; stu..score[0]=67..5;; stu..score[1]=89;; stu..score[2]=78..6;; print(stu);; }  void print(struct student stu)     {printf(FORMAT,,stu.num,,stu.name,, stu.score[0],, stu.score[1],,stu.score[2]);; printf("\n");;    } 运行结果为:运行结果为: 12345    Li Li    67..500000    89..000000 78..599998struct student被定义为外部类型,这样,同一源文被定义为外部类型,这样,同一源文件中的各个函数都可以用它来定义变量的类型。

件中的各个函数都可以用它来定义变量的类型main函数中的函数中的stu定义为定义为struct student类型变量,类型变量,print函数中的形参函数中的形参stu也定义为也定义为struct student类型类型变量在main函数中对函数中对stu的各成员赋值在调用的各成员赋值在调用print函数时以函数时以stu为实参向形参为实参向形参stu实行实行“值传递值传递”  在 在print函数中输出结构体变量函数中输出结构体变量stu各成员的值各成员的值例例11.6将上题改用指向结构体变量的指针作实参将上题改用指向结构体变量的指针作实参可以在上面程序的基础上作少量修改即可请注意可以在上面程序的基础上作少量修改即可请注意程序注释程序注释define FORMAT "%d\n%s\n%f\n%f\n%f\n"    struct student {int num;; char name[20];; float score[3];;} stu={12345,,"Li Li",,67.5,,89,,78.6};; main(){ void print(struct student *); /*形参类型修改成指向结形参类型修改成指向结构体的指针变量构体的指针变量*/ print(&stu);; /*实参改为实参改为stu的起始地址的起始地址*/ }  void print(struct student *p) /*形参类型修改了形参类型修改了*/     {printf(FORMAT,,p->num,,p->name,,p->score[0],,p->score[1],,p->score[2]);;/*用指针变量用指针变量调用各成员之值调用各成员之值*/ printf("\n");;     } 此程序改用在定义结构体变量此程序改用在定义结构体变量stu时赋初值,这样程序可简化些。

时赋初值,这样程序可简化些print函数中的形图函数中的形图11.9参参p被定被定义为指向义为指向struct student类型数据类型数据的指针变量注意在调用的指针变量注意在调用print函数时,用结构体变量函数时,用结构体变量stu的起的起始地址始地址&stu作实参在调用函数作实参在调用函数时将该地址传送给形参时将该地址传送给形参p(p是指是指针变量针变量)这样p就指向就指向stu,见,见图图11.9在print函数中输出函数中输出p所所指向的结构体变量的各个成员值,指向的结构体变量的各个成员值,它们也就是它们也就是stu的成员值的成员值图图11.9 main函数中的对各成员赋值也可以改用函数中的对各成员赋值也可以改用scanf函数输函数输入即用scanf("%d%s%f%f%f",,&stu..num,,stu..name,,&stu..score[0],,&stu..score[1],,&stu..score[2]);;  输入时用下面形式输入:  输入时用下面形式输入:12345Li-Li67..58978..6注意:输入项表列中注意:输入项表列中stu..name前没有前没有“&”符号,符号,因为因为stu..name是字符数组名,本身代表地址,不是字符数组名,本身代表地址,不应写成应写成&stu..name。

用指针作函数参数比较好,能提高运行效率用指针作函数参数比较好,能提高运行效率 11.7 用指针处理链表 用指针处理链表11.7.1 链表概述链表是一种常见的重要的数据结构它是动态地进行存储分配的一种结构我们知道,用数组存放数据时,必须事先定义固定的长度(即元素个数)比如,有的班级有100人,而有的班只有30人,如果要用同一个数组先后存放不同班级的学生数据,则必须定义长度为100的数组如果事先难以确定一个班的最多人数,则必须把数组定得足够大,以能存放任何班级的学生数据显然这将会浪费内存链表则没有这种缺点,它根据需要开辟内存单元图11.10表示最简单的一种链表(单向链表)的结构 图图11.10链表有一个链表有一个“头指针头指针”变量,图中以变量,图中以head表示,它存放表示,它存放一个地址该地址指向一个元素链表中每一个元素一个地址该地址指向一个元素链表中每一个元素称为称为“结点结点”,每个结点都应包括两个部分:一为用,每个结点都应包括两个部分:一为用户需要用的实际数据,二为下一个结点的地址可以户需要用的实际数据,二为下一个结点的地址可以看出,看出,head指向第一个元素;第一个元素又指向第二指向第一个元素;第一个元素又指向第二个元素个元素……直到最后一个元素,该元素不再指向其他直到最后一个元素,该元素不再指向其他元素,它称为元素,它称为“表尾表尾”,它的地址部分放一个,它的地址部分放一个“NULL”(表示表示“空地址空地址”),链表到此结束。

链表到此结束 可以看到链表中各元素在内存中可以不是连续存放可以看到链表中各元素在内存中可以不是连续存放的要找某一元素,必须先找到上一个元素,根的要找某一元素,必须先找到上一个元素,根据它提供的下一元素地址才能找到下一个元素据它提供的下一元素地址才能找到下一个元素如果不提供如果不提供“头指针头指针”(head),则整个链表都无,则整个链表都无法访问链表如同一条铁链一样,一环扣一环,法访问链表如同一条铁链一样,一环扣一环,中间是不能断开的打个通俗的比方:幼儿园的中间是不能断开的打个通俗的比方:幼儿园的老师带领孩子出来散步,老师牵着第一个小孩的老师带领孩子出来散步,老师牵着第一个小孩的手,第一个小孩的另一只手牵着第二个孩子手,第一个小孩的另一只手牵着第二个孩子……这就是一个这就是一个“链链”,最后一个孩子有一只手空着,,最后一个孩子有一只手空着,他是他是“链尾链尾”要找这个队伍,必须先找到老师,要找这个队伍,必须先找到老师,然后顺序找到每一个孩子然后顺序找到每一个孩子可以看到,这种链表的数据结构,必须利用指针变可以看到,这种链表的数据结构,必须利用指针变量才能实现即:一个结点中应包含一个指针变量才能实现。

即:一个结点中应包含一个指针变量,用它存放下一结点的地址量,用它存放下一结点的地址 前面介绍了结构体变量,用它作链表中的结点是最前面介绍了结构体变量,用它作链表中的结点是最合适的一个结构体变量包含若干成员,这些成合适的一个结构体变量包含若干成员,这些成员可以是数值类型、字符类型、数组类型,也可员可以是数值类型、字符类型、数组类型,也可以是指针类型我们用这个指针类型成员来存放以是指针类型我们用这个指针类型成员来存放下一个结点的地址例如,可以设计这样一个结下一个结点的地址例如,可以设计这样一个结构体类型:构体类型:    struct  student      {int  num;;       float  score;;       struct  studentnext;;      };; 其中成员其中成员num和和score用来存放结点中的有用数据用来存放结点中的有用数据(用户需要用到的数据用户需要用到的数据),相当于图,相当于图11.10结点中的结点中的A,,B,,C,,Dnext是指针类型的成员,它指向是指针类型的成员,它指向struct student类型数据类型数据(这就是这就是next所在的结构体所在的结构体类型类型)。

一个指针类型的成员既可以指向其他类型一个指针类型的成员既可以指向其他类型的结构体数据,也可以指向自己所在的结构体类的结构体数据,也可以指向自己所在的结构体类型的数据现在,型的数据现在,next是是struct student类型中的一类型中的一个成员,它又指向个成员,它又指向struct student类型的数据用类型的数据用这种方法就可以建立链表见图这种方法就可以建立链表见图11.11 图图11.11图中每一个结点都属于图中每一个结点都属于struct student类型,它的成类型,它的成员员next存放下一结点的地址,程序设计人员可以存放下一结点的地址,程序设计人员可以不必具体知道各结点的地址,只要保证将下一个不必具体知道各结点的地址,只要保证将下一个结点的地址放到前一结点的成员结点的地址放到前一结点的成员next中即可请中即可请注意:上面只是定义了一个注意:上面只是定义了一个struct student类型,类型,并未实际分配存储空间只有定义了变量才分配并未实际分配存储空间只有定义了变量才分配内存单元内存单元 11.7.2 简单链表 简单链表下面通过一个例子来说明如何建立和输出一个简单下面通过一个例子来说明如何建立和输出一个简单链表。

链表例例11.7 建立一个如图建立一个如图11.11所示的简单链表,它由所示的简单链表,它由3个个学生数据的结点组成输出各结点中的数据学生数据的结点组成输出各结点中的数据 #define NULL 0 struct student {long num; float score; struct student *next; }; main() { struct student a,,b,,c,,*head,,*p;a. num=99101; a.score=89.5;b. num=99103; b.score=90;c. num=99107; c.score=85;/*对结点的对结点的num和和score成成员赋值员赋值*/ head=&a; /*将结点将结点a的起始地址赋给头指的起始地址赋给头指针针head*/ a.next=&b; /*将结点将结点b的起始地址赋给的起始地址赋给a结点结点的的next成员成员*/ b.next=&c; /*将结点将结点c的起始地址赋给的起始地址赋给b结点结点的的next成员成员*/ c.next=NULL; /*c结点的结点的next成员不存放其成员不存放其他结点地址他结点地址*/ p=head; /*使使p指针指向指针指向a结点结点*/ do {printf("%ld %5.1f\n",,p->num,,p->score);/*输出输出p指向的结点的数据指向的结点的数据*/ p=p->next; /*使使p指向下一结指向下一结点点*/ } while(p!=NULL); /*输出完输出完c结点后结点后p的值的值为为NULL*/ } 请读者仔细考虑:请读者仔细考虑:①①各个结点是怎样构成链表的。

各个结点是怎样构成链表的②②没有头指针没有头指针head行不行?行不行?③③p起什么作用?没有起什么作用?没有它行不行?开始时使它行不行?开始时使head指向指向a结点,结点,a.next指向指向b结点,结点,b.next指向指向c结点,这就构成链表关系结点,这就构成链表关系c.next=NULL” 的作用是使的作用是使c.next不指向任何有不指向任何有用的存储单元在输出链表时要借助用的存储单元在输出链表时要借助p,先使,先使p指指向向a结点,然后输出结点,然后输出a结点中的数据,结点中的数据,“p=p->next” 是为输出下一个结点做准备是为输出下一个结点做准备p->next的值的值是是b结点的地址,因此执行结点的地址,因此执行“p=p->next”后后p就指就指向向b结点,所以在下一次循环时输出的是结点,所以在下一次循环时输出的是b结点中结点中的数据本例是比较简单的,所有结点都是在程的数据本例是比较简单的,所有结点都是在程序中定义的,不是临时开辟的,也不能用完后释序中定义的,不是临时开辟的,也不能用完后释放,这种链表称为放,这种链表称为“静态链表静态链表” 11.7.3 处理动态链表所需的函数 处理动态链表所需的函数前面讲过,链表结构是动态地分配存储的,即在需前面讲过,链表结构是动态地分配存储的,即在需要时才开辟一个结点的存储单元。

怎样动态地开要时才开辟一个结点的存储单元怎样动态地开辟和释放存储单元呢?辟和释放存储单元呢?C语言编译系统的库函数提语言编译系统的库函数提供了以下有关函数供了以下有关函数1.. malloc函数函数其函数原型为其函数原型为void *malloc(unsigned int size);其作用是在内存的动态存储区中分配一个长度为其作用是在内存的动态存储区中分配一个长度为sizze的连续空间此函数的值的连续空间此函数的值(即即“返回值返回值”)是一是一个指向分配域起始地址的指针个指向分配域起始地址的指针(基类型为基类型为void)如果果  此函数未能成功地执行 此函数未能成功地执行(例如内存空间不足例如内存空间不足),则,则返回空指针返回空指针(NULL)2.. calloc函数函数其函数原型为其函数原型为void *calloc(unsigned n,,unsigned sizze);其作用是在内存的动态区存储中分配其作用是在内存的动态区存储中分配n个长度为个长度为sizze的连续空间函数返回一个指向分配域起始地址的连续空间函数返回一个指向分配域起始地址的指针;如果分配不成功,返回的指针;如果分配不成功,返回NULL。

用用calloc函数可以为一维数组开辟动态存储空间,函数可以为一维数组开辟动态存储空间,n为数组元素个数,每个元素长度为为数组元素个数,每个元素长度为size 3.. free函数函数其函数原型为其函数原型为void free(void *p);其作用是释放由其作用是释放由p指向的内存区,使这部分内存区能被指向的内存区,使这部分内存区能被其他变量使用其他变量使用p是调用是调用calloc或或malloc函数时返回的函数时返回的值free函数无返回值函数无返回值请注意:以前的请注意:以前的C版本提供的版本提供的malloc和和calloc函数得到的函数得到的是指向字符型数据的指针是指向字符型数据的指针ANSI C提供的提供的malloc和和calloc函数规定为函数规定为void类型有了本节所介绍的初步知识,下面就可以对链表进行操有了本节所介绍的初步知识,下面就可以对链表进行操作了作了(包括建立链表、插入或删除链表中一个结点等包括建立链表、插入或删除链表中一个结点等)有些概念需要在后面的应用中逐步建立和掌握有些概念需要在后面的应用中逐步建立和掌握 11.7.4 建立动态链表 建立动态链表所谓建立动态链表是指在所谓建立动态链表是指在程序执行过程中从无到程序执行过程中从无到有地建立起一个链表,有地建立起一个链表,即一个一个地开辟结点即一个一个地开辟结点和输入各结点数据,并和输入各结点数据,并建立起前后相链的关系。

建立起前后相链的关系例例11.8写一函数建立一个有写一函数建立一个有3名学生数据的单向动态名学生数据的单向动态链表 先考虑实现此要求的算法先考虑实现此要求的算法(见图见图11.12)图图11.12 设设3个指针变量:个指针变量:head、、p1、、p2,它们都是用来指向,它们都是用来指向struct student类型数据的先用类型数据的先用malloc函数开辟第函数开辟第一个结点,并使一个结点,并使p1、、p2指向它然后从键盘读入一指向它然后从键盘读入一个学生的数据给个学生的数据给p1所指的第一个结点我们约定学所指的第一个结点我们约定学号不会为零,如果输入的学号为号不会为零,如果输入的学号为0,则表示建立链,则表示建立链表的过程完成,该结点不应连接到链碇小O仁筯表的过程完成,该结点不应连接到链碇小O仁筯ead的值为的值为NULL(即等于即等于0),这是链表为,这是链表为“空空”时时的情况的情况(即即head不指向任何结点,链表中无结点不指向任何结点,链表中无结点),,以后增加一个结点就使以后增加一个结点就使head指向该结点指向该结点如果输入的如果输入的p1->num不等于不等于0,则输入的是第一个结,则输入的是第一个结点数据点数据(n=1),令,令head=p1,即把,即把p1的值赋给的值赋给head,也就是使,也就是使head也指向新开辟的结点也指向新开辟的结点(图图11.13 )。

p1所指向的新开辟的结点就成为链表中第一个结点所指向的新开辟的结点就成为链表中第一个结点 然后再开辟另一个结点并使然后再开辟另一个结点并使p1指向它,接着输入该指向它,接着输入该结点的数据结点的数据(见图见图11.14(a))如果输入的如果输入的p1->num≠0,则应链入图,则应链入图11.13第第2个结点个结点(n=2),由于,由于n≠1,则将,则将p1的值赋给的值赋给p2->next,此时,此时p2指向第一指向第一个结点,因此执行个结点,因此执行“p2->next=p1” 就将新结点的就将新结点的地址赋给第一个结点的地址赋给第一个结点的next成员,使第一个结点成员,使第一个结点的的next成员指向第二个结点成员指向第二个结点(见图见图11.14(b))接着使使p2=p1,也就是使,也就是使p2指向刚才建立的结点,见图指向刚才建立的结点,见图11.14(c)接着再开辟一个结点并使接着再开辟一个结点并使p1指向它,并指向它,并输入该结点的数据输入该结点的数据(见图见图11.15(a)),在第三次循环,在第三次循环中,由于中,由于n=3(n≠1),又将,又将p1的值赋给的值赋给p2->next,也,也就是将第就是将第3个结点连接到第个结点连接到第2个结点之后,并使个结点之后,并使p2=p1,使,使p2指向最后一个结点指向最后一个结点(见图见图11.15(b))。

图图11.13 图图11.14 图图11.15 再开辟一个新结点,并使再开辟一个新结点,并使p1指向它,输入该结点的指向它,输入该结点的数据数据(见图见图11.16(a))由于p1->num的值为的值为0,不再,不再执行循环,此新结点不应被连接到链表中此时执行循环,此新结点不应被连接到链表中此时将将NULL赋给赋给p2->next,见图,见图11.16(b)建立链表建立链表过程至此结束,过程至此结束,p1最后所指的结点未链入链表中,最后所指的结点未链入链表中,第第3个结点的个结点的next成员的值为成员的值为NULL,它不指向任,它不指向任何结点虽然何结点虽然p1指向新开辟的结点,但从链表中指向新开辟的结点,但从链表中无法找到该结点无法找到该结点 图图11.16   建立链表的函数可以如下:  建立链表的函数可以如下:    #define NULL 0    #define LEN sizzeof(struct student)    struct student    {long num;; float score;; struct studentnext;; };; int n;; /*n为全局变量,本模块中各函数均可使为全局变量,本模块中各函数均可使用它用它*/     struct student *creat(void)/*定义函数。

此函数定义函数此函数带回一个指向链表头的指针带回一个指向链表头的指针*/    {struct studenthead;; struct studentp1,,*p2;; n=0;;p1=p2=( struct student*) malloc(LEN); ; /*开辟一开辟一个新单元个新单元*/ scanf("%ld,,%f",,&p1->>num,,&p1->>score);; head=NULL;; while(p1->>num!!=0)   {n=n+1;; if(n==1)head=p1;; else p2->>next=p1;; p2=p1;; p1=(struct student)malloc(LEN);; scanf("%ld,,%f",,&p1->>num,,&p1->>score);; } p2->>next=NULL;; return(head);; } 函数首部在括弧内写函数首部在括弧内写void,表示本函数没有形参,,表示本函数没有形参,不需要进行数据传递。

  不需要进行数据传递  可以在可以在main函数中调用函数中调用creat函数:函数: main() {… creat();/*调用调用creat函数后建立了一个单向动态函数后建立了一个单向动态链表链表*/ }调用调用creat函数后,函数的值是所建立的链表的第一函数后,函数的值是所建立的链表的第一个结点的地址个结点的地址(请查看请查看return语句语句) 请注意:请注意:(1) 第第1行为行为#define命令行,令命令行,令NULL代表代表0,用它,用它表示表示“空地址空地址”第2行令行令LEN代表代表struct student类型数据的长度,类型数据的长度,sizzeof是是“求字节数运算符求字节数运算符”2) 第第9行定义一个行定义一个creat函数,它是指针类型,即此函数,它是指针类型,即此函数带回一个指针值,它指向一个函数带回一个指针值,它指向一个struct student类型数据实际上此类型数据实际上此creat函数带回一个链表起始函数带回一个链表起始地址3) malloc(LEN)的作用是开辟一个长度为的作用是开辟一个长度为LEN的内的内存区,存区,LEN已定义为已定义为sizzeof(struct student),即结,即结构体构体struct student的长度。

的长度malloc带回的是带回的是  不指向任何类型数据的指针 不指向任何类型数据的指针(void *类型类型)而p1、、p2是指向是指向struct student类型数据的指针变量,因类型数据的指针变量,因此必须用强制类型转换的方法使指针的基类型改此必须用强制类型转换的方法使指针的基类型改变为变为struct student类型,在类型,在malloc(LEN)之前加了之前加了“(struct student *)”,它的作用是使,它的作用是使malloc返回返回的指针转换为指向的指针转换为指向struct student类型数据的指针类型数据的指针注意注意“*”号不可省略,否则变成转换成号不可省略,否则变成转换成struct student类型了,而不是指针类型了类型了,而不是指针类型了4) 最后一行最后一行return后面的参数是后面的参数是head(head已定义已定义为指针变量,指向为指针变量,指向struct student类型数据类型数据)因此函数返回的是函数返回的是head的值,也就是链表的头地址的值,也就是链表的头地址 (5) n是结点个数是结点个数6) 这个算法的思路是让这个算法的思路是让p1指向新开的结点,指向新开的结点,p2指向指向链表中最后一个结点,把链表中最后一个结点,把p1所指的结点连接在所指的结点连接在p2所所指的结点后面,用指的结点后面,用“p2->next=p1”来实现。

来实现我们对建立链表过程做了比较详细的介绍,读者如果我们对建立链表过程做了比较详细的介绍,读者如果对建立链表的过程比较清楚的话,对下面介绍的删对建立链表的过程比较清楚的话,对下面介绍的删除和插入过程也就比较容易理解了除和插入过程也就比较容易理解了11.7.5 输出链表 输出链表将链表中各结点的数据依次输出这个问题比较容易将链表中各结点的数据依次输出这个问题比较容易处理例11.7中已初步介绍了输出链表的方法首中已初步介绍了输出链表的方法首先要知道链表第一个结点的地址,也就是要先要知道链表第一个结点的地址,也就是要  知道 知道head的值然后设一个指针变量的值然后设一个指针变量p,先指向第,先指向第一个结点,输出一个结点,输出p所指的结点,然后使所指的结点,然后使p后移一个后移一个结点,再输出直到链表的尾结点结点,再输出直到链表的尾结点例例11.9编写一个输出链表的函数编写一个输出链表的函数print     void print(struct student *head)     {struct student*p;;      printf("\nNow,,These %d records are::\n",,n);;     p=head;;     if(head!!=NULL) do {printf("%ld %5.1f\n",,p->>num,,p->>score);; p=p->>next;; }while(p!!=NULL);;     }算法可用图算法可用图11.17表示。

表示其过程可用图其过程可用图11.18表示p先指向第一结点,在输先指向第一结点,在输出完第一个结点之后,出完第一个结点之后,p移到图中移到图中p'虚线位置,指虚线位置,指向第二个结点程序中向第二个结点程序中p=p->next的作用是将的作用是将p原原来所指向的结点中来所指向的结点中next的值赋给的值赋给p,而,而p->next的值的值就是第二个结点的起始地址将它赋给就是第二个结点的起始地址将它赋给p,就是使,就是使p指向第二个结点指向第二个结点 图图11.17图图11.18 head的值由实参传过来,也就是将已有的链表的头的值由实参传过来,也就是将已有的链表的头指针传给被调用的函数,在指针传给被调用的函数,在print函数中从函数中从head所所指的第一个结点出发顺序输出各个结点指的第一个结点出发顺序输出各个结点11.7.6 对链表的删除操作 对链表的删除操作已有一个链表,希望删除其中某个结点怎样考虑已有一个链表,希望删除其中某个结点怎样考虑此问题的算法呢?先打个比方:一队小孩此问题的算法呢?先打个比方:一队小孩(A、、B、、C、、D、、E)手拉手,如果某一小孩手拉手,如果某一小孩(C)想离队有事,想离队有事,而队形仍保持不变。

只要将而队形仍保持不变只要将C的手从两边脱开,的手从两边脱开,B改为与改为与D拉手即可,见图拉手即可,见图11.19图11.19(a)是原来是原来的队伍,图的队伍,图11.19(b)是是C离队后的队伍离队后的队伍 图图11.1911.19 与此相仿,从一个动态链表中删去一个结点,并不与此相仿,从一个动态链表中删去一个结点,并不是真正从内存中把它抹掉,而是把它从链表中分是真正从内存中把它抹掉,而是把它从链表中分离开来,只要撤消原来的链接关系即可离开来,只要撤消原来的链接关系即可例例11.10写一函数以删除动态链表中指定的结点写一函数以删除动态链表中指定的结点以指定的学号作为删除结点的标志例如,输入以指定的学号作为删除结点的标志例如,输入99103表示要求删除学号为表示要求删除学号为99103的结点解题的的结点解题的思路是这样的:从思路是这样的:从p指向的第一个结点开始,检查指向的第一个结点开始,检查该结点中的该结点中的num值是否等于输入的要求删除的那值是否等于输入的要求删除的那个学号如果相等就将该结点删除,如不相等,个学号如果相等就将该结点删除,如不相等,就将就将p后移一个结点,再如此进行下去,直到遇到后移一个结点,再如此进行下去,直到遇到表尾为止。

表尾为止 图图11.20 可以设两个指针变量可以设两个指针变量p1和和p2,先使,先使p1指向第一个结指向第一个结点点(图图11.20(a))如果要删除的不是第一个结点,则如果要删除的不是第一个结点,则使使p1后指向下一个结点后指向下一个结点(将将p1->next赋给赋给p1),在此,在此之前应将之前应将p1的值赋给的值赋给p2,使,使p2指向刚才检查过的那指向刚才检查过的那个结点,见图个结点,见图11.20(b)如此一次一次地使如此一次一次地使p后移,后移,直到找到所要删除的结点或检查完全部链表都找不直到找到所要删除的结点或检查完全部链表都找不到要删除的结点为止如果找到某一结点是要删除到要删除的结点为止如果找到某一结点是要删除的结点,还要区分两种情况:的结点,还要区分两种情况:①①要删的是第一个结要删的是第一个结点点(p1的值等于的值等于head的值,如图的值,如图11.20(a)那样那样),则,则应将应将p1->next赋给赋给head见图11.20(c)这时head指指向原来的第二个结点第一个结点虽然仍存在,但向原来的第二个结点第一个结点虽然仍存在,但它已与链表脱离,因为链表中没有一个结点或头指它已与链表脱离,因为链表中没有一个结点或头指针指向它。

虽然针指向它虽然p1还指向它,它仍指向第二个结点,还指向它,它仍指向第二个结点,  但仍无济于事,现在链表的第一个结点是原来的 但仍无济于事,现在链表的第一个结点是原来的第二个结点,原来第一个结点已第二个结点,原来第一个结点已“丢失丢失” ,即不,即不再是链表中的一部分了再是链表中的一部分了②② 如果要删除的不是第如果要删除的不是第一个结点,则将一个结点,则将p1->next赋给赋给p2->next,见图,见图11.20(d)p2->next原来指向原来指向p1指向的结点指向的结点(图中第图中第二个结点二个结点),现在,现在p2->next改为指向改为指向p1->next所指所指向的结点向的结点(图中第三个结点图中第三个结点)p1所指向的结点不所指向的结点不再是链表的一部分还需要考虑链表是毡再是链表的一部分还需要考虑链表是毡?无结点无结点)和链表中找不到要删除的结点的情况图和链表中找不到要删除的结点的情况图11.21表表示解此题的算法示解此题的算法 图图11.21 删除结点的函数删除结点的函数del如下:如下:     struct studentdel(struct student *head,,long num) {struct student *p1,,*p2; if (head==NULL){printf("\nlist null!\n");return (head);} p1=head; while(num!=p1->num && p1->next!==NULL) /*p1指向的不是所要找的结点,指向的不是所要找的结点,并且后面还有结点并且后面还有结点/ {p2=p1;;p1=p1->>next;;}/p1后移一个后移一个结点结点 */        if(num==p1->>num) /找到了找到了*/        {if(p1==head)head=p1->>next;;/若若p1指向的是首结点,把第二个结点地址赋予指向的是首结点,把第二个结点地址赋予head/          else p2->next=p1->next; /*否则将下一结点地址赋给前一结点地址否则将下一结点地址赋给前一结点地址*/         printf("delete::%ld\n",,num);;      n=n-1;;         }        else printf("%ld not been found!\n",,num); /找不到该结点找不到该结点/           return(head);;      }函数的类型是指向函数的类型是指向struct student类型数据的指针,类型数据的指针,它的值是链表的头指针。

函数参数为它的值是链表的头指针函数参数为head和要删和要删除的学号除的学号numhead的值可能在函数执行过程中的值可能在函数执行过程中被改变被改变(当删除第一个结点时当删除第一个结点时) 11.7.7 对链表的插入操作 对链表的插入操作对链表的插入是指将一个结点插入到一个已有的链表对链表的插入是指将一个结点插入到一个已有的链表中若已有一个学生链表,各结点是按其成员项中若已有一个学生链表,各结点是按其成员项num(学号学号)的值由小到大顺序排列的今要插入一的值由小到大顺序排列的今要插入一个新生的结点,要求按学号的顺序插入为了能做个新生的结点,要求按学号的顺序插入为了能做到正确插入,必须解决两个问题:到正确插入,必须解决两个问题:①① 怎样找到插入怎样找到插入的位置;的位置;②② 怎样实现插入如果有一群小学生,按怎样实现插入如果有一群小学生,按身高顺序身高顺序(由低到高由低到高)手拉手排好队现在来了一名手拉手排好队现在来了一名新同学,要求按身高顺序插入队中首先要确定插新同学,要求按身高顺序插入队中首先要确定插到什么位置可以将新同学先与队中第到什么位置可以将新同学先与队中第1名小学生名小学生比身高,若新同学比第比身高,若新同学比第1名学生高,就使新同学后名学生高,就使新同学后移一个位置,与第移一个位置,与第2名学生比,如果仍比第名学生比,如果仍比第2名学生名学生高,再往后移,与第高,再往后移,与第3名学生比名学生比……直到出现比直到出现比  第 第i名学生高,比第名学生高,比第i+1名学生低的情况为止。

显然,名学生低的情况为止显然,新同学的位置应该在第新同学的位置应该在第i名学生之后,在第名学生之后,在第i+1名学生名学生之前在确定了位置之后,让第之前在确定了位置之后,让第i名学生与第名学生与第i+1名学名学生的手脱开,然后让第生的手脱开,然后让第i名学生的手去拉新同学的手,名学生的手去拉新同学的手,让新同学另外一只手去拉第让新同学另外一只手去拉第i+1名学生的手这样就名学生的手这样就完成了插入,形成了新的队列根据这个思路来实完成了插入,形成了新的队列根据这个思路来实现链表的插入操作先用指针变量现链表的插入操作先用指针变量p0指向待插入的指向待插入的结点,结点,p1指向第一个结点见图指向第一个结点见图11.22(a)将p0->num与与p1->num相比较,如果相比较,如果p0->num>>p1->num,则待插入的结点不应插在,则待插入的结点不应插在p1所指的结点之前此所指的结点之前此时将时将p1后移,并使后移,并使p2指向刚才指向刚才p1所指的结点,见图所指的结点,见图11.22(b)再将p1->num与与p0->num比如果仍然是比如果仍然是p0->num大,则应使大,则应使p1继续后移,直到继续后移,直到   p0->num≤p1->num为止。

这时将为止这时将p0所指的结点插所指的结点插到到p1所指结点之前但是如果所指结点之前但是如果p1所指的已是表尾所指的已是表尾结点,则结点,则p1就不应后移了如果就不应后移了如果p0->num比所有比所有结点的结点的num都大,则应将都大,则应将p0所指的结点插到链表所指的结点插到链表末尾如果插入的位置既不在第一个结点之前,又不在表如果插入的位置既不在第一个结点之前,又不在表尾结点之后,则将尾结点之后,则将p0的值赋给的值赋给p2->next,使,使p2->next指向待插入的结点,然后将指向待插入的结点,然后将p1的值赋给的值赋给p0->next,使得,使得p0->next指向指向p1指向的变量见图指向的变量见图11.22(c)可以看到,在第一个结点和第二个结点可以看到,在第一个结点和第二个结点之间已插入了一个新的结点之间已插入了一个新的结点如果插入位置为第一个结点之前如果插入位置为第一个结点之前(即即p1等于等于head时时),则将,则将p0赋给赋给head,将,将p1赋给赋给p0->next见图   11.22(d)如果要插到表尾之后,应将如果要插到表尾之后,应将p0赋给赋给p1->next,,NULL赋给赋给p0->next,见图,见图11.22(e)。

以上以上算法可用图算法可用图11.23表示 图图11.2211.22 图图11.2311.23 例例11.11插入结点的函数插入结点的函数insert如下struct student*insert(struct student *head,,struct student *stud){struct student *p0,,*p1,,*p2;p1=head;;/使使p1指向第一个结点指向第一个结点/p0=stud;; /p0指向要插入的结点指向要插入的结点/if(head==NULL)   /原来的链表是空表原来的链表是空表/{head=p0; p0->next=NULL;} /*使使p0指向的结点作指向的结点作为头结点为头结点*/else {while((p0->num>p1->num) && (p1->next!=NULL)){p2=p1; /*使使p2指向刚才指向刚才p1指向的结点指向的结点*/p1=p1->next;} /*p1后移一个结点后移一个结点*/if(p0->num<<p1->num){if(head==p1) head=p0;    /插到原来第一个插到原来第一个结点之前结点之前*/         else p2->next=p0;   /插到插到p2指向的结点之后指向的结点之后*/         p0->next=p1;;} else{p1->next=p0; p0->next=NULL;}}/*插到最后的结插到最后的结点之后点之后*/ n=n+1; ;  /结点数加结点数加1*/ return(head);;     }函数参数是函数参数是head和和stud。

stud也是一个指针变量,也是一个指针变量,从实参传来待插入结点的地址给从实参传来待插入结点的地址给stud语句p0=stud的作用是使的作用是使p0指向待插入的结点函数类指向待插入的结点函数类型是指针类型,函数值是链表起始地址型是指针类型,函数值是链表起始地址head 11.7.8  对链表的综合操作 对链表的综合操作将以上建立、输出、删除、插入的函数组织在一个将以上建立、输出、删除、插入的函数组织在一个C程序中,即将例程序中,即将例11.8~~11.11中的中的4个函数顺序排个函数顺序排列,用列,用main函数作主调函数可以写出以下函数作主调函数可以写出以下main函数函数(main函数的函数的位置在以上各函数的后面位置在以上各函数的后面)main()    {struct student *head,,stu;; long del-num;; printf("input records::\n");; head=creat();;/返回头指针返回头指针*/ print(head);; /输出全部结点输出全部结点*/ printf("\ninput the deleted number::");; scanf("%ld",,&del-num);; /输入要删除输入要删除的学号的学号*/ head=del(head,,del-num);; /删除后链表删除后链表的头地址的头地址/ print(head);; /输出全部结点输出全部结点*/ printf("\ninput the inserted record:"); /输入要插入的结点输入要插入的结点*/ scanf("%ld,,%f",,&stu..num,,&stu..score);; head=insert(head,,&stu);; /返回地址返回地址/ print(head);;/*输出全部结点输出全部结点*/    }此程序运行结果是正确的。

它只删除一个结点,插此程序运行结果是正确的它只删除一个结点,插入一个结点但如果想再插入一个结点,重复写入一个结点但如果想再插入一个结点,重复写上程序最后上程序最后4行,共插入两个结点运行结果却是行,共插入两个结点运行结果却是错误的运行结果如下:运行结果如下: input records: : (建立链表建立链表)    99101,,90    99103,,98    99105..76    0,,0    Now,,These 3 records are::    99101   90..0    99103   98..0    99105   76..0    input the deleted number::99103(删除删除)    delete::99103     Now,,These 2 records are::    99101   90..099105   76..0  input the inserted record::99102,,90 (插入第插入第一个结点一个结点)    Now,,These 3 records are::    99101  90..0    99102  90..0    99105  76..0  input the inserted record::89104,,99(插入第二插入第二个结点个结点)     Now,,These 4 records are::    99101  90..0    99104  99..0    99104  99..0    99104  99..0    …  (无终止地输出无终止地输出99104的结点数据的结点数据)出现以上结果的原因是出现以上结果的原因是stu是一个有固定地址的结构是一个有固定地址的结构体变量。

第一次把体变量第一次把stu结点插入到链表中第二次结点插入到链表中第二次若再用它来插入第二个结点,就把第一次结点的若再用它来插入第二个结点,就把第一次结点的数据冲掉了实际上并没有开辟两个结点数据冲掉了实际上并没有开辟两个结点main  函数如下:函数如下:     main()    {struct student *head,,*stu;; long del-num;; printf("input records::\n");; head=creat();; print(head);; printf("\ninput the deleted number::");; scanf("%ld",,&del-num);; while(del-num!!=0) {head=del(head,,del-num);; print(head);; printf("input the deleted number::");; scanf("%ld",,&del-num);;} printf("\ninput the inserted record::");; stu=(struct student)malloc(LEN);; scanf("%ld,,%f",,&stu->>num,,&stu->>score);; while(stu->>num!!=0) {head=insert(head,,stu);; print(head);; printf("input the inserted record::");; stu=(struct student)malloc(LEN);; scanf("%ld,,%f",,&stu->>num,,&stu->>score);; }   }stu定义为指针变量,在需要插入时先用定义为指针变量,在需要插入时先用malloc函数函数开辟一个内存区,将其起始地址经强制类型转换开辟一个内存区,将其起始地址经强制类型转换后赋给后赋给stu,然后输入此结构体变量中各成员的值。

然后输入此结构体变量中各成员的值对不同的插入对象,对不同的插入对象,stu的值是不同的,每次指向的值是不同的,每次指向一个新的一个新的struct student变量在调用变量在调用insert函数时,函数时,实参为实参为head和和stu,将已建立的链表起始地址传给,将已建立的链表起始地址传给insert函数的形参,将函数的形参,将stu(即新开辟的单元即新开辟的单元  的地址 的地址)传给形参传给形参stud,返回的函数值是经过插入,返回的函数值是经过插入之后的链表的头指针之后的链表的头指针(地址地址)运行情况如下:运行情况如下:input records::    99101,,99    99103,,87    99105,,77    0,,0    Now,,These 3 records are::    9910199..0     99103   87..0    99105   77..0    input the deleted number::99103    delete::99103    Now,,these 2 records are::    99101   99..099105   77..0    input the deleted number::99105    delete::99105    Now,,These 1 records are::99101   99..0     input the deleted number::0    input the inserted record::99104,,87    Now,,These 2 records are::    99101   99..099104   87..0    input the inserted record::99106,,65    Now,,These 3 records are::    99101   99..0    99104   87..099106   65..0    input the inserted record::0,,0 11.8 共用体 共用体 11.8.1 共用体的概念 共用体的概念有时需要使几种不同类型的变量存放到同一段内存有时需要使几种不同类型的变量存放到同一段内存单元中。

例如,可把一个整型变量、一个字符型单元中例如,可把一个整型变量、一个字符型变量、一个实型变量放在同一个地址开始的内存变量、一个实型变量放在同一个地址开始的内存单元中单元中(见图见图11.24)以上3个变量在内存中占的字个变量在内存中占的字节数不同,但都从同一地址开始节数不同,但都从同一地址开始(图中设地址为图中设地址为1000)存放也就是使用覆盖技术,几个变量互相存放也就是使用覆盖技术,几个变量互相覆盖这种使几个不同的变量共占同一段内存的覆盖这种使几个不同的变量共占同一段内存的结构,称为结构,称为“共用体共用体”类型的结构类型的结构 定义共用体类型变量的一般形式为定义共用体类型变量的一般形式为union 共用体名 共用体名图图11.24 { 成员表列成员表列 }变量表列;变量表列;例如:例如:union data {int i;; char ch;; float f;;        }a,,b,,c;; 也可以将类型声明与变量定义分开:也可以将类型声明与变量定义分开:union data     {int i;; char ch;; float f;;     };;     union data  a,,b,,c;;即先声明一个即先声明一个union data类型,再将类型,再将a、、b、、c定义为定义为union data类型。

当然也可以直接定义共用体变量,类型当然也可以直接定义共用体变量,如:如: union     {int i;; char ch;; float f;;     }a,,b,,c;;可以看到,可以看到,“共用体共用体”与与“结构体结构体”的定义形式相似的定义形式相似但它们的含义是不同的但它们的含义是不同的结构体变量所占内存长度是各成员占的内存长度之和结构体变量所占内存长度是各成员占的内存长度之和每个成员分别占有其自己的内存单元每个成员分别占有其自己的内存单元有些有些C变量所占的内存长度等于最长的成员的长度例变量所占的内存长度等于最长的成员的长度例如,上面定义的如,上面定义的“共用体共用体”变量变量a、、b、、c各占各占4个字节个字节(因因为一个实型变量占为一个实型变量占4个字节个字节),而不是各占,而不是各占2+1+4=7个个字节 11.8.2 共用体变量的引用方式 共用体变量的引用方式只有先定义了共用体变量才能引用它而且不能引只有先定义了共用体变量才能引用它而且不能引用共用体变量,而只能引用共用体变量中的成员用共用体变量,而只能引用共用体变量中的成员例如,前面定义了例如,前面定义了a、、b、、c为共用体变量,下面的为共用体变量,下面的引用方式是正确的:引用方式是正确的:a..i(引用共用体变量中的类型变量引用共用体变量中的类型变量i)a..ch(引用共用体变量中的字符变量引用共用体变量中的字符变量ch)a..f (引用共用体变量中的实型变量引用共用体变量中的实型变量f)不能只引用共用体变量,例如:不能只引用共用体变量,例如:printf("%d",,a)是错误的,是错误的,a的存储区有好几种类型,分别占不同长的存储区有好几种类型,分别占不同长度的存储区,仅写共用体变量名度的存储区,仅写共用体变量名a,难以使系统,难以使系统  确定究竟输出的是哪一个成员的值。

应该写成 确定究竟输出的是哪一个成员的值应该写成printf("%d",,a..i)或或printf("%c",,a..ch)等11.8.3 共用体类型数据的特点 共用体类型数据的特点在使用共用体类型数据时要注意以下一些特点:在使用共用体类型数据时要注意以下一些特点:(1) 同一个内存段可以用来存放几种不同类型的成同一个内存段可以用来存放几种不同类型的成员,但在每一瞬时只能存放其中一种,而不是同员,但在每一瞬时只能存放其中一种,而不是同时存放几种也就是说,每一瞬时只有一个成员时存放几种也就是说,每一瞬时只有一个成员起作用,其他的成员不起作用,即不是同时都存起作用,其他的成员不起作用,即不是同时都存在和起作用在和起作用2) 共用体变量中起作用的成员是最后一次存放的共用体变量中起作用的成员是最后一次存放的成员,在存入一个新的成员后原有的成员就失去成员,在存入一个新的成员后原有的成员就失去作用如有以下赋值语句:作用如有以下赋值语句: a..i=1;;a..c='a';;a..f=1..5;;在完成以上在完成以上3个赋值运算以后,只有个赋值运算以后,只有a..f是有效的,是有效的,a..i和和a..c已经无意义了。

此时用已经无意义了此时用printf("%d",,a..i)是不行的,而用是不行的,而用printf("%f",,a..f)是可以的,是可以的,因为最后一次的赋值是向因为最后一次的赋值是向a..f赋值因此在引用赋值因此在引用共用体变量时应十分注意当前存放在共用体变量共用体变量时应十分注意当前存放在共用体变量中的究竟是哪个成员中的究竟是哪个成员3) 共用体变量的地址和它的各成员的地址都是同共用体变量的地址和它的各成员的地址都是同一地址例如:一地址例如:&a、、&a..i、、&a..c、、&a..f都是同都是同一地址值,其原因是显然的一地址值,其原因是显然的 (4) 不能对共用体变量名赋值,也不能企图引用变不能对共用体变量名赋值,也不能企图引用变量名来得到一个值,又不能在定义共用体变量时量名来得到一个值,又不能在定义共用体变量时对它初始化例如,下面这些都是不对的:对它初始化例如,下面这些都是不对的: ①① union {int i;; char ch;; float f;; }a={1,,'a',,1..5};;(不能初始化不能初始化) ②② a=1;; (不能对共用体变量赋值不能对共用体变量赋值) ③③ m=a;; (不能引用共用体变量名以得到一个值不能引用共用体变量名以得到一个值) (5) 不能把共用体变量作为函数参数,也不能使函数不能把共用体变量作为函数参数,也不能使函数带回共用体变量,但可以使用指向共用体变量的指带回共用体变量,但可以使用指向共用体变量的指针针(与结构体变量这种用法相仿与结构体变量这种用法相仿)。

6) 共用体类型可以出现在结构体类型定义中,也可共用体类型可以出现在结构体类型定义中,也可以定义共用体数组反之,结构体也可以出现在共以定义共用体数组反之,结构体也可以出现在共用体类型定义中,数组也可以作为共用体的成员用体类型定义中,数组也可以作为共用体的成员例例11.12设有若干个人员的数据,其中有学生和教师设有若干个人员的数据,其中有学生和教师学生的数据中包括:姓名、图学生的数据中包括:姓名、图11.25号码、性别、职号码、性别、职业、班级教师的数据包括:姓名、号码、性别、业、班级教师的数据包括:姓名、号码、性别、职业、职务可以看出,学生和教师所包含的数据职业、职务可以看出,学生和教师所包含的数据是不同的现要求把它们放在同一表格中,见图是不同的现要求把它们放在同一表格中,见图11.25如果“job”项为项为“s”(学生学生),则第,则第5项为项为   class(班班)即Li是是501班的如果班的如果“job”项是项是“t”(教师教师),则第,则第5项为项为position(职务职务)Wang是是prof(教授教授)显然对第显然对第5项可以用共用体来项可以用共用体来处理处理(将将class和和position放在同一段内存中放在同一段内存中)。

图图11.25 图图11.26 要求输入人员的数据,然后再输出可以写出下面要求输入人员的数据,然后再输出可以写出下面的算法的算法(见图见图11.26)按此写出程序为简化起见按此写出程序为简化起见只设两个人只设两个人(一个学生、一个教师一个学生、一个教师)    struct {int num;; char name[10];; char sex;; char job;; union {int class;; char position[10];; }category;; }person[2];;   main()     {int n,,i;;     for(i=0,,i<<2;;i++) {scanf("%d %s %c %c",,&person[i].num,, person[i].name,,&person[i].sex,,&person[i].job);if(person[i].job=='s') scanf("%d",, &person[i].category.class); else if (person[i].job=='t') scanf("%s",,person[i].category.position); else printf("input error!!");; }   printf("\n");;     printf("No. Namesex job class/position\n");     for(i=0;;i<<2;;i++) {if(person[i]..job=='s')printf("%\|6d %\|10s %\|3c %\|3c %\|6d\n",, person[i].num,,person[i].name,,person[i].sex,,person[i].job,,person[i].category.class);;         else printf("%\|6d %\|10s %\|3c %\|3c %\|6s\n",, person[i].num,, person[i].name,,person[i].sex,,person[i].job,,person[i].category.position); }     }运行情况如下:运行情况如下: 101 Li f s 501    102 WWang m t professor    No.. Name sex job class/position    101  Li     f s           501    102 W Wang m t professor 可以看到:在可以看到:在main函数之前定义了外部的结构体数函数之前定义了外部的结构体数组组person,在结构体类型声明中包括了共用体类,在结构体类型声明中包括了共用体类型,型,category(分类分类)是结构体中一个成员名,在这是结构体中一个成员名,在这个共用体中成员为个共用体中成员为calss和和position,前者为整型,,前者为整型,后者为字符数组后者为字符数组(存放存放“职务职务”的值的值——字符串字符串)。

11.9 枚 枚 举举 类类 型型枚举类型是枚举类型是ANSI C新标准所增加的新标准所增加的如果一个变量只有几种可能的值,可以定义为枚举如果一个变量只有几种可能的值,可以定义为枚举类型所谓类型所谓“枚举枚举”是指将变量的值一一列举出是指将变量的值一一列举出来,变量的值只限于列举出来的值的范围内声来,变量的值只限于列举出来的值的范围内声明枚举类型用明枚举类型用enum开头例如:开头例如: enum weekday{sun,,mon,,tue,,wed,,thu,,fri,,sat};声明了一个枚举类型声明了一个枚举类型enum weekday,可以用此类型,可以用此类型来定义变量如来定义变量如:enum  weekday    workday,,week-end;;workday和和week-end被定义为枚举变量,它们的值被定义为枚举变量,它们的值只能是只能是sun到到sat之一例如:之一例如:workday=mon;;week-end=sun;;是正确的是正确的当然,也可以直接定义枚举变量,如:当然,也可以直接定义枚举变量,如: enum{sun,,mon,,tue,,wed,,thu,,fri,,sat} workday,,week-end;;其中其中sun、、mon、、…、、sat等称为枚举元素或枚举常量。

等称为枚举元素或枚举常量它们是用户定义的标识符这些标识符并不自动它们是用户定义的标识符这些标识符并不自动地代表什么含义例如,不因为写成地代表什么含义例如,不因为写成sun,就自动,就自动代表代表“星期天星期天”其实不写其实不写sun而写成而写成sunday也可也可以用什么标识符代表什么含义,完全由程序员以用什么标识符代表什么含义,完全由程序员决定,并在程序中作相应处理决定,并在程序中作相应处理说明说明:(1) 在在C编译中,对枚举元素按常量处理,故称枚举编译中,对枚举元素按常量处理,故称枚举常量它们不是变量,不能对它们赋值例如常量它们不是变量,不能对它们赋值例如:sun=0;;mon=1;是错误的是错误的 (2) 枚举元素作为常量,它们是有值的,枚举元素作为常量,它们是有值的,C语言编译语言编译按定义时的顺序使它们的值为按定义时的顺序使它们的值为0,,1,,2,,…在上面定义中,在上面定义中,sun的值为的值为0,,mon的值为的值为1……sat为为6如果有赋值语句如果有赋值语句:workday=mon;;workday变量的值为变量的值为1这个整数是可以输出的这个整数是可以输出的如:printf("%d",,workday);将输出整数;将输出整数1。

也可以改变枚举元素的值,在定义时由程序员指定,也可以改变枚举元素的值,在定义时由程序员指定,如如:enum weekday{sun=7,,mon=1,,tue,,wed,,thu,,fri,,sat}workday,,week-end;定义;定义sun为为7,,mon=1,以后顺序加,以后顺序加1,,sat为为6 (3) 枚举值可以用来做判断比较如枚举值可以用来做判断比较如if(workday==mon)…if(workday>>sun)…枚举值的比较规则是按其在定义时的顺序号比较枚举值的比较规则是按其在定义时的顺序号比较如果定义时未人为指定,则第一个枚举元素的值如果定义时未人为指定,则第一个枚举元素的值认作认作0故mon大于大于sun,,sat>>fri4) 一个整数不能直接赋给一个枚举变量如:一个整数不能直接赋给一个枚举变量如:workday=2;;是不对的它们属于不同的类型应先进行强制类是不对的它们属于不同的类型应先进行强制类型转换才能赋值如:型转换才能赋值如:workday=(enum  weekday)2;; 它相当于将顺序号为它相当于将顺序号为2的枚举元素赋给的枚举元素赋给workday,相,相当于当于workday=tue;;甚至可以是表达式。

如甚至可以是表达式如:workday=(enum  weekday)(5-3);;例例11.13口袋中有红、黄、蓝、白、黑口袋中有红、黄、蓝、白、黑5种颜色的球种颜色的球若干个每次从口袋中先后取出若干个每次从口袋中先后取出3个球,问得到个球,问得到3种不同色的球的可能取法,打印出每种排列的情种不同色的球的可能取法,打印出每种排列的情况球只能是球只能是5种色之一,而且要判断各球是否同色,应种色之一,而且要判断各球是否同色,应该用枚举类型变量处理该用枚举类型变量处理设取出的球为设取出的球为i、、j、、k根据题意,根据题意,i、、j、、k分分 别是别是5种色球之一,种色球之一,并要求并要求i≠j≠k可以用穷举法,即一种可能穷举法,即一种可能一种可能地试,看哪一种可能地试,看哪一组符合条件算法一组符合条件算法可用图可用图11.27表示图图11.27 用用n累计得到累计得到3种不同色球的次数外循环使第种不同色球的次数外循环使第1个球个球i从从red变到变到black中循环使第中循环使第2个球个球j也从也从red变到变到black如果i和和j同色则不可取,只有同色则不可取,只有i、、j不同色不同色(i≠j)时才需要继续找第时才需要继续找第3个球,此时第个球,此时第3个球个球k也也有有5种可能种可能(red到到black),但要求第,但要求第3个球不能与第个球不能与第1个球或第个球或第2个球同色,即个球同色,即k≠i,,k≠j。

满足此条件就满足此条件就得到得到3种不同色的球输出这种种不同色的球输出这种3色组合方案然色组合方案然后使后使n加加1外循环全部执行完后,全部方案就已外循环全部执行完后,全部方案就已输出完了最后输出总数输出完了最后输出总数n 图图11.28下面的问题是如何实现图下面的问题是如何实现图11.28中的中的“输出一种取法输出一种取法”这里有一个问题:如何输出有一个问题:如何输出“red”、、“blue”……等单词不能写成不能写成printf("%s",,red)来输出来输出“red”字符串可以字符串可以采用图采用图11.28的方法 为了输出为了输出3个球的颜色,显然应经过个球的颜色,显然应经过3次循环,第次循环,第1次次输出输出i的颜色,第的颜色,第2次输出次输出j的颜色,第的颜色,第3次输出次输出k的的颜色在3次循环中先后将次循环中先后将i、、j、、k赋予赋予pri然后根据根据pri的值输出颜色信息在第的值输出颜色信息在第1次循环时,次循环时,pri的值为的值为i,如果,如果i的值为的值为red,则输出字符串,则输出字符串“red”,其他的类推其他的类推程序如下:程序如下:main(){enum color {red,yellow,blue,white,black}; enum color i,j,k,pri; int n,loop; n=0; for (i=red;i<=black;i++) for (j=red;j<=black;j++)if (i!=j){ for (k=red;k<=black;;k++)if ((k!=i) && (k!=j)){n=n+1;printf("%-4d",n);for (loop=1;loop<=3;loop++){switch (loop){case 1: pri=i;break;case 2: pri=j;break; case 3: pri=k;break;default:break;}switch (pri){case red:printf("%-10s","red"); break;case yellow: printf("%-10s","yellow"); break;case blue: printf("%-10s","blue"); break;case white: printf("%-10s","white"); break;case black: printf("%-10s","black"); break;default :break; }} printf("\n");} }printf("\ntotal:%5d\n",n);} 运行结果如下运行结果如下:1redyellowblue2redyellowwhite3redyellowblack…………58blackwhitered59blackwhiteyellow60blackwhiteblue total:60 有人说,不用枚举变量而用常数有人说,不用枚举变量而用常数0代表代表“红红”,,1代代表表“黄黄”……不也可以吗?是的,完全可以。

但不也可以吗?是的,完全可以但显然用枚举变量更直观,因为枚举元素都选用了显然用枚举变量更直观,因为枚举元素都选用了令人令人“见名知意见名知意”的标识符,而且枚举变量的值的标识符,而且枚举变量的值限制在定义时规定的几个枚举元素范围内,如果限制在定义时规定的几个枚举元素范围内,如果赋予它一个其他的值,就会出现出错信息,便于赋予它一个其他的值,就会出现出错信息,便于检查11.10 用 用typedef定义类型定义类型除了可以直接使用除了可以直接使用C提供的标准类型名提供的标准类型名(如如int、、char、、float、、double、、long等等)和自己声明的结构体、共和自己声明的结构体、共用体、指针、枚举类型外,还可以用用体、指针、枚举类型外,还可以用typedef声明声明新的类型名来代替已有的类型名如:新的类型名来代替已有的类型名如: typedef  int INTEGER;;typedef  float  REAL;;指定用指定用INTEGER代表代表int类型,类型,REAL代表代表float这样,以下两行等价:样,以下两行等价:①① int i,,j;; float a,,b;;②② INTEGER i,,j;; REAL a,,b;;这样可以使熟悉这样可以使熟悉FORTRAN的人能用的人能用INTEGER和和REAL定义变量,以适应他们的习惯。

定义变量,以适应他们的习惯如果在一个程序中,一个整型变量用来计数,可以:如果在一个程序中,一个整型变量用来计数,可以: typedef int COUNT;;COUNT i,,j;;即将变量即将变量i、、j定义为定义为COUNT类型,而类型,而COUNT等价等价于于int,因此,因此i、、j是整型在程序中将是整型在程序中将i、、j定为定为COUNT类型,可以使人更一目了然地知道它们是类型,可以使人更一目了然地知道它们是用于计数的用于计数的可以声明结构体类型:可以声明结构体类型:typedef  struct       {int  month;;         int  day;;         int  year;;        }DATE;;声明新类型名声明新类型名DATE,它代表上面指定的一个结构,它代表上面指定的一个结构体类型这时就可以用体类型这时就可以用DATE定义变量:定义变量:DATE birthday;; (不要写成不要写成struct DATE birthday;;)DATE  *p; ; (p为指向此结构体类型数据的指针为指向此结构体类型数据的指针)还可以进一步:还可以进一步:①① typedef  int  NUM[100];;(声明声明NUM为整型数为整型数组类型组类型)NUM  n;; (定义定义n为整型数组变量为整型数组变量) ②② typedef char *STRING;; (声明声明STRING为字符为字符指针类型指针类型)STRING  p,,s[10];;(p为字符指针变量,为字符指针变量,s为指针数组为指针数组)③③ typedef int (*POINTER)()(声明声明POINTER为指向为指向函数的指针类型,该函数返回整型值函数的指针类型,该函数返回整型值) POINTER p1,,p2; (p1、、p2为为POINTER类型类型的指针变量的指针变量)  归纳起来,声明一个新的类型名的方法是:归纳起来,声明一个新的类型名的方法是:①① 先按定义变量的方法写出定义体先按定义变量的方法写出定义体(如:如:int  i;;)。

②② 将变量名换成新类型名将变量名换成新类型名(如:将如:将i换成换成COUNT) ③③ 在最前面加在最前面加typedef(如:如:typedef  int  COUNT)④④ 然后可以用新类型名去定义变量然后可以用新类型名去定义变量再以定义上述的数组类型为例来说明:再以定义上述的数组类型为例来说明:①① 先按定义数组变量形式书写:先按定义数组变量形式书写:int  n[100];;②② 将变量名将变量名n换成自己指定的类型名:换成自己指定的类型名:int  NUM[100];;③③ 在前面加上在前面加上typedef,得到,得到typedef int NUM[100];;④④ 用来定义变量:用来定义变量:NUM  n;; 同样,对字符指针类型,也是:同样,对字符指针类型,也是:①①char  *p; ; ②②char *STRING;;③③typedef  char *STRING; ; ④④ STRING  p,,s[10];习惯上常把用;习惯上常把用typedef声明声明的类型名用大写字母表示,以便与系统提供的标的类型名用大写字母表示,以便与系统提供的标准类型标识符相区别准类型标识符相区别。

说明:说明:(1) 用用typedef可以声明各种类型名,但不能用来定可以声明各种类型名,但不能用来定义变量用义变量用typedef可以声明数组类型、字符串类可以声明数组类型、字符串类型,使用比较方便如定义数组,原来是用型,使用比较方便如定义数组,原来是用int  a[10],,b[10],,c[10],,d[10];由于都是一维数组,;由于都是一维数组, 大小也相同,可以先将此数组类型声明为一个名大小也相同,可以先将此数组类型声明为一个名字:字:typedef  int  ARR[10];然后用;然后用ARR去定义去定义数组变量:数组变量:ARR  a,,b,,c,,d;;ARR为数组类型,它包含为数组类型,它包含10个元素因此,个元素因此,a、、b、、c、、d都被定义为一维数组,含都被定义为一维数组,含10个元素可以看到,用可以看到,用typedef可以将数组类型和数组变量分可以将数组类型和数组变量分离开来,利用数组类型可以定义多个数组变量离开来,利用数组类型可以定义多个数组变量同样可以定义字符串类型、指针类型等同样可以定义字符串类型、指针类型等2) 用用typedef只是对已经存在的类型增加一个类型只是对已经存在的类型增加一个类型名,而没有创造新的类型。

名,而没有创造新的类型 例如,前面声明的整型类型例如,前面声明的整型类型COUNT,它无非是对,它无非是对int型另给一个新名字又如:型另给一个新名字又如:typedef  int  NUM[10];无非是把原来用;无非是把原来用“int n[10];;”定义定义的数组变量的类型用一个新的名字的数组变量的类型用一个新的名字NUM表示出来表示出来无论用哪种方式定义变量,效果都是一样的无论用哪种方式定义变量,效果都是一样的3) typedef 与  与 #define 有相似之处,如: 有相似之处,如:typedef  int  COUNT;;和和#define  COUNT  int的作用都是用的作用都是用COUNT代代表表int但事实上,它们二者是不同的但事实上,它们二者是不同的define是是在预编译时处理的,它只能作简单的字符串替换,在预编译时处理的,它只能作简单的字符串替换,而而typedef是在编译时处理的实际上它并不是作是在编译时处理的实际上它并不是作简单的字符串替换,例如:简单的字符串替换,例如: typedef  int  NUM[10];;并不是用并不是用“NUM[10]”去代替去代替“int”,而是采用如,而是采用如同定义变量的方法那样来声明一个类型同定义变量的方法那样来声明一个类型(就是前面就是前面介绍过的将原来的变量名换成类型名介绍过的将原来的变量名换成类型名)。

4) 当不同源文件中用到同一类型数据当不同源文件中用到同一类型数据(尤其是像数尤其是像数组、指针、结构体、共用体等类型数据组、指针、结构体、共用体等类型数据)时,常用时,常用typedef声明一些数据类型,把它们单独放在一个声明一些数据类型,把它们单独放在一个文件中,然后在需要用到它们的文件中用文件中,然后在需要用到它们的文件中用#include命令把它们包含进来命令把它们包含进来5) 使用使用typedef有利于程序的通用与移植有时程有利于程序的通用与移植有时程序会依赖于硬件特性,用序会依赖于硬件特性,用typedef便于移植例如,便于移植例如,有的计算机系统有的计算机系统int型数据用两个字节,数值型数据用两个字节,数值 范围为范围为-32768~~32767,而另外一些机器则以,而另外一些机器则以4个个字节存放一个整数,数值范围为字节存放一个整数,数值范围为±21亿如果把一亿如果把一个个C程序从一个以程序从一个以4个字节存放整数的计算机系统个字节存放整数的计算机系统移植到以移植到以2个字节存放整数的系统,按一般办法需个字节存放整数的系统,按一般办法需要将定义变量中的每个要将定义变量中的每个int改为改为long。

例如,将例如,将“int  a,,b,,c;;”改为改为“long  a,,b,,c;;”,,如果程序中有多处用如果程序中有多处用int定义变量,则要改动多处定义变量,则要改动多处现可以用一个现可以用一个INTEGER来声明来声明int::typedef  int  INTEGER;;在程序中所有整型变量都用在程序中所有整型变量都用INTEGER定义在移植定义在移植时只需改动时只需改动typedef定义体即可:定义体即可:typedef  long  INTEGER;; 习题习题11.1 定义一个结构体变量定义一个结构体变量(包括年、月、日包括年、月、日)计算该日在本年中是第几天?注意闰年问题该日在本年中是第几天?注意闰年问题11.2 写一个函数写一个函数days,实现上面的计算由主函数,实现上面的计算由主函数将年、月、日传递给将年、月、日传递给days函数,计算后将日子数函数,计算后将日子数传回主函数输出传回主函数输出11.3 编写一个函数编写一个函数print,打印一个学生的成绩数组,,打印一个学生的成绩数组,该数组中有该数组中有5个学生的数据记录,每个记录包括个学生的数据记录,每个记录包括num、、name、、score[3],用主函数输入这些记录,,用主函数输入这些记录,用用print函数输出这些记录。

函数输出这些记录 11.4 在上题的基础上,编写一个函数在上题的基础上,编写一个函数input,用来输,用来输入入5个学生的数据记录个学生的数据记录11.5 有有10个学生,每个学生的数据包括学号、姓名、个学生,每个学生的数据包括学号、姓名、3门课的成绩,从键盘输入门课的成绩,从键盘输入10个学生数据,要求打个学生数据,要求打印出印出3门课总平均成绩,以及最高分的学生的数据门课总平均成绩,以及最高分的学生的数据(包括学号、姓名、包括学号、姓名、3门课成绩、平均分数门课成绩、平均分数)11.6 编写一个函数编写一个函数new,对,对n个字符开辟连续的存储个字符开辟连续的存储空间,此函数应返回一个指针空间,此函数应返回一个指针(地址地址),指向字符,指向字符串开始的空间串开始的空间new(n)表示分配表示分配n个字节的内存空个字节的内存空间见图11.29 图图11.29 11.7 写一函数写一函数free,将上题用,将上题用new函数占的空间释放函数占的空间释放free(p)表示将表示将p(地址地址)指向的单元以后的内存段释指向的单元以后的内存段释放11.8 已有已有a、、b两个链表,每个链表中的结点包括学两个链表,每个链表中的结点包括学号、成绩。

要求把两个链表合并,按学号升序排号、成绩要求把两个链表合并,按学号升序排列11.9 有两个链表有两个链表a和和b,设结点中包含学号、姓名设结点中包含学号、姓名从从a链表中删去与链表中删去与b链表中有相同学号的那些结点链表中有相同学号的那些结点 11.10 建立一个链表,每个结点包括:学号、姓名、建立一个链表,每个结点包括:学号、姓名、性别、年龄输入一个年龄,如果链表中的结点性别、年龄输入一个年龄,如果链表中的结点所包含的年龄等于此年龄,则将此结点删去所包含的年龄等于此年龄,则将此结点删去。

下载提示
相似文档
正为您匹配相似的精品文档
相关文档