C语言 第八章 结构体

一、问题的引入

(一)一个绩点计算案例

例:学生成绩管理系统

一种解决方法:定义多个一维或二维数组

存在的主要问题:

结论:不是好的表示方法。

(二)结构体的概念

·结构体的概念

·声明一个结构体类型的一般形式为:

·还可以设计出许多结构体类型,例如:

表示教师信息的结构体

表示日期的结构体

·结构体成员可以属于另一个结构体类型:

二、构建用户自己需要的数据类型

(一)声明结构体类型变量

例:从键盘输入一个学生的成绩信息并输出

1.定义结构体变量的3种方式

方式1:声明结构体的同时定义结构体变量,可以同时声明多个变量。例如:

方式2:先声明结构体类型,再定义该类型变量。例如:

方式3:不指定类型名而直接定义结构体类型变量。例如:

2.说明

·类型和变量是不同的概念

·变量在内存中占有存储空间,存储空间的大小是变量中各成员所占空间的总和。

例:变量stu1在内存中占有存储空间,存储空间的大小是stu1中各成员所占空间的总和。

(二)结构体变量的初始化

(三)结构体变量的引用

引用结构体变量中的一个成员:

结构体变量名.成员名

结构体变量可以与普通变量同名

当结构体变量嵌套定义时,引用结构体的规则不变

(四)结构体数组

:假设每个学生都是3门课成绩,计算每个学生的平均绩点。

解题思路

1.定义一个结构体数组

struct student s[9];

2.计算结果保存在另一个结构体数组中

注:在定义结构体的同时定义结构体数组;也可以先定义结构体,再定义结构体数组

(源代码)

(运行结果截图)

结构体变量初始化时,每一个元素的一组值需要用一对大括号括起来,所有的元素值需要再用一对大括号括起来。

特别要注意,数据最后的分号不能丢

三、结构体指针的应用

(一)指向结构体的指针

·指针变量可以指向简单变量、指向数组,也可以指向结构体变量

·指向结构体变量的指针变量的基类型必须与结构体变量的类型相同。

例如:struct Studrnt *pt;

用指针p表示变量stu_1中的成员:

stu_1.num 表示为 (*p).num

stu_1.name 表示为 (*p).name

stu_1.sex 表示为 (*p).sex

stu_1.score 表示为 (*p).score

注:(*p)的括号()不能丢,因为 . 的优先级高于 * 

说明

例:通过指针变量引用结构体数据。

问题:有3个学生的信息,放在结构体数组中,要求输出全部学生的信息

解题思路:用指向变量的指针处理

(源代码)

(运行结果截图)

(二)动态内存分配

·无论简单变量、数组,在引用前都要被定义

但是在实际的编程中,往往所需的内存空间无法预先确定

·解决方法:采用动态数组

这种方法需要采用C语言的动态内存分配

所谓动态内存分配,就是在程序运行过程中,根据需要随时向系统申请内存空间,用完之后,系统收回内存单元

·申请内存空间函数malloc()

void *malloc (unsigned int size);

该函数可以在内存的动态存储区中,开辟一个程度为size字节的连续空间,函数的返回值是一个指向该区域的指针(地址)。

例如:动态申请数组a[n],n的大小从键盘输入

int n,*a;

scanf("%d",&n);

a=(int*)malloc(n*sizeof(int));

·释放内存空间free()

void free(void *p);

该函数的作用是释放由指针p所指向内存单元的空间。

例如:free(a);

例:通过指针变量引用结构体数组。

假设成绩记录个数不能事先确定,要在程序运行时根据键盘输入确定,从键盘输入学生成绩并输出

分析

(1)学生成绩保存在结构体数组中

(2)由于成绩记录不确定,所以如果用静态数组,必须事先定义一个足够大的数组,这里可以采用刚介绍的动态数组。

(源代码)

(运行结果截图)

(三)单链表

1.链表的概念

声明链表中的结点

2.建立动态单链表

建立动态链表是指在程序执行过程中从无到有地建立起一个链表,即一个一个地开辟节点,并输入各结点数据,并建立起前后相链的关系。

:写函数建立单向动态链表

问题:写一函数建立一个有多名学生数据的单向动态链表,学生数据从键盘输入,当输入学号为999时,输入结束。

解题思路:

·定义3个指针变量:head,p1和rear,它们都是用来指向struct Student类型数据,其中head为单链表的头指针,p1指向当前节点,rear指向当前链表的尾节点

