基于STL的 演讲比赛流程管理系统

目录

1 演讲比赛程序要求

1.1 比赛规则

1.2 程序功能

2 实现

2.1 创建管理类

2.2 菜单功能

2.3 退出功能

2.4 演讲比赛功能

2.4.1 功能分析

2.4.2 创建选手类

2.4.3 进行比赛

2.4.3.1 添加成员属性

2.4.3.2 初始化属性

2.4.3.3 创建选手

2.4.3.4 开始比赛成员函数添加

2.4.3.5 抽签

2.4.3.6 开始比赛

2.4.3.7 显示比赛分数

2.4.3.8 第二轮比赛

2.4.4 保存分数

2.5 查看比赛记录

2.5.1 读取记录分数

2.5.2 查看记录功能

2.5.3 bug解决

2.6 清空功能


1 演讲比赛程序要求

1.1 比赛规则

举办一场演讲比赛,共有12人参加,比赛共2轮,第一轮为淘汰赛,第二轮为决赛。

  • 每位选手都有对应的编号:如10001,10002...10012;A、B...、L

  • 比赛方式:分组比赛,每组6个人比赛一场,共2

  • 1轮比赛分为2个小组,整体按照选手编号进行抽签后顺序演讲

  • 10名评委分别给每位选手打分,去除最高分和最低分,取平均分为本轮选手的成绩

  • 当第一轮比赛结束后,每个小组的前三名晋级进入下一轮比赛,后三名淘汰

  • 第二轮决赛,六位选手前3名胜出,按成绩分为冠军亚军季军

  • 每轮比赛后显示晋级选手的信息


1.2 程序功能

  • 开始比赛功能:完成整届比赛的流程,比赛各个阶段需要给用户一个提示,用户按任意键后继续下一个阶段

  • 查看往届记录:查看之前比赛的前三名结果,包含姓名和成绩。每次比赛都会记录到文件中,以csv格式保存

  • 清空比赛记录:将文件中数据清空

  • 退出比赛程序:可以退出当前程序

2 实现

2.1 创建管理类

功能

  • 提供菜单界面与用户交互

  • 对演讲比赛流程进行控制

  • 与文件的读写交互


首先创建SpeachManager的头文件和源文件

img

然后在头文件里实现管理类Manager,并带上构造函数和析构函数

img

然后在源文件里空实现构造和析构函数,注意带作用域

img

2.2 菜单功能

功能:与用户交互的界面

首先在管理类中添加菜单显示接口show_menu();

img

然后在源文件中实现 :注意带上作用域Manager::

 void Manager::show_menu()
 {
   cout << "******************************************" << endl;
   cout << "********** 欢迎使用演讲比赛系统 **********" << endl;
   cout << "**********    1、开始演讲比赛   **********" << endl;
   cout << "**********    2、查看往届记录   **********" << endl;
   cout << "**********    3、清空比赛记录   **********" << endl;
   cout << "**********    0、退出比赛系统   **********" << endl;
   cout << "******************************************" << endl;
 }

然后在main函数中调用:我们先包含SpeachManager.h头文件,然后再根据管理类创建对象m

img

调用:

img

2.3 退出功能

功能:退出系统

目前系统只有一个显示功能,而且不支持输入与选择,因此我们使用while(true)switch语句来实现该功能

在头文件里声明exit_m();接口后在源文件中实现:

img

 // 退出功能
 void Manager::exit_m()
 {
   cout << "欢迎下次使用!" << endl;
   exit(0);
 }

修改main函数,添加while(true)循环和switch语句

 int main()
 {
   Manager m;
   int input;
   while (true)
   {
     m.show_menu();
     cout << "请选择:" << endl;
     cin >> input;
     switch (input)
     {
       case 1: // 开始比赛
         break;
       case 2: // 显示往届记录
         break;
       case 3: // 清空所有记录 
         break;
       case 0: // 退出系统
         m.exit_m();
       default:
         system("cls"); // 清屏
         break;
     }
   }
   return 0;
 }

img

2.4 演讲比赛功能

2.4.1 功能分析

比赛流程:

img

2.4.2 创建选手类

选手类speaker,应包含属性姓名name与分数score(分数有2轮)

在头文件中创建:

 class speaker  // 选手类
 {
 public:
   string s_name;
   double s_score[2]; // 2轮得分,double使不会出现同分的情况
 };

2.4.3 进行比赛

2.4.3.1 添加成员属性

