目录
一、演讲比赛程序要求
1、比赛规则
2、程序功能
二、创建管理类
三、菜单功能
四、退出功能
五、演讲比赛功能
1、功能分析
2、创建选手类
3、进行比赛
4、保存分数
六、查看比赛记录
1、读取记录分数
2、查看记录功能
3、bug解决
七、清空功能
一、演讲比赛程序要求
1、比赛规则
举办一场演讲比赛,共有12人参加,比赛共2轮,第一轮为淘汰赛,第二轮为决赛。
①每位选手都有对应的编号:如10001,10002...10012;A、B...、L
②比赛方式:分组比赛,每组6个人比赛一场,共2场
③第一轮比赛分为2个小组,整体按照选手编号进行抽签后顺序演讲
④十名评委分别给每位选手打分,去除最高分和最低分,取平均分为本轮选手的成绩
⑤当第一轮比赛结束后,每个小组的前三名晋级进入下一轮比赛,后三名淘汰
⑥第二轮决赛,六位选手前三名胜出,按成绩分为冠军亚军季军
⑦每轮比赛后显示晋级选手的信息
2、程序功能
①开始比赛功能:完成整届比赛的流程,比赛各个阶段需要给用户一个提示,用户按任意键后继续下一个阶段
②查看往届记录:查看之前比赛的前三名结果,包含姓名和成绩。每次比赛都会记录到文件中,以csv格式保存
③清空比赛记录:将文件中数据清空
④退出比赛程序:可以退出当前程序
二、创建管理类
功能:
①提供菜单界面与用户交互
②对演讲比赛流程进行控制
③与文件的读写交互
首先创建SpeachManager的头文件和源文件
然后在头文件里实现管理类Manager,并带上构造函数和析构函数
然后在源文件里空实现构造和析构函数,注意带作用域
三、菜单功能
功能:与用户交互的界面
首先在管理类中添加菜单显示接口show_menu();
然后在源文件中实现 :注意带上作用域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
调用:
四、退出功能
功能:退出系统
目前系统只有一个显示功能,而且不支持输入与选择,因此我们使用while(true)和switch语句来实现该功能
在头文件里声明exit_m();接口后在源文件中实现:
// 退出功能
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;
}
五、演讲比赛功能
1、功能分析
比赛流程:
2、创建选手类
选手类speaker,应包含属性姓名name与分数score(分数有2轮)
在头文件中创建:
class speaker // 选手类
{
public:
string s_name;
double s_score[2]; // 2轮得分,double使不会出现同分的情况
};
3、进行比赛
①添加成员属性
在管理类中添加各种属性:
vector<int> v1; // 存放第1轮比赛的选手,共12人
vector<int> v2; // 存放第2轮比赛的选手,共6人
vector<int> v3; // 存放比赛胜出的选手,共3人
map<int, speaker> m_speaker; // 存放编号以及对应的选手 容器
int index; // 记录比赛的轮数
②初始化属性
在头文件中生成初始化属性接口initSpeaker();在源文件中实现,并在构造函数中调用
// 初始化属性
void Manager::initSpeaker()
{
// 全部清空并置1
this->v1.clear();
this->v2.clear();
this->v3.clear();
this->m_speaker.clear();
this->index = 1;
}
调用:
③创建选手
首先在管理类中添加创建选手的接口createrSperker();
然后在源文件中实现
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));
}
}
然后在构造函数中调用
这样就完成了选手的创建,不过为了方便查看,我再实现一个查看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;
}
④开始比赛成员函数添加
首先在管理类中添加开始比赛成员函数startSpeach();
然后在源文件中空实现
// 开始比赛
void Manager::startSpeach()
{
// 第一轮比赛
// 1、抽签
// 2、比赛
// 3、显示比赛结果
// 第二轮比赛
// 1、抽签
// 2、比赛
// 3、显示比赛结果
}
⑤抽签
抽签分为2种:第一轮抽签与第二轮抽签
我们可以根据属性index来判断比赛进行的轮数,以给容器v1或是v2进行洗牌
我们先在管理类中加上抽签DrawSpeach();接口
然后在源文件中实现
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接口中调用
测试:
⑥开始比赛
比赛流程代码可以分为4部分:
Ⅰ创建比赛的人员的容器v_person,并根据不同轮数将对应容器赋给v_perso
Ⅱ创建人数的计数num,遍历v_person里所有选手,创建deque容器,每个选手进行10名评委的打分并将分数插入到deque容器,排序后去除最高分和最低分,最后获取总分和平均分,并将平均分赋值给对应轮数的score[0/1]中
Ⅲ创建小组容器以存储对组,以平均分和编号创建对组,每6个选手为一组,输出这6名选手的姓名编号与成绩
Ⅳ取出前3名,如果是第1场比赛,则将前三名插入到v2容器,如果是第2场比赛,则插入到v3中,每6个清空一次
先在管理类中创建进行比赛Speach(); 接口
然后在源文件中实现
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接口中调用
⑦显示比赛分数
显示比赛晋级的选手,即每轮比赛的前三名
先在管理类中添加显示比赛结果的接口函数showSpeach();
然后在源文件中实现
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接口中调用
⑧第二轮比赛
把第一轮比赛流程的代码加到第二轮即可
不过注意场次index要++
4、保存分数
先在管理类中添加保存比赛结果的接口函数saveSpeach();
然后在源文件中实现
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;
}
六、查看比赛记录
1、读取记录分数
先在管理类中添加读取记录分数的接口函数loadSpeach();以及记录文件是否为空的标识符fileISempty和存放往届记录的容器map<int,vector<string>> s_Load
然后在源文件中实现:
读取文件时,会有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、查看记录功能
先在管理类中添加查看记录分数的接口函数showRecord();
然后在源文件中实现:判断完文件不为空后,直接遍历输出即可
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");
}
3、bug解决
Ⅰ当Speach.csv中没有数据时,我们进行一场比赛,结束后数据会存放到文件中,但这时直接运行2查看记录分数时仍然会提示文件为空
进行比赛
再次使用2,仍然为空
解决:我们每次保存文件时,都应在最后使fileISempty为false
这样就解决了
Ⅱ不过就上面解决后,我们继续执行代码,重复操作
会发现程序没有任何输出,这是因为数据没有实时更新
解决:我们需要再次初始化属性、创建选手并获取往届记录
即将构造函数中的函数在进行比赛的接口Speach(0;中调用
Ⅲ 初始化时,没有初始化记录容器s_Load
会导致我们怎么清空容器,容器都存在数据
解决:在初始化接口initSpeaker();中初始化记录容器
七、清空功能
先在管理类中添加清空文件的接口函数destroyRecord();
然后在源文件中实现
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函数中调用
测试: