语言:C语言
工具:Dev-C++
CSDN源码下载:C语言成绩管理系统源码
接上一篇,C语言成绩管理系统实例 附源码(一)继续唠叨。
C语言允许加工对象不是一个完整的程序,而是多个源程序文件。在程序规模比较大时,一般是根据结构化程序设计方法将程序划分成多个源文件。在编译该程序时,可以以源文件为单位分别进行编译并产生与之对应的目标文件,然后再用链接程序把所生成的多个目标文件进接成一个可执行文件。C语言的这种编译过程称为分块编译,这种开发方法称为分块开发。
C语言的这种分块开发和编译处理方法使一个程序可以同时由多个人进行开发,为大型软件的集体开发提供了有力支持。分块编译的优点还在于修改一个源文件中的程序后,并不需要重新编译整个程序的所有文件,这就大大节省了时间。
分块开发中最重要的工作是程序结构的"物理"组织。虽然对于同一个程序,完全可能采用不同的物理组织结构,但都必须遵循这样一个原则:使同一个程序对象的定义点和所有使用点都能参照同一个描述。例如,让它们参照同一个头文件。
按照惯例,常把C程序分为后级为.c和.h文件的两类文件。前者是包含实际程序代码基本程序文件,后者是为基本程序文件提供必要信息的辅助性文件。
编译程序库中的许多函数都要与它们自己的专门类型的数据和变量一起工作,用户的程序也必须访问这些数据和变量。这些变量和类型由编译程序提供的"头文件"所定义。在任可一个使用这些特定函数的文件中,必须嵌入涉及这些特定函数的头文件(用#include语句包含)。此外,对于C库中的所有函数,均在相应的头文件中有它们的原型定义,以便提供一个更强的类型检查手段。把程序中使用的标准函数与对应的头文件链接进来,就可以查出溶在的类型不匹配错误。例如,库函数中战入字符串函数的头文件string.h,使下面的程序在编译时产生警告信息。
#include <string.h>
char s1 [20] = “hello”;
char s2[ ]=“there”;
void main()
{
int p;
p=strcat(s1,s2);
}
因为在头文件中是strcat()说明为返回一个字符型指针,而程序中定义的变量p是整型,所以编译程序会给出一个可能出错的标记,指出错误地把该文件参数赋给了整型变量P。C编译器使用的头文件有些是重复的。例如,有的编译器在a11oc.h中出现的说明,又在stdlib.h中重复说明。保留多余的头部文件是为了让早先为ANSI标准编写的源文件,在编译时可以不必改动。
因为明确的数据类型对检查和防止程序出错具有积极的作用,所以最好开放所有的编译程序警告信息,利用所有必要的嵌入文件来说明所用函数的参数,而不要管该函数是C标准库的还是一些C工具库的。用户在自己的战人文件中说明自定义的函数的参数,把做人文件用作程序模块之间对类型定义的唯一访问点。若编译程序指出类型冲突错误,而这些错误又确实存在,就需要仔细修改程序,直到警告信息不再出现为止。
若使用头文件来做类型定义,则由于这些定义是模块间共享的,所以能防止程序出玛的编译错误,且能及早查出某些不易察觉的错误。
强类型方式甚至能及早查出潜在的错误,尽管这些错误可能在目前情况下还没有产生不良的后果,但在将来却一定会产生不良后果。
C语言预处理器是C编译程序的一部分,在编制C程序时,应注意使用它们来提高程序质量。
头文件的内容安排可以遵循如下原则:
(1)头文件里只写不实际生成的代码、不导致实际分配存储的描述。例如,可写函数原型声明,不写函数定义;可用extern声明外部变量,但不定义变量;可以包含标准库头文件和其他头文件的预处理命令;可以包含枚举的声明。
(2)可以包括各种公用宏定义,但尽量少用宏定义。可以包括各种公用的类型定义(结构和枚举等),但建议把与多个程序文件有关的结构定义为类型。
(3)只用文件包含命令(#include)包含头文件,不用它包含程序文件。
(4)通过头文件解决在一个程序文件里定义而在另一个程序文件里使用的信息传递问题。通过这种方式保证使用和定义之间的联系,保证编译程序能进行一致性的类型检查。
正如函数分解一样,文件的分解也没有万能的准则,一般来说,首先划分".c"文件。可以从如下两个方面来考虑。
(1)首先初步估计程序的大小,将原文件划分为几块。随着程序开发过程的进行,再根 |据实际情况合理调整。
(2)根据程序实现的功能,将其划分为几块。一般将具有一定整体性的功能放在一起,建立一个程序文件。例如,与输人和输出有关的功能可以考虑放在一起,如果输入和输出都比较复杂,也可各自放在一个文件中。主函数通常单独建立一个文件,其中也可以包含少数与它关系密切的其他函数的定义,例如简单的菜单选择函数。
然后根据源程序的文件数量和功能,设计头文件。如果源文件比较复杂,完全可能需要为每个源文件设计一个头文件。对于一般情况,需要根据具体情况而定。一般可以参考如下原则决定头文件的数量和编写内容。
(1)把所有公用的类型定义,公用的结构、联合或枚举声明,公用的宏定义放在适当的头文件里,提供给各个文件参考。
(2)如果在许多地方都使用同一个标准头文件,或者某个头文件本身需要,则可以把它们写在一个自己定义的头文件里面供这些文件使用。有些头文件里还可能需要包含其他头文件,注意不要漏掉。
(3)如果只有一个文件需要某个标准头文件,则不要将它放在公共的头文件中,应让这E文件直接包含它,以提高编译效率。
(4)对于所有在一个源文件里定义而在其他文件中使用的东西,都需要在某个头文件里明(函数原型或者变量的外部声明)。
应正确设计源文件,以便与头文件配合。一般要注意如下问题。
(1)每个源文件的前面使用#include包含必要的头文件,不用的东西尽量不包含。
(2)如果既有标准头文件,又有自定义头文件,应将标准头文件写在前面,以防止本程序的局部定义影响库文件里的定义,
(3)在一个源程序文件中,所有局部的东西都写在各自的函数中;所有只在这个局部范围使用的外部变量和辅助函数,都使用static关键字定义为外部静态的。
(4)对于多个函数都需要访问的变量,应该根据谁使用谁管理的归属原则,分别定义;不同源文件里的外部变量。许多地方都使用的全局变量,一般在主程序文件里定义。
因为一个程序文件可能包含多个头文件,由此可能引起同一个头文件的重复包含问题。对头文件使用预处理命令可以避免这一问题。
标准头文件结构。
同一个编译单元里,同名的结构不能被重复声明。如果你的头文件里有结构的声明,很难这个头文
件不会在一个编译单元里被#include多次,所以需要“标准头文件结构”。
#ifndef __LIST__HEAD__ #define __LIST__HEAD__ #include "node.h" typedef struct _list{ Node* head; Node* tail; }list; #endif
使用条件编译。头文件中的语句
#ifndef H_STUDENT_HH
#define H_STUDENT_HH
也是用来防止重复包含的。这种比较怪异的形式,不如STUDENT_H那样比较清楚明了,目的是为了尽量避免可能出现的重复的宏定义。
在包含头文件时。对于系统库的头文件,使用尖括号”<>”的形式,表示从系统库目录查找该头文件:对干自己创建的头文件如student.h,使用#include "student"的形式,表除了系统库目录外,还要从工作目录去查找该头文件。
在主函数中,首先用malloc函数分配INITIAL_SIZE个stuInfo大小的数组给records,对各个全局变量进行初始化赋值后,反复进行菜单处理,直至选择退出命令。
在菜单选择函数menu_selected中,限定输入必须在0~9之间才有效,否则要求重新输人。不管用户按数字键还是按字母键,语句“gets(s)”都能将输入作为字符串接收,然后语句“cn=atoi(s);”再将所接收的字符串转成数值,提供给if语句判别。
显示学生信息的display函数是通过循环遍历数组中第0~numStus-1个函数,逐条显示学生信息。
增加新信息函数addRecord用来在当前表的尾部增加新的信息,这只要将新的信息保存到records_[numStus]中即可,然后numStus自加1,完成操作。
如果在增加新信息之前,numStus已经大于或等于arraySize,就要使用realloc函数重新为records分配一块大小为(arraySize+INCR_SIZE)个StuInfo的数组的存储块,并重新设置arraySize。
文件存储操作函数saveRecords是通过fwrite函数一次写人从records开始numstus个stuInfo大小的字节。
文件读取操作函数1oadRecords的情况比较复杂,需要根据具体情况进行相应的操作。
(1)将这个函数设计为可以连续读入文件,后面的文件可以追加到前面的记录数组之后,山而可以作为一个更大的文件存储。
(2)为了操作方便,希望在内存没有记录时,执行读取操作不要询问是否需要覆盖信息。当内存有通过增加追加的信息时,执行读取将是直接追加其后,不要询问是否覆盖。
(3)当将多个文件连续读取追加时,也不需要询问是否需要覆盖。
(4)当对信息执行存储之后,再执行读取,则询问是否需要覆盖。
(5)在进行两个文件拼接时,请在内存无记录的情况下进行,这时将对读入的第2个文件进行询问。
(6)如果要覆盖原来的记录,就保存原记录,然后令numStus为0,否则原来的numStus不变。
(7)在读取文件时,使用fread函数,每次读取sizeof(StuInfo)个字节,放在records[numStus]里面,并令numStus自加1,如此下去,直到读完文件。
(8)如果在每读入一条新信息之前,nunStus已经大于或等于arraySize,那么就使用realloc函数重新为records分配大小为(arraySize+INCR_SIZE)个StuInfo的数组,然后重新设置arraySize。
查找指定记录函数为:
int ftndRecord(char* target, Int targetType, int fronm)
它从指定序号的记录开始,顺次查找符合条件的记录,若找到则返回记录的序各不到则返回-1。
该函数使用三个参数:第一个参数是字符串target,表示欲查找记录的某一项与t、相同;第二个参数是targetType,表明通过哪一项来查找,0为学号,1为姓名;第三个参数是from,表示从第from个记录开始找。
调用该函数的函数是queryInfo、removeRecord和modifyRecord。当需要查找符合条件的记录时,只要首先调用
i=findRecord(target,tareType,0);
然后反复调用i=findRecord(target,targetType,1+1)直至1为-1即可,这样每得的i就都是符合条件的记录的序号了。
查询指定学生信息函数queryInfo用来找出所有符合查找条件的记录并显示出来。
删除记录函数removeRecord对于找到的每一条记录,首先显示出来让用户决定是否要删除这条记录,若确定要删除,则令numStus自减1,然后将数组中排在被删除记录后面的记录往前移一格。最后应该更新其他记录的名次,即将名次排在被删除记录后面的记录的名次减1。
修改指定学生信息函数modifyRecord对于找到的每一条记录,首先显示出来让用户决定是否要修改这条记录,若确定要修改,就重新输人学生信息。
修改记录函数modifyRecord对于找到的每一条记录,首先显示出来让用户决定是否要修改这条记录,若确定要修改,就重新输入学生信息,然后根据新记录中的总分来计算该记录的名次和更改其他用户的名次,也就是说,该学生的名次是总分高于他的学生总数加1,并且将原来名次排在被修改记录之后,而其总分小于等于修改后记录的总分的记录的名次减1将原来名次排在被修改记录之前或相同,而其总分大于修改后记录的总分的记录的名次增1。
可以选择按学号进行升序排序,按学号进行降序排序,按姓名进行升序排序,按姓名进行降序排序,按名次进行升序排序或者按名次进行降序排序,使用比较排序法,比较数组中的任两个记录,对于不符合顺序的两个记录对换位置。
未完持续…