一、问题的引入
(一)一个绩点计算案例
例:学生成绩管理系统
一种解决方法:定义多个一维或二维数组
存在的主要问题:
结论:不是好的表示方法。
(二)结构体的概念
·结构体的概念
·声明一个结构体类型的一般形式为:
·还可以设计出许多结构体类型,例如:
表示教师信息的结构体
表示日期的结构体
·结构体成员可以属于另一个结构体类型:
二、构建用户自己需要的数据类型
(一)声明结构体类型变量
例:从键盘输入一个学生的成绩信息并输出
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语言程序设计(上海电力大学)。