在管理类中添加各种属性:

     vector<int> v1; // 存放第1轮比赛的选手,共12人
   vector<int> v2; // 存放第2轮比赛的选手,共6人
   vector<int> v3; // 存放比赛胜出的选手,共3人
 ​
   map<int, speaker> m_speaker; // 存放编号以及对应的选手 容器
   int index; // 记录比赛的轮数
2.4.3.2 初始化属性

头文件中生成初始化属性接口initSpeaker();在源文件中实现,并在构造函数中调用

img

 // 初始化属性
 void Manager::initSpeaker()
 {
   // 全部清空并置1
   this->v1.clear();
   this->v2.clear();
   this->v3.clear();
   this->m_speaker.clear();
   this->index = 1;
 }

调用:

img

2.4.3.3 创建选手

首先在管理类中添加创建选手的接口createrSperker();

img

然后在源文件中实现

 void Manager::createSpeaker()
 {
   string nameSeed = "ABCDEFJHIJKL";
   for (int i = 0; i < nameSeed.size(); i++)
   {
     string name = "选手";
     name += nameSeed[i];
 ​
     speaker sp; // 创建一位选手
     sp.s_name = name; // 选手名字初始化
     for (int j = 0; j < 2; j++)
     {
       sp.s_score[j] = 0; // 选手分数初始化
     }
     // 为选手编号
     this->v1.push_back(i + 10001);
     // 将选手编号以及对应选手插入到map容器中,编号10001-100012
     this->m_speaker.insert(make_pair(i + 10001, sp));
   }
 }

然后在构造函数中调用

img

这样就完成了选手的创建,不过为了方便查看,我再实现一个查看map容器中元素的函数,打印出目前选手的信息

   for (map<int, speaker>::iterator it = m.m_speaker.begin(); it != m.m_speaker.end(); it++)
   {
     cout << "选手编号:"<<it->first<<"\t姓名:" << it->second.s_name << "\t分数:" << it->second.s_score[1] << endl;
   }

img

2.4.3.4 开始比赛成员函数添加

首先在管理类中添加开始比赛成员函数startSpeach();

img

然后在源文件中空实现

 // 开始比赛
 void Manager::startSpeach()
 {
   // 第一轮比赛
   //  1、抽签
   //  2、比赛
   //  3、显示比赛结果
   
   // 第二轮比赛
   //  1、抽签
   //  2、比赛
   //  3、显示比赛结果
 }
2.4.3.5 抽签

抽签分为2种:第一轮抽签与第二轮抽签

我们可以根据属性index来判断比赛进行的轮数,以给容器v1或是v2进行洗牌

我们先在管理类中加上抽签DrawSpeach();接口

img

然后在源文件中实现

 void Manager::DrawSpeach()
 {
   cout << "第" << this->index << "轮抽签正在进行" << endl;
   cout << "***************************************" << endl;
   Sleep(1000);
   cout << "抽签后演讲顺序如下" << endl;
   if (this->index == 1)
   {
     random_shuffle(v1.begin(), v1.end());
     for (vector<int>::iterator it = v1.begin(); it != v1.end(); it++)
     {
       cout << *it << " ";
     }
     cout << endl;
   }
   else
   {
     random_shuffle(v2.begin(), v2.end());
     for (vector<int>::iterator it = v2.begin(); it != v2.end(); it++)
     {
       cout << *it << " ";
     }
     cout << endl;
   }
   cout << "***************************************" << endl;
   system("pause");
   cout << endl;
 }

最后在开始比赛stratSpeach接口中调用

img

测试:

img

2.4.3.6 开始比赛

比赛流程代码可以分为4部分:

  1. 创建比赛的人员的容器v_person,并根据不同轮数将对应容器赋给v_person

  2. 创建人数的计数num,遍历v_person里所有选手,创建deque容器,每个选手进行10名评委的打分并将分数插入到deque容器,排序后去除最高分和最低分,最后获取总分和平均分,并将平均分赋值给对应轮数的score[0/1]

  3. 创建小组容器以存储对组,以平均分和编号创建对组,每6个选手为一组,输出这6名选手的姓名编号与成绩

  4. 取出前3名,如果是第1场比赛,则将前3名插入到v2容器,如果是第2场比赛,则插入到v3中,每6个清空一次

先在管理类中创建进行比赛Speach(); 接口

img

