利用C++容器实现演讲比赛流程管理系统

1、演讲比赛流程管理系统

1.1 比赛规则

  • 学校举行一场演讲比赛,共有 12 个人参加。比赛共两轮,第一轮为淘汰赛,第二轮为决赛。
  • 每名选手都有对应的编号,如 10001~10012
  • 比赛方式:分组比赛,每组 6 个人;
  • 第一轮分为两个小组,整体按照选手编号进行抽签后顺序演讲。
  • 十个评委分别给每名选手打分,去除最高分和最低分,求的平均分为本轮选手的成绩
  • 当小组演讲完后,淘汰组内排名最后的三个选手,前三名晋级,进入下一轮的比赛。
  • 第二轮为决赛,前三名胜出
  • 每轮比赛过后需要显示晋级选手的信息

1.2 程序功能

  • 开始演讲比赛:完成整届比赛的流程,每个比赛阶段需给用户提示,用户按任意键后继续下一个阶段。
  • 查看往届记录:查看之前比赛前三名结果,每次比赛记录到.csv 后缀的文件中。
  • 清空比赛记录:清空文件中的数据。
  • 退出比赛程序:退出当前程序。

运行环境:Visual Studio 2022,X64,Debug

整体思路与之前写过的博客类似:利用C++多态完成职工管理系统

https://blog.csdn.net/zhendeshini_/article/details/149124809

技术要求:

  1. 掌握C++的指针、数组、函数的用法,程序流程结构if、switch、for语句的用法
  2. 对封装有基本了解
  3. 熟悉vector、map、deque等容器的基本用法
  4. 文件的加载与保存
  5. 分文件编写的用法

1.3 创建管理类

在这里实现所有功能的访问,先绘制好大致框架,后续需要添加的在这里添加即可,采用分文件编写

头文件完成类的声明,包含函数的构造、声明与析构

speechManager.h:

//speechManager.h

#pragma once
#include<iostream>
using namespace std;

//设计演讲管理类
class SpeechManager {
public:
	//构造函数声明
	SpeechManager();

	//展示菜单
	void showMenu();

	//退出系统
	void exitSystem();
    
    //开始演讲比赛
    void startSpeech();
    
    //显示往届记录
    void showRecord();
    
    //清空比赛记录
	void clearRecord();
    
	//析构函数声明
	~SpeechManager();
};

speechManager.cpp

//speechManager.cpp

#include "speechManager.h"

//函数实现

//构造函数定义
SpeechManager::SpeechManager() {}

//展示菜单
void SpeechManager::showMenu() {}

//退出系统
void SpeechManager::exitSystem() {}

//开始演讲比赛
void SpeechManager::startSpeech() {}

//显示往届记录
void SpeechManager::showRecord() {}

//清空比赛记录
void SpeechManager::clearRecord() {}

//析构函数定义
SpeechManager::~SpeechManager() {}

在写出对应的main函数:

演讲比赛管理系统.cpp

//这是演讲比赛管理系统.cpp的实现

#include "speechManager.h"

int main() {
	SpeechManager sm;//实例化对象
	system("pause");
	return 0;
}

总结:在开始阶段先写好这些功能的函数的声明、对应的函数实现、main函数的创建,最后实例化对象,用来访问所有功能。框架已经整体搭建完毕,接下来实现其中的具体功能。

1.4 演讲者类

将演讲者创建为一个类,添加人的属性,由于比赛是两轮竞赛,所以创建两个数组,用来存放第一轮、第二轮的得分。

speaker.h

//speaker.h
#pragma once
#include <iostream>
using namespace std;

class Speaker {
public:

	string m_Name;
	double m_Score[2];//两轮得分,创建一个数组
};

1.5 交互界面

大体框架实现后我们接下来我们来实现一个互动性的功能。
思路:在显示菜单实现上面有数字提示,我们可以利用switch语句实行多条件分支语句

显示菜单界面实现:

speechManager.cpp

//展示菜单
void SpeechManager::showMenu() {
	cout << "******************************************" << endl;
	cout << "************* 欢迎参加演讲比赛 ************" << endl;
	cout << "************* 1.开始演讲比赛 **************" << endl;
	cout << "************* 2.查看往届记录 **************" << endl;
	cout << "************* 3.清空比赛记录 **************" << endl;
	cout << "************* 0.退出比赛程序 **************" << endl;
	cout << "******************************************" << endl;
	cout << endl;
}

//退出系统
void SpeechManager::exitSystem() {
	cout << "欢迎下次再来:" << endl;
	system("pause");
	exit(0);
}

演讲比赛管理系统.cpp

//这是演讲比赛管理系统.cpp的实现

#include "speechManager.h"