struct Student *head,*p1,*rear;

·初始状态下,链表为空

head=NULL;

rear=NULL;

·用malloc函数开辟一个结点,并使p1指向它

p1=(struct Student*)malloc(LEN);

·如果是第一个结点(head==NULL),那么当前节点既是单链表的头结点,也是尾节点

head=p1;

rear=p1;

·开辟一个新结点并使p1指向它

p1=(struct Student*)malloc(LEN);

·接着输入该结点的数据

scanf("%ld,%f",&p1->num,&p1->score);

·链接尾节点(rear)和当前结点(p1)

rear->next=p1;

·使rear指向新的尾结点

rear=p1;

·循环重复刚才的过程。如果某一个结点的学号输入为999,循环结束。

·最后一个结点的next域值为NULL;

rear->next=NULL;

(源代码)

p1总是开辟新结点

rear总是指向最后结点

Head总是指向头结点

例:编写一个输出单链表的函数printf()

解题思路

·p指向单链表的第一个结点

p=head;

·输出p所指的结点

printf("%ld %5.1f\n",p->num,p->score);

·p移动到后一个结点

p=p->next;

·输出p所指的结点

printf("%ld %5.1f\n",p->num,p->score);

·p再后移一个结点

p=p->next;

·输出p所指的结点

printf("%ld %5.1f\n",p->num,p->score);

·p再后移一个结点

p=p->next;

此时:p=NULL,循环结束

归纳总结

1.指针p指向单链表的头结点p=head;

2.输出p指向的结点的内容

3.指针p后移一个结点

4.重复2和3,直到指针p为空

(源代码)

上面的函数creat()和printf()可以用如下main()函数调用:

注:在结构化程序设计中,main函数只起桥梁作用,程序的所有功能都在各个函数里面来实现。

四、共用体

(一)什么是共用体类型

·有时想用同一段内存单元存放不同类型的变量

·使几个不同的变量共享同一段内存的结构,称为 “共用体” 类型的结构

·定义共用体类型变量的一般形式为:

例如:

· “共用体” 与 “结构体” 的定义形式相似,但它们的含义是不同的

结构体变量:所占内存长度是各成员占的内存长度之和,每个成员分别占有其自己的内存单元

共用体变量:所占的内存长度等于最长的成员的长度

例如:

(二)引用共用体变量的方式

·只有先定义了共用体变量才能引用它,但应注意,和结构体变量类似,不能整体引用共用体变量,而只能引用共用体变量中的成员。

例如,前面定义了a,b,c为共用体变量,下面的引用方式:

(三)共用体类型类型数据的特点

(1)同一个内存段可以用来存放几种不同类型的成员,但在每一瞬时只能存放其中一个成员,而不是同时存放几个。

例如:同时执行如下两条语句:

a.i=10;

a.ch='a';

内存中存储的是'a'而不是10

(2)可以对共用体变量初始化,但初始化表中只能有一个常量。

例如:

(3)共用体类型可以出现在结构体类型定义中,也可以定义共用体数。反之,结构体也可以出现在共用体类型定义中,数组也可以作为共用体的成员。

 

例:用表格处理数据

问题:有若干个人员的数据,其中有学生和教师。学生的数据中包括:姓名、编号、性别、职业、班级。教师的数据包括:姓名、编号、性别、职业、职务。要求用同一个表格来处理。

解题思路

·如果job项为s,则第5项为class。即Li是501班的。如果job项是t,则第5项为position。Wang是prof(教授)

·对第5项可以用共用体来处理(将class和position放在同一段存储单元中)

·在结构体内定义共用体

·先声明共用体类型,然后在结构体内定义共用体变量作为结构体的成员

(源代码)

五、枚举类型

1.枚举类型

·如果一个变量只有几种可能,则可以定义为枚举类型

·所谓 “枚举” 就是指把可能的值一一列举出来,变量的值只限于列举出来的值的范围内

·声明枚举类型用enum开头

例如:

enum Weekday {sun,mon,tue,wed,thu,fri,sat};  //sun,mon···sat称为枚举元素

-声明了一个枚举类型 Weekday

-然后可以用此类型来定义变量

enum Weekday workday,weekend;  //workday,weekend称为枚举变量

·可以将枚举的值赋给枚举变量,但枚举值不可以赋给枚举类型

2.说明

