C++大作业:简易评分系统

高级语言程序设计说明书

简易评分系统

  计算机工程系

专业班级 10计算机科学与技术1班

学生姓名 叶师哲

指导教师 廖 玲

注释:2010计算机工程系《高级语言程序设计》大作业

目录

概述…………………………………………………………(3)

程序设计概要………………………………………………(4)

数据读写相关函数与数据结构头文件…………………………(4)

管理员界面相关函数与流程……………………………………(5)

评委界面相关函数与流程………………………………………(5)

参赛者界面相关函数与流程……………………………………(6)

关键代码分析………………………………………………(6)

管理员初始化设置………………………………………………(7)

用户名与密码核对………………………………………………(8)

评分过程中的数据读写…………………………………………(9)

评分过程中特定状态的判定……………………………………(13)

对参赛者进行排序………………………………………………(14)

程序使用说明………………………………………………(17)

管理员界面………………………………………………………(17)

评分界面…………………………………………………………(17)

参赛者界面………………………………………………………(18)

总结…………………………………………………………(19)

参考书籍……………………………………………………(19)

  • 概述

程序名称:简易评分系统

开发平台:Windows 7

VC++ 6.0

主要功能:

  1. 管理员界面
    1. 于程序中设置评委信息
    2. 从文件导入评委信息
    3. 于程序中设置参赛者信息
    4. 从文件导入参赛者信息
    5. 导出赛果到文件

  1. 评委界面
    1. 显示参赛者列表与评分
    2. 根据名次显示赛果
    3. 根据名称显示赛果

  1. 参赛者界面:显示自己的最终成绩

程序说明:

该程序根据使用者身份分为三个可执行文件独立运行

该程序于使用期间可根据需要打开或退出而不会丢失中间数据,除非使用者自行更改程序运行时生成的文件。

管理员密码固定为administrator

程序所有相关文件需置于同一目录下

运行环境:win7、winXp

文件说明:

  1. 源代码
    1. 管理员相关函数:adm_function.h
    2. 评委相关函数:jdg_function.h
    3. 参赛者相关函数:cmpt_function.h
    4. 数据读写相关函数与数据结构:data.h
    5. 管理员界面源文件:adm.cpp
    6. 评委界面源文件:jdg.cpp
    7. 参赛者界面源文件:cmpt.cpp

  1. 程序执行文件
    1. 管理员界面:adm.exe
    2. 评委界面:jdg.exe
    3. 参赛者界面:cmpt.exe

  1. 主要数据存储文件
    1. 评委信息:jdg.dat
    2. 参赛者信息:cmpt.dat
    3. 赛果:result.dat

  • 程序设计概要
    • 数据读写相关函数与数据结构头文件

包含头文件

#include<fstream.h> // 文件读写

#include<stdio.h> // C语言标准输入输出

#include<string.h> // 字符串比较

数据结构

struct judge//评委

{

int j_id;//评委编号

char name[20],password[20];

};

struct competitor//参赛者

{

int c_id;//参赛者编号

char name[20],password[20];

float score;

int ranking;//排名

};

函数

  1. void scoring_mtrx()
    该函数生成一个二维数组[jdg_num][cmpt_num]用于保存评分过程并以二进制文件形式保存于外存
  2. void cmpt_counter()void cmpt_counter(int cmpt_id)

该函数生成数组[cmpt_num],标识某参赛者已被多少位评委评分并输出到文件cmpt_counter.dat

文件开头有评委与参赛者的人数供判断评分是否已全部结束

函数有重载,一个参数为无参,用于管理员完成人数设置后初始化cmpt_counter.dat用,另一个参数为(cmpt_id)用于在评分时更新cmpt_counter.dat数据用

3. void jdg_counter() void jdg_counter(int jdg_id)

该函数生成数组[jdg_num],标识某位评委已对多少位参赛者评分,数据输出保存到jdg_num.dat

该函数同样有一重载用于评分时更新jdg_counter.dat的数据

4. void sorting()

该函数用于对参赛者成绩进行排序

5. int if_end()

该函数用于判断所有评委的评分工作是否已经完成

  • 管理员界面相关函数与流程

包含头文件

#include"data.h"