int main() {
	SpeechManager sm;
    
	int select = 0;

	while (true) {
		sm.showMenu();
        
		cout << "请输入你的选择:" << endl;
		cin >> select;
		switch (select) {
		case 1://开始比赛
			sm.startSpeech();
			break;
		case 2://查看往届记录
			sm.showRecord();
			break;
		case 3://清空比赛记录
			sm.clearRecord();
			break;
		case 0://退出系统
			sm.exitSystem();
			break;
		default://清屏操作
			system("cls");
			break;
		}
	}

	system("pause");
	return 0;
}

1.6 功能分析

比赛流程

第一轮:抽签-开始演讲比赛-显示第一轮比赛结果
第二轮:抽签-开始演讲比赛-显示前三名结果-保存结果到文件。

  1. 在开始之前我们先想一个问题,目前有哪些数据类型?
    答:目前有speaker(m_Name,m_Score[2])这一数据类型,还缺少编号这一数据类型。

  2. 如何找到这些人呢?关联多轮比赛数据呢?
    显然根据姓名与分数都不合适,姓名可能有重复的,分数是随着轮数变化的,所以只剩下了静态属性编号,这是唯一的。

  3. 编号放在哪里?
    如果将编号放入类中,不使用容器,那么创建对象时需要手动创建类的实例化吗,数量固定Speaker s1 = {1, "张三", 0}; Speaker s2 = {2, "李四", 0};Speaker s3 = {3, "王五", 0};,抽签的顺序也得需要手动设置
    如n个人需要n!种可能所以需要大量代码,而且代码不能处理不确定性,人数变化,无法通过循环来完成批量处理,鲁棒性较差。所以最终选择容器,里面有随机打乱,可以批量创建,支持数据解耦如map容器map<int,Speaker>可以将编号与Speaker数据类型解藕,不像前者一样僵硬。

  4. 编号问题解决了,如何解决编号与人之间的强关联性、唯一性?

    编号是int数据类型,选手是Speaker类型,想到了map容器的key与value,键值对的关系,这样就建立起了强关联性、唯一性。

针对以上问题,我们建立以下容器,分别进行第一轮、第二轮、最总获胜者的编号管理容器vector,以及存放信息的容器map

speechManager.h

//speechManager.h
#include <vector>
#include <map>
……

//成员属性:
//保存第一轮比赛选手编号容器
vector<int>v1;

//保存第二轮比赛选手编号容器
vector<int>v2;

//显示最终胜利者容器
vector<int>vVictory;

//存放编号以及对应具体选手编号
map<int, Speaker>m_Speaker;

//存放比赛轮数
int m_Index;

1.7 开始比赛

可以看出开始比赛要完成的功能较多,所以将开始比赛作为一个大函数,里面封装具体的子功能函数

教程中是单独分两次进行比赛的,但是如果轮数变多之后,考虑到代码的可维护性,我们将其变为循环,整体框架如下:

创建选手-抽签-比赛-显示结果-下一轮-保存结果-重置比赛状态。

// 开始比赛
void SpeechManager::startSpeech() {
    // 比赛轮次循环
    for (int round = 0; round < 2; round++) {
        // 1、抽签
        this->speechDraw();
        // 2、比赛
        this->speechContest();
        // 3、显示结果
        this->showScore();

        // 准备下一轮
        if (round == 0) {
            this->m_Index++; // 只有从第一轮进入第二轮时需要更新索引
        }
    }

    // 4、保存结果到文件
    this->saveFile();

	// 重置比赛状态,为下一届比赛做准备
	this->initSPeech();//清除容器,重置轮次索引,清除当前届比赛
	this->createSpeaker(); //重新创建 12 名新选手
	this->loadFile();//重新加载往届记录

    cout << "本届比赛完毕!" << endl;
    system("pause");
    system("cls");
}

重置的目的:

  • initSPeech():清空容器(v1v2vVictorym_Speaker),重置轮次索引(m_Index=1),清除当前届比赛数据。
  • createSpeaker():重新创建 12 名新选手(否则选手 ID 会重复递增,导致冲突)。
  • loadFile():重新加载往届记录(确保 m_Record 反映最新文件状态,如用户在比赛后清空了记录)。

1.7.1 初始化属性

比赛开始之前先将属性进行初始化

speechManager.cpp:

//speechManager.cpp

#include "speechManager.h"

//函数实现

//构造函数定义
SpeechManager::SpeechManager() {
	//初始化容器和属性
	this->initSPeech();
}

//初始化容器和属性
void SpeechManager::initSPeech() {
	//容器都置空
	this->v1.clear();//第一轮容器置空
	this->v2.clear();//第二轮置空
	this->vVictory.clear();//最终结果容器置空
	this->m_Speaker.clear();//存放编号以及个人信息
	this->m_index = 0;//轮数置0
}

//析构函数定义
SpeechManager::~SpeechManager() {}

1.7.2 创建选手

//speechManager.cpp

#include "speechManager.h"

//函数实现

//构造函数定义
SpeechManager::SpeechManager() {
	//初始化容器和属性
	this->initSPeech();
	this->createSpeaker();

}

……
//创建选手
void SpeechManager::createSpeaker() {

	//创建对象
	string nameSeed = "ABCDEFGHIJKL";

	for (int i = 0; i < nameSeed.size(); i++) {
		Speaker speaker;

		//将姓名初始化
		speaker.m_Name = string("选手")+ nameSeed[i];

		//得分初始化
		for (int j = 0; j < 2; j++) {
			speaker.m_Score[j] = 0.0;
		}

		//12名选手编号,放入v1
		this->v1.push_back(i + 10001);

		//将编号与演讲者插入map,以键值对方式
		//map<int, Speaker>m_Speaker;已经创建好的map容器,忘记后vsStudio会智能提示注释
		this->m_Speaker.insert(make_pair(i + 10001, speaker));
	
	}
}

测试结果如下:

演讲比赛管理系统.cpp

SpeechManager sm;
//sm.createSpeaker();
//for (map<int, Speaker>::iterator it = sm.m_Speaker.begin(); it != sm.m_Speaker.end(); it++) {
//	cout << "编号:" << it->first 
//		<< "\t姓名:" << it->second.m_Name 
//		<< "\t分数:" << it->second.m_Score[0] << endl;
//}

在这里插入图片描述

1.7.3 抽签

流程中进行两轮抽签

利用记录比赛轮数的轮数作为判断依据,将对应的容器编号进行打乱顺序。

//抽签
void SpeechManager::speechDraw() {
	cout << "第" << 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;
	system("pause");
	cout << endl;
}

1.7.4 演讲比赛

思路:在前面的功能分析中,我们可以建立了第一轮容器v1、第二轮容器v2、最总获胜者的编号管理容器vector,以及存放编号的Speaker信息的容器map

  • 应该直接对原容器进行处理吗?

答:要对容器中的编号进行随机打乱,排序等等操作,如果直接使用原容器进行排序删除等操作,会破坏原来容器的内容,比如删除掉一个人,到下一届比赛时,人数就减少了,得重新初始化容器,不如直接copy一个副本,这样与那容器得数据始终保持初始状态,后续重复复用得时候,对副本进行修改即可,不会影响到原容器,数据安全性高。

生活实际例子:老板发给你的原版word文件让员工去修改,员工第一时间想到是的先下载原版初稿,然后多复制几份如1.0,1.1等等,在副本基础上进行修改,如果老板对1.0,1.1很不满意,可以打开原版初稿查看,保留了原有的版本,方便在原有基础进行重新修改。

下面来看一下关于演讲比赛的代码:

void SpeechManager::speechContest(){}

//比赛
void SpeechManager::speechContest() {
	cout << "-------------第" << this->m_Index << "轮比赛正式开始--------------" << endl;


	int num = 0;//用于统计记录人员个数,6人一组

	vector <int>v_Src;//比赛选手容器
	if (this->m_Index==1) {
		//执行第一轮
		v_Src = v1;
	}
	else{
		//执行第二轮
		v_Src = v2;
	}

	//遍历所有的选手进行比赛
	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 = (rand() % 401 + 600) / 10.f;
			//cout << score <<" ";
			d.push_back(score);
		}
	
		sort(d.begin(), d.end(),greater<double>());//降序排列
		d.pop_front();//去除最高
		d.pop_back();//去除最低

		double scoreSum = accumulate(d.begin(), d.end(),0.0f);//起始,终止,初始值
		double avg = scoreSum /(double) d.size();//小数/小数

		//"打印平均分" 
		//cout << "编号:" << *it << "\t姓名:" << this->m_Speaker[*it].m_Name
		//	<< "\t分数:" << avg ;
	
		//将平均分放入map容器
		this->m_Speaker[*it].m_Score[this->m_Index - 1] = avg;

		//准备临时容器存放小组成绩(降序排列)
		multimap<double, int, greater<double>>groupScore;//让分数作为键进行排序,编号是值

		//将打分数据放入到临时小组容器中
		groupScore.insert(make_pair(avg, *it));//均分作为键,value是具体选手编号

		//每六人取出前三名
		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 << "\t姓名:" << this->m_Speaker[it->second].m_Name
					<< "\t分数:" << this->m_Speaker[it->second].m_Score[this->m_Index-1] << endl;
			}

			//取走前三名
			int count = 0;
			for (multimap<double,int , greater<double>>::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 << "-------------------第"<<this->m_Index<<"轮比赛完毕-------------------"<<endl;
	system("pause");

}