然后在源文件中实现

 void Manager::Speach()
 {
   cout << "------------" << "第" << this->index << "组比赛正式开始" << "-------------" << endl;
   multimap<double, int, greater<double>> Group; // 存储分数与选手编号,降序排列,放在最外面,防止进入循环创建不同的容器
 ​
   //  1、创建比赛的人员的容器v_person,并根据不同轮数将对应容器赋给v_person
   vector<int>v_person;
   if (this->index == 1)
   {
     v_person = v1;
   }
   else
   {
     v_person = v2;
   }
 ​
   //  2、创建人数的计数num,遍历v_person里所有选手,创建deque容器,每个选手进行10名评委的打分
   //     排序后去除最高分和最低分,最后获取总分和平均分
   int num = 0;
   for (vector<int>::iterator it = v_person.begin(); it != v_person.end(); it++)
   {
     // 评委打分
     deque<double>d;
     for (int i = 0; i < 10; i++)
     {
       double score = (rand() % 401 + 600) / 10.f; // 随机数600-1000,除以10后保留1位小数,是60.0-100.0
       d.push_back(score); // 将分数插入到d中
     }
     sort(d.begin(), d.end(),greater<double>()); // 降序 排序
     d.pop_back(); // 去除最低分
     d.pop_front();  // 去除最高分
 ​
     double sum = accumulate(d.begin(), d.end(),0); // 获取总分
     double avg = sum / d.size();  // 获取均值
     this->m_speaker[*it].s_score[this->index - 1] = avg; // 将均分存入到选手对应轮数的score里
 ​
     //  3、创建小组容器,以平均分和编号创建对组,每6个选手为一组,输出这6名选手的姓名编号与成绩
     Group.insert(make_pair(avg, *it)); // 将每位选手的均分和编号插入到Group容器中
     num++;  // 插入后人数++
     if (num % 6 == 0) // 每插入6个人,进入一次遍历输出,此时输出的已经排序
     {
       cout <<"第" << num / 6 << "组选手比赛名次:" << endl;
       for (multimap<double, int, greater<double>>::iterator it = Group.begin(); it != Group.end(); it++)
       {
         cout << "编号:" << it->second << "\t姓名:"
           << this->m_speaker[it->second].s_name << "\t平均分:"
           << this->m_speaker[it->second].s_score[this->index - 1] << endl;
       }
 ​
       //  4、取出前3名,如果是第1场比赛,则将前三名插入到v2容器,如果是第2场比赛,则插入到v3中,每6个清空一次
       int count = 0; // 使我们可以取出前三名:0、1、2
       for (multimap<double, int, greater<double>>::iterator it = Group.begin(); it != Group.end() && count < 3; it++, count++)
       {
         if (this->index == 1)
         {
           v2.push_back((*it).second);
         }
         else
         {
           v3.push_back((*it).second);
         }
       }
       Group.clear(); // 如果不清空,则下次会有上次遍历的值
       cout << endl;
     }
   }
   cout << "--------------"<<"第" << this->index << "轮比赛结束" <<"---------------" << endl;   
 }

最后在开始比赛stratSpeach接口中调用

img

img

2.4.3.7 显示比赛分数

显示比赛晋级的选手,即每轮比赛的前三名

先在管理类中添加显示比赛结果的接口函数showSpeach();

img

然后在源文件中实现

 void Manager::showSpeach()
 {
   cout << "------------" << "第" << this->index << "轮选手晋级信息:" << "-------------" << endl;
   vector<int>v;
   if (this->index == 1)
   {
     v = v2;
   }
   else
   {
     v = v3;
   }
   for (vector<int>::iterator it = v.begin(); it != v.end(); it++)
   {
     cout << "编号:" << *it << "\t姓名:"
       << this->m_speaker[*it].s_name << "\t平均分:"
       << this->m_speaker[*it].s_score[this->index - 1] << endl;
   }
   cout << endl;
   system("pause");
   system("cls");
 }

最后在开始比赛stratSpeach接口中调用

img

img

2.4.3.8 第二轮比赛

把第一轮比赛流程的代码加到第二轮即可

img

不过注意场次index++

img

2.4.4 保存分数

先在管理类中添加保存比赛结果的接口函数saveSpeach();

img

然后在源文件中实现

 void Manager::saveSpeach()
 {
   ofstream ofs;
   ofs.open("Speach.csv", ios::out | ios::app);
   // 以输出和追加方式打开文件,后缀命名为csv
   // 将最终获胜的3人数据写入文件
   for (vector<int>::iterator it = v3.begin(); it != v3.end(); it++)
   {
     ofs << *it <<"," <<this->m_speaker[*it].s_score[1] << ",";
   }
   ofs << endl;
   ofs.close();
   cout << "文件已保存!" << endl;
 }

img

img

2.5 查看比赛记录

2.5.1 读取记录分数

先在管理类中添加读取记录分数的接口函数loadSpeach();以及记录文件是否为空的标识符fileISempty和存放往届记录的容器map<int,vector<string>> s_Load