函数

  1. void adm_log()
    管理员登陆界面
  2. void adm_in()
    管理员登入后的界面
  3. void set_jdg()
    设置评委数据
  4. void jdg_fromfile(int n,judge *p)
    从文件导入评委数据(被set_jdg()调用)
  5. void jdg_inprgrm(int n,judge *p)
    在程序中设置评委数据(被set_jdg()调用)
  6. void set_cmpt()
    设置参赛者数据
  7. void cmpt_fromfile(int n,competitor *p)
    从文件导入参赛者数据(被set_cmpt()调用)
  8. void cmpt_inprgrm(int n,competitor *p)
    在程序中设置参赛者数据(被set_cmpt()调用)
  9. void to_file()

导出赛果到文件

大致流程

  1. 程序运行后会检查是否有jdg.dat与cmpt.dat
  2. 若无以上两文件则提示设置评委与参赛者
  3. 若已存在以上两文件则判断评分工作是否已经完成
  4. 若否则提示管理员在评分工作完成后登陆
  5. 若是则提示导出赛果到文件
  • 评委界面相关函数与流程

包含头文件

#include"data.h"

#include<conio.h> // fflush(stdin)清除输入缓冲区残余数据

函数

  1. void show_by_name(int jdg_id)
    根据名字显示赛果
  2. void cmpr(int c,int i,char **p_p)
    用于 show_by_name 函数排列参赛者名字顺序时调用
  3. void show_by_score(int jdg_id)
    根据名次显示赛果
  4. void show_cmpt(int jdg_id)
    显示参赛者列表以及该评委对各个参赛者的评分
  5. void jdg_log()
    评委登陆界面
  6. void jdg_in(int jdg_id)

评委登入后的界面

大致流程

  1. 程序运行后检查管理员是否已经完成评委设置
  2. 若否则退出程序
  3. 若管理员已完成设置则输出提示:
    1.显示参赛者列表与评分
    2.根据名次显示赛果
    3.根据名称显示赛果
  4. 若选择1则检查该评委是否已完成评分工作
  5. 若已完成则输出提示并显示该评委对所有选手的评分
  6. 若否则提示输入选手编号进行评分
  7. 若选择2与3则检查所有评委是否都已完成评分工作
  8. 若否则输出提示信息
  9. 若已完成则根据评委要求对选手进行排序并输出
  • 参赛者界面相关函数与流程

包含头文件

#include"data.h"

函数

  1. void cmpt_login()
    参赛者登陆
  2. void show_score(int cmpt_id)

显示登陆的参赛者的最终成绩与排名

