最近把C++
的知识点过了一遍,虽然说不能完全的了解C++
语言的特性,但是也发现了它的美,后面想要学习C++
的热情高涨,故完成了一个小项目,来达到温故知新的效果吧。
主要涉及知识点:类、STL等
在这之前,我们首先创建两个类:
第一个类表示比赛选手类,记录了当前比赛的选手信息,如选手编号,以及分数等,记作speaker.h
,只是需要当作头文件即可;
第二个类表示比赛流程类,记录了整个比赛的信息,如比赛流程,记录显示等等,同样的,我们将其声明和其实现分开,分别记作speechmanager.h
和speechmanager.cpp
首先,观察可知,比赛总共的可以分为四个分支,那么总体main函数里面可以采用分支结构,switch…case…语句来完成,可以按照项目所给的需求得到顺序(也可以按照自己喜欢的)。
switch(choice){
case 1: //开始比赛
break;
case 2: //查看记录
break;
case 3: //清空记录
break;
case 0: //退出系统
break;
default:
system("cls"); //清屏
break;
}
得到了上面的这个流程,我们需要给用户提示功能,可以设计一个展示这个数字的含义的方法,可以将其加入到头文件中声明,cpp文件中实现:
void SpeechManager::showMenu() {
cout << "********************************************" << endl;
cout << "************* 欢迎参加演讲比赛 ************" << endl;
cout << "************* 1.开始演讲比赛 *************" << endl;
cout << "************* 2.查看往届记录 *************" << endl;
cout << "************* 3.清空比赛记录 *************" << endl;
cout << "************* 0.退出比赛程序 *************" << endl;
cout << "********************************************" << endl;
cout << endl;
}
我们首先设计的是退出系统功能的设计,相对于来说,这个功能是比较好实现的,只需要一个方法即可。可以将其声明在比赛的流程类的头文件中,然后在cpp文件中去实现这个方法,方法的简单实现如下:
void SpeechManager::systemExit() {
exit(0);
}
这一块主要的功能是能够分析清楚比赛的整体流程,按照项目的要求,来模拟整个比赛的流程:
可以分析如下:
首先是存在12名选手,然后随机将其分为2组,每组6个人(抽签分组),完成了这个内容之后,那么可以开始第一轮比赛,每个小组取前三名选手进入第二轮比赛,现在总共的就是存在6名选手,再次抽签,进行第二轮比赛,然后等到第二轮比赛结束之后,记录前三名选手的信息(编号,以及分数),保存到文件中。这个就是整体的流程,将上面的文字使用方法的流程体现出来如下:
// 比赛流程实现
void SpeechManager::startSpeech() {
//第一轮比赛
//1、抽签
//2、比赛
//3、显示晋级结果
//第二轮比赛
//1、抽签
//2、比赛
//3、显示最终结果
//4、保存分数
}
在这个里面,我们就是需要设计四个方法,分别是抽签、比赛、显示晋级结果、保存分数,有了整体的流程,那么可以开始来实现单个方法。
模拟抽签的话,就是将选手的编号信息即可。我们可以采用vector
来存储选手,然后将vector shuffle即可。
void SpeechManager::speechDraw(){
// 只是需要将选手的序号打乱即可
cout << "第 << " << this->m_index << " >> 轮比赛选手正在抽签"<<endl;
cout << "---------------------" << endl;
cout << "抽签后演讲顺序如下:" << endl;
if (this->m_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;
cout << endl;
}
对于比赛,整体的情况是分为两轮的,但是我们可以设计一个小小的trick,使其不用每次都进行判断,这里我们采用一个新的vector来保存新的选手的消息,从而得到预先就进行了判断,而不用重复分别写第一轮比赛和第二轮比赛的内容。
比赛,对于本项目而言,就是给每个选手进行打分,然后对于第一轮,我们直接可以每个裁判的打分采用deque
进行存储,去掉最高分、最低分之后求平均就是每个选手的得分,然后将每个小组的前三名选手及其得分存储起来,这即是第二轮参赛选手;对于第二轮选手,同样的先进行抽签,然后评委开始给每个选手进行打分,最后得到前三名即可。
// 开始比赛
void SpeechManager::speechContext() {
multimap<double ,int,greater<int>> groupScore; //临时容器,保存key分数 value 选手编号
vector <int>v_Src; //比赛的人员容器
if (this->m_index == 1){
v_Src = v1;
}
else{
v_Src = v2;
}
int num = 0; //记录人员数,6个为1组
// 直接给每个人员进行打分
for(vector<int>::iterator it=v_Src.begin();it!=v_Src.end();it++){
num++;
//十个评委,分别打分
deque<double> d;
for(int i=0;i<10;i++){
double score=0;
score=(rand() % 401 + 600) / 10.f; // 600 ~ 1000
d.push_back(score);
}
//排序
sort(d.begin(),d.end(),greater<double>());
//去掉一个最高分以及一个最低分
d.pop_front();
d.pop_back();
// 计算总和
double sum=accumulate(d.begin(),d.end(),0.0f);
// 计算均分
double avg = sum / (double)d.size();
this->m_Speaker[*it].m_score[this->m_index - 1]=avg;
//6个人一组,用临时容器保存
groupScore.insert(make_pair(avg, *it));
if (num % 6 == 0){
cout << "第" << num / 6 << "小组比赛名次:" << endl;
for (multimap<double, int, greater<int>>::iterator it = groupScore.begin(); it != groupScore.end(); it++)
{
cout << "编号: " << it->second << " 姓名: " << this->m_Speaker[it->second].m_name << " 成绩: " << this->m_Speaker[it->second].m_score[this->m_index - 1] << endl;
}
int count = 0;
//取前三名
for (multimap<double, int, greater<int>>::iterator it = groupScore.begin(); it != groupScore.end() && count < 3; it++, count++){
if (this->m_index == 1){
v2.push_back((*it).second);
}
else{
vVictory.push_back((*it).second);
}
}
groupScore.clear();
cout << endl;
}
}
cout << "------------- 第" << this->m_index << "轮比赛完毕 ------------- " << endl;
}
这里的显示,只是需要将容器中的内容打印出来即可。
void SpeechManager::showScore(){
cout << "---------第" << this->m_index << "轮晋级选手信息如下:-----------" << endl;
vector<int>v;
if (this->m_index == 1){
v = v2;
}
else{
v = vVictory;
}
for (vector<int>::iterator it = v.begin(); it != v.end(); it++){
cout << "选手编号:" << *it << " 姓名: " << m_Speaker[*it].m_name << " 得分: " << m_Speaker[*it].m_score[this->m_index - 1] << endl;
}
cout << endl;
}
保存分数的话,主要是将其写入到文件中,这里只是需要记录第二名比赛前三名的信息,在C++中,我们写文件主要是包含以下五个步骤:
包含头文件
#include
创建流对象
ofstream ofs;
打开文件
ofs.open(“文件路径”,打开方式);
写数据
ofs << “写入的数据”;
关闭文件
ofs.close();
void SpeechManager::saveRecord() {
fstream ofs;
// 用输出的方式打开文件 -- 写文件
ofs.open("../speech.csv", ios::out | ios::app);
//将每个人数据写入到文件中
for (vector<int>::iterator it = vVictory.begin(); it != vVictory.end(); it++){
ofs << *it << ","
<< m_Speaker[*it].m_score[1] << ",";
}
ofs << endl;
//关闭文件
ofs.close();
cout << "记录已经保存" << endl;
this->fileIsEmpty= false;
}
查看记录的功能主要是将内容从文件中读取出来,这样就是可以看到每一届选手的前三名的成绩了。即分析清楚之后,我们就可以看到这是一个简单的读文件的操作:
void SpeechManager::readRecord() {
ifstream ifs;
ifs.open("../speech.csv",ios::in);
if (!ifs.is_open()){
this->fileIsEmpty = true;
cout << "文件不存在!" << endl;
ifs.close();
return;
}
// 先尝试读取一个字符,
char ch;
ifs>>ch;
if(ifs.eof()){
cout << "文件为空!" << endl;
this->fileIsEmpty = true;
ifs.close();
return;
}
//文件不为空,将读取的字符放回去
ifs.putback(ch);
string data;
int index=0;
while(ifs>>data) {
vector<string> v;
int pos = -1;
int start = 0;
while (true) {
pos = data.find(",", start); //从0开始查找 ','
if (pos == -1) {
break; //找不到break返回
}
string tmp = data.substr(start, pos - start); //找到了,进行分割 参数1 起始位置,参数2 截取长度
v.push_back(tmp);
start = pos + 1;
}
this->m_Record.insert(make_pair(index, v));
index++;
}
ifs.close();
}
清空记录的话,主要是将文件中的所有的信息删除即可。
void SpeechManager::clearRecord(){
//打开模式 ios::trunc 如果存在删除文件并重新创建
ofstream ofs("../speech.csv", ios::trunc);
ofs.close();
//初始化属性
this->initSpeech();
//创建选手
this->createSpeaker();
//获取往届记录
this->readRecord();
cout << "清空成功!" << endl;
}
至此,所有的小功能均已设计完毕。
当然,还有一些小bug的解决,在代码中有详细的体现哦.
欢迎到我的github上观看完整内容
C++ STL
bilibili 项目课(网址没找到,待补充)
通过这个小项目,可以重温一下stl相关的内容,熟悉流程。通过分析流程,更加能够对语言的认识更加的深刻。