测试结果:
在这里插入图片描述

代码流程图

下面这张可视化图片是对speechContest()这一函数的逻辑解释,先整体查看数据流向
在这里插入图片描述

详细解释

(1) void SpeechManager::createSpeaker()函数:

创建容器v1,v2,map,进行初始化,填充编号,分数初始化,将编号、Speaker放入map容器,实现数据解耦与强关联(功能分析)。这里key值对应的是编号,value对应的是Speaker,里面存放的是姓名以及两轮分数,实现了唯一性。
在这里插入图片描述

当然void SpeechManager::speechDraw() 函数有编号打乱功能,较为简单,序号无固定规律,为了可读性,这里就不展示打乱后的了,同时编号以1-12代替10001-10012。

(2) 创建副本容器v_Src
在这里插入图片描述

	vector <int>v_Src;//比赛选手容器
	if (this->m_Index==1) {
		//执行第一轮
		v_Src = v1;
	}
	else{
		//执行第二轮
		v_Src = v2;
	}

(3) 评委打分,更新map容器中的数据

由于评委打的分数要进行去掉最高分、最低分,所以使用sort算法进行降序排列,虽然vector的数据删除功能针对尾部元素效率很高,只需调整尾部指针,无需移动其他元素。但是vector内部是连续内存,导致删除头部元素时较为困难,需要将后续所有的元素进行向前移动,时间消耗较大。而deque内存布局是分段连续的,通过buffer缓冲区进行指针数组管理,对于头部删除不需要移动其他元素。
在这里插入图片描述

排序之后经过pop_frontpop_back进行掐头去尾,在利用accumulate实现累加,最后除以个数得到平均分。

将每个人计算好的平均分放入到map容器中,it是key值索引指针,解引用即可得到编号,利用这个编号(key)可以找到map容器中的key值对应得Speaker,在利用Speaker下具体key值即可访问到具体的speaker中的m_Score信息对应这行代码:

this->m_Speaker[*it].m_Score[this->m_Index - 1] = avg;,但注意此时的排序是为了得到每一个选手掐头去尾的平均分,选手与选手之间的分数还并未排序,这也就是图中红色的部分尚未排序。

同时,map容器中的key值是编号,无法完成按照分数进行排序。

至此,这一步完成了每位选手的平均分,并且存入到map中,达到了唯一性与解耦。

//遍历所有的选手进行比赛
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 = (rand() % 401 + 600) / 10.f;
		//cout << score <<" ";
		d.push_back(score);
	}

	sort(d.begin(), d.end(),greater<double>());//降序排列
	d.pop_front();//去除最高
	d.pop_back();//去除最低

	double scoreSum = accumulate(d.begin(), d.end(),0.0f);//起始,终止,初始值
	double avg = scoreSum /(double) d.size();//小数/小数

	//"打印平均分" 
	//cout << "编号:" << *it << "\t姓名:" << this->m_Speaker[*it].m_Name
	//	<< "\t分数:" << avg ;

	//将平均分放入map容器
	this->m_Speaker[*it].m_Score[this->m_Index - 1] = avg;

(4) 对所有选手得分进行降序排列

上一步我们已经完成了对map容器数据的保存,但是由于key值的原因,导致无法进行所有人的分数排序,所以又面临一个问题是对原容器进行修改还是新copy一个,在功能分析已经解释过这个原因了,所以毫无疑问是副本了。

但是会有新问题,我们知道map容器可以实现自动排序,需要把分数作为key值,也就是互换,所以这次的副本不再是简单的拷贝map了,如何实现呢?我们知道可以利用map容器中的insert功能创建一对新的键值对,将数据类型进行重新调整即可解决新问题。

但又有新问题,按照分数进行排序,分数很有可能会重复,map是不允许有重复的,所以使用允许重复的multimap容器。

所以最终使用的是multimap这个临时容器存储map中的数据,实现自动排序。
在这里插入图片描述

对应的代码:

int num = 0;//用于统计记录人员个数,6人一组
//准备临时容器存放小组成绩(降序排列),放在for循环外,要记录6人成绩,否则会导致只记录2人
multimap<double, int, greater<double>>groupScore;//让分数作为键进行排序,编号是值
……
// for循环:

//将打分数据放入到临时小组容器中
groupScore.insert(make_pair(avg, *it));//均分作为键,value是具体选手编号

输出代码这里较为简单,只需要注意一下键值对的位置即可,编号是second,输出姓名则告诉m_Speaker的对应编号即可访问到其下的m_Name;

每六个人进行取走前三名进行下一轮晋级,所以对multimap进行操作即可,*it即可获得编号,将编号插入到下一轮对应的容器,第一轮的结果就插入到第二轮容器v2,第二轮容器就插入到最终容器vVector

