从简单的C程序看完美程序的开发

本文从一道简单的编程题出发,逐步深入探讨如何提升程序的质量。通过不断改进和完善程序,作者展示了如何站在开发者、维护者和用户的角度考虑问题,从而创造出更加完美的作品。
欢迎到我的博客www.clyce3630.cn来坐坐
文章原地址  http://www.clyce3630.cn/CLab/?p=48

首先说说这个标题,我不敢说完美,世界上不存在完美的程序
但我始终觉得,一个开发者,就要让他的作品趋于完美。
好了言归正传

一直想写篇文章,谈谈如何开发一个“好软件”
却一直无从下笔

首先,很多话想说,可是不知从何说起
其次,手头没什么素材
再者说,也很难掌握文章的深浅,或许是开发者的通病,我想写一篇新人老手都能看的文章,
    就像开发者一直期望有一个万能统一的接口,写一个不依赖平台的真正的“一次编写到处运行”。

前一阵子,看到一道题,突然来了灵感,便写了下来
初涉代码海洋,分析叙述中不免有错,小弟欢迎各位指正,也算是抛砖引玉了吧
本来是一道很简单的编程问题,
 题目取自我一个同学的大学C语言基础作业,如下

 
编写一个程序,使用户可以录入最多不大于50个学生的数学、英语和C语言成绩, 并能够在录入后按照成绩以冒泡排序的方式进行排名。 【提示: 可使用两个数组,一个存储学生姓名,一个存储学生成绩】
很好,就是这么个破烂的程序,于是,当即立下,我帮他按照原题给出了如下答案:
#include "stdio.h" void strswap(char **pStr1, char **pStr2) { //通过指针交换字符串 char *temp = *pStr1; *pStr1 = *pStr2; *pStr2 = temp; } main() { int grade[50][4],i,j,k,Stu_Num; char rank[50][100]; printf("/n/n Enter number of students [lower than 50]: > "); scanf("%d",&Stu_Num); printf("/n/n Enter indicated grade [ Math,English,CProgramming ] /n"); printf("/n Student No. :/n"); for ( i = 1; i <= Stu_Num; i++) { /*依次录入学生成绩*/ grade[i - 1][3] = 0; printf(" %-3d ",i); scanf("%d,%d,%d",&grade[i - 1][0],&grade[i - 1][1],&grade[i - 1][2]); for ( j = 0; j <= 2; j++) { grade[i - 1][3] += grade[i - 1][j]; } } printf("/n Enter names of students: /n"); for ( i = 0; i < Stu_Num; i++ ) { /*按学号输入学生姓名*/ printf( " %d: ",i + 1); fflush(stdin); gets(rank + i); } /*冒泡排序*/ for(i = 0; i < Stu_Num; i++) { for ( j = Stu_Num - 1; j > 0; j--) { if ( grade[j][3] > grade[j - 1][3] ) { for (k = 0; k < 4; k++) { grade[j][k] ^= grade[j - 1][k]; grade[j - 1][k] ^= grade[j][k]; grade[j][k] ^= grade[j - 1][k]; } strswap((rank + j),(rank + j - 1)); } } } printf("/n No. Name Math Eng C_Pro Sum/n"); for (i = 0; i < Stu_Num; i++) printf(" %-3d %-10s %4d %3d %5d %5d /n", (i + 1),(rank + i),grade[i][0],grade[i][1],grade[i][2],grade[i][3]); }
运行结果如下: 毋庸置疑,这个程序只提供了最基本的功能。 那么,最基本的功能至上,有什么需要改进的呢?
  • 程序可扩展性,如果由于各种情况,开发者想调节学生数量上限,将会非常麻烦
  • 程序可靠性,虽然在声明数组的时候规定了上限50,输入操作时却没有检测
  • 界面友好性,学生数量一多,很难看清哪一行是哪一行
