函数开始前,是结构体数组的定义。这里直接使用typedef
定义结构体数组,一步到位。后续可用STUDENT
来进行变量声明了
这个是主界面的显示函数,完整的声明为:void home_page()
,无形参,无返回值,单纯用printf()
函数打印的界面。
完整代码
void home_page()
{
printf("\n\t\t******************************** \n");
printf("\t\t 学生成绩信息管理系统 \n\n");
printf("\t\t 1、录入信息 \n\n");
printf("\t\t 2、浏览信息 \n\n");
printf("\t\t 3、查询信息 \n\n");
printf("\t\t 4、修改信息 \n\n");
printf("\t\t 5、成绩排序 \n\n");
printf("\t\t 6、删除信息 \n\n");
printf("\t\t 7、向文件写入数据 \n\n");
printf("\t\t 8、从文件读出数据 \n\n");
printf("\t\t 9、退出系统 \n\n");
printf("\t\t******************************** \n\n");
}
运行效果
其中,“输入标号选择功能:”字样是在 main() 函数中调用 home_page() 函数后,打印的提示语。
这是实现信息录入功能的函数,完整的函数声明是:int add_infor(STUDENT stu[], int n)
,形参为结构体数组stu[]
和结构体数组个数n
,返回值n+i
是添加完信息后的结构体数组的总个数。函数内部,i
变量是每次添加的信息的个数,sign
变量作为连续输入的标志。这里判断是否连续输入,我原来用的方法是判断stu[n+i].num
的值是否为-1
,编译成功没有报错,但是实际运行过程中会出现一些问题,应该是逻辑上的问题。我暂且换成每输入一条信息后,判断是否继续输入第二条信息,实际使用时会有一些繁琐,但总体效果相差无几。
完整代码
int add_infor(STUDENT stu[], int n)
{
int i = 0;
char sign = 'Y';
while ((sign == 'Y') || (sign == 'y'))
{
printf("\n\t\t\t学号:");
scanf("\t\t%s", stu[n + i].num);
printf("\t\t\t姓名:");
scanf("\t\t%s", stu[n + i].name);
printf("\t\t\t成绩:");
scanf("\t\t%f", &stu[n + i].score);
printf("\n\t\t\t是否继续录入?(Y/N):");
sign = getchar();
i++;
}
return (n + i);
}
上述代码在 Visual Studio 2019 IDE 中编译时,scanf()
函数会报出警告,警告代码为:C6031,说明为:返回值被忽略。解决方法有两个,第一种是将scanf
替换为scanf_s
,我没有采用这种方法,因为scanf_s
并不是 C 语言中的,而是 Visual Studio 开发工具提供的一个功能相同的安全标准输入函数,如果换为scanf_s
,在另一个编译器下就可能会出问题;第二种是使用#define _CRT_SECURE_NO_WARNINGS 1
,具体做法可百度查找,经实测,这种方法是可行的。但是,如果两种方法都不采取,即不消除这个警告,实际上对程序的正确运行也没有影响。
运行效果
“录入学生成绩信息”字样由 main() 函数在调用 add_infor() 函数前打印
这是实现浏览录入信息功能的函数,完整声明为:void browse_infor(STUDENT stu[], int n)
,形参为结构体数组stu[]
和结构体数组个数n
,无返回值。函数内部,i
是作为循环控制变量,传入数组个数n
,然后通过 for 循环,依次打印出结构体成员的值。
完整代码
void browse_infor(STUDENT stu[], int n)
{
int i;
printf("\n\n");
printf("\t\t学号\t姓名\t成绩\n\n");
for (i = 0; i < n; i++)
{
printf("\t\t%s\t%s\t %3.1f\n",stu[i].num,stu[i].name,stu[i].score);
}
}
运行效果
“浏览学生成绩信息”字样由 main() 函数在调用 browse_infor() 函数前打印
这是实现信息查找功能的函数,完整的声明为:void find_infor(STUDENT stu[], int n)
,形参为结构体数组stu[]
和结构体数组个数n
,无返回值。函数内部,又分为通过学号查找和通过姓名查找两种方式,变量option
存放键入的选项,两个字符串数组分别存放查找的学号和姓名。先选择查找方式,输入对应方式的值,然后遍历数组,通过strcmp()
函数判断数组中是否有与输入相同的值,若无相同的值则显示“未找到!”的信息,若有相同值则将该结构体成员的信息打印到屏幕上。
完整代码
void find_infor(STUDENT stu[], int n)
{
int option;
int i = 0;
char search_num[10] = { 0 };
char search_name[20] = { 0 };
printf("\n\t\t\t1、按学号查找\n");
printf("\t\t\t2、按姓名查找\n");
printf("\n\t\t\t请选择相应的选项:");
scanf("%d",&option);
system("cls");
if (1 == option)
{
printf("\n\t\t请输入要查找的学号:");
scanf("%s", search_num);
while ((strcmp(stu[i].num, search_num) != 0) && i < n)
{
i++;
}
}
if (2 == option)
{
printf("\n\t\t请输入要查找的姓名:");
scanf("%s", search_name);
while ((strcmp(stu[i].name, search_name) != 0) && i < n)
{
i++;
}
}
if (i == n)
{
printf("\n\t\t未找到!");
return;
}
printf("\n\t\t学号:%s\n", stu[i].num);
printf("\t\t姓名:%s\n", stu[i].name);
printf("\t\t成绩:%3.1f\n", stu[i].score);
}
从上述代码中可以看到,在定义两个字符串数组时,均赋了初值0,若此处没有赋初值0时,上段代码在 Visual Studio 2019 IDE 中编译时,strcmp()
函数会报警告,警告代码为:C6054,说明为:可能没有为字符串“xxx”添加字符串零终止符。深入了解strcmp()
函数可以知道,其功能是两个字符串自左向右逐个字符相比(按 ASCII 值大小相比较),直到出现不同的字符或遇 ‘\0’ 为止。所以此处在定义字符串数组时,将其初始化为 ‘\0’ ,即char search_num[10] = { 0 };
,就可以消除此类警告。
运行效果
这是实现信息修改功能的函数,函数声明为:int modify_infor(STUDENT stu[], int n)
,形参为结构体数组stu[]
和结构体数组个数n
。函数内部,可选择通过学号查找修改和通过姓名查找修改两种方式。查找部分与void find_infor(STUDENT stu[], int n)
函数一致;修改部分有修改学号、姓名、成绩三个选项,修改的实现形式是:通过查找部分得出待修改的数组下标,然后通过strcpy()
函数,将输入的修改后的学号和姓名赋值给下标对应的成员,分数的修改则直接赋值。
完整代码
int modify_infor(STUDENT stu[], int n)
{
int option, choice;
int i = 0;
char old_temp[20] = { 0 };
char new_temp[20] = { 0 };
float new_score;
printf("\n\t\t\t1、按学号查找修改\n");
printf("\t\t\t2、按姓名查找修改\n");
printf("\n\t\t请输入选项选择功能:");
scanf("%d", &option);
system("cls");
if (1 == option)
{
printf("\n\t\t请输入学号:");
scanf("%s", &old_temp);
while ((strcmp(stu[i].num, old_temp) != 0) && i < n)
{
i++;
}
if (i == n)
{
printf("\n\t\t未找到!");
}
}
if (2 == option)
{
printf("\n\t\t请输入姓名:");
scanf("%s", &old_temp);
while ((strcmp(stu[i].name, old_temp) != 0) && i < n)
{
i++;
}
if (i == n)
{
printf("\n\t\t未找到!");
}
}
printf("\n\t\t待修改学生的信息:\n");
printf("\n\t\t学号:%s\n\t\t姓名:%s\n\t\t成绩:%3.1f\n", stu[i].num, stu[i].name, stu[i].score);
printf("\n\t\t请选择要修改的信息(1.学号 2.姓名 3.成绩):");
scanf("%d", &choice);
if (1 == choice)
{
printf("\n\t\t输入修改后的学号:");
scanf("%s", &new_temp);
strcpy(stu[i].num, new_temp);
printf("\t\t修改成功!\n");
}
if (2 == choice)
{
printf("\n\t\t输入修改后的姓名:");
scanf("%s", &new_temp);
strcpy(stu[i].name, new_temp);
printf("\t\t修改成功!\n");
}
if (3 == choice)
{
printf("\n\t\t输入修改后的成绩:");
scanf("%f", &new_score);
stu[i].score = new_score;
printf("\t\t修改成功!\n");
}
}
此函数中的两个字符串数组在定义时,也需要赋初值,以消除函数strcmp()
和strcpy()
使用时的警告。
运行效果
此函数完整声明为:void sort_score(STUDENT stu[], int n)
,形参为结构体数组stu[]
和结构体数组个数n
,实现的功能是排序,用的是冒泡排序法,按从小到大的顺序给成绩排序。具体实现形式就是,通过两层循环,外层循环是排序趟数,内层循环是每趟比较的次数,相邻两元素比较,若前者大于后者,则两者进行交换,此时为顺序,即从小到大;若前者小于后者且两者交换,则为逆序,即从大到小。此函数为顺序,且在交换两者值时,学号和姓名使用strcpy()
函数,成绩的交换使用指针。
完整代码
void sort_score(STUDENT stu[], int n)
{
int i, j;
float * p, * q, s;
char temp[20];
for (i = 0; i < n - 1; i++)
{
for (j = 0; j < n - 1 - i; j++)
{
if (stu[j].score > stu[j + 1].score)
{
strcpy(temp, stu[j].num);
strcpy(stu[j].num, stu[j + 1].num);
strcpy(stu[j + 1].num, temp);
strcpy(temp, stu[j].name);
strcpy(stu[j].name, stu[j+1].name);
strcpy(stu[j+1].name,temp);
p = &stu[j].score;
q = &stu[j + 1].score;
s = *p;
*p = *q;
*q = s;
}
}
}
}
此函数是信息删除函数,完整声明为:int delete_infor(STUDENT stu[], int n)
,形参为结构体数组stu[]
和结构体数组个数n
,返回值是删除后的数组个数。函数内部,可通过按学号和按姓名两种方式查找并删除,其中查找部分与void find_infor(STUDENT stu[], int n)
函数一致;删除部分的实现形式是,从查找部分获得找到的数组下标,从下标处开始,将后一值赋给前一值,直至数组的末尾,以此实现删除的功能。
完整代码
int delete_infor(STUDENT stu[], int n)
{
char search_num[10] = { 0 };
char search_name[20] = { 0 };
int i = 0;
int j;
int option;
printf("\n\t\t\t1、按学号查找删除\n");
printf("\t\t\t2、按姓名查找删除\n");
printf("\n\t\t\t请输入选项选择功能:");
scanf("%d", &option);
system("cls");
if (1 == option)
{
printf("\n\t\t请输入要删除的学生的学号:");
scanf("%s", &search_num);
while ((strcmp(stu[i].num, search_num)) != 0 && (i < n))
{
i++;
}
if (i == n)
{
printf("\t\t未找到!\n");
return n;
}
}
if (2 == option)
{
printf("\n\t\t请输入要删除的学生的姓名:");
scanf("%s", &search_name);
while ((strcmp(stu[i].num, search_name) != 0) && (i < n))
{
i++;
}
if (i == n)
{
printf("\t\t未找到!\n");
return n;
}
}
for (j = i; j < n; j++)
{
strcpy(stu[j].num, stu[j + 1].num);
strcpy(stu[j].name, stu[j + 1].name);
stu[j].score = stu[j + 1].score;
}
printf("\t\t删除成功!\n");
return (n - 1);
}
注意此函数的返回值,由于删除了一条信息,即从结构体数组中删除了一项,所以返回值应当是传入参数n
减去1之后的值,即返回值return (n-1);
。
运行效果
此函数的功能是将录入的数据保存到文件中,完整声明为:void writetofile(STUDENT stu[], int n)
,形参为结构体数组stu[]
和结构体数组个数n
。函数内部,用 FILE 结构体类型定义文件类型的指针变量 fp,输入要写入的文件的名称,然后以只写 “w” 的方式,向文件写入数据,若指定的文件不存在则创建它,如果存在则先删除它再重建一个新文件,首先向文件内写结构体数组的个数,然后通过 while 循环,将所有信息写入文件中,最后fclose(fp)
关闭文件。
完整代码
void writetofile(STUDENT stu[], int n)
{
FILE* fp;
int i = 0;
char filename[20] = { 0 };
printf("\n\t\t\t请输入文件名称:");
scanf("%s", filename);
if ((fp = fopen(filename, "w")) == NULL)
{
printf("\n\t\t\t打开文件失败!\n");
return;
}
fprintf(fp, "%d\n", n);
while (i < n)
{
fprintf(fp, "%-6s%-10s%3.1f\n", stu[i].num, stu[i].name, stu[i].score);
i++;
}
fclose(fp);
printf("\n\t\t\t写入文件成功!\n");
}
由于只需要向文件写数据,所以fopen()
中 mode 参数只需要"w"
即可,数据写入文件成功后,一定要使用fclose(fp)
关闭文件。
运行效果
“向文件写入数据”字样由 main() 函数在调用 writetofile() 函数前打印
此函数的功能是从文件中读出数据,完整声明为:int readfromfile(STUDENT stu[], int n)
,形参为结构体数组stu[]
和结构体数组个数n
,返回值为读入数据后的数组总个数。函数内部,用 FILE 结构体类型定义文件类型的指针变量 fp,输入要读出数据的文件的名称,以只读方式,通过 while 循环,从文件中读出数据,存放到数组中,然后将读到的数据个数加上传进的参数 n 作为返回值,最后关闭文件。
完整代码
int readfromfile(STUDENT stu[], int n)
{
FILE* fp;
int i, num;
char filename[20];
i = 0;
printf("\n\t\t\t请输入文件名称:");
scanf("%s", filename);
if ((fp = fopen(filename, "r")) == NULL)
{
printf("\n\t\t\t无法打开文件!\n");
return n;
}
fscanf(fp, "%d", &num);
while (i < num)
{
fscanf(fp, "%-5s%-10s%3.1f\n", stu[n + i].num, stu[n + i].name, stu[n + i].score);
i++;
}
n += num;
fclose(fp);
printf("\n\t\t\t读出数据成功!\n");
return n;
}
同样,在打开文件并执行操作完毕后,需要fclose(fp)
来关闭文件。注意,此函数的返回值应该是读到的数据个数 num 加上传进的参数 n ,返回的才是此时总的数组个数。
运行效果
“从文件读出数据”字样由 main() 函数在调用 readfromfile() 函数前打印
main函数部分,主要是由while(1)
死循环构成的,循环内部首先调用home_page()
函数显示主界面,然后通过 switch-case 语句,来匹配用户输入的选项。
完整代码
int main()
{
int select = 0;
int n = 0;
while (1)
{
system("cls");
home_page();
printf("\t\t输入标号选择功能: ");
scanf("%d", &select);
switch (select)
{
case 1:
system("cls");
printf("\n\t\t********录入学生成绩信息********\n");
n = add_infor(stu, n);
break;
case 2:
system("cls");
printf("\n\t\t********浏览学生成绩信息********\n");
browse_infor(stu, n);
getchar();
break;
case 3:
system("cls");
printf("\n\t\t**********查询学生信息**********\n");
find_infor(stu, n);
getchar();
break;
case 4:
system("cls");
printf("\n\t\t*********修改学生信息*********\n");
n = modify_infor(stu, n);
getchar();
break;
case 5:
system("cls");
printf("\n\t\t*********按成绩排序*********\n");
sort_score(stu, n);
printf("\n\t\t\t排序成功!");
getchar();
break;
case 6:
system("cls");
printf("\n\t\t*********删除学生信息*********\n");
n = delete_infor(stu, n);
getchar();
break;
case 7:
system("cls");
printf("\n\t\t********向文件写入数据********\n");
writetofile(stu, n);
getchar();
break;
case 8:
system("cls");
printf("\n\t\t********从文件读出数据********\n");
n = readfromfile(stu, n);
getchar();
break;
default:
system("cls");
printf("\n\n\t\t**********谢谢使用!**********\n");
exit(0);
break;
}
getchar();
}
}
注意,switch-case 语句的每个 case 结束都应加上break;
,且最后要加上default
,同时,为了美观,在每个 case 语句调用函数前,都加上system("cls")
清屏函数;在调用后,加上getchar ()
语句等待用户操作。
链接: 完整代码
第一次写博客,方方面面的都有些不熟悉,脑子里有很多想法,但无从下手,拿了以前写的程序作主要内容,进行一次尝试。虽然内容啥的都很简单,程序也没有什么技术含量,但这是一次从0到1的尝试吧,以后也会继续努力。文中如有错误之处,还请指正,如遇大佬,还请多多指教!