//每六人取出前三名
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 << "\t姓名:" << this->m_Speaker[it->second].m_Name
			<< "\t分数:" << this->m_Speaker[it->second].m_Score[this->m_Index-1] << endl;
	}

	//取走前三名
	int count = 0;
	for (multimap<double,int , greater<double>>::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();//小组容器清空
}

最后给临时容器清空,方便下一轮循环。

最后展示一下map容器(m_Speaker)是如何实现与临时容器multimap(groupScore)实现数据交互的,桥梁是"编号"!

在这里插入图片描述

  • 测试

main函数:

sm.speechContest();

在这里插入图片描述

1.7.5 显示比赛分数

根据m_Index判断处于哪一轮,然后将map容器(m_Speaker)中的信息进行显示,同理迭代器it进行解引用得到编号,利用编号可以访问到m_Speaker唯一对应的信息,只不过需要注意的是m_Score有两个数组,第一轮和第二轮由于Speaker属性是0和1,m_Index是1和2,所以要进行减一即可。

//显示得分
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 << "\t姓名:" << this->m_Speaker[*it].m_Name
			<< "\t分数:" << this->m_Speaker[*it].m_Score[this->m_Index-1] << endl;//不指明具体数组索引会显示数组首地址
	}
	cout << endl;

	system("pause");
	system("cls");
	this->showMenu();
}

至此演讲比赛这一功能已经实现!

1.8 保存文件

speechManager.h添加文件相关的函数声明

//保存文件
void saveFile();

//加载文件
void loadFile();
……

//清空文件
void clearRecord();

//判断文件是否为空
bool fileIsEmpty;

函数定义

#define FILENAME "speech.csv"//宏定义 文件名

//保存文件
void SpeechManager::saveFile() {
	ofstream ofs;
	ofs.open(FILENAME,ios::out|ios::app);//用追加方式写文件

	//将每个选手数据写入到文件中
	for (vector<int>::iterator it = vVictory.begin(); it != vVictory.end(); it++) {
		ofs<< *it <<","<<this->m_Speaker[*it].m_Name <<","<< this->m_Speaker[*it].m_Score[m_Index - 1]<<",";
	}
	ofs << endl;
	ofs.close();
 
	cout << "已经保存完成!" << endl;

	//更改文件不为空的状态
	this->fileIsEmpty = false;
}

在保存文件这里记得指定文件状态。

加载文件

speechManager.h添加记录往届比赛的容器

//往届记录
map<int, vector<string>>m_Record;
//加载文件
void SpeechManager::loadFile() {
   //清空m_Record中内存中的遗留数据,不是真实数据,后面会补充 
	this->m_Record.clear();

	ifstream ifs;
	ifs.open(FILENAME, 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;
	}

	// 3、文件存在,并且记录数据
	this->fileIsEmpty = false;
	ifs.putback(ch); // 如果读取了一个字符,将其放回输入流

	string line;
	int index = 0; // 记录总记录数

	while (getline(ifs, line)) { // 按行读取完整数据
		vector<string> v; // 存储当前行的所有字段
		int pos = 0;      // 搜索起始位置
		int commaPos = 0; // 逗号位置

		// 解析当前行的所有字段(按逗号分割)
		while ((commaPos = line.find(',', pos)) != string::npos) {
			v.push_back(line.substr(pos, commaPos - pos));
			pos = commaPos + 1;
		}
		v.push_back(line.substr(pos)); // 添加最后一个字段(无逗号后的内容)

		// 确保字段数量足够(至少3个:编号、姓名、分数)
		if (v.size() >= 3) {
			this->m_Record.insert(make_pair(index, v));
			index++;
		}
		else {
			cout << "警告:格式错误的行 - " << line << endl;
		}
	}	
	ifs.close();

	////测试读取结果,以读取每一行第一个为列
	//for (map<int, vector<string>>::iterator it = this->m_Record.begin(); it != this->m_Record.end(); it++) {
	//	cout << "第" << it->first+1 << "届记录" << endl;
	//	cout << "编号:" << it->second[0] << "\t名字" << it->second[1] << "\t分数" << it->second[2] << endl;
	//}
}

在这里插入图片描述

在这里插入图片描述

第三种情况主要针对这种有记录的,利用一个map容器接受这些数据,key代表每一届的记录,string容器代表从","分割得到的编号,姓名,分数。

1.10 查看往届记录

从csv文件中读取

与上面的测试代码雷士,从m_Record中获取每一行的从头到尾数据。

