(史上最详细)C++实战-基于STL 演讲比赛流程管理系统

项目需求

本案例来源于黑马 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;

包含知识点:STL 常用容器 vector map
(史上最详细)C++实战-基于STL 演讲比赛流程管理系统_第1张图片

初始化属性

功能描述:
初始化比赛记录相关容器 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,上面是使用的第二种
(史上最详细)C++实战-基于STL 演讲比赛流程管理系统_第2张图片

(史上最详细)C++实战-基于STL 演讲比赛流程管理系统_第3张图片
测试(利用下列for语句)

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→创建流对象 →打开文件→写数据→关闭文件
(史上最详细)C++实战-基于STL 演讲比赛流程管理系统_第4张图片
在比赛流程控制的函数中,调用保存分数记录函数

/开始比赛
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();
}

知识点:
读文件步骤如下:

  1. 包含头文件
    #include
  2. 创建流对象
    ifstream ifs;
  3. 打开文件并判断文件是否打开成功
    ifs.open(“文件路径”,打开方式);
  4. 读数据
    四种方式读取
  5. 关闭文件
    ifs.close();
#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");
}

完结撒花!!!

你可能感兴趣的:(C++,c++,开发语言)