最近把c++给学了一些,暑假实习平时上班没什么事做就想写一写博客记录一下,水平不算特别高,也是跟着网课一起来设计的。
想要设计一个学生演讲比赛管理系统,思路大抵是这样。弹出主界面让用户选择功能:a.进行演讲比赛,b.查看往届冠亚季军,c.清空往届记录,d.退出。
具体效果图如下:
(github的SSH如下:git@github.com:Ca1iber/SpeechContestManagingSystem.git)
既然要举办比赛,参赛选手和赛制是必不可少的:十二名选手分成两组,每组六个人分别比赛,选出分数前三高的选手进入第二轮比赛,第二轮六名选手角逐出冠亚季军并记录在文件中。因此我们需要创建两个类:赛制和选手。
先说赛制:最开始我们需要实现的功能是展示菜单并对参赛选手初始化以获得参赛人员名单,需要创建比赛流程函数,还需要用以存储每轮比赛参赛选手的容器,我们一步一步来。
class Manager
{
public:
Manager();
~Manager();
void ShowMenu(); //展示菜单,提供用户接口
void Init(); //在比赛开始时对容器进行初始化,在构造函数中调用
void UpLoadContestors(const Speaker& spkr);//用以将参赛选手的名单放入容器中
std::vector<Speaker> Round1;
std::vector<Speaker> Round2;
std::vector<Speaker> Final;
int Round;
}
//管理系统构造函数
Manager::Manager()
{
this->Init();
}
//管理系统析构函数
Manager::~Manager()
{
;
}
//展示菜单函数
void Manager::ShowMenu()
{
std::cout << "*********************************************" << std::endl;
std::cout << "******* Welcome to the speech contest *******" << std::endl;
std::cout << "******* Press 1 to start the contest *******" << std::endl;
std::cout << "******* Press 2 to check the record *******" << std::endl;
std::cout << "******* Press 3 to clear the record *******" << std::endl;
std::cout << "************** Press 0 to quit **************" << std::endl;
std::cout << "*********************************************" << std::endl;
std::cout << std::endl;
}
//初始化函数
void Manager::Init()
{
this->Round1.clear();
this->Round2.clear();
this->Final.clear();
this->Round = 1;
}
现在我们就定义了比赛正式开始前需要定义的操作。定义完了赛制管理,我们就来开始定义参赛选手的相关参数了:参赛选手的参数有:选手的名字和选手的成绩。注意有的选手可能不止一轮成绩,我们可以定义两个变量来存储,也可以定义一个数组才存储,这里我选择用数组来定义:
class Speaker
{
public:
//名字种子,用以创建选手名字
std::string NameSeed[12] = { "A","B","C","D","E","F","G","H","I","J","K","L" };
Speaker(int i);
~Speaker();
std::string m_name;
int m_number;
double GeadeArray[2];
}
Speaker::Speaker(int i)
{
this->m_name = "选手" + this->NameSeed[i];
this->m_number = 10001 + i;
for (int j = 0; j < 2; j++)
{
this->GeadeArray[j] = 0;
}
}
Speaker::~Speaker()
{
;
}
有了这些,我们就可以在main函数中开始我们的初步框架了:
int main()
{
Manager manager;//创建manager对象得到管理系统
int choice =1;
//创建选手信息,并将选手上传至容器
for (int i = 0; i < 12; i++)
{
Speaker speaker(i);
manager.UpLoadContestors(speaker);
}
//在while循环中不断询问用户操作直到用户选择退出
while(choice!=0)
{
manager.ShowMenu();
std::cout << "请输入您的选择:" << std::endl;
std::cin >> choice;
switch (choice)
{
case 1:
std::cout << "比赛开始!" << std::endl;
break;
case 2:
std::cout << "以下是往届比赛记录:" << std::endl;
break;
case 3:
//删除往届记录
break;
case 0:
//退出系统
break;
default:
system("cls");//清屏
break;
}
}
system("pause");
}
这就是我们比赛管理系统的初步框架了,接着我们只需要根据功能慢慢添加成员函数就可以了。
那么接下来我们先来做最简单的一部分,输入0退出系统,我们在Manager类中声明函数并在cpp文件中实现
class Manager
{
void Quit();
}
void Manager::Quit()
{
std::cout << "感谢使用,欢迎下次使用" << std::endl;
}
退出系统功能是最容易实现的,只需要输出一行提示用户,系统自己就会因为choice=0退出while循环,进而直接结束系统。
完成设计之后,我们就要开始写最复杂也是最主要的比赛流程实现部分了
比赛流程大概如下:
class Manager
{
void ContestBegin();
}
void Manager::ContestBegin()
{
//第一步抽签
//比赛
//显示晋级结果
//第二轮比赛
//抽签
//比赛
//显示最终结果冠亚季
//保存名单
}
按照步骤,我们来写抽签函数:
在Manager类中添加Draw()对象
class Manager
{
void Draw();
}
//抽签函数
void Manager::Draw()
{
if (this->Round == 1) //这里添加判断,是因为第二轮比赛只有六个选手,并不需要抽签
{
std::cout << "第" << this->Round << "轮比赛选手正在抽签" << std::endl;
std::cout << "---------------------------------------------" << std::endl;
std::cout << "抽签结果如下:" << std::endl;
}
if (this->Round == 1)
{//用到的random_shuffle()对象在头文件algorithm中
//当然,在C++11更高版本中已经被废弃,取而代之的是shuffle()。我更加推荐使用后者
std::random_shuffle(Round1.begin(), Round1.end());//随机打乱
for (std::vector<Speaker>::iterator iter = Round1.begin(); iter != Round1.end(); iter++)
{
(*iter).PrintID();
}
std::cout << std::endl;
std::cout << "---------------------------------------------" << std::endl;
}
else if (this->Round == 2)
{
std::random_shuffle(Round2.begin(), Round2.end());//随机打乱
for (std::vector<Speaker>::iterator iter = Round2.begin(); iter != Round2.end(); iter++)
{
(*iter).PrintID();
}
std::cout << std::endl;
std::cout << "---------------------------------------------" << std::endl;
}
//std::cout << "--------------------------------------------" << std::endl;
system("pause");
std::cout << std::endl;
}
这样抽签函数就写完了。抽签过程完成后就要正式开始比赛了,也就来到了最难最容易出错的部分。
我们依然还是在Manager类中添加比赛对象RoundOneBegin()
class Manager
{
void RoundOneBegin();
}
void Manager::RoundOneBegin()
{
std::cout << "----------------第" << this->Round << "轮比赛开始-----------------" << std::endl;
//统计六人一组,利用临时数据
int num = 0;
std::vector<Speaker>v_Src;//临时容器对数据进行操作
if (this->Round == 1)
{
v_Src = this->Round1;
}
else
{
v_Src = this->Round2;
}
//遍历所有选手开始比赛
for (std::vector<Speaker>::iterator iter = v_Src.begin(); iter != v_Src.end(); iter++)
{
num++;
double score;
//评委打分
std::deque<double>d;//存储分数的容器,需要包含deque头文件,用于存储分数计算平均分
for (int i = 0; i < 10; i++)
{
score = (rand() % 401 + 600) / 10.0f; //600~1000
//printf("%.1f ", score);//打印分数
d.push_back(score);
}
sort(d.begin(),d.end(),std::greater<double>());//排序
d.pop_front();//去除最高分,最低分
d.pop_back();
double sum = std::accumulate(d.begin(), d.end(),0.0f);//总分
//accumulate对象在<numeric>中,接受三个参数:起始迭代器,末尾迭代器和初始累加值
//作用是对某个范围内的数据进行累加操作
double avg = sum / (double)d.size();//平均分
//将成绩根据比赛轮数录入
(*iter).SetGrade(avg,*this);
if (this->Round == 1)
{
Round1 = v_Src;
}
else if (this->Round == 2)
{
Round2 = v_Src;
}
if (this->Round == 1)//第一轮的处理
{
//每六人取前三名
if (num % 6 == 0)
{
std::cout << std::endl;
std::cout << std::endl;
std::cout << "第" << num / 6 << "小组比赛名次如下:" << std::endl;
if (num / 6 == 1)//排序第一组
{
VectorBubble(*this);//排序的逻辑将在稍后讲解
//Test Code,测试是否正确排序
for (size_t u = 0; u < this->Round1.size()/2; u++)
{
this->Round1[u].PrintName();
std::cout << "的成绩是:";
this->Round1[u].PrintGrade(*this);
}
//取走第一组的前三名,根据当前比赛轮次将其放入新的容器中
for (size_t q = 0; q < 3; q++)
{
this->Round2.push_back(this->Round1[q]);
}
}
else if (num / 6 == 2)//排序第二组
{
VectorBubble(*this);
for (int u = 6; u < 12; u++)
{
this->Round1[u].PrintName();
std::cout << "的成绩是:";
this->Round1[u].PrintGrade(*this);
}
//取走第一组的前三名,根据当前比赛轮次将其放入新的容器中
for (size_t q = 6; q < 9; q++)
{
this->Round2.push_back(this->Round1[q]);
}
}
}
// std::cout << std::endl;
}
else if (this->Round == 2)//第二轮处理
{
if (num % 6 == 0)
{
std::cout << std::endl;
std::cout << std::endl;
std::cout << "第二轮比赛名次如下:" << std::endl;
if (num / 6 == 1)
{
VectorBubble(*this);
for (size_t u = 0; u < this->Round2.size(); u++)
{
this->Round2[u].PrintName();
std::cout << "的成绩是";
this->Round2[u].PrintGrade(*this);
}
//取走前三名,决定冠亚季军
for (size_t q = 0; q < 3; q++)
{
this->Final.push_back(this->Round2[q]);
}
}
}
}
//std::cout << std::endl;
}
if (this->Round == 1)
{
std::cout << std::endl;
std::cout << "第" << this->Round << "轮比赛完毕,以下选手将晋级下一轮比赛:"<<std::endl;
for (std::vector<Speaker>::iterator iter2 = this->Round2.begin(); iter2 != this->Round2.end(); iter2++)
{
(*iter2).PrintName(); std::cout << " ";
}
std::cout << std::endl;
system("pause");
}
else if (this->Round == 2)
{
std::cout << std::endl;
std::cout << "第" << this->Round << "两轮比赛已完毕,将产生冠亚季军" << std::endl;
}
}
在这之中我们用到了非常低效的BubbleSort算法,我的实现如下:
void VectorBubble(Manager& man)
{
for (int i = 0; i < 6; i++)
{
for (int j = i; j < 6; j++)
{
if (man.Round == 1)
{
if (man.Round1[j].GeadeArray[0] > man.Round1[i].GeadeArray[0])
{
Speaker Temp = man.Round1[j];
man.Round1[j] = man.Round1[i];
man.Round1[i] = Temp;
}
}
else if (man.Round == 2)
{
if (man.Round2[j].GeadeArray[1] > man.Round2[i].GeadeArray[1])
{
Speaker Temp = man.Round2[j];
man.Round2[j] = man.Round2[i];
man.Round2[i] = Temp;
}
}
}
}
for (int i = 6; i < 12; i++)
{
for (int j = i; j < 12; j++)
{
if (man.Round == 1)
{
if (man.Round1[j].GeadeArray[0] > man.Round1[i].GeadeArray[0])
{
Speaker Temp = man.Round1[j];
man.Round1[j] = man.Round1[i];
man.Round1[i] = Temp;
}
}
}
}
}
作用是在封装元素为Speaker的Roundx容器中依据各个Speaker元素中封装的GradeArray[]的大小来对Roundx中的Speaker元素进行排序。其实用冒泡排序是最低效的方法,c++已经内置了排序函数sort(),但是sort是对元素直接排序,并不能对元素中某一子元素,比如依据struct a中的b变量的大小对struct进行排序,因此我们需要自己写一个CompareA函数来实现
#include <iostream>
#include <vector>
#include <algorithm>
struct Data {
int a;
int b;
int c;
// 可以添加其他变量
};
// 自定义比较函数,按照a变量从大到小排序
bool compareA(const Data& lhs, const Data& rhs) {
return lhs.a > rhs.a;
}
int main() {
std::vector<Data> v1 = { {10, 5, 8}, {5, 3, 9}, {8, 1, 4} };
std::vector<Data> v2 = { {15, 6, 2}, {7, 4, 7}, {12, 8, 3} };
std::vector<Data> v3 = { {20, 9, 1}, {3, 2, 6}, {9, 7, 5} };
// 将三个vector合并为一个新的vector
std::vector<Data> merged;
merged.insert(merged.end(), v1.begin(), v1.end());
merged.insert(merged.end(), v2.begin(), v2.end());
merged.insert(merged.end(), v3.begin(), v3.end());
// 使用自定义比较函数对新的vector进行排序
std::sort(merged.begin(), merged.begin()+4, compareA);
std::sort(merged.begin()+4, merged.end(), compareA);
// 打印排序后的结果
for (const auto& data : merged) {
std::cout << "a: " << data.a << ", b: " << data.b << ", c: " << data.c << std::endl;
}
return 0;
}
自定义一个比较函数就可以直接实现间接比较大小的功能,但是我在实际使用过程中遇到了非常大的无法调和的麻烦,因为我很菜......
所以我又尝试使用lambda将这一段函数复现
std::sort(this->Round1.begin()+5, this->Round1.end(), [this](const Speaker& sprk1, const Speaker& sprk2) {
if (Round == 1)
{
return sprk1.GeadeArray[0] > sprk2.GeadeArray[0];
}
else if (Round == 2)
{
return sprk1.GeadeArray[1] > sprk2.GeadeArray[1];
}
});
但是依然没有办法成功排序,于是我自己写了那个非常笨的排序方法:(
还记得我们一开始写的流程框架吗,我们现在可以把他填上一些东西了!
void Manager::ContestBegin()
{
//第一步抽签
this->Draw();
//比赛
this->RoundOneBegin();
//显示晋级结果
//第二轮比赛
//抽签
//比赛
//显示最终结果冠亚季
//保存名单
那么第一轮的比赛流程就已经写好了,那么第二轮呢?
其实很简单,两轮比赛的流程是一样的,我们可以直接copy过来:
void Manager::ContestBegin()
{
//第一步抽签
this->Draw();
//比赛
this->RoundOneBegin();
//显示晋级结果
//第二轮比赛
this->Round++;
//抽签
this->Draw();
//比赛
this->RoundOneBegin();
//显示最终结果冠亚季
//保存名单
就这样,我们功能a进行比赛的功能就做好了,那接下来就结束了吗?
不不不,因为我们比赛系统是在while函数中不断循环执行,我们连续比两届赛的时候,由于没有对上一次存储在容器中的数据进行处理,后续会出现错误,这是我们不希望看到的,所以我们还需要在每一届比赛结束之后再次处理一下容器
{
//保存名单
this->SaveRecord();
std::cout << "本届比赛圆满结束!" << std::endl;
system("pause");
system("cls");
this->Round--;
this->Round2.clear();
this->Final.clear();
}
在保存名单后面放上这些就好啦,这就是比赛的功能的全部实现啦
那么接下来就是保存数据的功能实现,我们依旧在Manager类中声明
class Manager
{
void SaveRecord();
void CheckRecord();
}
对于读写文件的操作,我们需要包含fstream头文件
void Manager::SaveRecord()
{
std::ofstream Out;
Out.open("FormerRecord.csv",std::ios::out|std::ios::app);//用追加的方式写文件
//将每个选手的数据写入文件中
for (std::vector<Speaker>::iterator iter = this->Final.begin(); iter != this->Final.end(); iter++)
{
Out << iter->m_name << "," << iter->GeadeArray[1] << ", ";
}
Out << std::endl;
Out.close();
std::cout << "文件记录完毕" << std::endl;
我们这里以.csv记录数据,是因为.csv文件以‘,’分割不同数据,这样就会有这样的效果
void Manager::CheckRecord()
{
std::ifstream In("FormerRecord.csv", std::ios::in);
if (!In.is_open())
{
std::cout << "文件不存在" << std::endl;
//this->FileEmpty() = true;
In.close();
return;
}
//文件清空
char ch;
In >> ch;
if (In.eof())
{
//this->FileEmpty() = true;
std::cout << "文件空" << std::endl;
In.close();
return;
}
//文件不空
//this->FileEmpty() = false;
In.putback(ch);//将刚才读取的单个字符收回
std::string data;
int index = 0;
while (In >> data)
{
std::vector<std::string> v;//存放六个string字符串
//std::cout << data;// << std::endl;
int pos = -1;
int start = 0;
while (true)
{
pos=data.find(",", start);
if (pos == -1)
{
break;
}
else
{
std::string temp = data.substr(start, pos - start);
//std::cout << temp << std::endl;
v.push_back(temp);
start = pos + 1;
}
}
this->m_Record.insert(std::make_pair(index,v) );
index++;
}
In.close();
for (std::map<int, std::vector<std::string>>::iterator iter = m_Record.begin(); iter != m_Record.end(); iter++)
{
std::cout << (iter->first)/3+1 << "三甲编号: " << iter->second[0] << " 分数 " << iter->second[1] << std::endl;
}
std::cout << std::endl;
}
这样就做好第二个功能啦
接下来就是删除功能
void ClearRecord();
void Manager::ClearRecord()
{
std::cout << "确认要清空历史记录吗?" << std::endl;
std::cout << "Press 1 to confirm" << std::endl;
std::cout << "Press 2 to cancal" << std::endl;
int Select = 0;
std::cin >> Select;
if (Select == 1)
{
std::ofstream ofs("FormerRecord.csv", std::ios::trunc);
ofs.close();
std::cout << "清空成功" << std::endl;
}
system("pause");
system("cls");
}
那么所有功能就都实现好啦