img

img

然后在源文件中实现:

读取文件时,会有3种情况:

  • Ⅰ文件不存在

   ifstream ifs("Speach.csv", ios::in); // 读方式打开文件
 ​
   // 1、文件不存在
   if (!ifs.is_open())
   {
     this->fileISempty = true;
     cout << "文件不存在!" << endl;
     ifs.close();
     return;
   }
  • Ⅱ文件为空(或被清空)

 
  // 2、文件为空(被清空)
   char ch;
   ifs >> ch;
   if (ifs.eof())
   {
     cout << "文件为空!" << endl;
     this->fileISempty = true;
     ifs.close();
     return;
   }
  • Ⅲ 文件不为空

因为不同数据之间用逗号, 隔开,我们通过substr截取开头到逗号,中间的数据来分割每个数据

而下一个截取的开头就是上次截取结尾+1,结尾就是下一个逗号,的位置

然后将每个数据存放到一个vector容器中,最后将容器以对组插入到map容器

 
  // 3、文件不为空
   this->fileISempty = false;
   ifs.putback(ch);// 将上面截取的1个字符放回
   string data; // 存储文件中读取到的信息
   int session = 1; // 比赛的届数
   while (ifs >> data)
   {
     vector<string>v; // 存储选手编号以及分数的容器,直接都用string类型
     int pos = -1; // 查到,逗号的位置,将位置存储在pos
     int start = 0;// 起始位置
     while (true)
     {
       pos = data.find(",", start);// 从start 0开始查找 ,逗号 
     // 10003,80.375,10004,80,10007,79.375
     //  start 1  pos ,逗号所在的位置,中间要截取的就是pos-start
       if (pos == -1)
       {
         // 没找到
         break;
       }
       string tmp = data.substr(start, pos - start);
       v.push_back(tmp);
       start = pos + 1; // 让start移到pos+1的位置
     }
     this->s_Load.insert(make_pair(session, v)); // 将届数和选手信息插入到s_Load里
     session++;
   }

2.5.2 查看记录功能

先在管理类中添加查看记录分数的接口函数showRecord();

img

然后在源文件中实现:判断完文件不为空后,直接遍历输出即可

 void Manager::showRecord()
 {
   if (this->fileISempty)
   {
     cout << "文件为空或不存在!" << endl;
   }
   else
   {
     for (map<int, vector<string>>::iterator it = s_Load.begin(); it != s_Load.end(); it++)
     {
       cout << "第" << it->first << "届 "
         << "\t冠军编号为:" << it->second[0] << "\t分数为:" << it->second[1] << endl
         << "\t亚军编号为:" << it->second[2] << "\t分数为:" << it->second[3] << endl
         << "\t季军编号为:" << it->second[4] << "\t分数为:" << it->second[5] << endl;
       Sleep(40);
     }
   }
   Sleep(500);
   system("pause");
   system("cls");
 }

img

2.5.3 bug解决

  • Speach.csv中没有数据时,我们进行一场比赛,结束后数据会存放到文件中,但这时直接运行2查看记录分数时仍然会提示文件为空

img

进行比赛

img

再次使用2,仍然为空

img

解决:我们每次保存文件时,都应在最后使fileISemptyfalse

img

这样就解决了

  • Ⅱ不过就上面解决后,我们继续执行代码,重复操作

img

会发现程序没有任何输出,这是因为数据没有实时更新

解决:我们需要再次初始化属性、创建选手并获取往届记录

即将构造函数中的函数在进行比赛的接口Speach();中调用

img

img

  • 初始化时,没有初始化记录容器s_Load

会导致我们怎么清空容器,容器都存在数据

解决:在初始化接口initSpeaker();中初始化记录容器

img

2.6 清空功能

先在管理类中添加清空文件的接口函数destroyRecord();

img

然后在源文件中实现

 void Manager:: destoryRecord()
 {
   cout << "是否确认清空?" << endl;
   cout << "确认:1" << endl;
   cout << "取消:0" << endl;
   cout << "请输入:" << endl;
   int input;
   cin >> input;
 ​
   if (input == 1)
   {
     ofstream ofs("Speach.csv", ios::trunc);
     // trunc:如果文件存在,删除并重新创建
     ofs.close();
 ​
     // 然后将属性初始化
     this->initSpeaker(); // 初始化
     this->createSpeaker(); // 创建选手
     this->loadSpeach(); // 获取往届记录
     cout << "清空成功!" << endl;
   }
   system("pause");
   system("cls");
 }

最后在main函数中调用

img

测试:

img

img

img

至此该项目全部完成

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值