大致流程

  1. 程序运行后检查管理员是否已经完成初始化设置
  2. 若未完成则输出提示信息
  3. 若已完成则检查所有评委的评分工作是否已经完成
  4. 若是则输出该参赛者的最终成绩
  5. 若否则输出提示信息
  • 关键代码分析
    • 管理员初始化设置
      程序提供两种方式进行评委与参赛者的初始化设置,若选择于程序中输入初始化数据,则完成输入后程序后生成jdg.dat与cmpt.dat。
      jdg.dat的格式为:
      评委人数
      编号 名称 密码
      编号 名称 密码
      编号 名称 密码


      cmpt.dat的格式为:
      参赛者人数
      编号 名称 密码 分数 排名
      编号 名称 密码 分数 排名


      若选择从文件导入,则需事先准备set_jdg.dat与set_cmpt.dat两个文件
      格式都为:
      人数
      名称 密码
      名称 密码
      名称 密码


      无论采用哪种方式,程序最后都会将管理员设置的用户信息输入(从键盘输入或从文件输入)到一个结构体数组并利用此结构体生成jdg.dat与cmpt.dat。
      生成jdg.dat与cmpt.dat的主要代码如下:
      fstream outfile("jdg.dat",ios::out);或 fstream outfile("cmpt.dat",ios::out);
      outfile<<num<<endl; //此处输出评委或参赛者的人数
      for(i=0;i<num;i++){ //将结构体数组的内容输出到文件
      outfile<<p_jdg->j_id<<" " 或outfile<<p_cmpt->c_id<<" "
      <<p_jdg->name<<" " <<p_cmpt->name<<" "
      <<p_jdg->password<<endl; <<p_cmpt->password<<" "
      if(i<num-1) <<0<<" "<<0<<endl;
      p_jdg++; 或p_cmpt++;
      }
      outfile.close();
    • 用户名与密码核对
      该模块根据使用界面不同由void jdg_log()、void adm_log()、void cmpt_login()分别实现核对功能。管理员界面直接核对密码(administrator),评委与参赛者界面则通过载入包含所有用户的用户名与密码的jdg.dat或cmpt.dat文件,对使用者输入的信息进行核对,该模块每次提供给使用者的输入机会为3次,错误达到3次则自动退出程序。
      管理员模块的密码核对代码
      void adm_log()
      {
      char adm_pswrd[14]="administrator";//程序中管理员密码固定为administrator
      static int pswrd_c=3;//剩余输入密码次数
      cout<<endl;
      cout<<" 输入密码:";
      char p[14];
      cin>>p;
      cout<<endl;
      if(strcmp(p,adm_pswrd)==0)//密码验证
      adm_in();//若密码正确则调用adm_in()函数进入管理员界面
      else{
      pswrd_c--;//若密码错误则减少一次密码输入的机会
      if(pswrd_c==0)
      ;//若输入错误达3次则通过执行空语句退出管理员界面
      else{
      若密码错误但仍有输入密码的机会则显示提示信息;
      adm_log();
      }
      }
      }
      该段代码通过函数adm_log()的自我递归调用实现为用户提供3次输入密码的机会,计数器为adm_log()内定义的一个静态整型变量。
      评委/参赛者模块的用户名与密码核对代码
      评委模块与参赛者模块的登陆核对代码相似,故只列出评委部分的主要代码,另外用一些自然语句描述代替了部分代码(^_^)
      该部分通过for语句实现3次机会输入,验证用户名用一个for语句,验证密码则用另一个for语句嵌套于“用户名验证for语句”中,在“用户名验证for语句”中,若核对得使用者输入的用户名正确,则执行“密码验证for语句”,通过goto语句实现
      {
      先将jdg.dat文件载入程序并动态新建一结构体数组用于保存文件信息;
      }
      for(int usrnm_c=1;usrnm_c<=3;usrnm_c++){
      cout<<" 输入用户名:";
      cin>>name;
      for(i=0;i<=jdg_num;i++){//验证用户名是否存在
      if(strcmp(name,p_jdg->name)==0){//用户名正确则输入密码
      char password[20];
      for(i=3;i>0;i--){//3次机会输入密码
      cout<<" 输入密码:";
      cin>>password;
      if(strcmp(password,p_jdg->password)==0){//密码正确
      int jdg_id;//该评委的编号
      jdg_id=p_jdg->j_id;
      jdg_in(jdg_id);
      goto exit;
      }
      else{
      输出密码错误提示信息;
      continue;//再给次输入的机会(^_^)
      }
      }
      goto exit;//三次错误则强制退出
      }
      if(i<jdg_num-1)
      p_jdg++;
      }
      输出用户名错误提示信息;
      for(i=0;i<jdg_num-1;i++)//指针复位!
      p_jdg--;
      }
      exit: ;
      编写该模块时曾出现定义的变量被goto跳过的情况,查得是if语句缺少了一个结尾花括号。
      另外编写过程中曾忘记将指针复位导致测试的输出显示为乱码甚至导致操作系统不给运行程序(没复位的指针直接进入下一个for语句不断+1+1+1最后指到哪里去都不知道),这个debug了好久才找到原因,一开始我还怀疑是win7对VC的兼容不良导致的。呵呵呵……=_=!,后来的代码我就不采用直接增加指针值的方法了,而是*(p+c),用另一个变量c来表示指针要移动的相对位移,这样就不用再进行复位了
    • 评分过程中的数据读写
      除了jdg.dat和cmpt.dat之外,管理员完成初始化设置后,程序还会生成scoring_mtrx.dat、jdg_counter.dat和cmpt_counter.dat三个文件。
      scoring_mtrx.dat
      scoring_mtrx.dat为一关系矩阵[num of jdg][num of cmpt]记录某位评委对某位选手的评分,该文件用二进制形式存储。该文件所有元素在初始化都置0。
      评委进行评分工作时,程序会读取该文件并将其内容载入一个二维数组中用于记录该评委对所有选手的打分。评委每对一位选手进行评分,程序就会更新一次scoring_mtrx.dat文件的内容。因此即便评分过程被打断,程序被关闭也不会导致评委的工作进度丢失。
      该文件也用于程序中评委查看自己对所有选手的打分情况。
      初始化scoring_mtrx.dat文件的函数void scoring_mtrx()主要代码如下:
      //动态定义大小为[jdg_num][cmpt_num]的矩阵
      float **p_mtrx;
      p_mtrx=new float * [jdg_num];
      for(int j=0;j<jdg_num;j++)
      *(p_mtrx+j)=new float [cmpt_num];
      //置0初始化并输出到文件'scoring_mtrx.dat'
      for(j=0;j<jdg_num;j++){//全部元素置0
      for(int c=0;c<cmpt_num;c++){
      *(*(p_mtrx+j)+c)=0;
      }
      }
      for(j=0;j<jdg_num;j++){//输出到文件'scoring_mtrx.dat
      for(int c=0;c<cmpt_num;c++){
      scoring_mtrx.write((char *)&*(*(p_mtrx+j)+c),4);
      }
      }
      scoring_mtrx.close();
      评委评分过程中程序更新scoring_mtrx.dat的代码如下:
      事先动态定义大小为[jdg_num][cmpt_num]的二维数组,将scoring_mtrx.dat文件的内容载入该数组,然后在数组中修改指定元素的值(即某一评委对某一选手的打分)
      cin>>*(*(p_mtrx+jdg_id)+cmpt_id);// cmpt_id由评委输入,jdg_id为该评委的编号
      然后在用下列代码将数组的内容覆盖进文件scoring_mtrx.dat
      file.open("scoring_mtrx.dat",ios::binary|ios::out);
      for(j=0;j<jdg_num;j++){
      for(c=0;c<cmpt_num;c++){
      file.write((char *)&*(*(p_mtrx+j)+c),4);
      }
      }
      file.close();//至此scoring_mtrx.dat更新完成
      通过编写这段代码使我基本掌握如何编写代码对二进制文件进行特定位置的读写
      jdg_counter.dat和cmpt_counter.dat
      当选手在评分未结束时登陆查看、评委在其它评委未完成工作时要求查看赛果、管理员在评分未结束时试图导出赛果时,就需要程序能对人们的工作进度进行判断并显示相应信息。这就需要jdg_counter.dat和cmpt_counter.dat两个文件。
      这两个文件保存的数据都为一维数组。
      jdg_counter.dat数组的每个元素分别代表每个评委已打分的参赛者人数
      cmpt_counter.dat数组的每个元素分别代表每个参赛者已被多少位评委打分
      若jdg_counter.dat的每个元素都等于参赛者总人数、cmpt_counter.dat的每个元素都等于评委总人数,那么就表示评分工作已经结束。
      jdg_counter.dat和cmpt_counter.dat在管理员界面分别由无参函数cmpt_counter()和
      jdg_counter()对所有数组元素进行置0初始化,jdg_counter.dat开头有一个整数表示评委总人数,cmpt_counter.dat开头有两个整数分别表示评委总人数和参赛者总人数。
      初始化jdg_counter.dat的函数的代码如下:
      void jdg_counter()
      {
      int jdg_num;
      fstream file("jdg.dat",ios::in);//从jdg.dat文件获取本次比赛的评委总数
      file>>jdg_num;
      file.close();
      file.open("jdg_counter.dat",ios::out);
      file<<jdg_num<<endl;//文件开头记录评委总数
      for(int j=0;j<jdg_num;j++)//数组元素全部置0初始化
      file<<0<<endl;
      file.close();
      }
      初始化cmpt_counter.dat的函数的代码如下:
      void cmpt_counter()
      {
      int jdg_num,cmpt_num;
      fstream file("jdg.dat",ios::in);
      file>>jdg_num;//获取本次比赛的评委总数
      file.close();
      file.open("cmpt.dat",ios::in);
      file>>cmpt_num;//获取本次比赛的参赛者总数
      file.close();
      file.open("cmpt_counter.dat",ios::out);
      file<<jdg_num<<endl;//文件第一个整数表示评委总数
      file<<cmpt_num<<endl;//第二个整数表示参赛者总数
      for(int c=0;c<cmpt_num;c++)//数组元素全部置0初始化
      file<<0<<endl;
      file.close();
      }
      jdg_counter.dat和cmpt_counter.dat在管理员界面初始化,在评委界面进行数据更新,更新数据由初始化文件的函数的重载函数进行
      更新jdg_counter.dat的函数代码如下:
      void jdg_counter(int jdg_id)// jdg_id为评委编号,用于确定要更新文件中哪个评委的数据
      {
      int pass,counter;//pass用于跳过不需要修改的元素,counter用于修改目标元素
      fstream file("jdg_counter.dat",ios::in);
      file>>pass;//跳过jdg_num项
      for(int j=0;j<jdg_id;j++)
      file>>pass;//跳过其它评委的jdg_counter
      //载入该评委的jdg_counter
      file>>counter;
      file.close();
      //更新该评委的jdg_counter
      counter++;//将该jdg_counter值加1
      file.open("jdg_counter.dat",ios::in|ios::out);
      //将修改后的数值保存入文件
      for(j=0;j<=jdg_id;j++)
      file>>pass;
      file<<endl<<counter<<endl;
      file.close();
      }
      void cmpt_counter(int cmpt_id)的实现方式与以上函数相同,代码类似,故在此略过(^_^)
      可以看到,所谓的更新jdg_counter.dat和cmpt_counter.dat文件其实就是将文件中特定元素在原来的数值基础上加1,识别特定元素的参数在评委界面模块代码中给出。
      评委在对任一选手进行评分前都得输入该选手的编号,参数cmpt_id来自于评委的输入。
      至于参数jdg_id:在这份说明书的“程序设计概要”的“评委界面相关函数与流程”处可看到该模块很多函数都带有参数jdg_id。在评委登陆界面函数jdg_log()验证得使用者身份合法后会调用进入评委界面的函数jdg_in(int jdg_id),然后再根据使用者的输入由jdg_in(int jdg_id)函数调用实现相应功能的函数,参数jdg_id便是由jdg_in(int jdg_id)开始传递给其它需要该参数的函数。
      在此附上jdg_in函数的代码:
      void jdg_in(int jdg_id)
      {
      printf("╔═════════════╗\n");
      printf("║ 1.显示参赛者列表与评分 ║\n");
      printf("║ 2.根据名次显示赛果 ║\n");
      printf("║ 3.根据名字显示赛果 ║\n");
      printf("║ 0.退出登录 ║\n");
      printf("╚═════════════╝\n");
      int swtch;
      swtch=getch()-48;
      while(swtch>3){
      cout<<" 请输入正确指令!"<<endl;
      swtch=getch()-48;
      }
      switch(swtch){
      case 1:show_cmpt(jdg_id);break;// 修改两个counter文件的两个函数便是
      case 2:show_result(jdg_id);break; 由这个函数调用的
      case 3:show_by_name(jdg_id);break;
      case 0:jdg_log();
      }
      }
    • 评分过程中特定状态的判定

