007 - 下载 第7章 结构体与共用体

Info iconThis preview shows page 1. Sign up to view the full content.

View Full Document Right Arrow Icon
This is the end of the preview. Sign up to access the rest of the document.

Unformatted text preview: 下载 第7章 结构体与共用体 前面的课程我们学习了一些简单数据类型(整型、实型、字符型)的定义和应用,还学 习了数组(一维、二维)的定义和应用,这些数据类型的特点是:当定义某一特定数据类型, 就限定该类型变量的存储特性和取值范围。对简单数据类型来说,既可以定义单个的变量, 也可以定义数组。而数组的全部元素都具有相同的数据类型,或者说是相同数据类型的一个 集合。 在日常生活中,我们常会遇到一些需要填写的登记表,如住宿表、成绩表、通讯地址等。 在这些表中,填写的数据是不能用同一种数据类型描述的,在住宿表中我们通常会登记上姓 名、性别、身份证号码等项目;在通讯地址表中我们会写下姓名、邮编、邮箱地址、电话号 码、 E - m a i l 等项目。这些表中集合了各种数据,无法用前面学过的任一种数据类型完全描述, 因此 C引入一种能集中不同数据类型于一体的数据类型 — 结构体类型。结构体类型的变量可 以拥有不同数据类型的成员,是不同数据类型成员的集合。 7.1 结构体类型变量的定义和引用 在上面描述的各种登记表中,让我们仔细观察一下住宿表、成绩表、通讯地址等。 住宿表由下面的项目构成: 姓 名 性 (字符串) 别 职 (字符) 业 年 (字符串) 龄 身份证号码 (整型) (长整型或字符串) 成绩表由下面的项目构成: 班 级 (字符串) 学 号 (长整型) 姓 名 操作系统 数据结构 计算机网络 (实型) (实型) (实型) (字符串) 通讯地址表由下面的项目构成: 姓 名 (字符串) 工作单位 家庭住址 (字符串) (字符串) 邮 (长整型) 这些登记表用 C提供的结构体类型描述如下: 住宿表 : struct accommod { char name[20]; char sex; char job[40]; int age; long number; 编 /* 姓名 */ /* 性别 */ /* 职业 */ /* 年龄 */ /* 身份证号码 */ 电话号码 E-mail (字符串或长整型) (字符串) 126 C语言程序设计 下载 }; 成绩表 : struct score { char grade[20]; /* 班级*/ long number; /* 学号*/ char name[20]; /* 姓名*/ float os; /* 操作系统 */ float datastru; /* 数据结构 */ float compnet; /* 计算机网络 */ }; 通讯地址表 : struct addr { char name[20]; char department[30];/* 部门*/ char address[30]; /* 住址*/ long box; /* 邮编*/ long phone; /* 电话号码 */ char email[30]; /*Email*/ }; 这一系列对不同登记表的数据结构的描述类型称为结构体类型。由于不同的问题有不同 的数据成员,也就是说有不同描述的结构体类型。我们也可以理解为结构体类型根据所针对 的问题其成员是不同的,可以有任意多的结构体类型描述。 下面给出 C对结构体类型的定义形式: struct 结构体名 { 成员项表列 }; 有了结构体类型,我们就可以定义结构体类型变量,以对不同变量的各成员进行引用。 7.1.1 结构体类型变量的定义 结构体类型变量的定义与其它类型的变量的定义是一样的,但由于结构体类型需要针对 问题事先自行定义,所以结构体类型变量的定义形式就增加了灵活性,共计有三种形式,分 别介绍如下: 1) 先定义结构体类型,再定义结构体类型变量: struct stu /* 定义学生结构体类型 */ { char name[20]; /* 学生姓名 */ char sex; /* 性别*/ long num; /* 学号*/ float score[3]; /* 三科考试成绩 */ }; struct stu student1,student2; 定义结构体类型变量 */ /* 第7章 结构体与共用体 下载 127 struct stu student3,student4; 用此结构体类型,可以定义更多的该结构体类型变量。 2) 定义结构体类型同时定义结构体类型变量: struct data { int day; int month; int year; } time1,time2; 也可以再定义如下变量: struct data time3,time4; 用此结构体类型,同样可以定义更多的该结构体类型变量。 3) 直接定义结构体类型变量: struct { char name[20]; char sex; long num; float score[3]; } person1,person2; /* 学生姓名 */ /* 性别 */ /* 学号 */ /* 三科考试成绩 */ /* 定义该结构体类型变量 */ 该定义方法由于无法记录该结构体类型,所以除直接定义外,不能再定义该结构体类型 变量。 7.1.2 结构体类型变量的引用 学习了怎样定义结构体类型和结构体类型变量,怎样正确地引用该结构体类型变量的成 员呢? C 规定引用的形式为: <结构体类型变量名 >.<成员名 > 若我们定义的结构体类型及变量如下: struct data { int day; int month; int year; } time1,time2; 则变量 time1和time2各成员的引用形式为:time1.day、 time1.month 、time1.year及 time2.day、 time2.month、 time2.year,如图 7-1所示。 其结构体类型变量的各成员与相应的简单类型变量使 用方法完全相同。 time 1 day timel.day month year timel.month timel.year month year time2 day time2.day time2.month time2.year 图7-1 结构体类型示例中变量各成员 的引用形式 7.1.3 结构体类型变量的初始化 由于结构体类型变量汇集了各类不同数据类型的成员,所以结构体类型变量的初始化就 128 C语言程序设计 下载 略显复杂。 结构体类型变量的定义和初始化为: struct stu /* 定义学生结构体类型 */ { char name[20]; /* 学生姓名 */ char sex; /* 性别*/ long num; /* 学号*/ float score[3]; /* 三科考试成绩 */ }; struct stu student={"liping",'f',970541,98.5,97.4,95}; 上述对结构体类型变量的三种定义形式均可在定义时初始化。结构体类型变量完成初始 化后,即各成员的值分别为: s t u d e n t . n a m e = " l i p i n g "、 s t u d e n t . s e x = ' f ' 、 s t u d e n t . n u m = 9 7 0 5 4 1、 s t u d e n t . s c o r e [ 0 ] = 9 8 . 5、s t u d e n t . s c o r e [ 1 ] = 9 7 . 4 、s t u d e n t . s c o r e [ 2 ] = 9 5。其存储在内存的情况如图 7-2 所示。 Liping f 970541 98.5 97.4 95 图7-2 结构体类型变量在内存中的存储 我们也可以通过 C提供的输入输出函数完成对结构体类型变量成员的输入输出。由于结构 体类型变量成员的数据类型通常是不一样的,所以要将结构体类型变量成员以字符串的形式 输入,利用 C 的类型转换函数将其转换为所需类型。类型转换的函数是: int atoi( char *str) ;转换 str所指向的字符串为整型,其函数的返回值为整型。 double atof(char *str) ;转换 str所指向的字符串为实型,其函数的返回值为双精度的实型。 long atol(char *str);转换 str所指向的字符串为长整型,其函数的返回值为长整型。 使用上述函数,要包含头文件 "stdlib.h"。 对上述的结构体类型变量成员输入采用的一般形式: char temp[20]; gets(student.name); /* 输入姓名 */ student.sex=getchar(); /* 输入性别 */ gets(temp); /* 输入学号 */ student.num=atol(temp); /* 转换为长整型 */ for(i=0;i<3;i++) /* 输入三科成绩 */ { gets(temp); student.score[i]=atoi(temp); } 对该结构体类型变量成员的输出也必须采用各成员独立输出,而不能将结构体类型变量 以整体的形式输入输出。 C允许针对具体问题定义各种各样的结构体类型,甚至是嵌套的结构体类型。 struct data { int day; 第7章 结构体与共用体 下载 129 int mouth; int year; }; struct stu { char name[20]; struct data birthday; long num; } person; 出生年月,嵌套的结构体类型 */ /* 该结构体类型变量成员的引用形式: person.name 、person.birthday.day、person. birthday. month、person. birthday.year、person.num 。 7.2 结构体数组的定义和引用 单个的结构体类型变量在解决实际问题时作用不大,一般是以结构体类型数组的形式出 现。结构体类型数组的定义形式为 struct stu { char name[20]; char sex; long num; float score[3]; }; struct stu stud[20]; /* 定义学生结构体类型 */ /* 学生姓名 */ /* 性别 */ /* 学号 */ /* 三科考试成绩 */ 定义结构体类型数组 stud ,*/ /* /* 该数组有 20 个结构体类型元素 */ 其数组元素各成员的引用形式为: stud[0].name 、stud[0].sex 、stud[0].score[i]; stud[1].name 、stud[1].sex 、stud[1].score[i]; ... ... stud[19].name 、stud[19].sex 、stud[19].score[i]; [例7-1] 设某组有 4 个人,填写如下的登记表,除姓名、学号外,还有三科成绩,编程实 现对表格的计算,求解出每个人的三科平均成绩,求出四个学生的单科平均,并按平均成绩 由高分到低分输出。 Number English Mathema Physics 1 Liping Name 78 98 76 2 Wangling 66 90 86 3 Jiangbo 89 70 76 4 Yangming 90 100 67 题目要求的问题多,采用模块化编程方式,将问题进行分解如下: 1) 结构体类型数组的输入。 2) 求解各学生的三科平均成绩。 3) 按学生的平均成绩排序。 Average 130 C语言程序设计 下载 4) 按表格要求输出。 5) 求解组内学生单科平均成绩并输出。 6) 定义 main()函数,调用各子程序。 第一步,根据具体情况定义结构体类型。 struct stu { char name[20]; /* 姓名*/ long number; /* 学号*/ float score[4]; /* 数组依此存放 English 、Mathema 、Physics ,及Average*/ }; 由于该结构体类型会提供给每个子程序使用,是共用的,所以将其定义为外部的结构体 类型,放在程序的最前面。 第二步,定义结构体类型数组的输入模块。 void input(arr,n) /* 入结构体类型数组 arr 的n个元素 */ 输 struct stu arr; int n; { int i,j; char temp[30]; for (i=0;i<n;i++) { printf("\ninput name,number,English,mathema,physic\n"); /*打印提示信息*/ gets(arr[i].name); /* 输入姓名 */ gets(temp); /* 输入学号 */ arr[i].number=atol(temp); for(j=0;j<3;j++) { gets(temp); /* 输入三科成绩 */ arr[i].score[j]=atoi(temp); }; } } 第三步,求解各学生的三科平均成绩。 在结构体类型数组中第 i个元素 a r r [ i ] 的成员 s c o r e的前三个元素为已知,第四个 Av e r a g e需 计算得到。 void aver(arr,n) struct stu arr; int n; { int i,j; 个 for(i=0;i<n;i++) /*n 学生 */ { arr[i].score[3]=0; for(j=0;j<3;j++) arr[i].score[3]=arr[i].score[3]+arr[i].score[j];求和*/ /* 下载 arr[i].score[3]=arr[i].score[3] /3; } 第7章 结构体与共用体 131 平均成绩 */ /* } 第四步,按平均成绩排序,排序算法采用冒泡法。 void order(arr,n) struct stu arr; int n; { struct stu temp; int i,j,x,y; for(i=0;i<n-1;i++) for(j=0;j<n-1-i;j++) if (arr[j].score[3]>arr[j+1].score[3]) { temp=arr[j]; /* 结构体类型变量不允许以整体输入或输出,但允许相互赋值 */ arr[j]=arr[j+1]; /* 行交换 */ 进 arr[j+1]=temp; } } 第五步,按表格要求输出。 void output(arr,n) /* 表格形式输出有 n个元素的结构体类型数组各成员 */ 以 int n; struct stu arr; {int i,j; printf("********************TABLE********************\n"); /* */ 打印表头 printf("----------------------------------------------------\n"); /* 输出一条水平线 */ printf("|%10s|%8s|%7s|%7s|%7s|%7s|\n","Name","Number","English","Mathema", "physics","average"); /* 输出效果为: | Name| Number|English|Mathema|Physics|Average|*/ printf("----------------------------------------------------\n"); for (i=0;i<n;i++) { printf("|%10s|%8ld|",arr[i].name,arr[i].number);/* 输出姓名、学号 */ for(j=0;j<4;j++) printf("%7.2f|",arr[i].score[j]);/* 输出三科成绩及三科的平均 */ printf("\n"); printf("---------------------------------------------------\n"); } } 第六步,求解组内学生单科平均成绩并输出。在输出表格的最后一行,输出单科平均成 绩及总平均。 void out_row(arr,n) /* 对n个元素的结构体类型数组求单项平均 */ int n; struct stu arr; { float row[4]={0,0,0,0};* 定义存放单项均的一维数组 */ / int i,j; 132 C语言程序设计 for(i=0;i<4;i++) { for(j=0;j<n;j++) row[i]=row[i]+arr[j].score[i];/* 计算单项总和 */ row[i]=row[i]/n; 计算单项平均 */ /* } printf("|%19c|",' '); 按表格形式输出 */ /* for (i=0;i<4;i++) printf("%7.2f|",row[i]); printf("\n------------------------------------------\n"); } 第七步,定义 main()函数,列出完整的程序清单。 #include <stdlib.h> #include <stdio.h> struct stu { char name[20]; long number; float score[4]; }; main() { void input(); /* 函数声明 */ void aver(); void order(); void output(); void out_row(); struct stu stud[4]; /* 定义结构体数组 */ float row[3]; input(stud,4); /* 依此调用自定义函数 */ aver(stud,4); order(stud,4); output(stud,4); out_row(stud,4); } /****************************/ void input(arr,n) struct stu arr; int n; { int i,j; char temp[30]; for (i=0;i<n;i++) { printf("\nInput Name,Number,English,Mathema,Physic\n"); gets(arr[i].name); gets(temp); arr[i].number=atol(temp); 下载 第7章 结构体与共用体 下载 for(j=0;j<3;j++) { gets(temp); arr[i].score[j]=atoi(temp); }; } } /***********************/ void aver(arr,n) struct stu arr; int n; { int i,j; for(i=0;i<n;i++) { arr[i].score[3]=0; for(j=0;j<3;j++) arr[i].score[3]=arr[i].score[3]+arr[i].score[j]; arr[i].score[3]=arr[i].score[3] /3; } } /***********************/ void order(arr,n) struct stu arr; int n; { struct stu temp; int i,j,x,y; for(i=0;i<n-1;i++) for(j=0;j<n-1-i;j++) if (arr[j].score[3]>arr[j+1].score[3]) { temp=arr[j]; arr[j]=arr[j+1]; arr[j+1]=temp; } } /******************/ void output(arr,n) int n; struct stu arr; {int i,j; printf("********************TABLE********************\n"); printf("----------------------------------------------------\n"); printf("|%10s|%8s|%7s|%7s|%7s|%7s|\n","Name","Number","English","mathema", "physics","average"); printf("----------------------------------------------------\n"); for (i=0;i<n;i++) { printf("|%10s|%8ld|",arr[i].name,arr[i].number); 133 134 C语言程序设计 for(j=0;j<4;j++) printf("%7.2f|",arr[i].score[j]); printf("\n"); printf("---------------------------------------------------\n"); } } /*************************************/ void out_row(arr,n) int n; struct stu arr; { float row[4]={0,0,0,0}; int i,j; for(i=0;i<4;i++) { for(j=0;j<n;j++) row[i]=row[i]+arr[j].score[i]; row[i]=row[i]/n; } printf("|%19c|",' '); for (i=0;i<4;i++) printf("%7.2f|",row[i]); printf("\n------------------------------------------\n"); } 运行程序: RUN ↵ Input Name,Number,English,Mathema,Physic Liping ↵ 1↵ 78 ↵ 98 ↵ 76 ↵ Input Name,Number,English,Mathema,Physic Wangling ↵ 2↵ 66 ↵ 90 ↵ 86 ↵ Input Name,Number,English,Mathema,Physic Jiangbo ↵ 3↵ 89 ↵ 70 ↵ 76 ↵ Input Name,Number,English,Mathema,Physic Yangming ↵ 4↵ 90 ↵ 100 下载 第7章 结构体与共用体 下载 135 67 ↵ *******************TABLE********************* ------------------------------------------------------| Number| Name|English| Mathema| Physics| Average| ------------------------------------------------------| Yangming| 4| 90.00| 100.00| 67.00| 85.67| ------------------------------------------------------| Liping| 1| 78.00| 98.00| 76.00| 84.00| ------------------------------------------------------| Wangling| 2| 66.00| 90.00| 86.00| 80.72| ------------------------------------------------------| Jiangbo| 3| 89.00| 70.00| 76.00| 78.33| ------------------------------------------------------| | 80.75| 89.50| 76.25| 82.18| ------------------------------------------------------- 程序中要谨慎处理以数组名作函数的参数。由于数组名作为数组的首地址,在形参和实 参结合时,传递给子程序的就是数组的首地址。形参数组的大小最好不定义,以表示与调用 函数的数组保持一致。在定义的结构体内,成员 s c o r e [ 3 ] 用于表示计算的平均成绩,也是我们 用于排序的依据。我们无法用数组元素进行相互比较,而只能用数组元素的成员 s c o r e [ 3 ]进行 比较。在需要交换的时候,用数组元素的整体包括姓名、学号、三科成绩及平均成绩进行交 换。在程序 o r d e r ()函数中,比较采用: a r r [ j ] . s c o r e [ 3 ] > a r r [ j + 1 ] . s c o r e [ 3 ] ,而交换则采用: arr[j] ← arr[j+1] → 7.3 结构体指针的定义和引用 指针变量非常灵活方便,可以指向任一类型的变量,若定义指针变量指向结构体类型变 量,则可以通过指针来引用结构体类型变量。 7.3.1 指向结构体类型变量的使用 首先让我们定义结构体: struct stu { char name[20]; long number; float score[4]; }; 再定义指向结构体类型变量的指针变量: struct stu *p1, *p2 ; 定义指针变量 p1、p2,分别指向结构体类型变量。引用形式为:指针变量→成员; [例7-2] 对指向结构体类型变量的正确使用。输入一个结构体类型变量的成员,并输出。 #include <stdlib.h> struct data { /* 用malloc() 需要*/ 使 /* 定义结构体 */ 136 C语言程序设计 int day,month,year; }; struct stu /* 定义结构体 */ { char name[20]; long num; struct data birthday; 下载 /* 嵌套的结构体类型成员 */ }; main() /* 定义main() 函数 */ { struct stu *student; 定义结构体类型指针 */ /* student=malloc(sizeof(struct stu)); 为指针变量分配安全的地址 */ /* printf("Input name,number,year,month,day:\n"); scanf("%s",student->name); 输入学生姓名、学号、出生年月日 */ /* scanf("%ld",&student->num); scanf("%d%d%d",&student->birthday.year,&student->birthday.month, &student->birthday.day); printf("\nOutput name,number,year,month,day\n" ); /* 打印输出各成员项的值 */ printf("%20s%10ld%10d//%d//%d\n",student->name,student->num, student->birthday.year,student->birthday.month, student->birthday.day); } 程序中使用结构体类型指针引用结构体变量的成员,需要通过 C 提供的函数 m a l l o c ( ) 来为 指针分配安全的地址。函数 s i z e o f ( )返回值是计算给定数据类型所占内存的字节数。指针所指 各成员形式为: student->name student->num student->birthday.year student->birthday.month student->birthday.day 运行程序: RUN ↵ Input name,number,year,month,day: Wangjian 34 1987 5 23 ↵ Wangjian 34 1987//5//23 7.3.2 指向结构体类型数组的指针的使用 定义一个结构体类型数组,其数组名是数组的首地址,这一点前面的课程介绍得很清楚。 定义结构体类型的指针,既可以指向数组的元素,也可以指向数组,在使用时要加以区分。 [例7-3] 在例 7 - 2中定义了结构体类型,根据此类型再定义结构体数组及指向结构体类型 的指针。 struct data 第7章 结构体与共用体 下载 137 { int day,month,year; }; struct stu /* 定义结构体 */ { char name[20]; long num; struct data birthday; }; struct stu student[4],*p; /* 嵌套的结构体类型成员 */ 定义结构体数组及指向结构体类型的指针 */ /* 作 p=student,此时指针 p就指向了结构体数组 student。 p是指向一维结构体数组的指针,对数组元素的引用可采用三种方法。 1) 地址法 student+i和p+i 均表示数组第 i个元素的地址,数组元素各成员的引用形式为: (s t u d e n t + i ) - > n a m e 、( s t u d e n t + i ) - > n u m和( p + i ) - > n a m e 、 p + i )- > n u m 等。 s t u d e n t + i 和 p + i ( 与&student[i]意义相同。 2) 指针法 若p指向数组的某一个元素,则 p++就指向其后续元素。 3) 指针的数组表示法 若 p = s t u d e n t ,我们说指针 p 指向数组 s t u d e n t , p [ i ] 表示数组的第 i 个元素,其效果与 student[i] 等同。对数组成员的引用描述为 :p[i].name、p[i].num等。 [例7-4] 指向结构体数组的指针变量的使用。 struct data /* 定义结构体类型 */ { int day,month,year; }; struct stu /* 定义结构体类型 */ { char name[20]; long num; struct data birthday; }; main() { int i; struct stu *p,student[4]={{"liying",1,1978,5,23},{"wangping",2,1979,3,14}, {"libo",3,1980,5,6},{"xuyan",4,1980,4,21}}; /* 定义结构体数组并初始化 */ p=student; /* 数组的首地址赋值给指针 p,p 指向了一维数组 student*/ 将 printf("\n1----Output name,number,year,month,day\n" ); for(i=0;i<4;i++) /* 用指针法输出数组元素的各成员 */ 采 printf("%20s%10ld%10d//%d//%d\n",(p+i)->name,(p+i)->num, (p+i)->birthday.year,(p+i)->birthday.month, (p+i)->birthday.day); 138 C语言程序设计 下载 printf("\n2----Output name,number,year,month,day\n" ); for(i=0;i<4;i++,p++) /* 采用指针法输出数组元素的各成员 */ printf("%20s%10ld%10d//%d//%d\n",p->name,p->num, p->birthday.year,p->birthday.month, p->birthday.day); printf("\n3-----Output name,number,year,month,day\n" ); for(i=0;i<4;i++) /* 采用地址法输出数组元素的各成员 */ printf("%20s%10ld%10d//%d//%d\n",(student+i)->name,(student+i)->num, (student+i)->birthday.year,(student+i)->birthday.month, (student+i)->birthday.day); p=student; printf("\n4-----Output name,number,year,month,day\n" ); for(i=0;i<4;i++) /* 采用指针的数组描述法输出数组元素的各成员 */ printf("%20s%10ld%10d//%d//%d\n",p[i].name,p[i].num, p[i].birthday.year,p[i].birthday.month, p[i].birthday.day); } 运行程序: RUN ↵ 1----Output name,number,year,month,day liying 1 1978//5//23 wangping 2 1979//3//14 libo 3 1980//5//6 xuyan 4 1980//4//21 2----Output name,number,year,month,day liying 1 1978//5//23 wangping 2 1979//3//14 libo 3 1980//5//6 xuyan 4 1980//4//21 3----Output name,number,year,month,day liying 1 1978//5//23 wangping 2 1979//3//14 libo 3 1980//5//6 xuyan 4 1980//4//21 4----Output name,number,year,month,day liying 1 1978//5//23 wangping 2 1979//3//14 libo 3 1980//5//6 xuyan 4 1980//4//21 对二维或多维数组的指针,有兴趣的同学可课后讨论,总结出来。 7.4 链表的建立、插入和删除 数组作为存放同类数据的集合,给我们在程序设计时带来很多的方便,增加了灵活性。 第7章 结构体与共用体 下载 139 但 数组也同样存在一些弊病。如数组的大小在定义时要事先规定,不能在程序中进行调整, 这样一来,在程序设计中针对不同问题有时需要 3 0 个大小的数组,有时需要 5 0个数组的大小, 难于统一。我们只能够根据可能的最大需求来定义数组,常常会造成一定存储空间的浪费。 我们希望构造动态的数组,随时可以调整数组的大小,以满足不同问题的需要。链表就 是我们需要的动态数组。它是在程序的执行过程中根据需要有数据存储就向系统要求申请存 储空间,决不构成对存储区的浪费。 链表是一种复杂的数据结构,其数据之间的相互关系使链表分成三种:单链表、循环链 表、双向链表,下面将逐一介绍。 7.4.1 单链表 图7-3是单链表的结构。 1200 2000 1800 2400 head 12 34 56 78 2000 1200 1800 2400 NULL 图7-3 单链表 单链表有一个头节点 h e a d ,指向链表在内存的首地址。链表中的每一个节点的数据类型 为结构体类型,节点有两个成员:整型成员(实际需要保存的数据)和指向下一个结构体类 型节点的指针即下一个节点的地址(事实上,此单链表是用于存放整型数据的动态数组) 。链 表按此结构对各节点的访问需从链表的头找起,后续节点的地址由当前节点给出。无论在表 中访问那一个节点,都需要从链表的头开始,顺序向后查找。链表的尾节点由于无后续节点, 其指针域为空,写作为 NULL。 图7 - 3 还给出这样一层含义,链表中的各节点在内存的存储地址不是连续的,其各节点的 地址是在需要时向系统申请分配的,系统根据内存的当前情况,既可以连续分配地址,也可 以跳跃式分配地址。 看一下链表节点的数据结构定义: struct node { int num; struct node *p; }; 在链表节点的定义中,除一个整型的成员外,成员 p指向与节点类型完全相同的指针。 在链表节点的数据结构中,非常特殊的一点就是结构体内的指针域的数据类型使用了未定义 成功的数据类型。这是在 C 中唯一规定可以先使用后定义的数据结构。 • 单链表的创建过程有以下几步: 1) 定义链表的数据结构。 2) 创建一个空表。 3) 利用 malloc()函数向系统申请分配一个节点。 140 C 语言程序设计 下载 4 ) 将新节点的指针成员赋值为空。若是空表,将新节点连接到表头;若是非空表,将新 节点接到表尾。 5) 判断一下是否有后续节点要接入链表,若有转到3),否则结束。 • 单链表的输出过程有以下几步 1) 找到表头。 2) 若是非空表,输出节点的值成员,是空表则退出。 3) 跟踪链表的增长,即找到下一个节点的地址。 4) 转到 2)。 [例7-5] 创建一个存放正整数(输入 -999做结束标志)的单链表,并打印输出。 #include <stdlib.h> /* malloc() 的头文件 */ 包含 #include <stdio.h> 链表节点的结构 */ struct node /* { int num; struct node *next; }; main() { struct node *creat(); /* 函数声明 */ void print(); struct node *head; /* 定义头指针 */ head=NULL; /* 建一个空表 */ head=creat(head); /* 创建单链表 */ print(head); /* 打印单链表 */ } /******************************************/ struct node *creat(struct node *head) 函数返回的是与节点相同类型的指针 */ /* { struct node *p1,*p2; p1=p2=(struct node*) malloc(sizeof(struct node));申请新节点 */ /* scanf("%d",&p1->num); /* 输入节点的值 */ p1->next=NULL; /* 将新节点的指针置为空 */ while(p1->num>0) /* 输入节点的数值大于 0*/ { if (head==NULL) head=p1; /* 空表,接入表头 */ else p2->next=p1; /* 非空表,接到表尾 */ p2=p1; p1=(struct node *)malloc(sizeof(struct node)); 申请下一个新节点 */ /* scanf("%d",&p1->num); /* 输入节点的值 */ } 返回链表的头指针 */ return head; /* } /*******************************************/ void print(struct node *head) 输出以head 为头的链表各节点的值 */ /* { 第7章 结构体与共用体 下载 struct node *temp; temp=head; while (temp!=NULL) { printf("%6d",temp->num); temp=temp->next; } 141 /* 取得链表的头指针 */ /* 只要是非空表 */ /* 输出链表节点的值 */ /* 跟踪链表增长 */ } 在链表的创建过程中,链表的头指针是非常重要的参数。因为对链表的输出和查找都要 从链表的头开始,所以链表创建成功后,要返回一个链表头节点的地址,即头指针。 运行程序: RUN ↵ 1 2 1 2 3 3 4 4 5 5 6 6 -999 ↵ 7 7 链表的创建过程用图示如下: 第一步,创建空表: head NULL 第二步,申请新节点: p1 p2 1 NULL 第三步,若是空表,将新节点接到表头: head P1 1 NULL P2 若是非空表, head ... p2 p1 NULL P2->next=p1。 第四步, p2=p1: head 第五步,申请新节点: p1 NULL NULL p2 若数值为负,则结束;否则转到第三步。 7.4.2 单链表的插入与删除 在链表这种特殊的数据结构中,链表的长短需要根据具体情况来设定,当需要保存数据 时向系统申请存储空间,并将数据接入链表中。对链表而言,表中的数据可以依此接到表尾 或连结到表头,也可以视情况插入表中;对不再需要的数据,将其从表中删除并释放其所占 空间,但不能破坏链表的结构。这就是下面将介绍的链表的插入与删除。 1. 链表的删除 在链表中删除一个节点,用图 7-4描述如下: [ 例7-6] 创 建一个学生学号及姓名的单链表,即节点包括学生学号、姓名及指向下一个 节点的指针,链表按学生的学号排列。再从键盘输入某一学生姓名,将其从链表中删除。 首先定义链表的结构: struct 142 C 语言程序设计 下载 { int num;/* 学生学号 */ char str[20]; /* 姓名*/ struct node *next; }; 删除表中节点s'p->next=s->next head p s NULL 删除表头节点head=head->next head NULL 删除表尾节点s,p->next=NULL head p s NULL NULL 图7-4 链表中节点的删除 从图 7 - 4中看到,从链表中删除一个节点有三种情况,即删除链表头节点、删除链表的中 间节点、删除链表的尾节点。题目给出的是学生姓名,则应在链表中从头到尾依此查找各节 点,并与各节点的学生姓名比较,若相同,则查找成功,否则,找不到节点。由于删除的节 点可能在链表的头,会对链表的头指针造成丢失,所以定义删除节点的函数的返回值定义为 返回结构体类型的指针。 struct node *delet(head,pstr)/*ead 为头指针,删除 pstr 所在节点 */ 以h struct node *head; char *pstr; { struct node *temp,*p; temp=head; /* 链表的头指针 */ if (head==NULL) /* 链表为空 */ printf("\nList is null!\n"); else /* 非空表*/ { temp=head; while (strcmp(temp->str,pstr)!=0&&temp->next!=NULL) /* 若节点的字符串与输入字符串不同,并且未到链表尾 */ { p=temp; temp=temp->next; /* 跟踪链表的增长,即指针后移 */ } if(strcmp(temp->str,pstr)==0 ) /* 找到字符串 */ { if(temp==head) { /* 表头节点 */ printf("delete string :%s\n",temp->str); head=head->next; free(temp); /* 释放被删节点 */ 第7章 结构体与共用体 下载 143 } else { p->next=temp->next; /* 表中节点 */ printf("delete string :%s\n",temp->str); free(temp); } } else printf("\nno find string!\n");/* 没找到要删除的字符串 */ } return(head); } /* 返回表头指针 */ 2. 链表的插入 首先定义链表的结构: struct { int num; /* 学生学号 */ char str[20]; /* 姓名*/ struct node *next; }; 在建立的单链表中,插入节点有三种情况,如图 7-5所示。 在表头插入节点p1,head=p1 head NULL p1 在表中插入节点p1,p3->next=p1;p1->next=p2; p3 head p2 NULL p1 在表尾插入节点p1,p2->next=p1;p1->next=NULL head p2 NULL p1 图7-5 单链表中插入节点 插入的节点可以在表头、表中或表尾。假定我们按照以学号为顺序建立链表,则插入的 节点依次与表中节点相比较,找到插入位置。由于插入的节点可能在链表的头,会对链表的 头指针造成修改,所以定义插入节点的函数的返回值定义为返回结构体类型的指针。节点的 插入函数如下: struct node *insert(head,pstr,n) /* 插入学号为 n、姓名为 pstr 的节点 */ struct node *head; /* 链表的头指针 */ char *pstr; 144 C语言程序设计 下载 int n; { struct node *p1,*p2,*p3; p1=(struct node*)malloc(sizeof(struct node));/* 分配一个新节点 */ strcpy(p1->str,pstr); /* 写入节点的姓名字串 */ p1->num=n; /* 学号*/ p2=head; if (head==NULL) /* 空表*/ { head=p1; p1->next=NULL;/* 新节点插入表头 */ } else { /*非空表 */ while(n>p2->num&&p2->next!=NULL) /* 输入的学号小于节点的学号,并且未到表尾 */ { p3=p2; p2=p2->next; /* 跟踪链表增长 */ } if (n<=p2->num) /* 找到插入位置 */ if (head==p2) /* 插入位置在表头 */ { head=p1; p1->next=p2; } else { /*插入位置在表中 */ p3->next=p1; p1->next=p2; } else { /*插入位置在表尾 */ p2->next=p1; p1->next=NULL; } } return(head); } /* 返回链表的头指针 */ 3. 实例 [例7-7] 创建包含学号、姓名节点的单链表。其节点数任意个,表以学号为序,低学号的在前, 高学号的在后,以输入姓名为空作结束。在此链表中,要求删除一个给定姓名的节点,并插 入一个给定学号和姓名的节点。 # include "stdlib.h" # include "malloc. h" 下载 第7章 结构体与共用体 struct node /* 节点的数据结构 */ { int num; char str[20]; struct node *next; }; /****************************/ main( ) { /* 函数声明 */ struct node *creat(); struct node *insert(); struct node *delet(); void print( ); struct node *head; char str[20]; int n; 做空表 */ head=NULL; /* head=creat (head); /* 调用函数创建以 head 为头的链表 */ print(head) ;/* 调用函数出节点 */ printf("\n input inserted num,name:\n"); 输入学号 */ gets(str); /* n=atoi (str); 输入姓名 */ gets(str); /* 将节点插入链表 */ head=insert (head, str, n); /* ; /* 调用函数输出节点 */ print (head) printf("\n input deleted name:\n"); 输 gets(str); /* 入被删姓名 */ 调用函数删除节点 */ head=delet(head,str); /* 调用函数输出节点 */ print (head); /* return; } /**********************/ /*** 创建链表 ************/ struct node *creat(struct node *head) { char temp[30]; struct node *pl,*p2; pl=p2=(struct node*) malloc(sizeof(struct node)); printf ("input num, name: \n") ; printf("exit:double times Enter!\n"); gets(temp); gets (p1->str); pl->num=atoi (temp); pl->next=NULL; while (strlen (pl->str)>0 { ; if (head==NULL) head=pl else p2->next=p1; 145 146 C 语言程序设计 P2=pl ; pl=(struct node *)malloc(sizeof(struct node)); printf ("input num, name: \n"); printf("exit:double times Enter!\n"); gets(temp); gets(pl ->str); p1->num=atoi (temp); P1->next=NULL; } return head; } /********************/ /********** 插入节点 **********/ struct node *insert (head, pstr,n); struct node *head; char *pstr; int n; { struct node *pl,*p2,*p3; p1=(struct node*)malloc(sizeof(struct node)); strcpy (p1->str, pstr); p1->num=n; p2=head; if(head==NULL) { head=pl;pl->next=NULL; } else { while (n>p2->num&&p2->next!=NULL) { p3=P2 p2=p2->next; } if (n<=p2->num) if (head==p2) { head=pl; pl->next=p2; } else { p3->next=pl; pl->next=p2; } else { p2->next=pl; pl->next=NULL; 下载 下载 第7章 结构体与共用体 147 } } return(head); } /*************************/ /***** 删除节点 *************/ struct node *delet (head, pstr) struct node *head; char *pstr; { struct node *temp,*p; temp=head; if (head==NULL) printf("\nList is null!\n"); else { temp=head; while (strcmp(temp->str,pstr)!=O&&temp->next!=NULL) { p=temp; temp=temp->next, } if(strcmp(temp->str,pstr)==0) { if (temp== head) { head=head->next; free(temp); } else { p->next =temp->next; printf("delete string :%s\n",temp->str); free(temp); } } else printf("\nno find string!\n"); } return(head); } /**********************************/ /********** 链表各节点的输出 **********/ void print (struct node *head) { struct node *temp; temp=head; printf("\n output strings:\n"); while (temp!=NULL) { ,temp->str) ; printf("\n%d----%s\n",temp->num 148 C 语言程序设计 temp=temp->next } return; ; } 运行程序: RUN ↵ input num,name: exit:double times Enter! 1↵ Huangping ↵ input num,name: exit:double times Enter! 3↵ Lixiaobo ↵ input num,name: exit:double times Enter! 4↵ Yangjinhua ↵ input num,name: exit:double times Enter! 7↵ xuehong ↵ input num,name: exit:double times Enter! ↵ ↵ output strings: 1------- Huangping 3--------Lixiaobo 4--------Yangjinhua 7--------xuehong input inserted num,name: 5↵ Liling ↵ output strings: 1------- Huangping 3--------Lixiaobo 4--------Yangjinhua 5--------Liling 7--------xuehong input deleted name: Lixiaobo ↵ delete string : Lixiaobo 1------- Huangping 4--------Yangjinhua 5--------Liling 7--------xuehong 下载 第7章 结构体与共用体 下载 149 7.5 共用体 所谓共用体类型是指将不同的数据项组织成一个整体,它们在内存中占用同一段存储单 元。其定义形式为: union 共用体名 {成员表列 }; 7.5.1 共用体的定义 union data { int float double char } obj; a; b; c; d; 该形式定义了一个共用体数据类型 union data ,定义了共用体数据类型变量 o b j。共用体 数据类型与结构体在形式上非常相似,但其表示的含义及存储是完全不同的。先让我们看一 个小例子。 [例7-8] union data /* 共用体 */ { int a; float b; double c; char d; }mm; struct stud /* 结构体*/ { int a; float b; double c; char d; }; main() { struct stud student printf("%d,%d",sizeof(struct stud),sizeof(union data)); } 运行程序输出: RUM ↵ 15 ,8 程序的输出说明结构体类型所占的内存空间为其各成员所占存储空间之和。而形同结构 体的共用体类型实际占用存储空间为其最长的成员所占的存储空间。详细说明如图 7-6所示。 150 C语言程序设计 下载 结构体 a 共用体 2 d 4 b a b 15 c d 8 8 c 1 图7-6 共用体类型与结构体类型占用存储空间的比较 对共用体的成员的引用与结构体成员的引用相同。但由于共用体各成员共用同一段内存 空间,使用时,根据需要使用其中的某一个成员。从图中特别说明了共用体的特点,方便程 序设计人员在同一内存区对不同数据类型的交替使用,增加灵活性,节省内存。 7.5.2 共用体变量的引用 可以引用共用体变量的成员,其用法与结构体完全相同。若定义共用体类型为: union data { int a; float b; double c; char d; }mm; /* 共用体 */ 其成员引用为: mm.a, mm.b, mm.c, mm.d 但是要注意的是,不能同时引用四个成员,在某一时刻,只能使用其中之一的成员。 [例7-9] 对共用体变量的使用。 main() { union data { int a; float b; double c; char d; }mm; mm.a=6; printf("%d\n",mm.a); mm.c=67.2; printf("%5.1lf\n",mm.c); mm.d='W'; mm.b=34.2; printf("%5.1f,%c\n",mm.b,mm.d); } 运行程序输出为: 6 第7章 结构体与共用体 下载 151 67 .2 34 .2,= 程序最后一行的输出是我们无法预料的。其原因是连续做 m m . d = ' W ' ; m m . b = 3 4 . 2 ;两个 连续的赋值语句最终使共用体变量的成员 m m . b所占四字节被写入 34 .2 ,而写入的字符被覆盖 了,输出的字符变成了符号“ = ” 。事实上,字符的输出是无法得知的,由写入内存的数据决 定。 例子虽然很简单,但却说明了共用体变量的正确用法。 [例7-10] 通过共用体成员显示其在内存的存储情况。 定义一个名为 time的结构体,再定义共用体 dig: struct time { int year; /* 年*/ int month; * 月*/ / int day; /* 日*/ }; union dig { struct time data; /* 套的结构体类型 */ 嵌 char byte[6]; }; 假定共用体的成员在内存的存储是从地址 1 0 0 0 单元开始存放,整个共用体类型需占存储 空间 6 个字节,即共用体 d i g 的成员 d a t a 与 b y t e 共用这 6个字节的存储空间,存储空间分配示意 如图 7-7所示。 存储器 1000 byte[0] 1001 1002 byte[2] 1003 byte[3] 1004 byte[4] 1005 data.year byte[1] 共 用 体 类 型 byte[5] data.month data.day 图7-7 共用体dig成员在存储空间中的分配示意图 由于共用体成员 d a t a 包含三个整型的结构体成员,各占 2 个字节。由图 7 - 7所示可见, d a t a . y e a r 是由 2 个字节组成,用 b y t e 字符数组表示为 b y t e [ 0 ] 和 byte[1] 。 b y t e [ 1 ] 是高字节, byte[0] 是低字节。下面用程序实现共用体在内存中的存储。 struct time { int year; /* */ 年 int month; /* 月*/ int day; /* 日*/ }; 152 C 语言程序设计 下载 union dig { struct time data; /* 套的结构体类型 */ 嵌 char byte[6]; }; main() { union dig unit; int i; printf("enter year:\n"); scanf("%d",&unit.data.year); /* 输入年*/ printf("enter month:\n"); scanf("%d",&unit.data.month); /* 输入月*/ printf("enter day:\n"); scanf("%d",&unit.data.day); /* 输入日*/ printf("year=%d month=%d day=%d\n", unit.data.year,unit. data. month, un data.day); /* 打印输出 */ for(i=0;i<6;i++) printf("%d,",unit.byte[i]); /* 按字节以十进制输出 */ printf("\n"); } 运行程序: RUN ↵ enter year: 1976 ↵ enter month: 4↵ enter day: 23 ↵ year=1976 month=4 184,7,4,0,23,0 day=23 从程序的输出结果来看, 1 9 7 6 占两个字节,由第 0、 1字节构成,即 7 ×2 5 6 + 1 8 4 = 1 9 7 6。 4 同样占两个字节,由第 2、3字节构成, 0×256+4=4,23由第 4、5字节构成, 23=0×256+23。 ...
View Full Document

This note was uploaded on 04/05/2010 for the course FINANCE AN FRE6851 taught by Professor Gallagher,evan during the Spring '09 term at NYU Poly.

Ask a homework question - tutors are online