本案例来源于黑马 C++视频课程,以下内容为学习笔记
1.1 比赛规则
学校举行一场演讲比赛,共有12个人参加。比赛共两轮,第一轮为淘汰赛,第二轮为决赛。
比赛方式:分组比赛,每组6个人;选手每次要随机分组,进行比赛
每名选手都有对应的编号,如 10001 ~ 10012
第一轮分为两个小组,每组6个人。 整体按照选手编号进行抽签后顺序演讲。
当小组演讲完后,淘汰组内排名最后的三个选手,前三名晋级,进入下一轮的比赛。
第二轮为决赛,前三名胜出
每轮比赛过后需要显示晋级选手的信息
1.2 程序功能
开始演讲比赛:完成整届比赛的流程,每个比赛阶段需要给用户一个提示,用户按任意键后继续下一个阶段
查看往届记录:查看之前比赛前三名结果,每次比赛都会记录到文件中,文件用.csv后缀名保存
清空比赛记录:将文件中数据清空
退出比赛程序:可以退出当前程序
本案例使用 VS 2022,打开→ 空项目→输入项目名称→选择项目路径→添加文件
功能描述:
提供菜单界面与用户交互
对演讲比赛流程进行控制
与文件的读写交互
练习知识点:
1 面向对象的三大特性为:封装、继承、多态
2 对象的初始化(构造函数)
3 文件的读写
代码实现:创建 speechManager.h 和 speechManager.cpp
头文件常包含的内容:(c++ primer plus 原话)
函数原型
使用#define 或const 定义的符号变量
结构声明
类声明
模板声明
内联函数
speechManager.h
#pragma once
#include
using namespace std;
class SpeechManager
{
public:
SpeechManager();//构造函数
~SpeechManager();//析构函数
};
speechManager.cpp
源代码文件:实现项目的功能代码文件
#pragma once
#include
using namespace std;
SpeechManager::SpeechManager()//构造函数
{
}
SpeechManager::~SpeechManager()//析构函数
{
}
功能描述:
与用户交互的界面
在管理类中,添加成员函数
void show_Menu()
void SpeechManager::show_Menu()
{
cout << "********************************************" << endl;
cout << "************* 欢迎参加演讲比赛 ************" << endl;
cout << "************* 1.开始演讲比赛 *************" << endl;
cout << "************* 2.查看往届记录 *************" << endl;
cout << "************* 3.清空比赛记录 *************" << endl;
cout << "************* 0.退出比赛程序 *************" << endl;
cout << "********************************************" << endl;
cout << endl;
}
然后,在主函数调用函数进行测试。
功能描述:
main()函数中,提供分支选择,提供每个功能接口
#include
#include "speechManager.h"
using namespace std;
int main()
{
SpeechManager sm;
int choice = 0;//用来存储选择项
while (true)
{
sm.show_Menu();
cout << "please input your choice:" << endl;
cin >> choice;
switch (choice)
{
case 1://start speech
break;
case 2://查看记录
break;
case 3://清空记录
break;
case 0: //退出系统
break;
default:
system("cls");//clear the screem
break;
}
}
system("pause");
return 0;
}
功能描述:
当选择为0时, 实现程序的退出
> 涉及知识点:
Exit() 是电脑函数。
函数名: exit()
所在头文件:stdlib.h ,在C++e外部依赖项中可以找到这个
功 能: 关闭所有文件,终止正在执行的进程。
exit(1)表示异常退出.这个1是返回给操作系统的。
exit(x)(x不为0)都表示异常退出
exit(0)表示正常退出
在管理类头文件中添加,.cpp文件中提供功能的实现
void exitSystem()
void SpeechManager::exitSystem()
{
cout << "see u!bye bye!" << endl;
system("pause");
exit(0);
}
测试:
在主函数中测试
case 0: //退出系统
sm.exitSystem();
break;
> 功能分析
比赛流程分析
抽签→开始演讲比赛→显示第一轮比赛结果
抽签→开始演讲比赛→显示前三名结果→保存分数
类属性:选手姓名(string),分数(double)
头文件中创建speaker.h文件,并添加代码
#pragma once
#include
using namespace std;
class speaker
{
public:
string m_Name;
double m_Score[2];// **进入第二轮的选手,会获得两次分数,所以是数组
};
需求分析:
1、比赛开始总共12名选手,每个选手:编号 10001 ~ 10012,姓名,评委评分后的得分
2、对于流程管理系统来说,利用编号或者姓名就可以区分每个选手
//container all 12 speakers
vector<int>v1;
//container all second step speakers
vector<int>v2;
//container all winner speakers
vector<int>vVictory;
//container contain the num of speaker and the class of speaker
map<int,Speaker>m_Speaker;
功能描述:
初始化比赛记录相关容器 v1、 v2、 vVictory、m_Speaker
在speechManager.h中添加成员函数 void initSpeech();在speechManager.cpp中实现成员函数 void initSpeech();
void SpeechManager::initSpeech()
{
this->v1.clear();
this->v2.clear();
this->vVictory.clear();
this->m_Speaker.clear();
//初始化比赛轮数
this->m_Index=1;
}
在speechManager.cpp中构造函数调用void initSpeech();
SpeechManager::SpeechManager()
{
this->initSpeech();
}
知识点:
vector 容器 map 容器的清空操作 ~.clear();
功能描述:
生成12名选手,基本思路是,利用string 函数
在speechManager.h中添加成员函数 void creatSpeaker();在speechManager.cpp中实现成员函数 void creatSpeaker();
void SpeechManager::creatSpeaker();
在speechManager.cpp中构造函数调用void initSpeech();
SpeechManager::creatSpeaker()
{
string nameSeed="ABCDEFGHIJKL";
for(int i=0;i<nameSeed.size();i++)
{
string name="选手";
name+=nameSeed[i];
Speaker sp;
sp.m_Name=name;
for(int j=0;j<2;j++)//初始化成绩,成绩共两轮
{
sp.m_Score[i]=0;
}
this->v1.push_back(i+10001);//初始化v1容器
this->m_Speaker.insert(make_pair(i+10001,sp));//初始化m_Speaker
}
在构造函数中调用void createSpeaker()
SpeechManager::SpeechManager()
{
this->createSpeaker();
}
知识点:
string 函数:字符串拼接操作 +=
vector容器:vector 容器的插入 push_back(尾插法)
map容器:map容器的插入 push_back,上面是使用的第二种
for (map<int, speaker>::iterator it = sm.m_Speaker.begin(); it != sm.m_Speaker.end(); it++)
{
cout << "选手编号:" << it->first
<< "姓名" << it->second.m_Name
<< "成绩" << it->second.m_Score[0] << endl;
}
功能描述:
利用一个成员函数 void startSpeech()来管理比赛流程
在speechManager.h中添加成员函数 void startSpeech();在speechManager.cpp中实现成员函数 void startSpeech();
void SpeechManager::startSpeech()
{
//第一轮比赛开始,初始化,已经在构造函数中完成
//1、抽签(12名选手)
//2、比赛(择优选择6名)
//3、显示晋级结果
//第二轮比赛开始,初始化
//1、抽签(6名晋级的选手)
//2、比赛(6名选手分数择优选择3名)
//3、显示最终结果
//4、保存分数
}
功能描述:
为了比赛的公平公正,需要将12名选手随机编号
在speechManager.h中添加成员函数 void speechDraw();在speechManager.cpp中实现成员函数void speechDraw();
void SpeechManager::speechDraw()
{
if(this->m_Index==1)
{
cout << "第" << this->m_Index << "轮比赛选手正在抽签" << endl;//提示信息
cout << "--------------------------------" << endl;
cout << "抽签后演讲顺序如下:" << endl;
random_shuffle(v1.begin(),v1.end()); //利用 random_shuffle 洗牌算法打乱顺序
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;
}
在startSpeech比赛流程控制的函数中,调用抽签函数
void SpeechManager::startSpeech()
{
//第一轮比赛
//1、抽签
this->speechDraw();
//2、比赛...
}
知识点:
random_shuffle洗牌算法,底层原理在csdn 有相关介绍
vector容器的遍历输出(内置数据类型的写法 自定义数据类型的写法)
功能描述:
**第一轮:**为了比赛的公平公正,需要将12名选手随机分为两组,
并且每个选手由10名评委进行打分,去掉最高分,去掉最低分,求平均分
根据分数的高低,从两组中,各选出3名选手,共6名选手进入下一轮**第二轮:**6名晋级的选手,随机抽签,再进行比赛,由10名评委进行打分,去掉最高分,去掉最低分,求平均分
根据分数的高低,最终选出三名 冠军 亚军 季军
在speechManager.h中添加成员函数 void speechContest();在speechManager.cpp中实现成员函数 void speechContest();
void SpeechManager::speechDraw()
{
if(this->m_Index==1)
{
cout << "第" << this->m_Index << "轮比赛开始" << endl;//提示信息
cout << "--------------------------------" << endl;
multimap<double,int,greater<double>>groupScore;//key 为分数 int 为选手
int num=0;
vector<int>v_Scr; //比赛人员的容器
if(this->m_Index==1) //参数选手容器是v1 还是v2
{
v_Scr=v1;
}
else
{
v_Scr=v2;
}
//遍历所有选手
for(vector<int>::iterator it=v_Scr.begin();it!=v_Scr.end();it++)
{
num++;//便于将人员进行分组
//评委打分
deque<double>d;//使用deque方便去掉最高分 和最低分 双端数组 pop_back() pop_front
for(int i=0;i<10;i++)
{
double Score=(rand()401+600)%/10.f;
d.push_back(score);
}
sort(d.begin(),d.end(),greater<double>());
// sort()进行排序,并利用内建关系仿函数 降序排列 头文件应该包括#include #include
d.pop_front();
d.pop_back();
double sum=accumulate(d.begin(),d.end(),0.0f);//算术生成算法 应包含头文件#include
double avg=sum/(double)d.size();//queue 容器的大小的方法 .size()
//将分数存入map容器的 对象 的分数 数组中
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<double>>::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->Index-1]<<endl;
}
int count=0;
for(multimap<double,int,greater<double>>::iterator it=groupScore.begin();it!=groupScore.end()&&count<3;it++,count++)
{
if(m_Index==1)
{
v2.push_back((*it).second);
}
else
{
vVictory.push_back((*it).second);
}
}
groupScore.clear();//数据传递完成,清空即可,若不清空,执行第二轮的时候,将输出12人一组的情况
cout<<endl;
}
}
cout << "------------- 第" << this->m_Index << "轮比赛完毕 ------------- " << endl;system("pause");
}
在startSpeech比赛流程控制的函数中,调用抽签函数
void SpeechManager::startSpeech()
{
//第一轮比赛
//1、抽签
this->speechDraw();
//2、比赛
this->speechContest();
}
测试 ,在startSpeech比赛流程控制的函数中,调用比赛函数
知识点:
、queue 容器、排序算法、multimap容器等
功能描述:
显示晋级的人员信息,若为第一轮,显示晋级6人,若为第二轮,显示晋级3人信息
在speechManager.h中提供比赛的的成员函数 void showScore();
在speechManager.cpp中实现成员函数 void showScore();
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[m_Index - 1] << endl;
}
cout << endl;
system("pause");
system("cls");
this->show_Menu();
}
功能描述:第二轮比赛参赛为6人,从6人中晋级3人。流程为抽签→比赛→显示最终结果→保存分数
因为在抽签环节设置了容器的分支,所以,可以直接调用之前的函数,this->m_Index++
//开始比赛
void SpeechManager::startSpeech()
{
//第一轮比赛
//1、抽签
this->speechDraw();
//2、比赛
this->speechContest();
//3、显示晋级结果
this->showScore();
//第二轮比赛
this->m_Index++;
//1、抽签
this->speechDraw();
//2、比赛
this->speechContest();
//3、显示最终结果
this->showScore();
功能描述:将最终获胜的3人的数据,记录到文件中
在speechManager.h中添加保存记录的成员函数void saveRecord();
在speechManager.cpp中实现成员函数void saveRecord();
void SpeechManager::saveRecord()
{ ofstream 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;
}
涉及知识点:文件的输入输出
文件操作三大类:读 写 读写
上述涉及写文件操作:步骤如下: 头文件#include→创建流对象 →打开文件→写数据→关闭文件
在比赛流程控制的函数中,调用保存分数记录函数
/开始比赛
void SpeechManager::startSpeech()
{
//第一轮比赛
//1、抽签
this->speechDraw();
//2、比赛
this->speechContest();
//3、显示晋级结果
this->showScore();
//第二轮比赛
this->m_Index++;
//1、抽签
this->speechDraw();
//2、比赛
this->speechContest();
//3、显示最终结果
this->showScore();
//4、保存分数
this->saveRecord();
在主函数中测试。
功能描述:实现主功能,显示历史记录。
在speechManager.h中添加保存记录的成员函数void loadRecord();
判断文件是否为空的标志,bool fileIsEmpty;
添加往届记录的容器map
>m_Record
其中,m_Record中key 代表第几届,value 记录具体的信息
读取文件之前,要判断 1 、是否文件存在 2、判断文件是否为空?
void SpeechManager::loadRecord()
{
//第一步创建流对象
//打开文件
ifstream ifs("speech.csv",ios::in);
if(!ifs.is_open()) // ifs.is_open() 若能打开是 TRUE 打不开是Flase
{
this->fileIsEmpty=true;
cout<<"文件不存在"<<endl;
//关闭文件
ifs.close();
return;
}
char ch;
ifs>>ch;
if(ifs.eof())//ifs.eof 为true 表示无内容
{
cout<<"文件为空!"<<endl;
this->fileIsEmpty=true;
ifs.close();
return;
}
//文件不为空
this->fileIsEmpty=false;
ifs.push_back(ch);//读取的单个字符放回去
string data;
int index=0;//存放比赛届数
while(ifs>>data)//遍历每个字符,并摘抄出来 直到 eof
{
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;//+1 是加一个int 的长度
}
this->m_Record.insert(make_pair(index,v));
index++;
}
ifs.close();
}
在SpeechManager构造函数中调用获取往届记录函数
后果就是,当打开程序的时候,选择显示往届记录的时候,会无显示内容。
SpeechManager::SpeechManager()//构造函数
{
this->initSpeech(); //初始化属性
this->createSpeaker(); //创建选手
this->loadRecord();
}
知识点:
读文件步骤如下:
#include
#include
void test01()
{
ifstream ifs;
ifs.open("test.txt", ios::in);
if (!ifs.is_open())
{
cout << "文件打开失败" << endl;
return;
}
//第一种方式
//char buf[1024] = { 0 };
//while (ifs >> buf)
//{
// cout << buf << endl;
//}
//第二种
//char buf[1024] = { 0 };
//while (ifs.getline(buf,sizeof(buf)))
//{
// cout << buf << endl;
//}
//第三种
//string buf;
//while (getline(ifs, buf))
//{
// cout << buf << endl;
//}
char c;
while ((c = ifs.get()) != EOF)
{
cout << c;
}
ifs.close();
}
int main() {
test01();
system("pause");
return 0;
}
//总结:
//读文件可以利用 ifstream ,或者fstream类
//利用is_open函数可以判断文件是否打开成功
//close 关闭文件
功能描述:将数据读取了之后,需要将历史记录显示出来,也就是显示map m_Record中的内容
在speechManager.h中添加保存记录的成员函数void showRecord();
void SpeechManager::showRecord()
{
for(int i=0;i<this->m_Record.size();i++)
{
cout << "第" << i + 1 << "届 " <<
"冠军编号:" << this->m_Record[i][0] << " 得分:" << this->m_Record[i][1] << " "
"亚军编号:" << this->m_Record[i][2] << " 得分:" << this->m_Record[i][3] << " "
"季军编号:" << this->m_Record[i][4] << " 得分:" << this->m_Record[i][5] << endl;
}
system("pause");
system("cls");
}
知识点:[https://blog.csdn.net/m0_56494923/article/details/123239292?ops_request_misc=&request_id=&biz_id=102&utm_term=map%E5%87%BD%E6%95%B0%20%20%E7%9B%B4%E6%8E%A5%E7%94%A8%5B%20%5D%E8%AE%BF%E9%97%AE&utm_medium=distribute.pc_search_result.none-task-blog-2allsobaiduweb~default-1-123239292.142v35pc_rank_34,185v2control&spm=1018.2226.3001.4187]
可参考上面map函数的用法,比视频讲解的要详细
测试
功能描述:将文件和记录全部清空,在清空之前给用户二次确认
在speechManager.h中添加保存记录的成员函数 void clearRecord();
void SpeechManager::clearRecord()
{
cout << "确认清空?" << endl;
cout << "1、确认" << endl;
cout << "2、返回" << endl;
int select=0;
cin>>select;
if(select==1)
{
ofstream ofs("speech.csv",ios::trunc);//通过删除文件并重建的方式打开文件
ofs.close();
//初始化属性
this->initSpeech(); //初始化中,将相应的容器清空
//创建选手
this->createSpeaker();//用于下一次比赛
//获取往届记录
this->loadRecord();//将fileIsEmpty 置为TRUE
cout << "清空成功!" << endl;
}
system("pause");
system("cls");
}
完结撒花!!!