于是,针对以下问题,可以编写如下的改进程序了:
#include "stdio.h" #define MAX_STU_NUM 50 void strswap(char **pStr1, char **pStr2) { //通过指针交换字符串 char *temp = *pStr1; *pStr1 = *pStr2; *pStr2 = temp; } main() { int grade[50][4],i,j,k,Stu_Num,Comp_Bool; char rank[50][100]; do { printf("/n/n Enter number of students [lower than %d]: > ", MAX_STU_NUM ); scanf("%d",&Stu_Num); if ( Stu_Num > MAX_STU_NUM ) printf("/n/n ERROR, the number must be lower than %d /n",(MAX_STU_NUM + 1)) ; } while ( Stu_Num > MAX_STU_NUM ); printf("/n/n Enter indicated grade [ Math,English,CProgramming ] /n"); printf("/n Student No. :/n"); for ( i = 1; i <= Stu_Num; i++) { /*依次录入学生成绩*/ grade[i - 1][3] = 0; printf(" %-3d ",i); scanf("%d,%d,%d",&grade[i - 1][0],&grade[i - 1][1],&grade[i - 1][2]); for ( j = 0; j <= 2; j++) { grade[i - 1][3] += grade[i - 1][j]; } } printf("/n Enter names of students: /n"); for ( i = 0; i < Stu_Num; i++ ) { /*按学号输入学生姓名*/ printf( " %d: ",i + 1); fflush(stdin); gets(rank + i); } /*冒泡排序*/ for(i = 0; i < Stu_Num; i++) { for ( j = Stu_Num - 1; j > 0; j--) { if ( grade[j][3] > grade[j - 1][3] ) { for (k = 0; k < 4; k++) { grade[j][k] ^= grade[j - 1][k]; grade[j - 1][k] ^= grade[j][k]; grade[j][k] ^= grade[j - 1][k]; } strswap((rank + j),(rank + j - 1)); } } } printf("/n/n/n/n/n RESULT: /n/n/--------------------------------------------///n| No. Name Math Eng C_Pro Sum | /n|============================================|/n"); for (i = 0; i < Stu_Num; i++) printf("| %-3d %-10s %4d %3d %5d %5d | /n|--------------------------------------------|/n", (i + 1),(rank + i),grade[i][0],grade[i][1],grade[i][2],grade[i][3]); printf("/n/n/n/n/n"); }
程序运行结果如下: 接下来又出现一个问题,用户一口气输入这么多的学生数据,却没有一个可以给用户核实的机会 岂不是太不人性化了 接下来就是针对这个问题的解决:
#include "stdio.h" #define MAX_STU_NUM 50 void strswap(char **pStr1, char **pStr2) { //通过指针交换字符串 char *temp = *pStr1; *pStr1 = *pStr2; *pStr2 = temp; } main() { int grade[50][4],i,j,k,Stu_Num,Comp_Bool; char rank[50][100]; char tem_rank[100]; char chk = 'N'; while ( chk == 'n' && chk == 'N' ) { do { printf("/n/n Enter number of students [lower than %d]: > ", MAX_STU_NUM ); scanf("%d",&Stu_Num); if ( Stu_Num > MAX_STU_NUM ) printf("/n/n ERROR, the number must be lower than %d /n",(MAX_STU_NUM + 1)) ; } while ( Stu_Num > MAX_STU_NUM ); printf("/n/n Enter indicated grade [ Math,English,CProgramming ] /n"); printf("/n Student No. :/n"); for ( i = 1; i <= Stu_Num; i++) { /*依次录入学生成绩*/ grade[i - 1][3] = 0; printf(" %-3d ",i); scanf("%d,%d,%d",&grade[i - 1][0],&grade[i - 1][1],&grade[i - 1][2]); for ( j = 0; j <= 2; j++) { grade[i - 1][3] += grade[i - 1][j]; } } printf("/n Enter names of students: /n"); for ( i = 0; i < Stu_Num; i++ ) { /*按学号输入学生姓名*/ printf( " %d: ",i + 1); fflush(stdin); gets(rank + i); } printf("No. Name Math Eng C_Pro Sum /n"); /*给用户机会确认输入是否正确*/ printf("/n/n/n/n/n Please Check: /n/n/--------------------------------------------///n| No. Name Math Eng C_Pro Sum | /n|============================================|/n"); for (i = 0; i < Stu_Num; i++) printf("| %-3d %-10s %4d %3d %5d %5d | /n|--------------------------------------------|/n", (i + 1),(rank + i),grade[i][0],grade[i][1],grade[i][2],grade[i][3]); printf("/n/n Press Y to continue and N to reset...[y/n]: "); //如果不正确,重新输入 fflush(stdin); chk = getchar(); } /*冒泡排序*/ for(i = 0; i < Stu_Num; i++) { for ( j = Stu_Num - 1; j > 0; j--) { if ( grade[j][3] > grade[j - 1][3] ) { for (k = 0; k < 4; k++) { grade[j][k] ^= grade[j - 1][k]; grade[j - 1][k] ^= grade[j][k]; grade[j][k] ^= grade[j - 1][k]; } strswap((rank + j),(rank + j - 1)); } } } printf("/n/n/n/n/n RESULT: /n/n/--------------------------------------------///n| No. Name Math Eng C_Pro Sum | /n|============================================|/n"); for (i = 0; i < Stu_Num; i++) printf("| %-3d %-10s %4d %3d %5d %5d | /n|--------------------------------------------|/n", (i + 1),(rank + i),grade[i][0],grade[i][1],grade[i][2],grade[i][3]); printf("/n/n/n/n/n"); }
这里使用了循环语句来实现这个功能,以用户输入的确认信息 (“Y/N”,注意用户可能输入大写或小写,这个要考虑) 作为触发条件。 运行截图就不给出了,和上面的差不多 /*************************************************************************/ 可依然觉得不完美,为什么呢,你想啊,用户在发现自己输错了之后只能重头开始 如果学生数量非常多,那岂不是让人吐血! 于是这样的想法果断催生了下一个“功能强大的版本” 具体功能有,在用户核实完信息后可以提供添加、删除、修改等功能,而不必重头写起; 另外,再合适的时候使用system函数的cls清屏,使得界面更加整洁; 不过这cls带来的问题是,程序的可移植性将变差,只能在windows上运行,需要注意。 代码如下:
#include "stdio.h" #include "string.h" #include "conio.h" #define MAX_STU_NUM 50 void strswap(char **pStr1, char **pStr2) { //通过指针交换字符串 char *temp = *pStr1; *pStr1 = *pStr2; *pStr2 = temp; } main() { int grade[MAX_STU_NUM][4],i,j,k,Stu_Num,Comp_Bool,NumToEdit; char rank[MAX_STU_NUM][100]; char chk = 'N'; char chks = 'N'; char chkb = 'N'; /*以下部分用来输入、编辑学生数据*/ while ( chk == 'n' || chk == 'N' ) { //此循环用来实现RESET ALL命令 system( "cls "); do { //此循环用来实现学生数量的重置 printf("/n/n Enter number of students [lower than %d]: > ", MAX_STU_NUM ); scanf("%d",&Stu_Num); if ( Stu_Num > MAX_STU_NUM ) printf("/n/n ERROR, the number must be lower than %d /n",(MAX_STU_NUM + 1)) ; } while ( Stu_Num > MAX_STU_NUM ); printf("/n/n Enter indicated grade [ Math,English,CProgramming ] /n"); printf("/n Student No. :/n"); for ( i = 1; i <= Stu_Num; i++) { /*依次录入学生成绩*/ grade[i - 1][3] = 0; printf(" %-3d ",i); scanf("%d,%d,%d",&grade[i - 1][0],&grade[i - 1][1],&grade[i - 1][2]); for ( j = 0; j <= 2; j++) { grade[i - 1][3] += grade[i - 1][j]; } } printf("/n Enter names of students: /n"); for ( i = 0; i < Stu_Num; i++ ) { /*按学号输入学生姓名*/ printf( " %d: ",i + 1); fflush(stdin); gets(rank + i); } /*给用户机会确认输入是否正确*/ while ( chk == 'E' || chk == 'e' ) { //编辑学生信息 printf("/n Enter M if you want to MODIFY a single student, A to ADD, D to DELETE: "); fflush(stdin); chk = getchar(); switch ( chk ) { case 'M': //修改特定学生信息 case 'm': printf("/n Enter the number you want to edit: "); scanf("%d",&NumToEdit); printf("/n/n Enter indicated grade [ Math,English,CProgramming ]: "); /*录入学生成绩*/ grade[NumToEdit - 1][3] = 0; scanf("%d,%d,%d",&grade[NumToEdit - 1][0],&grade[NumToEdit - 1][1],&grade[NumToEdit - 1][2]); for ( j = 0; j <= 2; j++) grade[NumToEdit - 1][3] += grade[NumToEdit - 1][j]; printf("/n Enter the name of the student: "); fflush(stdin); gets(rank + NumToEdit - 1); break; case 'A': //插入或添加新学生 case 'a': if ( Stu_Num >= MAX_STU_NUM ) { //检测学生数量,添加后不应超过最大值 printf("Unable to Add, Students in all must be less than %d./n", MAX_STU_NUM ); break; } printf("/n Enter the student number to INSERT and /"0/" to ADD to the end: "); scanf("%d",&NumToEdit); if (NumToEdit == 0) { //在尾部添加新生 printf("/n/n Enter indicated grade [ Math,English,CProgramming ]: "); grade[Stu_Num][3] = 0; scanf("%d,%d,%d",&grade[Stu_Num][0],&grade[Stu_Num][1],&grade[Stu_Num][2]); for ( j = 0; j <= 2; j++) grade[Stu_Num][3] += grade[Stu_Num][j]; printf("/n Enter the name of the student: "); fflush(stdin); gets(rank + Stu_Num); Stu_Num++; } else { //插入新生 Stu_Num++; for ( i = Stu_Num - 1; i >= NumToEdit; i-- ) { for ( j = 0; j <= 2; j++ ) grade[i][j] = grade[i - 1][j]; strcpy(rank + i,rank + i - 1); } printf("/n/n Enter indicated grade [ Math,English,CProgramming ]: "); grade[NumToEdit - 1][3] = 0; scanf("%d,%d,%d",&grade[NumToEdit - 1][0],&grade[NumToEdit - 1][1],&grade[NumToEdit - 1][2]); for ( j = 0; j <= 2; j++) { grade[NumToEdit - 1][3] += grade[NumToEdit - 1][j]; } printf("/n Enter the name of the student: "); fflush(stdin); gets(rank + NumToEdit - 1); } break; case 'D': continue; //删除学生 case 'd': printf("/n Enter the student number to delete: "); scanf("%d",&NumToEdit); printf("/n Are you sure to delete student No.%d, Name %s ? [y/n]", NumToEdit, rank + NumToEdit - 1); //防止用户误删 fflush(stdin); chks = getchar(); if ( chks == 'Y' || chks == 'y' ) { for ( i = NumToEdit - 1; i < Stu_Num; i++ ) { for ( j = 0; j <= 2; j++ ) grade[i][j] = grade[i + 1][j]; strcpy(rank + i,rank + i + 1); } Stu_Num--; } break; default: break; } } system( "cls "); printf("Please Check: /n/n/--------------------------------------------///n| No. Name Math Eng C_Pro Sum | /n|============================================|/n"); for (i = 0; i < Stu_Num; i++) printf("| %-3d %-10s %4d %3d %5d %5d | /n|--------------------------------------------|/n", (i + 1),(rank + i),grade[i][0],grade[i][1],grade[i][2],grade[i][3]); printf("/n/n Press Y to continue and N to RESET ALL,press E to edit...[y/n/e]: "); fflush(stdin); chkb = getchar(); do { printf("Are you sure to RESET ALL student data ? [y/n]"); //防止用户误重置 fflush(stdin); chks = getchar(); if ( chks == 'Y' || chks == 'y' ) { printf("RESET will delete all student data, Continue? [y/n]"); //进一步防止用户误重置 fflush(stdin); chks = getchar(); } if ( chks == 'N' || chks == 'n' ) { continue; } else if ( chks == 'Y' || chks == 'y' ) { chk = 'N'; break; } } while ( chkb == 'N' || chkb == 'n' ); } /*学生数据编辑、输入完毕*/ /*冒泡排序*/ for(i = 0; i < Stu_Num; i++) { for ( j = Stu_Num - 1; j > 0; j--) { if ( grade[j][3] > grade[j - 1][3] ) { for (k = 0; k < 4; k++) { grade[j][k] ^= grade[j - 1][k]; grade[j - 1][k] ^= grade[j][k]; grade[j][k] ^= grade[j - 1][k]; } strswap((rank + j),(rank + j - 1)); } } } /* 输出结果 */ system( "cls "); printf("RESULT: /n/n/--------------------------------------------///n| No. Name Math Eng C_Pro Sum | /n|============================================|/n"); for (i = 0; i < Stu_Num; i++) printf("| %-3d %-10s %4d %3d %5d %5d | /n|--------------------------------------------|/n", (i + 1),(rank + i),grade[i][0],grade[i][1],grade[i][2],grade[i][3]); printf("/n/n/n/n/n"); }
注意这里的一些小细节,比如
  • 用户可能在输入命令的时候并不区分大小写
  • 添加新学生的时候不应忘记检测学生数量是否超过上限
  • 用户删除特定学生数据以及重置全部学生数据的时候应该给用户确认的机会以避免误操作
  • 添加/删除操作的时候不应忘记对学生重新编码