判断某一参赛者是否已被某一评委评分

该功能的实现需要scoring_mtrx.dat文件

判断某一参赛者是否已被该评委评分,只需在程序中读取scoring_mtrx.dat文件特定位置的元素,若该元素值非0,则表明该参赛者已被该评委评分,否则元素值为0。

用于判断的if语句如下(文件的内容保存于一个二维数组中):

if(*(*(p_mtrx+jdg_id)+cmpt_id)!=0)//p_mtrx是动态建立的二维数组的指针

若某一评委试图对某一选手重复打分则会显示相应提示信息

判断任一评委的工作是否已结束

该功能的实现需要jdg_counter.dat文件

评委界面有如下选项:

  1. 显示参赛者列表与评分
  2. 根据名次显示赛果
  3. 根据名称显示赛果

对于选项(1)若是该评委已对所有选手都进行过一次评分则会显示提示信息,判断功能直接由一个if语句实现:if(counter==cmpt_num)

cmpt_num为参赛者总人数,counter为一整形变量,定义于void show_cmpt(int jdg_id)函数中,用于读取jdg_counter.dat中特定评委的数据并随着该评委评分工作的进行而更新数据,若整形变量counter在某次更新后其值等于参赛者总人数,则表明该评委已对所有参赛者都进行过一次评分,故由此可判定该评委的工作已经结束从而显示相应的提示信息。

