制 作:方 斌
C语言程序设计教程郧阳师范高等专科学校计算机科学系方 斌 制作制 作:方 斌第 11章 结构体与共同体
11.1 概述 11.7 用结构体指针处理链表
11.2 结构体变量的定义 11.8 共用体
11.3 结构体变量的引用 11.9 枚举类型
11.4 结构体变量的初始化 11.10 用 typedef定义类型
11.5 结构体数组 本章要求及作业
11.6 结构体指针制 作:方 斌
11.1 概 述结构体( structure)是一种数据类型,它把互相联系的数据组合成一个整体。例、
一个学生的学号、姓名、性别、年龄、成绩、地址,是互相联系的数据,在 C语言中用“结构体( structure)”来定义。
制 作:方 斌
struct student
{
int num; /* 学号 */
char name[20]; /* 姓名 */
char sex; /* 性别 */
int age; /* 年龄 */
float score; /* 成绩 */
char addr[30]; /* 地址 */
};
struct 是关键字,不能省略。
student 是定义的结构体类型名。
结构体中的每一项数据,称为结构体“成员”( member)或“分量”。
“结构体”在大多数资料中被称为“结构”。
制 作:方 斌
11.2 结构体变量的定义结构体是一种数据类型(像 int,char,flaot是数据类型一样),
可以用它定义变量。
用结构体类型定义变量的方式有三种:
制 作:方 斌一、先定义结构体类型,再定义变量,例、
struct student
{
int num; /* 学号 */
char name[20]; /* 姓名 */
char sex; /* 性别 */
int age; /* 年龄 */
float score; /* 成绩 */
char addr[30]; /* 地址 */
};
struct student student1,student2;
/*定义结构体 student类型的变量 student1和 student2*/
制 作:方 斌结构体变量中的各成员,在内存中顺序存放。结构体变量所占内存大小用运算符
sizeof计算。 例、
printf("%d %d\n",sizeof(struct student),sizeof(student1));
制 作:方 斌为了方便,可以这样定义结构体变量:
#define STUDENT struct student
STUDENT
{
int num;
char name[20];
char sex;
int age;
float score;
char addr[30];
};
STUDENT student1,student2;
制 作:方 斌二、在定义类型的同时定义变量
struct student
{
int num; /* 学号 */
char name[20]; /* 姓名 */
char sex; /* 性别 */
int age; /* 年龄 */
float score; /* 成绩 */
char addr[30]; /* 地址 */
} student1,student2;
制 作:方 斌三、直接定义变量
struct
{
int num; /* 学号 */
char name[20]; /* 姓名 */
char sex; /* 性别 */
int age; /* 年龄 */
float score; /* 成绩 */
char addr[30]; /* 地址 */
} student1,student2;
制 作:方 斌四、结构的成员是另一个结构体变量
struct date /*日期结构 */
{
int month; /* 月 */
int day; /* 日 */
int year; /* 年 */
};
struct student
{
int num; /* 学号 */
char name[20]; /* 姓名 */
char sex; /* 性别 */
int age; /* 年龄 */
struct date birthday; /*成员是另一个结构体变量 */
char addr[30]; /* 地址 */
} student1,student2;
制 作:方 斌上例的定义也可以写成如下形式:
struct student
{
int num; /* 学号 */
char name[20]; /* 姓名 */
char sex; /* 性别 */
int age; /* 年龄 */
struct date
{
int year;
int month;
int day;
}birthday; /*成员是另一个结构体变量 */
char addr[30]; /* 地址 */
} student1,student2;
制 作:方 斌
[案例 11.1] 定义一个反映学生基本情况的结构类型,用以存储学生的相关信息。
/*将以下案例代码文件存储为文件名 struct.h*/
/*功能:定义一个反映学生基本情况的结构类型 */
struct date /*日期结构类型:由年、月、日三项组成 */
{ int year;
int month;
int day;
};
struct std_info /*学生信息结构类型:由学号、姓名、性别和生日共 4项组成 */
{ char no[7];
char name[9];
char sex[3];
struct date birthday;
};
struct score /*成绩结构类型:由学号和三门成绩共 4项组成 */
{ char no[7];
int score1;
int score2;
int score3;
};
返回制 作:方 斌
11.3 结构体变量的引用
1、一般情况下,不能将一个结构体变量作为整体来引用,只能引用其中的成员 (分量)。引用结构体成员的方式:
结构体变量名,成员名
,是“成员运算符”(分量运算符)
例 1、
printf("%d,%s,%c,%d,%f,%s",student1.num,student1.name,
student1.sex,student1.age,student1.score,sutdent1.addr);
printf(“%d,%s,%c,%d,%f,%s”,student1); /*错误 */
例 2、
student2.score = student1.score;
sum = student1.score + student2.score;
student1.age++;
++student1.age;
例 3、
scanf("%d",&student1.num);
制 作:方 斌
2、当成员是另一个结构体变量时,应一级一级地引用成员。例 4、
student1.num;
student1.name;
student1.birthday.month;
student1.birthday.day;
student1.birthday.year;
3、在某些情况下可以把结构体变量作为一个整体来访问。
( 1) 错误的引用:结构体变量整体赋值,例、
student2 = student1;
( 2)取结构体变量地址,例、
printf("%x",&student1);
/*输出 student1的地址即第一个成员的地址 */
制 作:方 斌
11.4 结构体变量的初始化
struct student
{
long int num; /* 学号 */
char name[20]; /* 姓名 */
char sex; /* 性别 */
char addr[20]; /* 地址 */
}a = {89031,"Li Lin",'M',"123 Beijing Road"};
或者是,struct student a={8903,“张三”,?M?,"123 Beijing Road"};
注意:不能在结构体内赋初值。例、
struct student
{
long int num = 89031;
char name[20] = "Li Lin";
char sex = 'M';
char addr[30] = "123 Bejing Road";
}a;
制 作:方 斌对于结构的成员是另一个结构体变量可以这样初始化:
struct date /*日期结构 */
{
int month; /* 月 */
int day; /* 日 */
int year; /* 年 */
};
struct student
{
int num; /* 学号 */
char name[20]; /* 姓名 */
char sex; /* 性别 */
int age; /* 年龄 */
struct date birthday; /*成员是另一个结构体变量 */
char addr[30]; /* 地址 */
} a={102,“张三”,?M?,25,{1980,9,20},“123 Bejing Road”};
或者这样初始化:
struct student a={102,“张三”,?M?,25,{1980,9,20},“123 Bejing Road”};
制 作:方 斌
[案例 11.2] 利用 [案例 11.1]中定义的结构类型 struct std_info,定义一个结构变量 student,用于存储和显示一个学生的基本情况。
/*案例代码文件名,AL10_2.C*/
#include "struct.h" /*定义并初始化一个外部结构变量 student */
struct std_info student={"000102","张三 ","男 ",{1980,9,20}};
void main()
{ printf("No,%s\n",student.no);
printf("Name,%s\n",student.name);
printf("Sex,%s\n",student.sex);
printf("Birthday,%d-%d-%d\n",student.birthday.year,
student.birthday.month,student.birthday.day);
}
程序运行结果:
No,000102
Name,张三
Sex,男
Birthday:1980-9-20
制 作:方 斌
11.5 结构体数组一个结构体变量中可以存放一组相关的数据 (如一个学生的学号,姓名,成绩等数据 )。如果有 10个这样的学生数据需要参加运算,显然应该用数组,这就是结构体数组。结构体数组与我们以前讲过的数值型数组的不同之处在于每个数组元素都是一个结构体类型的数据,它们都分别包括有该结构体类型的各个成员。
一、结构体数组的定义和定义结构体变量类似,只需说明其为数组即可,如:
制 作:方 斌
struct student
{
int num;
char name[20];
char sex;
int age;
float score;
char addr[30];
};
struct student stu[3]; /*有 3个元素的结构体数组 */
制 作:方 斌在前面我们讲过,一个结构体变量在内存中所分配的空间是各个成员所占空间之和,并且是连续存放。数组也是在内存中分配连续的存储空间。所以,对于结构体数组,它也满足上述两条,即结构体数组在内存中也是占有连续的存储空间,参考教材 P266图 11.5
制 作:方 斌二、结构体数组的初始化与其他类型的数组一样,对结构体数组也可以初始化。如:
struct student
{
int num;
char name[20];
char sex;
int age;
float score;
char addr[30];
}stu[3] = {10101,"Li Lin",'M',18,87.5,"103 Bejing Road"},
{10102,"Zhang Fun",'M',19,99,"130 Shanghai Roaad"},
{10104,"Wang Min",'F',20,78.5,"1010 Zhongshan Road"}};
或者:
struct student stu[3]={ {…},{…},{…}};
制 作:方 斌三、举例 [例 11.2] 对候选人得票的统计程序。
设有三个候选人,每次输入一个得票的候选人的名字,要求最后输出各候选人得票结果。
#include "stdio.h"
#include "string.h"
struct person /*候选人信息结构体 */
{
char name[20]; /* 姓名 */
int count; /* 得票数 */
}leader[3]={“Li”,0,“Zhang”,0,“Fun”,0 }; /*定义有三个元素的结构体并初始化 */
void main()
{ int i,j;
char leader_name[20]; /* 输入得票人姓名 */
for(i=1; i<=10; i++) /* 设有 10个人参加投票 */
{
scanf("%s",leader_name);
for (j=0; j<3; j++) /* 得票人姓名与 3个候选人姓名比较 */
if (strcmp(leader_name,stu[i].name) == 0)
stu[i].count++;
}
printf("\n");
for(i=0; i<3; i++) /* 输出 3个候选人的姓名和得票数 */
printf("%5s,%d\n",stu[i].name,stu[i].count);
}
制 作:方 斌
[案例 11.3] 利用 [案例 11.1]中定义的结构类型 struct std_info,定义一个结构数组
student,用于存储和显示三个学生的基本情况。
/*案例代码文件名,AL10_3.C*/
#include "struct.h"
/*定义并初始化一个外部结构数组 student[3] */
struct std_info student[3]={{“000102”,“张三”,“男”,{1980,9,20}},
{“000105”,“李四”,“男”,{1980,8,15}},
{“000112”,“王五”,“女”,{1980,3,10}}};
/*主函数 main()*/
void main()
{ int i;
/*打印表头," □ "表示 1个空格字符 */
printf("No.□□□□ Name□□□□□ Sex□ Birthday\n");
/*输出三个学生的基本情况 */
for(i=0; i<3; i++)
{ printf("%-7s",student[i].no);
printf("%-9s",student[i].name);
printf("%-4s",student[i].sex);
printf("%d-%d-%d\n",student[i].birthday.year,
student[i].birthday.month,student[i].birthday.day);
}
}
制 作:方 斌程序运行结果:
No,Name Sex Birthday
000102 张三 男 1980-9-20
000105 李四 男 1980-8-15
000112 王五 女 1980-3-10
制 作:方 斌
11.6 指向结构体数据类型的指针结构体变量在内存中的起始地址称为结构变量的指针。
一个结构体变量的指针就是该变量在内存中所占的存储空间的起始地址,也就是第一个成员的地址。如果设一个指针变量,用来指向一个结构体变量,此时该指针变量的值就是结构体变量的起始地址。
像其他类型的数组一样,我们可以用指针指向该数组或数组中的元素,指针变量也可以用来指向结构体数组或结构体数组中的元素。
制 作:方 斌一、指向结构体变量的指针结构体指针:指向结构体变量的指针。例、
struct student
{
long int nnum;
char name[20];
char sex;
float score;
};
struct student stu_1={89101,”Li Lin”,?M?,89.5}; /*结构体变量 */
struct student *p; /*结构体指针 */
p = &stu_1; /*p指向结构体变量 stu_1*/
制 作:方 斌
p=&stu_1 是将结构体变量 stu_1的地址赋给 p,也就是使 p指向 stu_1,
则对结构体变量 stu_1各成员的引用方式就有如下两种对应等价的方式:
stu_1的成员 常规变量引用方式 指针引用方式 1 指针引用方式 2
num stu_1.num (*p).num p->num
name stu_1.name (*p).name p->name
sex stu_1.sex (*p).sex p->sex
score stu_1.score (*p).score p->score
制 作:方 斌在一页表格中,(*p)表示 p所指向的结构体变量,(*p).num是
p所指向的结构体变量中的成员 num。注意的是,(*p)两边的括号不可省略,因为成员运算符 "." 优先于 "*" 运算符,*p.num等价于
*(p.num)。
例如给结构体变量 stu_1的成员 num赋值就可以用如下形式:
stu_1.num=8901;
或 (*p).num=8901;
或 p->num=8901;
给结构体变量 stu_1的成员 name赋值就可以用如下形式:
strcpy(stu_1.name,"Li Lin");
或 strcpy((*p).name="Li Lin");
或 strcpy(p->.name="Li Lin");
制 作:方 斌在 C语言中,为了使用方便和使之直观,可以把 (*p).num改用 p->num
来代替,它表示 *p所指向的结构体变量中 num成员。同样,
(*p).name等价于 p->name。也就是说,如果结构体指针 p指向结构体变量,则以下三种方式等价:
(1) 结构体变量名,成员
(2) (*p).成员 --------------(这种方式几乎不用 )
(3) p->成员 ----------------(这种方式经常使用 )
其中,->称为指向运算符 。
分析以下几种运算:
p->n 得到 p指向的结构体变量中的成员 n的值。
p->n++ 得到 p指向的结构体变量中的成员 n的值,用完后该变量 n自增 (不是 p自增 )。
++p->n 得到 p指向的结构体变量中的成员 n的值自增,然后再使用 n。
制 作:方 斌
[案例 11.4] 使用指向结构变量的指针来访问结构变量的各个成员。
/*案例代码文件名,AL11_4.C*/
#include,struct.h”
struct std_info student={“000102”,“张三”,“男”,{1980,9,20}};
void main()
{ struct std_info *p_std=&student;
printf("No,%s\n",p_std->no);
printf("Name,%s\n",p_std->name);
printf("Sex,%s\n",p_std->sex);
printf("Birthday,%d-%d-%d\n",p_std->birthday.year,
p_std->birthday.month,p_std->birthday.day);
} 程序运行结果:No,000102
Name,张三
Sex,男
Birthday,1980-9-20
制 作:方 斌三、指向结构体数组的指针
struct student stu[3];
struct student *p = stu; /*p指向 stu[0] */
p++; /* p指向 stu[1] */
p++; /* p指向 stu[2] */
p = stu; /*p指向数组 stu*/
p->num; /*引用 stu[0].num*/
p++;
p->num; /*引用 stu[1].num*/
p++;
p->num;/*引用 stu[2].num*/
p=stu是将数组名赋值给指针 p,即使 p指向该数组。也可以是 p=&stu[0]。
制 作:方 斌
[案例 11.5] 使用指向结构数组的指针来访问结构数组。
/*案例代码文件名,AL10_5.C*/
#include "struct.h"
/*定义并初始化一个外部结构数组 student */
struct std_info student[3]={{"000102","张三 ","男 ",{1980,5,20}},
{"000105","李四 ","男 ",{1980,8,15}},
{“000112”,“王五”,“女”,{1980,3,10}}};
void main()
{ struct std_info *p_std=student;
int i=0;
printf("No.□□□□ Name□□□□□ Sex□ Birthday\n"); /*打印表头 */
/*输出结构数组内容 */
for( ; i<3; i++,p_std++)
{ printf("%-7s%-9s%-4s",p_std->no,p_std->name,p_std->sex);
printf("%4d-%2d-%2d\n",p_std->birthday.year,
p_std->birthday.month,p_std->birthday.day);
}
} 程序运行结果同案例 11.3
制 作:方 斌如果指针变量 p已指向某结构数组,则 p+1指向结构数组的下一个元素,而不是当前元素的下一个成员。
另外,如果指针变量 p已经指向一个结构变量(或结构数组),就不能再使之指向结构变量(或结构数组元素)的某一成员。
制 作:方 斌三、用结构体变量和指向结构体的指针作函数的参数将一个结构体变量的值传给一个函数作为实参有三种方法:
(1) 用结构体变量的成员作为函数的实参。例如,用 stu[1].num或
stu[2].name作为函数的实参传给形参。这种用法和用普通变量作为函数的实参是一样的,属于值传递,但就注意实参和形参的数据类型要一致。
(2) 用结构体变量作为函数的实参。这种方式同样是采用值传递的方式,将结构体变量所占的内存单元的内容全部复制一份传递给形参,要注意形参也必须是同类型的结构体变量。这种方式在参数传递时,因为要将实参的全部内容复制后传递给形参,所以形参也要占用和实参同样大小的内存空间,因此,在时间和空间上开销较大。如果结构体的规模很大,开销是很可观的。另外,值传递不能改变实参的内容 (实参和形参占用不同的存储空间 ),往往造成使用上的不便,因此一般较少使用这种方法。
(3) 用指向结构体变量或结构体数组的指针作为函数的实参。这种方法是将结构体变量或结构体数组的地址传递给形参,是属于地址传递,形参和实参共同占用存储单元,使用方便,因此这种方法使用较多。
制 作:方 斌
[例 11.5]有一个结构体变量 stu,内含学生学号、姓名和三门课程的成绩。要求在 main中赋以值,在另一函数 print中打印输出。
#include "stdio.h"
#include "string.h"
#define FORMAT "%d\n %s\n %.2f\n %.2f\n %f\n"
struct student /*结构体定义 */
{
int num; /*学号 */
char name[20]; /*姓名 */
float score[3]; /*三门课程的成绩 */
};
void print(struct student); /*print函数原型声明 */
void main()
{
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 p) /*print函数定义 */
{
printf(FORMAT,p.num,p.name,p.score[0],p.score[1],p.score[2]);
printf("\n");
}
程序运行结果
12345
Li Li
67.50
89.00
78.60
制 作:方 斌
[例 11.6]将例 11.5改为用指向结构体变量的指针为实参。
#include "stdio.h"
#include "string.h"
#define FORMAT "%d\n %s\n %f\n %f\n %f\n"
struct student
{
int num; /*学号 */
char name[20]; /*姓名 */
float score[3]; /*三门课程的成绩 */
};
void print(struct student *); /*print函数原型声明 */
void main()
{
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 *p) /*print函数定义 */
{
printf(format,p->num,p->name,p->score[0],
p->score[1],p->score[2]);
printf("\n");
}
制 作:方 斌[案例 11.6] 用函数调用方式,改写 [案例 11.5]:编写一个专门的显示函数 display(),通过主函数调用来实现显示。
/*案例代码文件名,AL11_6.C*/
#include "struct.h,/*定义并初始化一个外部结构数组 student */
struct std_info student[3]={{"000102","张三 ","男 ",{1980,5,20}},
{"000105","李四 ","男 ",{1980,8,15}},
{“000112”,“王五”,“女”,{1980,3,10}} };
/*主函数 main()*/
void main()
{ void display(struct std_info *); /*函数说明 */
int i=0;
printf("No.□□□□ Name□□□□□ Sex□ Birthday\n"); /*打印表头 */
for( ; i<3; i++) /*打印内容 */
{ display( student + i ); printf("\n");
}
}
void display(struct std_info *p_std)
{ printf("%-7s%-9s%-4s",p_std->no,
p_std->name,p_std->sex);
printf("%4d-%2d-%2d\n",p_std->birthday.year,
p_std->birthday.month,p_std->birthday.day);
}
制 作:方 斌
[例 11.7] 有 4个学生,每个学生包括学号、姓名和成绩。要求找出成绩最高者的姓名和成绩。
#include "stdio.h"
struct student
{
int num; /* 学号 */
char name[20]; /* 姓名 */
float score; /* 成绩 */
};
制 作:方 斌
void main()
{
struct student stu[4]; /* 4个学生 */
struct student *p;
int i;
int temp = 0; /*成绩最高学生在数组中的序号,0~ 3 */
float max; /*最高成绩 */
for(i=0; i<4; i++) /* 输入 4个学生的学号、姓名、成绩 */
scanf("%d%s%f",&stu[i].num,stu[i].name,&stu[i].score);
for(max=stu[0].score,i=1; i<4; i++) /*stu[0]已赋给 max */
{
if (stu[i].score > max)
{
max = stu[i].score;
temp = i;
}

p = stu + temp; /* p指向成绩最高的学生结构 */
printf("\nThe maximum score:\n");
printf("No.,%d\n name,%s\n score,%4.1f\n",
p->num,p->name,p->score);
}
制 作:方 斌
10.7 用结构体指针处理链表一、链表概述链表是一种最常见的数据结构,它动态地进行存储分配。
数组:必须事先定义固定的长度(元素个数),不能适应数据动态地增减的情况。当数据增加时,可能超出原先定义的元素个数;当数据减少时,造成内存浪费。
链表动态地进行存储分配,可以适应数据动态地增减的情况,且可以方便地插入、删除数据项。(数组中插入、删除数据项时,需要移动其它数据项)。
链表有单向链表、双向链表、环形链表等形式。以单向链表为例、
制 作:方 斌链表有一个“头指针” head,它指向链表的第一个元素。链表的一个元素称为一个“结点”( node)。结点中包含两部分内容,第一部分是结点数据本身,称为数据域,如图中的A、B、C、D所示。结点的第二部分是一个指针,称为指针域,它指向下一个结点。
最后一个结点称为“表尾”,表尾结点的指针域为空( NULL)。
在链表中插入一个结点,比如,在结点A后插入结点 P,只需使P指向
B,使A指向P。
在链表中删除一个结点,比如删除结点 C,只需使B指向D,并删除结点 C所占内存。
因此,链表的插入、删除非常方便。
制 作:方 斌链表结点数据可以用结构体来描述,例如、
struct student
{
int num;
float score;
struct student *next; /* 指向下一结点 */
};
制 作:方 斌链表需要动态地进行存储分配,在C语言中,使用以下函数进行动态存储分配和释放。
1,void * malloc(unsigned size)
在动态存储区分配长度为 size的连续空间,并返回指向该空间起始地址的指针。若分配失败(系统不能提供所需内存),则返回 NULL。
2,void * calloc(unsigned n,unsigned size)
在动态存储区分配 n个长度为 size的连续空间,并返回指向该空间起始地址的指针。若分配失败(系统不能提供所需内存),则返回 NULL。
3,void free(void * ptr)
释放 ptr指向的内存空间。 ptr是 malloc()或 calloc()函数返回的值。
注意、
上述函数的原型在 alloc.h和 stdlib.h中定义。在程序中必须包含这两个头文件。
制 作:方 斌
[例 11.7] 写一个函数 creat(),建立一个学生信息的单向链表。
分析建立过程:
设:链表结构为:
struct student /*结点有三个域 */
{
long num;
float score;
struct student *next;
};
当输入的 num等于零时,表示建立过程结束。
定义以下变量:
struct student *head; /* 表头 */
struct student *p1; /* 新建结点 */
struct student *p2; /* 表尾结点 */
num
score
next
结点结构制 作:方 斌链表创建过程如下:
p1=(struct student *)malloc(sizeof(struct student));
head=p1;
p2=p1;
p1=(struct student *)malloc(sizeof(struct student));
制 作:方 斌
p2->next=p1;
p2=p1;
制 作:方 斌
p1=(struct student *)malloc(sizeof(struct student));
p2->next=p1;
p2=p1;
制 作:方 斌
p1=(struct student *)malloc(sizeof(struct student));
输入时因为 p1->num=0;
所以新结点不加入链表;执行
P2->next=NULL;
free(p1); 释放 p1。
通过以上分析,可以得到创建链表的算法,其中,n=1表示创建的是第一个结点。
制 作:方 斌程序:
#include "stdio.h"
#include "alloc.h"
#include "stdlib.h"
struct student
{
long num;
float score;
struct student *next;
};
#define LEN sizeof(struct student)
/*#define NULL 0在 stdio.h中已经定义 */
int n;
制 作:方 斌struct student * creat() /* 创建链表,并返回表头指针 */
{
struct student *head; /* 表头 */
struct student *p1; /* 新建结点 */
struct student *p2; /* 表尾结点 */
n = 0; /* 结点数为 0 */
head = NULL; /* 还没有任何结点,表头为指向空 */
p1 = (struct student *)malloc(LEN); /* 创建一个新结点 p1 */
p2 = p1; /* 表尾 p2也指向 p1 */
scanf("%ld,%f",&p1->num,&p1->score); /*读入第一个结点的学生数据 */
while(p1->num != 0) /* 假设 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); /*读入新建结点的学生数据 */
}
free(p1); /* 对于 num=0的结点,未加入链表,应删除其空间 */
p2->next = NULL; /* 输入结束,表尾结点的下一个结点为空 */
return (head); /* 返回表头指针 */
}
制 作:方 斌二、输出链表函数只要已知表头结点,通过 p->next可以找到下一个结点,从而可以输出链表的全部结点数据。
void print(struct student *head)
{
struct student *p;
p = head;
while(p)
{
printf("%ld %5.1f\n",p->num,p->score);
p = p->next;
}
}
制 作:方 斌四、删除一个结点删除一个结点的算法:
删除结点时应考虑以下情况:
1、找到需要删除的结点,用 p1指向它。并用 p2指向 p1的前一个结点。
制 作:方 斌
2、要删除的结点是头结点。
p1=head;
head->next=pq->next;
free(p1);
制 作:方 斌
3、要删除的结点不是头结点用指针 p2指向要删除结点的前一个结点
p2->next=p1->next;
free(p1);
制 作:方 斌程序:
在链表 head中删除学号为 num的结点。以表头指针 head和需要删除的结点的 num
(学号)为参数,返回删除操作后的链表表头。
struct student * del(struct student *head,long num)
{
struct student *p1; /* 指向要删除的结点 */
struct student *p2; /* 指向 p1的前一个结点 */
if (head == NULL) /* 空表 */
{
printf("\n List is NULL\n");
return (head);
}
制 作:方 斌
p1 = head;
while(num != p1->num && p1->next != NULL) /* 查找要删除的结点 */
{
p2 = p1;
p1 = p1->next;
}
if (num == p1->num) /* 找到了 */
{
if (p1 == head) /* 要删除的是头结点 */
head = p1->next;
else /* 要删除的不是头结点 */
p2->next = p1->next;
free(p1); /* 释放被删除结点所占的内存空间 */
printf("delete,%ld\n",num);
}
else printf("%ld not found\n"); /* 在表中未找到要删除的结点 */
return (head); /* 返回新的表头 */
}
制 作:方 斌五、对链表的插入操作设在链表中,各结点按成员 num(学号)由小到大顺序存放,把结点
p0插入链表。
用指针 p0指向待插入结点,设把 p0插在 p1结点之前,插入算法:
1、在 p1之前,p2之后插入 p0
制 作:方 斌
2、将 p0插入第一个结点之前制 作:方 斌
3、将 p0插入表尾结点之后制 作:方 斌4、找到应插入的位置应插入的位置,p1之前,p2之后。
在链表中,各结点按成员 num(学号)由小到大顺序存放,从第一个结点开始,把待插入结点 p0->num与每一个结点 p1->num比较,若 (p0-
>num) > (p1->num),则 p1移到下一个结点。同时,p2也移到下一个位置。根据以上分析,得出插入算法:
制 作:方 斌程序:
在链表 head中,插入 stud结点,并返回新链表的表头。
struct student * insert(struct student *head,struct student *stud)
{
struct student *p0; /* 待插入结点 */
struct student *p1; /* p0插入 p1之前,p2之后 */
struct student *p2;
p1 = head;
p0 = stud;
if(head == NULL) /* 原链表是空表 */
{
head = p0;
p0->next = NULL;
}
else
制 作:方 斌
{
while ((p0->num > p1->num) && p1->next) /*查找待插入位置 */
{
p2 = p1;
p1 = p1->next;
}
if(p0->num <= p1->num) /* num从小到大排列,p0应插入表内(不是表尾) */
{
if (p1 == head) /* p1是表头结点 */
{
head = p0;
p0->next = p1;
}
else
{
p2->next = p0;
p0->next = p1;
}
}
else /* p0插入表尾结点之后 */
{
p1->next = p0;
p0->next = NULL;
}
}
return (head);
}
制 作:方 斌
11.8 共用体一、共用体的概念和定义当需要把不同类型的变量存放到同一段内存单元,或对同一段内存单元的数据按不同类型处理,则需要使用“共用体”数据结构。
例、把一个整型变量、一个字符型变量、一个实型变量放在同一个地址开始的内存单元中。
共用体的定义形式:
union 共用体名
{
成员列表;
}变量列表;
制 作:方 斌例、定义共用体 data类型
union data
{
int i;
char ch;
float f;
};
注意区分:
“共用体”:各成员占相同的起始地址,所占内存长度等于最长的成员所占内存。
“结构体”:各成员占不同的地址,所占内存长度等于全部成员所占内存之和。
制 作:方 斌共用变量的定义 ── 与结构变量的定义类似
( 1)间接定义 ── 先定义类型、再定义变量例如,定义 data共用类型变量 un1,un2,un3的语句如下:
union data un1,un2,un3;
( 2)直接定义 ── 定义类型的同时定义变量例如,
union data
{ int i;
char ch;
float f;
}un1,un2,un3;
制 作:方 斌二、共用体变量的引用共用变量的引用 ── 与结构变量一样,也只能逐个引用共用变量的成员,
例如,
union data a;
a.i;
a.ch;
a.f;
制 作:方 斌三、共用体类型数据的特点
1、系统采用覆盖技术,实现共用变量各成员的内存共享,所以在某一时刻,存放的和起作用的是最后一次存入的成员值,即共用体变量中的值是最后一次存放的成员的值,如、
a.i = 1;
a.ch = 'a';
a.f = 1.5;
完成以上三个赋值语句后,共用体边量的值是 1.5,而 a.i=1和 a.ch=?a?
已无意义。
2、不能对共用变量进行初始化(注意:结构变量可以);也不能将共用变量作为函数参数,以及使函数返回一个共用数据,但可以使用指向共用变量的指针。如
union data
{
int i;
char ch;
float f;
}a={1,'a',1.5};
制 作:方 斌
3、由于所有成员共享同一内存空间,故共用变量与其各成员的地址相同。
例如,& a=& a.i=& a.ch=& a.f。
4、共用类型可以出现在结构类型定义中,反之亦然。
2、共用体变量不能初始化,
[例 11.11] 设有若干个人员的数据,其中有学生和教师。学生数据包括:
姓名、号码、性别、职业、班级。教师数据包括:姓名、号码、性别、
职业、职务。如图、
要求输入人员数据,然后输出。
制 作:方 斌分析:仅学生数据的 class(班级 )和教师数据的 position(职务 )类型不同,
所以在同一表格中,可以使用“共用体”数据结构。
struct data
{
int num;
char name[10];
char sex;
char job;
union
{
int class; /* 班级 */
char position[10]; /* 职务 */
}category;
}person[2]; /* 2个人员数据 */
制 作:方 斌void main()
{ int i;
for(i=0;i<2;i++) /* 输入 2个人员的数据 */
{ 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(“\nNo,name sex 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);
}
}
制 作:方 斌
11.9 枚举类型如果一个变量只有几种可能的值,可以定义为枚举类型。
“枚举”:将变量可能的值一一列举出来。变量的值只能取列举出来的值之一。
用关键字 enum定义枚举类型。
1.枚举类型的定义
enum 枚举类型名 {取值表 };
例如,enum weekdays {Sun,Mon,Tue,Wed,Thu,Fri,Sat};
2.枚举变量的定义 ── 与结构变量类似
weekday是枚举类型名,可以用于定义变量
( 1)间接定义例如,enum weekdays workday,week_end;
( 2)直接定义例如 enum [weekdays]{Sun,Mon,Tue,Wed,Thu,Fri,Sat } workday;
enum weekdays workday,week_end;
制 作:方 斌上例中定义了两个枚举变量,workday和 week_end,它们的取值只能
sun到 sat之一,如、
workday = mon;
week_end = sun;
sun,mon,....,sat 称为“枚举元素”或“枚举常量”。
说明:
1、枚举元素是常量。在C编译器中,其默认值按定义的顺序取值 0,1、
2,...。
例、
workday = mon; /*workday常量的值是 1*/
printf("%d",weekday); 输出整数 1。
制 作:方 斌
2、枚举元素是常量,不是变量,因此不能赋值。
sun = 0; mon = 1;
但在定义枚举类型时,可以指定枚举常量的值,如、
enum weekday {sun=7,mon=1,tue,wed,thu,fri,sat};
此时,tue,wed,...的值从 mon的值顺序加 1。如,tue=2。
3、枚举值可以作判断,例、
if (weekday == mon)....
if (weekday > sun)....
4、整型与枚举类型是不同的数据类型,不能直接赋值,如、
workday = 2; /* workday是枚举类型 */
但可以通过强制类型转换赋值,如、
workday = (enum weekday)2;
制 作:方 斌[例 11.12] 口袋里有红、黄、蓝、白、黑五种颜色的球若干个。每次从口袋中取出三个球,打印出三种不同颜色球的可能取法。
分析:球的颜色只可能取五种值,可以用枚举类型变量处理。
#include "stdio.h"
void 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; /* 第 1个球 */
case 2,pri = j; break; /* 第 2个球 */
case 3,pri = k; break; /* 第 3个球 */
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("\n total:%5d\n",n);
}
制 作:方 斌
11.10 用 typedef定义类型除可直接使用C提供的标准类型和自定义的类型(结构、共用、枚举)外,
也可使用 typedef定义已有类型的别名。该别名与标准类型名一样,可用来定义相应的变量。
一、概念关键字 typedef用于定义一种新的数据类型,它代表已有数据类型,是已有数据类型的别名。例、
typedef int INTEGER;
typedef float REAL;
定义新数据类型 INTEGER,它代表已有数据类型 int。
定义新数据类型 REAL,它代表已有数据类型 float。
通过上述定义后,以下两行等价:
int i,j ; float a,b;
INTEGER i,j; REAL a,b;
制 作:方 斌二、典型用法:
1、定义一种新数据类型,专用于某种类型的变量,使程序更清晰明了。
例 1,typedef unsigned int size_t
定义 size_t数据类型,专用于内存字节计数。
size_t size; /* 变量 size用于内存字节计数 */
例 2,typedef int COUNT
定义 COUNT数据类型,专用于计数。
COUNT i,j;
2、简化数据类型的书写。
制 作:方 斌例 3,typedef struct
{
int month;
int day;
int year;
}DATE,*Date;
DATE birthday;
DATE *p; /*定义 DATE类型的指针 p*/
Date q; /*定义 DATE类型的指针 q*/
DATE d[7];
例 4,typdef unsigned int UINT;
UINT i,j;
制 作:方 斌三、定义新类型的更一般形式:(只要求掌握典型用法)
( 1) typedef int NUM[100]; /*数据类型 NUM为整型数组类型 */
NUM n; /*NUM是数据类型,n是整型数组 变量 */
( 2) typedef char * STRING; /*STRING是字符指针类型 */
STRING p,s[10]; /*p是字符指针变量,s[10]是字符指针数组 */
( 3) typedef int (*POINTER)(); /* POINTER是指向函数的指针类型,
该函数返回整型 */
POINTER p1,p2;
四,typedef 与 #define的区别,如
typedef int COUNT
#define COUNT int
typedef定义一种新的数据类型( COUNT),它是已有类型( int)的别名。在编译时 COUNT类型与 int类型相同。
#define定义一个宏( COUNT),在预编译时,把字符串 COUNT替换为字符串 int。
制 作:方 斌例如,我们把前面定义的 struct student数据类型定义一个别名
STUDENT,把 struct student类型的指针定义一个别名 STU:
typedef struct student{
long num;
char name[10];
float score;
}STUDENT,*STU;
那么,定义 struct student类型的变量
struct student stu1,stu2;
等价于 STUDENT stu1,stu2;
定义 struct student类型的指针变量
struct student *p;
等价于 STU p;
制 作:方 斌本章要求及作业
1、结构体的概念,结构体指针,结构体数组。
2、链表的概念,用指针处理链表。
3、共用体的概念。
4、枚举类型。
5,typedef定义新数据类型。
作业:
11.3 11.8 11.12