//显示往届记录
void SpeechManager::showRecord() {
	if (this->fileIsEmpty) {
		cout << "文件为空或不存在!" << endl;
	}
	for (int i = 0; i < this->m_Record.size(); i++) {
		cout << "第" << i + 1 << "届" << endl;
		cout<< "冠军编号" << this->m_Record[i][0] 
			<< "\t名字:" << this->m_Record[i][1] << "\t分数:" << this->m_Record[i][2] << endl;
		cout<< "亚军编号" << this->m_Record[i][3]
			<< "\t名字:" << this->m_Record[i][4] << "\t分数:" << this->m_Record[i][5] << endl;
		cout << "季军编号" << this->m_Record[i][6]
			<< "\t名字:" << this->m_Record[i][7] << "\t分数:" << this->m_Record[i][8] << endl;
	}
	system("pause");
	system("cls");
}

结果如下:
在这里插入图片描述

1.11 清空功能

//清空记录
void SpeechManager::clearRecord() {
	cout << "是否确定清空操作?" << endl;
	cout << "1-是、2-否" << endl;
	int select = 0;
	cin >> select;
	if (select == 1) {
		ofstream ofs(FILENAME, ios::trunc);
		ofs.close();
        //重置
		this->initSPeech();
		this->createSpeaker();
		this->loadFile();
		cout << "清空操作" << endl;

	}
	else {
		system("pause");
		system("cls");
	}
}

最后在管理一下初始化函数:

新增当前记录清空

//初始化容器和属性
void SpeechManager::initSPeech() {
	//容器都置空
	this->v1.clear();
	this->v2.clear();
	this->vVictory.clear();
	this->m_Speaker.clear();
	this->m_Index = 1;

	//将记录的容器也清空
	this->m_Record.clear();

}

由于没有在堆区开辟新内存,不需要析构函数。

2、完整代码

speechManager.h

//speechManager.h

#pragma once
#include<iostream>
#include <vector>
#include <map>
#include "speaker.h"
#include<algorithm>
#include<deque>
#include<functional>
#include <numeric>
#include <string>
#include<fstream>
#include<string>
#include "ctime"
using namespace std;


//设计演讲管理类
class SpeechManager {
public:
	//构造函数声明
	SpeechManager();

	//展示菜单
	void showMenu();

	//退出系统
	void exitSystem();

	//初始化容器和属性
	void initSPeech();

	//创建选手
	void createSpeaker();

	//开始比赛:控制整个流程控制函数
	void startSpeech();

	//抽签
	void speechDraw();
	
	//比赛
	void speechContest();

	//显示得分
	void showScore();

	//保存文件
	void saveFile();

	//加载文件
	void loadFile();

	//显示往届记录
	void showRecord();

	//清空记录
	void clearRecord();


	//判断文件是否为空
	bool fileIsEmpty;

	//往届记录
	map<int, vector<string>>m_Record;

	//析构函数声明
	~SpeechManager();


	//成员属性:
	//保存第一轮比赛选手编号容器
	vector<int>v1;

	//保存第二轮比赛选手编号容器
	vector<int>v2;

	//显示最终胜利者容器
	vector<int>vVictory;

	//存放编号以及对应具体选手编号
	map<int, Speaker>m_Speaker;

	//存放比赛轮数
	int m_Index;

};

speaker.h

//speaker.h

#pragma once
#include <iostream>
using namespace std;

class Speaker {
public:

	string m_Name;
	double m_Score[2];//两轮得分,创建一个数组
};

speechManager.cpp

//speechManager.cpp

#include "speechManager.h"
#define FILENAME "speech.csv"

//函数实现

//构造函数定义
SpeechManager::SpeechManager() {
	//初始化容器和属性
	this->initSPeech();
	this->createSpeaker();
	this->loadFile();
}

//展示菜单
void SpeechManager::showMenu() {
	cout << "******************************************" << endl;
	cout << "************* 欢迎参加演讲比赛 ************" << endl;
	cout << "************* 1.开始演讲比赛 **************" << endl;
	cout << "************* 2.查看往届记录 **************" << endl;
	cout << "************* 3.清空比赛记录 **************" << endl;
	cout << "************* 0.退出比赛程序 **************" << endl;
	cout << "******************************************" << endl;
	cout << endl;
}

//退出系统
void SpeechManager::exitSystem() {
	cout << "欢迎下次再来:" << endl;
	system("pause");
	exit(0);
}

//初始化容器和属性
void SpeechManager::initSPeech() {
	//容器都置空
	this->v1.clear();
	this->v2.clear();
	this->vVictory.clear();
	this->m_Speaker.clear();
	this->m_Index = 1;

	//将记录的容器也清空
	this->m_Record.clear();

}