依然没有运行结果截图,因为多次清屏之后运行结果已经无法显示整个程序的运行流程。 /*************************************************************************/ 写到这里,程序里面有一样东西实在是让我受不了了——内核! 我被那个小小的破烂提示限制住了思维,居然还在用两个数组间的同步 这样写出来的程序,开发和维护都非常麻烦而且混乱,说不定啥时候就会出BUG 我们可不可以把学生看成一个个单独的个体,而不是身首异处地储存在两个数组里面? 恭喜你,如果你也想到了这一点,你已经迈出了面向对象程序设计思想的第一步~! 因为这个是C语言的作业,我不能在里面使用class 但这种最基本的想法,利用结构体(struct)就可以实现 简单说,就是,每个学生就是一个struct 这个struct由一个代表姓名的字符串变量和四个表示成绩的整型变量组成 (这些变量,我们可以称之为属性) 利用struct数组存储学生数据 好,开工!先写内核试试,还是只完成题目的基本功能(第一次改版后):
#include "stdio.h" struct student { int grade[4]; char name[10]; }stu[50],stutemp; void input_data (struct student *studt ) { int j; printf(" -Enter indicated grade [ Math,English,CProgramming ]: "); studt -> grade[3] = 0; scanf("%d,%d,%d",&(studt -> grade[0]),&(studt -> grade[1]),&(studt -> grade[2])); for ( j = 0; j <= 2; j++) studt -> grade[3] += studt -> grade[j]; printf(" -Enter names of students: "); fflush(stdin); gets(&(studt -> name)); } void MKTable ( int i ) { //制表 printf("| %-3d%-10s %4d %3d %5d %5d | /n|--------------------------------------------|/n", (i + 1),stu[i].name,stu[i].grade[0],stu[i].grade[1],stu[i].grade[2],stu[i].grade[3]); } main() { int i,j,k,Stu_Num; /*以下部分用来输入、编辑学生数据*/ do { //此循环用来实现学生数量的重置 printf("Enter number of students [lower than 50]: "); scanf("%d",&Stu_Num); if ( Stu_Num > MAX_STU_NUM ) printf("/n/n ERROR, the number must be lower than 51 /n") ; } while ( Stu_Num > MAX_STU_NUM ); printf("/n Student No. :/n"); for ( i = 1; i <= Stu_Num; i++) { /*依次录入学生数据*/ printf(" %d:",i); input_data (&stu[ i - 1 ]); } for(i = 0; i < Stu_Num; i++) { for ( j = Stu_Num - 1; j > 0; j--) { if ( stu[j].grade[3] > stu[j - 1].grade[3] ) { stutemp = stu[j];stu[j] = stu[j - 1];stu[j - 1] = stutemp; } } } /*输出结果*/ system( "cls "); printf("RESULT: /n/n/--------------------------------------------///n| No. Name Math Eng C_Pro Sum | /n|============================================|/n"); for (i = 0; i < Stu_Num; i++) MKTable ( i ); printf("/n/n/n/n"); }
写完之后,不由镜湖,哇擦列,多简洁! 于是马上着手写struct的“完整”版本,如下:
#include "stdio.h" #include "string.h" #include "conio.h" #define MAX_STU_NUM 50 struct student { int grade[4]; char name[10]; }stu[MAX_STU_NUM],stutemp; void input_data (struct student *studt ) { int j; printf(" -Enter indicated grade [ Math,English,CProgramming ]: "); studt -> grade[3] = 0; scanf("%d,%d,%d",&(studt -> grade[0]),&(studt -> grade[1]),&(studt -> grade[2])); for ( j = 0; j <= 2; j++) studt -> grade[3] += studt -> grade[j]; printf(" -Enter names of students: "); fflush(stdin); gets(&(studt -> name)); } void MKTable ( int i ) { //制表 printf("| %-3d%-10s %4d %3d %5d %5d | /n|--------------------------------------------|/n", (i + 1),stu[i].name,stu[i].grade[0],stu[i].grade[1],stu[i].grade[2],stu[i].grade[3]); } main() { int i,j,k,Stu_Num,Comp_Bool,NumToEdit; char chk = 'N'; char chks = 'N'; char chkb = 'N'; /*以下部分用来输入、编辑学生数据*/ while ( chk == 'n' || chk == 'N' ) { //此循环用来实现RESET ALL命令 system( "cls "); do { //此循环用来实现学生数量的重置 printf("Enter number of students [lower than %d]: " ,MAX_STU_NUM ); scanf("%d",&Stu_Num); if ( Stu_Num > MAX_STU_NUM ) printf("/n/n ERROR, the number must be lower than %d /n" ,( MAX_STU_NUM + 1 )) ; } while ( Stu_Num > MAX_STU_NUM ); printf("/n Student No. :/n"); for ( i = 1; i <= Stu_Num; i++) { /*依次录入学生数据*/ printf(" %d:",i); input_data (&stu[ i - 1 ]); } } /*给用户机会确认输入是否正确*/ while ( chk == 'E' || chk == 'e' ) { //编辑学生信息 printf("/n Enter M if you want to MODIFY a single student, A to ADD, D to DELETE: "); fflush(stdin); chk = getchar(); switch ( chk ) { case 'M': //修改特定学生信息 case 'm': printf("/n Enter the number you want to edit: "); scanf("%d",&NumToEdit); input_data (&stu[ NumToEdit - 1 ]); break; case 'A': //插入或添加新学生 case 'a': if ( Stu_Num >= MAX_STU_NUM ) { //检测学生数量,添加后不应超过最大值 printf("Unable to Add, Students in all must be less than 51./n"); break; } printf("/n Enter the student number to INSERT and /"0/" to ADD to the end: "); scanf("%d",&NumToEdit); Stu_Num++; if (NumToEdit == 0) { //在尾部添加新生 input_data (&stu[ Stu_Num - 1 ]); } else { //插入新生 for ( i = Stu_Num - 1; i >= NumToEdit; i++ ) { stu[i] = stu[i - 1]; } input_data (&stu[ NumToEdit - 1 ]); } break; case 'D': continue; //删除学生 case 'd': printf("/n Enter the student number to delete: "); scanf("%d",&NumToEdit); printf("/n Are you sure to delete student No.%d, Name %s ? [y/n]", NumToEdit, rank + NumToEdit - 1); //防止用户误删 fflush(stdin); chks = getchar(); if ( chks == 'Y' || chks == 'y' ) { Stu_Num--; for ( i = NumToEdit; i < Stu_Num; i++ ) { stu[i] = stu[ i + 1 ] } } break; default: break; } } system( "cls "); printf("Please Check: /n/n/--------------------------------------------///n| No. Name Math Eng C_Pro Sum | /n|============================================|/n"); for (i = 0; i < Stu_Num; i++) MKTable ( i ); printf("/n/n Press Y to continue and N to RESET ALL,press E to edit...[y/n/e]: "); fflush(stdin); chkb = getchar(); do { printf("Are you sure to RESET ALL student data ? [y/n]"); //防止用户误重置 fflush(stdin); chks = getchar(); if ( chks == 'Y' || chks == 'y' ) { printf("RESET will delete all student data, Continue? [y/n]"); //进一步防止用户误重置 fflush(stdin); chks = getchar(); } if ( chks == 'N' || chks == 'n' ) { continue; } else if ( chks == 'Y' || chks == 'y' ) { chk = 'N'; break; } } while ( chkb == 'N' || chkb == 'n' ); } /*学生数据编辑、输入完毕,排序*/ for(i = 0; i < Stu_Num; i++) { for ( j = Stu_Num - 1; j > 0; j--) { if ( stu[j].grade[3] > stu[j - 1].grade[3] ) { stutemp = stu[j];stu[j] = stu[j - 1];stu[j - 1] = stutemp; } } } /*输出结果*/ system( "cls "); printf("RESULT: /n/n/--------------------------------------------///n| No. Name Math Eng C_Pro Sum | /n|============================================|/n"); for (i = 0; i < Stu_Num; i++) MKTable ( i ); printf("/n/n/n/n"); }
写完了才发现,原来可以省这么多行代码 另外这里定义了两个函数:inputdata()用来让用户输入数据,MKtable()用来输出数据。 这两个函数的调用也大幅度缩短了代码的长度。 把程序按功能分成一小块一小块然后逐个击破(自顶向下程序设计) 这是在程序开发、维护中十分重要的,可以是代码整洁,去除重复劳动 /***********************************************************/ 但是仍然觉得这个程序的前台不够完善,不够友好。 简单说,程序完整了,但并不完善。 我们先简单分析一下现在的程序:
  • 拥有了最基本的输入输出与排序功能,支持表格输出
  • 必要时监测数据以确保安全性
  • 使用结构体、预声明常量、自定义函数等是代码简洁而便于维护,有一定的可扩展性
  • 允许用户核实并且编辑数据
  • 在用户执行删除、重置等命令时会提示用户确认,以避免误操作
  • 用户输入命令是可以不用区分大小写