判断比赛是否已经结束

该功能的实现需要cmpt_counter.dat文件

实现该功能的函数为int if_end()

若全部评分工作都已结束(所有参赛者都被评分)则调用sorting()函数对参赛者进行排名并返回整数值1;若评分工作尚未结束则显示相应提示信息并返回整数值0。

int if_end()的代码如下:

int if_end()

{

int jdg_num,cmpt_num;

fstream file("cmpt_counter.dat",ios::in);

file>>jdg_num;

file>>cmpt_num;

int *p_array=new int [cmpt_num];//新建一维数组用于保存cmpt_counter内容

for(int c=0;c<cmpt_num;c++)//将文件内容载入数组

{

file>>*(p_array+c);

if(*(p_array+c)<jdg_num)//若发现文件中有小于评委人数的元素,则表明仍

break; 有参赛者未被所有评委评分,break退出

else if(c==cmpt_num-1&&*(p_array+c)==jdg_num)

{若进行到最后一次循环且该次循环的元素也等于评委人数则表明已全部评分

sorting();//调用排序函数

return 1;

goto exit;执行后直接退出而不执行下面的语句

}

}

cout<<"┌────────────┐"<<endl;

cout<<"│仍有选手未被所有评委评分│"<<endl;

cout<<"└────────────┘"<<endl;

return 0;

exit:

;

}

  • 对参赛者进行排序