//创建选手
void SpeechManager::createSpeaker() {

	//创建对象
	string nameSeed = "ABCDEFGHIJKL";

	for (int i = 0; i < nameSeed.size(); i++) {
		Speaker speaker;

		//将姓名初始化
		speaker.m_Name = string("选手")+ nameSeed[i];

		//得分初始化
		for (int j = 0; j < 2; j++) {
			speaker.m_Score[j] = 0.0;
		}

		//12名选手编号,放入v1
		this->v1.push_back(i + 10001);

		//将编号与演讲者插入map,以键值对方式
		//map<int, Speaker>m_Speaker;已经创建map容器
		this->m_Speaker.insert(make_pair(i + 10001, speaker));
	
	}
}


// 开始比赛
void SpeechManager::startSpeech() {
	// 比赛轮次循环
	for (int round = 0; round < 2; round++) {
		// 1、抽签
		this->speechDraw();
		// 2、比赛
		this->speechContest();
		// 3、显示结果
		this->showScore();

		// 准备下一轮
		if (round == 0) {
			this->m_Index++; // 只有从第一轮进入第二轮时需要更新索引
		}
	}

	// 4、保存结果到文件
	this->saveFile();

	// 重置比赛状态,为下一届比赛做准备
	this->initSPeech();//清除容器,重置轮次索引,清除当前届比赛
	this->createSpeaker(); //重新创建 12 名新选手
	this->loadFile();//重新加载往届记录

	cout << "本届比赛完毕!" << endl;
	system("pause");
	system("cls");
}

//抽签
void SpeechManager::speechDraw() {
	cout << "第" << 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;
	system("pause");
	cout << endl;
}

//比赛
void SpeechManager::speechContest() {
	cout << "-------------第" << this->m_Index << "轮比赛正式开始--------------" << endl;

	//准备临时容器存放小组成绩(降序排列)
	multimap<double, int, greater<double>>groupScore;//让分数作为键进行排序,编号是值
	int num = 0;//用于统计记录人员个数,6人一组

	vector <int>v_Src;//比赛选手容器
	if (this->m_Index==1) {
		//执行第一轮
		v_Src = v1;
	}
	else{
		//执行第二轮
		v_Src = v2;
	}

	//遍历所有的选手进行比赛
	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 = (rand() % 401 + 600) / 10.f;
			//cout << score <<" ";
			d.push_back(score);
		}
	
		sort(d.begin(), d.end(),greater<double>());//降序排列
		d.pop_front();//去除最高
		d.pop_back();//去除最低

		double scoreSum = accumulate(d.begin(), d.end(),0.0f);//起始,终止,初始值
		double avg = scoreSum /(double) d.size();//小数/小数

		//"打印平均分" 
		//cout << "编号:" << *it << "\t姓名:" << this->m_Speaker[*it].m_Name
		//	<< "\t分数:" << avg ;
	
		//将平均分放入map容器
		this->m_Speaker[*it].m_Score[this->m_Index - 1] = avg;


		//将打分数据放入到临时小组容器中
		groupScore.insert(make_pair(avg, *it));//均分作为键,value是具体选手编号

		//每六人取出前三名
		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 << "\t姓名:" << this->m_Speaker[it->second].m_Name
					<< "\t分数:" << this->m_Speaker[it->second].m_Score[this->m_Index-1] << endl;
			}

			//取走前三名
			int count = 0;
			for (multimap<double,int , greater<double>>::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 << "-------------------第"<<this->m_Index<<"轮比赛完毕-------------------"<<endl;
	system("pause");

}

//显示得分
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 << "\t姓名:" << this->m_Speaker[*it].m_Name
			<< "\t分数:" << this->m_Speaker[*it].m_Score[this->m_Index-1] << endl;//不指明具体数组索引会显示数组首地址
	}
	cout << endl;

	system("pause");
	system("cls");
	this->showMenu();
}

//保存文件
void SpeechManager::saveFile() {
	ofstream ofs;
	ofs.open(FILENAME,ios::out|ios::app);//用追加方式写文件

	//将每个选手数据写入到文件中
	for (vector<int>::iterator it = vVictory.begin(); it != vVictory.end(); it++) {
		ofs<< *it <<","<<this->m_Speaker[*it].m_Name <<","<< this->m_Speaker[*it].m_Score[m_Index - 1]<<",";
	}
	ofs << endl;
	ofs.close();

	cout << "已经保存完成!" << endl;

	//更改文件不为空的状态
	this->fileIsEmpty = false;
}

//加载文件
void SpeechManager::loadFile() {

//清空m_Record中内存中的遗留数据,不是真实数据,后面会补充
	this->m_Record.clear();

	ifstream ifs;
	ifs.open(FILENAME, 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;
	}

	// 3、文件存在,并且记录数据
	this->fileIsEmpty = false;
	ifs.putback(ch); // 如果读取了一个字符,将其放回输入流

	string line;
	int index = 0; // 记录总记录数

	while (getline(ifs, line)) { // 按行读取完整数据
		vector<string> v; // 存储当前行的所有字段
		int pos = 0;      // 搜索起始位置
		int commaPos = 0; // 逗号位置

		// 解析当前行的所有字段(按逗号分割)
		while ((commaPos = line.find(',', pos)) != string::npos) {
			v.push_back(line.substr(pos, commaPos - pos));
			pos = commaPos + 1;
		}
		v.push_back(line.substr(pos)); // 添加最后一个字段(无逗号后的内容)

		// 确保字段数量足够(至少3个:编号、姓名、分数)
		if (v.size() >= 3) {
			this->m_Record.insert(make_pair(index, v));
			index++;
		}
		else {
			cout << "警告:格式错误的行 - " << line << endl;
		}
	}	
	ifs.close();

	////测试读取结果,以读取每一行第一个为列
	//for (map<int, vector<string>>::iterator it = this->m_Record.begin(); it != this->m_Record.end(); it++) {
	//	cout << "第" << it->first+1 << "届记录" << endl;
	//	cout << "编号:" << it->second[0] << "\t名字" << it->second[1] << "\t分数" << it->second[2] << endl;
	//}
}


//查看往届记录
void SpeechManager::showRecord() {
	if (this->fileIsEmpty) {
		cout << "文件为空或不存在!" << endl;
	}
	for (int i = 0; i < this->m_Record.size(); i++) {
		cout << "第" << i + 1 << "届" << endl;
		cout<< "冠军编号" << this->m_Record[i][0] 
			<< "\t名字:" << this->m_Record[i][1] << "\t分数:" << this->m_Record[i][2] << endl;
		cout<< "亚军编号" << this->m_Record[i][3]
			<< "\t名字:" << this->m_Record[i][4] << "\t分数:" << this->m_Record[i][5] << endl;
		cout << "季军编号" << this->m_Record[i][6]
			<< "\t名字:" << this->m_Record[i][7] << "\t分数:" << this->m_Record[i][8] << endl;
	}
	system("pause");
	system("cls");
}

//清空记录
void SpeechManager::clearRecord() {
	cout << "是否确定清空操作?" << endl;
	cout << "1-是、2-否" << endl;
	int select = 0;
	cin >> select;
	if (select == 1) {
		ofstream ofs(FILENAME, ios::trunc);
		ofs.close();
		this->initSPeech();
		this->createSpeaker();
		this->loadFile();
		cout << "清空操作" << endl;

	}
	else {
		system("pause");
		system("cls");
	}
}

//析构函数定义
SpeechManager::~SpeechManager() {

}

演讲比赛管理系统.cpp

//这是演讲比赛管理系统.cpp的实现

#include "speechManager.h"


int main() {
	//随机数种子
	srand((unsigned int)time(NULL));
	SpeechManager sm;

	//sm.createSpeaker();
	//for (map<int, Speaker>::iterator it = sm.m_Speaker.begin(); it != sm.m_Speaker.end(); it++) {
	//	cout << "编号:" << it->first 
	//		<< "\t姓名:" << it->second.m_Name 
	//		<< "\t分数:" << it->second.m_Score[0] << endl;
	//}

	int select = 0;

	while (true) {
		sm.showMenu();

		cout << "请输入你的选择:" << endl;
		cin >> select;
		switch (select) {
		case 1://开始比赛
			sm.startSpeech();
			break;
		case 2://查看往届记录
			sm.showRecord();
			break;
		case 3://清空比赛记录
			sm.clearRecord();
			break;
		case 0://退出系统
			sm.exitSystem();
			break;
		default://清屏操作
			system("cls");
			break;
		}
	}

	system("pause");
	return 0;
}

3、总结

这个例子主要是学会各种容器的搭配使用,涉及掌握的知识点:
1. 面向对象编程(OOP)
类与对象:定义 SpeechManager 类,封装比赛管理逻辑,包括成员函数(如 showMenu、startSpeech)和数据成员(容器、状态变量)。要掌握用一个大类去管理类的成员函数。
模块化设计:将抽签、比赛、文件操作等功能拆分为独立函数,提高代码可维护性。

2. 容器使用:
vector:存储选手编号(v1、v2、vVictory),支持动态增删。
map/multimap:m_Speaker(编号 - 选手映射)、groupScore(分数 - 编号映射,降序排序),实现快速查找和排序,这种数据解耦的思想要掌握。
deque:存储评委打分,方便去除极值(最高、最低分)。
算法应用:
random_shuffle:抽签时打乱选手顺序。
sort + accumulate:评委分数排序、求和,计算平均分。

3. 文件操作
读写模式:ios::app(追加写入,saveFile)、ios::trunc(清空文件,clearRecord)
loadfilea三种情况的编写。
文件解析:按行读取csv格式,分割逗号字段。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值