现在的程序有什么不足之处呢?
  1. 用户的操作是即时性的,在关闭程序后用户数据不会被保存
  2. 没有批量添加/删除功能
  3. 在操作大量数据时用户需要反复输入指令,非常繁琐
  4. 系统自动排序,没有给用户选择机会
问题已经提出了,那下一步就是怎么解决 第一个问题很容易,用文件读写功能,将struct的二进制数据直接读取或写入 第二个问题也很容易,在添加、删除功能下给用户一个编号范围即可 第四个问题,只要加入一个排序指令,然后写相应代码即可 主要就是第三个问题了,如何解决呢 我们可以这么想,第三个问题的产生是因为用户必须输入指令 或者说,我们没有一个有好的GUI 但问题在于,必须要有GUI,才能让用户不必反复执行相同的操作么? 答案是否定的。 首先我们分析一下GUI为什么高效。 GUI与非GUI最大的区别在于, GUI操作基于鼠标位置 而控制台操作基于光标所在行 位置可分上下左右,但行是自上而下的 简单说就是,GUI是二维操作,而控制台是一维操作 GUI点哪算哪,而控制台必须一步一步来 所以,我们可以说,空间的局限性,带来了两种操作方式最直接的效率差别 那么可不可以有一种方法来弥补这种空间局限性呢? 答案是肯定的 我们把控制台的操作方式视为一条线,而每一行指令视为一个节点 有节点,就可以有分支 有了分支,程序的操作便不再是一维的了 于是问题变成了,如何恰当的加入分支来提高用户的操作效率 我的办法是在程序中加入“编辑模式” 编辑模式便是程序的一条分支 用户可以进入编辑模式 在编辑模式中,用户可以反复输入各种编辑指令直至退出编辑模式 以程序功能为单位, 我们把(含批量)添加/删除、修改、排序等单独拿出来,集成进“编辑模式” (当然,有人想说大一点,“编辑平台 Editing Platform”我也没意见嘿嘿~~~) 这样,一个较为完善的程序就出现了:
#include "stdio.h" #include "io.h" #include "conio.h" #define MAX_STU_NUM 50 int Stu_Num,AStu_Num; char sav = 'Y'; int sd = 2; // 0为未保存新文件,1为未保存文件,2为已保存 char fp[100]; int fk; const char TableTop[] = "┌───────────────────────────┐/n"; const char TableTtl[] = "│ No. SNumber Name Math Eng C_Pro Sum │/n"; const char TableLne[] = "├───────────────────────────┤/n"; const char TableBtm[] = "└───────────────────────────┘/n"; struct student { long SNum; int grade[4]; char name[10]; }stu[MAX_STU_NUM],stutemp; void input_data (struct student *studt ) { int j; printf("/n -Enter the number of the student: "); scanf("%d",&(studt -> SNum)); printf("/n -Enter the name of the student: /n -"); fflush(stdin); gets(&(studt -> name)); printf("/n -Enter indicated grade [ Math,English,CProgramming ]: /n -"); studt -> grade[3] = 0; scanf("%d,%d,%d",&(studt -> grade[0]),&(studt -> grade[1]),&(studt -> grade[2])); for ( j = 0; j <= 2; j++) studt -> grade[3] += studt -> grade[j]; } void MKTable ( int i ) { //制表 printf("%s│ %-3d %-8d %-10s %4d %3d %5d %5d │/n", TableLne,(i + 1),stu[i].SNum,stu[i].name,stu[i].grade[0],stu[i].grade[1],stu[i].grade[2],stu[i].grade[3]); } int Fsav ( char filepath[] ) { int i,j; FILE *ToSav; ToSav = fopen( filepath, "wb" ); fwrite (&Stu_Num,sizeof(int),1,ToSav); for ( i = 0; i < Stu_Num; i++ ) { fwrite(&stu[i],sizeof(struct student),1,ToSav); } if (ToSav == 0) return 0; fclose(ToSav); return 1; } void Fop ( char filepath[] ) { int i,j; FILE *ToOp; ToOp = fopen( filepath, "rb" ); fread (&Stu_Num,sizeof(int),1,ToOp); for ( i = 0; i < Stu_Num; i++ ) { fread(&stu[i],sizeof(struct student),1,ToOp); } fclose(ToOp); } void Usav () { int fs; char cmdsav = 'y'; if ( sd == 0 ) { printf("Enter file path and file name: /n "); fflush(stdin); gets(fp); if ( access(fp,0) == 0 ) { printf("File exist, continue? [y/n] :"); fflush(stdin); cmdsav = getchar(); if ( cmdsav == 'n' ) Usav(); } } fs = Fsav (fp); if ( fs == 0 ) { printf("ERROR to save file,try again? [y/n]:"); fflush(stdin); cmdsav = getchar(); if ( cmdsav == 'y' || cmdsav == 'Y' ) Usav (); } else { sd = 2; } } main() { int c,i,k,j,NumToEdit,DFrom,DTo,TStu_Num,sortway,ssortway,sortnum; char cmd = 'h'; char chk = 'M'; char chks = 'A'; while (1) { c = 0; printf("/nPlease enter a command,Enter /'H/' for help : "); fflush(stdin); cmd = getchar(); if ( cmd == 'h' || cmd == 'H') { printf(" Help:/n H - Help/n N - New File/n O - Open/n S - Save/n A - Save As/n E - Enter Edit Mode/n C - Close File/n Q - Quit/n"); } else if ( cmd == 'n' || cmd == 'N') { if ( sd == 0 || sd == 1 ) { printf(" FILE not saved,Save now? [y/n]: "); fflush(stdin); sav = getchar(); if ( sav == 'y' || sav == 'y' ) Usav(); } sd = 0; system( "cls "); do { printf("/n/n Enter number of students [lower than %d]: > ", MAX_STU_NUM ); scanf("%d",&Stu_Num); if ( Stu_Num > MAX_STU_NUM ) printf("/n/n ERROR, the number must be lower than %d /n",(MAX_STU_NUM + 1)) ; if (Stu_Num <= 0) printf("/n/n ERROR, the number must be higher than 0 /n"); } while ( Stu_Num > MAX_STU_NUM || Stu_Num <= 0 ); printf("/n Student No. :/n"); for ( i = 1; i <= Stu_Num; i++) { printf(" %d:",i); input_data (&stu[ i - 1 ]); } system( "cls "); printf("Please Check: /n/n%s%s",TableTop,TableTtl); for (i = 0; i < Stu_Num; i++) MKTable ( i ); printf("%s",TableBtm); } else if ( cmd == 'E' || cmd == 'e' ) { sd = ( sd == 0 )? 0:1; do { system( "cls "); printf("Please Check: /n/n%s%s",TableTop,TableTtl); for (i = 0; i < Stu_Num; i++) MKTable ( i ); printf("%s",TableBtm); printf("/n Edit Mode:/n M - MODIFY a single student/n A - ADD/n S - sort/n D - DELETE/n B - Back To Main Menu/n Please enter a command: "); fflush(stdin); chk = getchar(); switch ( chk ) { case 'M': case 'm': printf("/n Please enter the number you want to edit, /'0/' to go back to Edit Mode: "); scanf("%d",&NumToEdit); if ( NumToEdit > 0 ) input_data (&stu[ NumToEdit - 1 ]); break; case 'A': case 'a': if ( Stu_Num >= MAX_STU_NUM ) { printf("Unable to Add, Students in all must not be more than %d/n" , MAX_STU_NUM ); break; } printf("/n Please enter the student number to INSERT/n /'0/' to ADD to the end/n /'-1/' to go back to Edit Mode: "); scanf("%d",&NumToEdit); if ( NumToEdit > -1 ) { if (NumToEdit == 0) { TStu_Num = Stu_Num; do { printf("/nPlease enter number to add : "); scanf("%d",&AStu_Num); if ( ( Stu_Num + AStu_Num ) > MAX_STU_NUM ) printf("/n/n ERROR, the student in all must not be more than %d /n" , MAX_STU_NUM ) ; } while ( Stu_Num > MAX_STU_NUM || Stu_Num <= 0 ); printf("/n Student No. :/n"); for ( i = TStu_Num + 1; i <= TStu_Num + AStu_Num; i++) { printf(" %d:",i); input_data (&stu[ i - 1 ]); Stu_Num++; } } else { for ( i = Stu_Num - 1; i >= NumToEdit; i-- ) { stu[i] = stu[i - 1]; } input_data (&stu[ NumToEdit - 1 ]); Stu_Num++; } } break; case 'D': case 'd': do { printf("/n Please enter the student number to delete/n /'0/' for multi-deleting/n /'-1/' to go back to Edit Mode: "); scanf("%d",&NumToEdit); if ( NumToEdit < -1 ) printf("/n/n ERROR, the number must not be lower than 0 /n"); if ( NumToEdit > Stu_Num ) printf("/n/n ERROR, Student %d does not exist /n", NumToEdit); } while ( NumToEdit < -1 || NumToEdit > Stu_Num ); if ( NumToEdit != 0 && NumToEdit != -1 ) { printf("/n Are you sure to delete student No.%d, Name %s ? [y/n]", NumToEdit, stu[NumToEdit - 1].name); //防止用户误删 fflush(stdin); chks = getchar(); if ( chks == 'Y' || chks == 'y' ) { for ( i = NumToEdit - 1; i < Stu_Num - 1; i++ ) { stu[i] = stu[ i + 1 ]; } Stu_Num--; } } else if ( NumToEdit == 0 ) { do { printf("/n Please enter the number from A to B [A,B] :"); scanf("%d,%d",&DFrom,&DTo); if ( DFrom <= 0 ) printf("ERROR, A must be higher than 0"); if ( DTo > Stu_Num ) printf("ERROR, student B does not exist"); if ( DTo <= DFrom ) ("ERROR, B must be higher than A"); } while ( DFrom <= 0 || DTo > Stu_Num || DTo <= DFrom ); printf("/n Are you sure to delete student/n from No.%d, Name %s/n to No.%d, Name %s ? [y/n]: ", DFrom, stu[DFrom - 1].name, DTo, stu[DTo - 1].name); //防止用户误删 fflush(stdin); chks = getchar(); if ( chks == 'Y' || chks == 'y' ) { for (i = ( DFrom - 1 ); i < DTo; i++ ) { if ( ( DTo + i ) <= Stu_Num ) stu[i] = stu[DTo + i - 1]; Stu_Num--; } } } break; case 'S': case 's': printf (" Select a way/n 0 - Sort by Student Number/n 1 - Sort by Head Letter/n 2 - Sort by Total Grade/n 3 - Sort by Single Grade/n -1 - Reverse/n Please enter a number: "); scanf("%d",&sortway); switch ( sortway ) { case -1: if ( ( ( sortnum = Stu_Num ) % 2 ) == 1 ) sortnum--; sortnum /= 2; for(i = 0; i < sortnum; i++) { for ( j = Stu_Num - 1; j >= sortnum + ( sortnum % 2 ) ; j--) { stutemp = stu[j];stu[j] = stu[i];stu[i] = stutemp; } } break; case 0: for(i = 0; i < Stu_Num; i++) { for ( j = Stu_Num - 1; j > 0; j--) { if ( stu[j].SNum < stu[j - 1].SNum ) { stutemp = stu[j];stu[j] = stu[j - 1];stu[j - 1] = stutemp; } } } break; case 1: for(i = 0; i < Stu_Num; i++) { for ( j = Stu_Num - 1; j > 0; j--) { if ( stu[j].name[0] < stu[j - 1].name[0] ) { stutemp = stu[j];stu[j] = stu[j - 1];stu[j - 1] = stutemp; } } } break; case 2: for(i = 0; i < Stu_Num; i++) { for ( j = Stu_Num - 1; j > 0; j--) { if ( stu[j].grade[3] > stu[j - 1].grade[3] ) { stutemp = stu[j];stu[j] = stu[j - 1];stu[j - 1] = stutemp; } } } break; case 3: printf (" Select a subject/n 0 - Math/n 1 - English/n 2 - C Programming/n Please Unter a number: "); scanf("%d",ssortway); for(i = 0; i < Stu_Num; i++) { for ( j = Stu_Num - 1; j > 0; j--) { if ( stu[j].grade[ssortway] > stu[j - 1].grade[ssortway] ) { stutemp = stu[j];stu[j] = stu[j - 1];stu[j - 1] = stutemp; } } } default: break; } default: break; } } while ( chk != 'B' && chk !='b' ); } else if ( cmd == 'O' || cmd == 'o' ) { if ( sd == 0 || sd == 1 ) { printf(" FILE not saved,Save now? [y/n]: "); fflush(stdin); sav = getchar(); if ( sav == 'y' || sav == 'y' ) Usav(); } printf("Enter file path and file name: /n "); fflush(stdin); gets(fp); Fop(fp); sd = 2; system( "cls "); printf("Please Check: /n/n%s%s",TableTop,TableTtl); for (i = 0; i < Stu_Num; i++) MKTable ( i ); printf("%s",TableBtm); } else if ( cmd == 'S' || cmd == 's' ) { Usav(); } else if ( cmd == 'A' || cmd == 'a' ) { sd = 0; Usav(); } else if ( cmd == 'C' || cmd == 'c' ) { if ( sd == 0 || sd == 1 ) { printf(" FILE not saved,Save now? [y/n]: "); fflush(stdin); sav = getchar(); if ( sav == 'y' || sav == 'y' ) Usav(); } for (i = 1; i < MAX_STU_NUM; i++) stu[i] = stu[1]; c = 1; } else if ( cmd == 'Q' || cmd == 'q' ) { if ( sd == 0 || sd == 1 ) { printf(" FILE not saved,Save now? [y/n]: "); fflush(stdin); sav = getchar(); if ( sav == 'y' || sav == 'y' ) Usav(); } return 0; } } }
注意以下几个细节:
  • 制表符保存成了常量,使代码更整洁且便于维护
  • 提供帮助功能,可以让用户进行指令查询
  • 保存文件时检测文件是否存在
  • 危险性动作根据危险级别有一次、二次确认
  • 编辑模式下,每一次编辑都会刷新显示表格,以实时观察
写到这里,我很想说的是, 并不是只有GUI可以友好,真的 当然这个程序写到这里很没有必要 但我相信写好了这个,开发一个严谨而又好的有用程序不是难事 读者还可以从这个思路继续完善下去 使完善变得趋于完美 比如,编辑模式下加入撤销/重做功能 实时保存临时文件以防程序崩溃而使数据丢失 排序的算法可以更改为更加高效的快排、堆排 等等,我们甚至还可以编写出更加友好的GUI界面,这些都是可行的。 我写这篇文章的关键,不是在于技术实现 说白了,这个程序毫无技术含量可言 我只是想表达, 作为一个开发者,我们应当如何 同时站在开发者、维护者和用户的角度上 去发现问题,思考问题,解决问题 程序开发是一种创造,创造需要灵感 我们该怎样获得这些灵感 令我们的程序更加完美 程序需要被使用 需要被维护 开发、维护、使用都是需求 我们应当怎样从需求出发,来看待我们的程序 我一直相信 对于开发者而言,每一行代码都是一部作品 没有完美的作品 但是作品若不是趋于完美,就永远不能称为作品 我希望每个开发者都能把自己的程序看成是 作品
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值