该模块在程序判断得评分工作已全部完成后执行。由data.h文件中的sorting()函数根据参赛者的最终得分得到各个参赛者的名次并添加名次信息到cmpt.dat文件

程序中显示最终赛果有两种方式:一是根据排名,二是根据名称。第一种由show_by_score()函数实现,第二种由show_by_name()函数实现。

计算参赛者的排名

sorting()函数的代码较长,其主要流程如下:

  1. 载入文件scoring_mtrx.dat的数据到二维数组[jdg_num][cmpt_num]
  2. 载入文件cmpt.dat的内容到结构体数组[cmpt_num]
  3. 将原来的[jdg_num][cmpt_num]倒置为[cmpt_num][jdg_num]
  4. 根据[cmpt_num][jdg_num]求各个选手的最终得分
  5. 将选手的最终得分载入一个一维数组final_score并进行排序
  6. 将选手的排名存入结构体数组[cmpt_num]的ranking成员
  7. 将结构体数组的内容载入cmpt.dat以更新文件

接下来拣些主要代码进行详细分析,先对相关变量名进行说明:

jdg_num和cmpt_num是评委和参赛者的总人数,另外有for语句的循环变量j与c与之分别对应,动态新建的二维数组[jdg_num][cmpt_num]是浮点型变量用于保存scoring_mtrx.dat文件的内容,其指针是p_mtrx,倒置后的二维数组[cmpt_num][jdg_num]其指针是mtrx_p,浮点型变量temp在进行排序时做交换用,浮点型一维数组[cmpt_num]的指针是final_score,结构体数组[cmpt_num]的指针是p_cmpt。

计算各个选手最终得分的代码如下:

float *final_score=new float [cmpt_num];//新建一个一维数组[cmpt_num]

for(c=0;c<cmpt_num;c++){

float sum=0;

for(j=1;j<jdg_num-1;j++){//去掉一个最高分和一个最低分

sum=sum+*(*(mtrx_p+c)+j);//指针表示的元素可视为mtrx_p[c][j]

}

*(final_score+c)=sum/(jdg_num-2);//将计算得的最终得分保存进一维数组

}

排序直接在一维数组内进行,排序完成后将一维数组各元素的值分别与结构体数组中各个元素的score成员进行比较,若一维数组某一元素的值等于结构体某一元素的score成员的值,则该一维数组元素于数组中的位序即是该结构体代表的参赛者的名次,然后将该位序赋值予该结构体的成员ranking,赋值代码如下:

//每个参赛者从final_score得到自己的排名(若某一参赛者的分数等于final_score的某一元素,则该元素于数组中的位序即参赛者排名)

for(c=0;c<cmpt_num;c++){通过该for语句实现为每一位参赛者的ranking赋值

for(int i=0;i<cmpt_num;i++){该for实现一位参赛者遍历final_score的所有元素并找到与自己的分数相同的那个final_score的元素

if(*(final_score+i)==(p_cmpt+c)->score){

if(i!=0){

//处理两名参赛者分数相同的情况

if(*(final_score+i-1)==(p_cmpt+c)->score)(p_cmpt+c)->ranking=i;

else

(p_cmpt+c)->ranking=i+1;

}

else

(p_cmpt+c)->ranking=1;

}

}

}

由于一维数组由0号开始,故名次等于一维数组位序+1,另外第一名为特殊情况,由红色的代码实现对第一名的识别并进行特殊处理。

另:排序算法为冒泡法……

根据排名显示赛果

这一功能由两个for语句嵌套实现,外层for语句用于输出每一个名次的参赛者,内层for语句用于查找符合该名次的参赛者。

for(int i=0;i<cmpt_num;i++){

for(c=0;c<cmpt_num;c++){

if(((p_cmpt+c)->ranking)==i+1){

printf("║No.%3d║%10s║ score:%7.2f║\n",

(p_cmpt+c)->ranking,

(p_cmpt+c)->name,

(p_cmpt+c)->score);

if(i!=cmpt_num-1)

printf("╠═══╬═════╬═══════╣\n");

}

continue;

}

}

根据名称显示赛果

新建结构体数组,将cmpt.dat文件的内容保存在里面,再新建一个保存字符变量的二维数组保存结构体中的name成员(参赛者名称),用此二维数组进行排序,排序代码如下:

for(int t=0;t<cmpt_num-1;t++){

for(c=0;c<cmpt_num-1-t;c++){

int i=0;

cmpr(c,i,p_p);

}

}

可以看到排序代码很像冒泡法,只是红色代码部分有所不同,红色部分是调用一个专门用于比较两个字符串的先后次序的函数,是个自我递归调用的函数:

对两个字符串的第i位进行比较,若得不出先后顺序(即两个字符串的该位字符相同)则通过递归调用对第i+1位进行比较

该函数有三个参数,c与i用于标明排序的循环体进行到了哪一步,正在对哪两个字符串进行比较,指针参数p_p是指向字符串二维数组的指针。

该函数代码如下:

void cmpr(int c,int i,char **p_p)

{

if(*(*(p_p+c)+i)>*(*(p_p+c+1)+i)){

char *temp;

temp=*(p_p+c);

*(p_p+c)=*(p_p+c+1);

*(p_p+c+1)=temp;

}

else if(*(*(p_p+c)+i)==*(*(p_p+c+1)+i)){

i++;比较两个字符串的第i+1位字符

if(*(*(p_p+c)+i)!='\0'&&*(*(p_p+c+1)+i)!='\0')

cmpr(c,i,p_p);

}

}

以上是对个人认为比较关键的代码所进行的详细说明,其它更细节琐碎的代码实现请看源代码文件(^_^)

  • 程序使用说明
    • 管理员界面

运行程序后会要求输入密码,若密码错误则提示重新输入并显示剩余的输入次数

若密码正确,且未进行初始化设置,则显示如下提示

  1. 设置评委
  2. 设置参赛者

输入的命令不会显示到屏幕上而是直接进入下一步骤,下面是在程序中设置评委

设置参赛者的情况类似故略去,若选择从文件中导入则直接显示“设置成功”及后面的内容。下面箭头处为使用者的输入

设置完成后,若在评分未结束时登陆则显示相关提示信息要求管理员在评委的评分工作结束再登陆

评分完成后登陆则提示“任意键导出赛果到文件”

顺利导出文件后程序运行结束

  • 评分界面

有三次机会输入用户名,输入正确用户名后有三次机会输入密码

成功登陆后显示以下提示:

  1. 显示参赛者列表与评分
  2. 根据名次显示赛果
  3. 根据名字显示赛果

选择1进行评分,程序显示如下

若输入了不存在的编号则会显示提示信息

