第 11 章结构体与共用体
DESCRIPTION
第 11 章结构体与共用体. 11.1 概述 11.2 定义结构体类型变量的方法 11.3 结构体变量的引用 11.4 结构体变量的初始化 11.5 结构体数组 11.6 指向结构体类型数据的指针 11.7 用指针处理链表 11.8 共用体 11.9 枚举类型 11.10 用 typedef 定义类型. 大纲要求. ( 1 )掌握结构体和共用体类型的说明、结构体和共用体变量的定义及初始化方法 ( 2 )掌握结构体与共用体成员的引用 ( 3 )领会动态存储分配和释放 ( 4 )掌握链表的基本概念和基本操作 ( 5 )领会枚举类型变量的定义 - PowerPoint PPT PresentationTRANSCRIPT
第 11 章结构体与共用体11.1 概述
11.2 定义结构体类型变量的方法
11.3 结构体变量的引用
11.4 结构体变量的初始化
11.5 结构体数组
11.6 指向结构体类型数据的指针
11.7 用指针处理链表
11.8 共用体
11.9 枚举类型
11.10 用 typedef 定义类型
( 1)掌握结构体和共用体类型的说明、结构体和共用体变量的定义及初始化方法
( 2)掌握结构体与共用体成员的引用
( 3)领会动态存储分配和释放
( 4)掌握链表的基本概念和基本操作
( 5)领会枚举类型变量的定义
( 6)了解 typedef 的作用
大纲要求
11.1 概述记录--由多种数据组成,如成绩册中每个学生的数据是一个记录,可以包括:学号、姓名及各科成绩
又如学生基本情况表中可以包括每个学生的:学号、姓名、性别、年龄、成绩、地址等。
C 中实现记录的方法:使用结构体类型
struct student
{int num;
char name[20],sex;
int age;
float score;
char addr[30];
};
结构类型定义struct 结构体名 /* struct 是结构类型关键字 */
{ 数据类型 数据项 1; 数据类型 数据项 2; …… …… 数据类型 数据项n ; }; /* 此行分号不能少! */
例 : 定义一个反映学生基本情况的结构类型,用以存储学生的相关信息。
结构体类型定义格式
struct date /* 用于存放日期型数据 * 、 {int year;
int month;
int day;
};
struct std_info /* 学生信息结构类型:由学号、姓 {char no[7]; 名、性别和生日共 4 项组成 */
char name[9];
char sex[3];
struct date birthday;
};
struct score /* 成绩结构类型:由学号和三门成绩共 {char no[7]; 4 项组成 */
int score1;
int score2;
int score3;
};
struct student
{long num;
char nname[20];
char sex;
int age;
float score;
char addr[30];
};
( 1 )“结构类型名”和“数据项”的命名规则,与变量名相同。( 2 )数据类型相同的数据项,既可逐个、逐行分别定义,也可合并成一行定义。 例如,本例代码中的日期结构类型,也可改为如下形式: struct date {int year, month, day; };( 3 )结构类型中的数据项,既可以是基本数据类型,也允许是另一个已经定义的结构类型。 例如,本例代码中的结构类型 std_info ,其数据项“ birthday”就是一个已经定义的日期结构类型 date 。( 4 )本书将1个数据项称为结构类型的1个成员(或分量)。
用户自己定义的结构类型,与系统定义的标准类型( int 、 char 等)一样,可用来定义结构变量的类型。 定义结构变量的方法,可概括为 3种: 1.间接定义法──先声明结构类型、再定义结构变量 一般格式为: struct 结构体名 结构体变量名表;例如,利用例1中定义的学生信息结构类型 std_info ,定义了一个相应的结构变量 student1 : struct std_info student1;结构变量 student1 :拥有结构类型的全部成员,其中 birthday 成员是一个日期结构类型,它又由 3个成员构成。结构体变量所占的存储空间为其各成员所占存储空间之和。注意:使用间接定义法定义结构变量时,必须同时指定结构体名。
11.2 定义结构体类型变量的方法
2. 直接定义法──在定义结构类型的同时,定义结构变量例struct student{int num; char name[20]; char sex; int age; float score; char addr[30]; }student1,student2;同时定义结构类型及其结构变量的一般格式如下: struct 结构体名 { 成员表列 } 结构变量名表;
3. 直接定义结构体类型变量其一般格式如下: struct { 成员表列; } 结构变量表;struct student{int num; char name[20]; char sex; int age; float score; struct date birthday; char addr[30]; }student1,student2 ;
说明
( 1 )结构类型与结构变量是两个不同的概念,其区别如同 int 类型与 int 型变量的区别一样。编译时类型不占存储空间。
( 2 )结构类型中的成员名,可以与程序中的变量同名,它们代表不同的对象,互不干扰。
( 3 )成员可以是基本类型或构造类型(数组,结构体类型,共用体类型)、指针类型。
11.3 结构体变量的引用结构体变量的引用规则:
1 ,对于结构变量,要通过成员运算符“ .” ,逐个访问其成员,且访问的格式为: 结构变量 .成员 /*其中的“ .” 是成员运算符 */
不能将一个结构体变量作为一个整体进行输入输出及其它运算2 ,如果成员本身是一个结构体或共用体,则要用若干成员运算符,直到找到最低一级的成员为止。如在前面的例中对日期的引用为:student1.birthday.year, student1.birthday.month, student1.birthday.day
3 ,对结构体变量成员可以像普通变量一样进行各种操作。4 ,可以引用结构体变量成员的地址。
11.4 结构变量的初始化结构变量初始化的格式,与一维数组相似: 结构变量 ={ 初值表 }不同的是:如果某成员本身又是结构类型,则该成员的初值为一个初值表。例 11.1 对结构体变量初始化#include<stdio.h>void main(){struct student{long int num; char name[20]; char sex; char addr[30]; }a={89031, "LiLin", 'M', "123Beijing Road"};printf("NO:%ld\nname:%s\nsex:%c\naddress:%s\n",a.num,a.name,a.sex,a.addr);}
注意:初值的数据类型,应与结构变量中相应成员所要求的一致,否则会出错。
11.5 结构体数组11.5.1 定义结构体数组
结构数组的每一个元素,都是结构类型数据,均包含结构类型的所有成员。 与结构变量的定义相似,结构数组的定义也分直接定义和间接定义两种方法,只需说明为数组即可。 struct student {int num; char nname[20]; char sex; int age; float score; char addr[30]; }stu[3]; struct student stu1[10];
11.5.2 结构体数组的初始化 与普通数组一样,结构数组也可在定义时进行初始化。初始化的格式为:结构数组 [n] = {{ 初值表 1} , { 初值表 2} , ... , { 初值表 n}}
11.5.3 结构体数组应用举例
例 11.2 对候选人得票的统计程序。设有 3 个候选人,每次输入一个得票候选人的名字,要求最后输出各得票结果。
#include<string>
#include<stdio.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=0;i<10;i++)
{scanf(%s",leader_name);
for(j=0;j<3;j++)
if(strcmp(leader_name,leader[j].name)==0)
leader[j].count++;
}
for(i=0;i<3;i++)
printf("\n%5s:%d",leader[i].name,leader[i].count);
}
11.6 指向结构体类型数据的指针结构变量在内存中的起始地址称为结构变量的指针。
通过指向结构变量的指针来访问结构变量的成员,与直接使用结构变量的效果一样。一般地说,如果指针变量 p 已指向结构变量 stu_1 ,则以下三种形式等价:( 1 ) stu_1. 成员( 2 ) (*p). 成员 /* “*p” 外面的括号不能省! */
( 3 ) p-> 成员注意:在格式( 1 )中,分量运算符左侧的运算对象,只能是结构变量,;而在格式( 2 )中,指向运算符左侧的运算对象,只能是指向结构变量(或结构数组)的指针变量,否则都出错
例 11.3 指向结构体变量的指针的应用#include<string.h>#include<stdio.h>void main(){struct student{long num;char name[20];char sex;float score;}stu_1,*p;p=&stu_1;stu_1.num=89101;strcpy(p->name,"LiLin");(*p).sex='M';stu_1.score=89.5;printf("NO:%ld\n name:%s\n sex:%c\n score:%f\n", stu_1.num, stu_1.name, stu_1.sex, stu_1.score);printf("NO:%ld\n name:%s\n sex:%c\n score:%f\n", (*p).num, (*p).name, (*p).sex, (*p).score);}
11.6.2 指向结构体数组的指针
如果指针变量 p 已指向某结构数组,则 p+1 指向结构数组的下一个元素,而不是当前元素的下一个成员。 另外,如果指针变量 p 已经指向一个结构变量(或结构数组),就不能再使之指向结构变量(或结构数组元素)的某一成员。
例 11.4 指向结构体数组的指针的应用#include<stdio.h>
struct student
{int num,age;
char name[20],sex;};
struct student stu[3]={{10101,"Li Lin",'M',18},
{10102,"Zhang Fun",'M',19},
{10104,"Wang Min",'F',20}};
void main(){
struct student *p;
printf("No. Name Sex age\n");
for(p=stu;p<stu+3;p++)
printf("%5d%-20s%2c%4d\n",p->num,p->name,p->sex,p->age);
}
11.6.3 用结构体变量主指向结构体的指针作函数参数
例 11.5 有一个结构体变量 stu ,内含学生学号、姓名和 3 门课程成绩,要求在 main 函数中赋矛值,在另一函数 print 中将它们输出
要完成的工作:
建立结构体
定义结构体变量
给结构体变量赋值
调用函数输出
定义 print 函数
#include<stdio.h>#include<string.h>struct student{int num;char name[20];float score[3];};void 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("%d\n%s\n%f\n%f\n%f\n",stu.num,stu.name,stu.score[0],stu.score[1],stu.score[2]);}
例 11.6 使用指向结构体变量的指针作为函数参数
#include<stdio.h>#include<string.h>struct student{int num;char name[20];float score[3];}stu={12345,"Li Li",67.5,89,78.6};void main(){void print(struct student *);print(&stu);}void print(struct student *p){printf("%d\n%s\n%f\n%f\n%f\n",p->num, p->name, p->score[0],p->score[1],p->score[2]);}
11.7 用指针处理链表11.7.1 链表概述1 .链表结构 链表作为一种常用的、能够实现动态存储分配的数据结构,图 11.10 所示为单链表。一个链表有以下几个特点:
头指针变量 head──指向链表的首结点。每个结点由 2个域组成:1)数据域──存储结点本身的信息。2)指针域──指向后继结点的指针。尾结点的指针域置为“ NULL (空)”,作为链表结束的标志。
head
1249
1249
A
1356
1356
B
1475
1475
C
1021
1021
DNULL
2 .对链表的基本操作
对链表的基本操作有:创建、检索(查找)、插入、删除和修改等。
( 1 )创建链表是指,从无到有地建立起一个链表,即往空链表中依次插入若干结点,并保持结点之间的前驱和后继关系。
( 2)检索操作是指,按给定的结点索引号或检索条件,查找某个结点。如果找到指定的结点,则称为检索成功;否则,称为检索失败。
在C语言中,用结构类型来描述结点结构。例如
struct student
{long num; /*用于存放学生的学号 */
float score; /*用于存放学生的成绩*/
struct student *next; /*指针域,用于存放下一个结点的地址 */
}
11.7.2 简单链表例 11.7 建立一个如图 11.11 所示的简单链表,它由 3个学生数据的结点组成,输出各结点中的数据算法设计:1,建立结构体;2,用 3个结构体变量存储 3个学生信息;3,形成链表,并将其首结点的地址存放在头指针中4,输出
#include<stdio.h>#define NULL 0struct student{long num; float score; struct student *next; };main(){struct student a,b,c,*head,*next;*p; a.num=99101; a.score=89.5; b.num=99103; b.score=90; c.num=99107; c.score=85;
head=&a; a.next=&b; b.next=&c; c.next=NULL; p=head;do{printf("%ld\t%5.1f\n",p->num,p->score); p=p->next; }while(p!=NULL);}
11.7.3 处理动态链表所需的函数1 , malloc 函数函数原型:void *malloc(unsigned int size);
功能:在内存的动态存储区中分配一个长为 si
ze 的连续空间。返回一个指向分配域起始地址的指针,指针类型为 void
使用格式:指针变量 =(struct 结构体名 *)malloc(sizeof(struct 结构体名 ));
2 , calloc 函数函数原型:void *calloc(unsigned n, unsigned size);
功能:在内存的动态存储区中分配 n 个长为size 的连续空间,返回一个指向该空间的起始地址3 , free 函数函数原型: void free(void *p)
功能:释放由 p 所指向的内存区。使用格式: free(p);
11.7.4 建立动态链表基本思路: 首先向系统申请一个结点的空间,然后输入结点数据域的( 2 个)数据项,并将指针域置为空(链尾标志),最后将新结点插入到链表尾。对于链表的第一个结点,还要设置头指针变量。算法设计:1 定义结构体(含两个数据成员和一个指针成员)2 定义结构体指针变量: head,p1,p2;3 分配空间,并将其返回值赋给 p1,p2 ;4 输入新结点的数据5 while(p1->num!=0) 5.1 将新结点插入链表中 5.2 分配空间,并将其返回值赋给 p1,p2 ; 5.3 输入新结点的数据6 返回 head
#include<stdio.h>#include<malloc.h>#define LEN sizeof(struct student)struct student{long num;float score;struct student *next;};struct student *creat(void) /* 一个返回链表指针的函数 */
{ struct student *head, *p1, *p2; int n=0; p1=p2=(struct student*)malloc(LEN); scanf("%ld%f",&p1->num,&p1->score); head=NULL;
while(p1->num!=0)
{n++;
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);}
11.7.5 输出链表
用结构体指针变量 p 指向其第一个结点,输出该结点的数
据,然后将其指向下一个结点,方法是: p=p->next ,当 p为
NULL 时结束。
void print(struct student *head)
{struct student *p;
p=head;
if(head!=NULL)
do
{printf("%ld %5.1f\n",p->num,p->score);
p=p->next;}while(p!=NULL);
}
11.7.6 对链表进行删除操作(删除学号为已知值的结点)
算法设计:
1 寻找要删除的结点,
2 删除结点。
99101
99107
NULL
9910399101
99103 99107
NULL
head
要删除的结点为第一个结点
head
99101
99107
NULL
9910399101
99103 99107
NULL
head
要删除的结点为中间结点
head
p1p2
p2->next=p1->next
1 寻找要删除的结点,
while(p1->next!=NULL&&p1->num!=num)
{p2=p1;p1=p1->next;}
2 删除结点。 (p1 为要删除的结点, p2 为要删除的结点的前一个结点。)
if 要删除的结点为首结点,则只须修改 head
else p2->next=p1->next;
struct student *del(struct student *head,long num)
{struct student *p1,*p2;
if(head= =NULL) return head;
p1=head;
while(p1->next!=NULL&&p1->num!=num)
{p2=p1;p1=p1->next;}
if(num= =p1->num)
{ if(p1= =head) head=p1->next;
else p2->next=p1->next;
}
return head;
}
11.7.7 对链表的插入操作
使一个有序链表在插入后仍为有序链表
算法设计:
1 ,对空表的插入
2 ,查找插入点
3 ,插入
3.1 插入为头结点
3.2 插入为中间结点
3.3 插入为最后一个结点
插入为头结点p0 指向要插入的结点,将 p0插入到 p1 之前
headA B C D
NULL
e
head=p0;
p0->next=p1;
ai-1
e
aiai-1
插入为中间结点p0 指向要插入的结点,将 p0插入到 p2 之后 p1 之前
p0
p2 p1
p0->next=p2->next
p2->next=p0;
插入为最后一个结点p0 指向要插入的结点,将 p0插入到 p1 之后
ai-1
e
ai-1
p0
p2
p1->next=p0;
p0->next=NULL;
例 11.11 插入结点的函数 insert
struct student *insert(struct student *head, struct student *stud)
{struct student *p0,*p1,*p2;
p1=head; p0=stud;
if(head==NULL) //插入到空链表中{head=p0;p0->next=NULL;return(head);}
while((p0->num>p1->num)&&p1->next!=NULL)
//查找插入点 {p2=p1;
p1=p1->next;}
if(p0->num<=p1->num)//插入到 p1 之前 if(head==p1) //插入为头结点 { head=p0; p0->next=p1;} else //插入为中间结点 { p2->next=p0; p0->next=p1;}else //插入为最后一个结点 {p1->next=p0;p0->next=NULL;}return(head);}
11.7.8 对链表的综合操作例:建立一个链表,实现按学号删除、插入结点和输出所有结点
目标: 1,建立一个链表
2,按输入的学号删除相应的结点
3,插入多个结点
每一步操作完成后要查看结果
算法设计S1 :建立结构体和定义结构指针变量
S2 :调用 creat函数建立链表
S3 :输出全部结点
S4 :输入学号并调用 del函数删除相应结点
S5 :输出全部结点
S6 :插入多个结点(当学号为 0时结束)
S7:输出全部结点
作业:
编写一个程序使用动态链表实现下面的功能:
A. 建立一个链表用于存储学生的学号、姓名和三门课程的成绩和平均成绩
B. 输入学号后输出该学生的学号、姓名和三门课程的成绩
C. 输入学号后删除该学生的数据
D.插入学生的数据
E. 输出平均成绩在 80 分及以上的记录
要求用循环语句实现 2-- 5 的多次操作
11.8 共用体11.8.1 共用体的概念 1 ,使几个不同的变量占用同一段内存空间的结构称为共用型。
2 ,共用类型的定义 union 共用类型名 {
成员列表 ;
}; 3, 共用变量的定义 union data
{int i;
char ch;
float f;}a,b,c;
或 union data a,b,c;
11.8.2 共用体变量的引用方式
与结构变量一样,也只能逐个引用共用变量的成员
例如,访问共用变量 a 的 各成员的格式为 :a.i , a.ch, a.f 。
11.8.3 共用体类型的特点( 1)同一个内存段用来存放几种不同类型的成员 ,但在每一时刻只能存放其中的一种( 2)共用体变量中起作用的最后赋值的成员例如,执行 a.i=1, a.ch='c', a.f=3.14 后, un1.f才是有效的成员。( 3)由于所有成员共享同一内存空间,故共用变量与其各成员的地址相同。例如,& a=& a.i=& a.ch=& a.f 。( 4)不能对共用体变量进行初始化(注意:结构变量可以)( 5)不能将共用体变量作为函数参数,以及使函数返回一个共用体数据,但可以使用指向共用体变量的指针。( 6)共用体类型可以出现在结构类型定义中,反之亦然。
例 11.12 设有若干个人员的数据,其中有学生和教师,学生和教师的数据均有:姓名、号码、性别、职业,学生的数据还包括班级、教师的数据则包括职称。定义一个结构实现对这些人员的存储管理。
方法:使用结构体实现,其中不同部分使用共用体实现
#include<stdio.h>#define N 2struct{int num;char name[20];char sex;char job;union{ int banji; char position[10]; }category;}p[N];
void main(){int i;for(i=0;i<N;i++){scanf("%d%s%c%c",&p[i].num, p[i].name,&p[i].sex, &p[i].job);if(p[i].job=='s') scanf("%d",&p.category.banji);elsescanf("%s",p[i].category.position);}printf("\n");for(i=0;i<N;i++)if(p[i].job=='s')printf("%d\t%s\t%c\t%c\t%d", p[i].num, p[i].name, p[i].sex, p[i].job,p[i].category.banji);elseprintf("%d\t%s\t%c\t%c\t%s", p[i].num, p[i].name, p[i].sex, p[i].job,p[i].category.position);}
1 .枚举类型的定义 enum 枚举类型名 {取值表 } ;例如, enum weekday {Sun,Mon,Tue,Wed,Thu,Fri,Sat} ;2.枚举变量的定义──与结构变量类似( 1 )间接定义例如, enum weekdays workday;( 2 )直接定义例如, enum [weekdays] {Sun,Mon,Tue,Wed,Thu,Fri,Sat } workday;3.说明( 1 )枚举元素是一个常量,称为枚举常量。枚举型仅适应于取值有限的数据例如,根据现行的历法规定,1周7天,1年12个月。( 2 )取值表中的值称为枚举元素,其含义由程序解释。例如,不是因为写成“ Sun” 就自动代表“星期天”。事实上, 枚举元素用什么表示都可以。
11.9 枚举类型
( 3 )枚举元素作为常量是有值的──定义时的顺序号(从0开始),所以枚举元素可以进行比较,比较规则是:序号大者为大!例如,上例中的 Sun=0 、 Mon=1 、……、 Sat=6 ,所以 Mon>Sun 、 Sat 最大。( 4 )枚举元素的值也是可以人为改变的:在定义时由程序指定。例如,如果 enum weekdays {Sun=7 , Mon =1 ,Tue, Wed, Thu, Fri, Sat} ;则 Sun=7, Mon=1,从 Tue=2开始,依次增1。
但不能将一个数直接赋给枚举变量如:
workday=2;
是不允许的。
11.10 用 typedef 定义类型除可直接使用C提供的标准类型和自定义的类型(结构、共用、枚举)外,也可使用 typedef 定义已有类型的别名。该别名与标准类型名一样,可用来定义相应的变量。 定义已有类型别名的格式如下: typedef 已有的类型名 别名;如: typedef int INTEGER
typedef int Status; typedef float REAL;
然后在程序中可以用 INTEGER代替 int , 如: INTEGER a,b;
与 int a,b;
作用相同。
定义一个结构体类型typedef int ElemType;
typedef structure{
ElemType *elem;
int length;
int listsize;
}SqList;
structure SqList{
int *elem;
int length;
int listsize;
};