(1)C编译系统对枚举类型的枚举元素按常量处理,故称枚举常量。不要因为它们是标识符(有名字)而把它们看作变量,不能对它们赋值。

(2)每一个枚举元素都代表一个整数,C语言编译系统按定义时的顺序默认它们的值为0,1,2,3,4,5··· ···

(3)枚举元素可以用来作判断比较。

例:求球的可能取法和排列情况

问题:口袋中有红、黄、蓝、白、黑5种颜色的球若干。每次从口袋中先后取出3个球,问得到3种不同颜色的球的可能取法,输出每种排列的情况。

分析:5种颜色设置为枚举类型

enum Color{red,yellow,blue,white,black};

编程思路:用穷举法,设置三重循环:

(源代码)

(运行结果截图)

程序改进:

(源代码)

(运行结果截图)

六、用typedef定义

1.简单地用一个新的类型名代替原有的类型名

2.命名一个简单的类型名代替复杂的类型表示方法

(1)命名一个新的类型名代表结构体类型

(2)命名一个新的类型名代表数组类型

(3)命名一个新的类型名代表一个指针类型

3.归纳起来,声明一个新的类型名的方法是:

·先按定义变量的方法写出定义体(int i;)

·将变量名换成新类型名(将i换成Count)

·在最前面加typedef(typedef int Count)

·用新类型名去定义变量

例如:Count i;  相当于  int i;

4.说明

·以上的方法实际上是为特定的类型指定了一个同义词(synonyms)。

·用typedef只是对已经存在的类型指定一个新的类型名,而没有创造新的类型。

·用typedef声明数组类型、指针类型、结构体类型、共用体类型、枚举类型,使得编程更加方便。

·当不同源文件中用到同一类型数据时,常用typedef声明一些数据类型。可以把所有的typedef名称声明单独放在一个头文件中,然后在需要用到它们的文件中用#include指令把它们包含到文件中。这样编程者就不需要在各文件中自己定义typedef名称了。

·使用typedef名称有利于程序的通用与移植。有时程序会依赖于硬件特性,用typedef类型就便于移植。

七、结构体的应用案例

例:学生成绩平均绩点计算

问题:学生成绩保存在文件score_list.text中,每个学生的课程门数可能不一样,计算学生的平均绩点,并将结果写回到文件GPA.text中。

1.分析

(1)根据程序功能,程序可以由如下模块组成

要点1:每个函数只完成一件事

要点2:函数之间通过结构体数组传递数据

要点3:主函数起的作用仅仅是串接前面实现的各函数

(2)各函数的参数及类型定义

(3)函数之间通过结构体数组 struct student s[ ] 传递数据

(4)函数 int read(struct student s[ ])将文件中的成绩读入结构体数组 s[ ] ,同时确定s数组中实际元素个数n,即成绩记录个数,并返回给调用函数

(5)函数 void printf_list(struct student s[ ],int n) 输出数组 s[ ] 中的成绩记录

(6)函数 void CAL_GPA(struct student s[ ],int n) 计算每个同学的 GPA ,并将结果写入文件

(7)函数 void print_GPA( ) 从文件读取每个同学的学号和 GPA 并输出

(8)主函数起的作用仅仅是串接前面实现的各函数,这是模块化程序设计的设计思想

(源代码)

本程序中各函数间通过结构体数组作为函数参数传递数据,注意 main( ) 函数中的实际数组 stu[ ] 和各函数中形参数组 s[ ] 的关系。

八、常见错误

例:书店购书

问题:本程序定义一个书店购书的结构体类型,包括书名、单价、数量和金额四个元素。数组b中已经存放了一个人购书基本情况。通过自定义sum函数计算每种书金额和购书总额,再根据购书总额打折,折扣规则如下:

输出书名、单价、数量、金额和购书总额,折扣后应付额。运行结果如下:

1.带错误的程序

(1)·结构体声明后面忘记加分号;

        ·数组作为函数参数时,代表数组的符号[ ]不能丢,否则a就成为了普通结构体变量

        ·引用结构体成员时,必须是:结构体变量.成员变量

(2)·未用typedef说明新数据类型时,声明结构体数组时的struct不能少

        ·结构体数组赋值时,每组数据的大括号都不能少

        ·数组作为实参时,因为已经声明,所以直接用数组名即可

2.正确的源程序

3.使用typedef的正确的源程序

该文章属于个人课后学习笔记,内容来源于知到智慧树共享课-C语言程序设计(上海电力大学)。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值