输入编号进行评分,若输入了某一位已被评过分的参赛者的编号则显示提示信息

评分完成后会显示相关提示信息

若在评分工作未完成时选择2或3则显示如下

若评分工作已完成则根据使用者的选择出现如下两种显示之一

  • 参赛者界面
    若参赛者在管理员未完成初始化设置时运行程序则显示相关提示信息。
    p.s:其实评委界面原本也有以上功能。程序允许评委界面在一次运行中给多个评委轮流使用(即是一个评委登陆后退出然后下一个评委在登陆),但是却导致若某一评委在前面j个评委使用后登陆,该评委试图退出后必须敲j下任意键并看着屏幕显示j个“任意键退出”后才能退出,后来为了让使用者不会以为程序会一直输出“任意键退出”而无法退出,我又将“任意键退出”改为“请按任意键直到退出”(⊙﹏⊙b),考虑到使用者可能因此产生被耍的感觉,我就干脆把评委界面的这个功能取消了。这个bug好像是由于多个评委登陆会在jdg_log()函数内多次嵌套调用到jdg_log()函数,评委退出必须连同其外层的函数也退出,所以会多次执行jdg_log()函数末尾的“任意键退出”。可是若按这么说,某一评委退出时会连同其所有外层jdg_log()也一起退出,那么下一个使用者登陆时不就又位于第一层调用了吗?又怎么会出现多出“任意键退出”呢?对于这个bug我想出了唯一一个可能的出错原因,但这个原因也可以用它自己来说明它是一个矛盾的命题。因此,此bug我至今对其无解。
    下面是参赛者查分的显示(参赛者界面唯一的显示)

  • 总结
    从7月9号开始开始着手暑假大作业,断断续续做到7月27号把程序写完。一开始花了两天时间来画流程图,确定程序要实现的具体功能以及实现功能的步骤。编程后期没有出现足以令人精神崩溃的巨大bug,是因为一开始就把整个程序分成了几个模块,又把模块分成了小模块,一小块一小块仔细地写,又花了不少功夫仔细地调试debug,到了后期拼装各个模块时程序能如预期地正常运行。写这个程序我得到的主要收获是:先规划好再动手写代码,这样有助于把握整个编写过程,不会因为出现了意想不到的问题而导致思维混乱;写注释要写清楚,注释对后期的调试很重要;对于出现的bug要用正确的心态去对待:debug是编程的一部分而不是一种可恶可怕的工作,只要静下心来好好分析问题,debug与设计代码同样富有乐趣。
    个人认为这个程序的缺点主要是在于输入检错不严格,在这方面程序健壮性不强,另外未能实现可视化。
  • 参考书籍

《C++程序设计》 谭浩强 编著

《C语言解析教程》 (美)Al Kelley、Ira Pohl编著 (中)麻志毅 译

相关推荐

  1. C语言作业 12.8

    2024-06-12 19:02:02       29 阅读
  2. C语言作业 12.14

    2024-06-12 19:02:02       41 阅读

最近更新

  1. TCP协议是安全的吗?

    2024-06-12 19:02:02       16 阅读
  2. 阿里云服务器执行yum,一直下载docker-ce-stable失败

    2024-06-12 19:02:02       16 阅读
  3. 【Python教程】压缩PDF文件大小

    2024-06-12 19:02:02       15 阅读
  4. 通过文章id递归查询所有评论(xml)

    2024-06-12 19:02:02       18 阅读

热门阅读

  1. Excel分组做散点图

    2024-06-12 19:02:02       9 阅读
  2. 最大N个数与最小N个数之和

    2024-06-12 19:02:02       7 阅读
  3. 【MeshLib & VTK】MeshLib PK VTK

    2024-06-12 19:02:02       8 阅读
  4. userservice

    2024-06-12 19:02:02       6 阅读
  5. NLP--逻辑回归

    2024-06-12 19:02:02       6 阅读
  6. 【Spring Cloud】配置中心详细介绍及使用

    2024-06-12 19:02:02       6 阅读
  7. 【镜像制作】Oracle JDK项目镜像压缩

    2024-06-12 19:02:02       7 阅读
  8. Spring Boot整合Knife4j-3.0.3

    2024-06-12 19:02:02       11 阅读