演講系統(SpeechContext)小結
前記
最近把C++
的知識點過了一遍,雖然說不能完全的瞭解C++
語言的特性,但是也發現了它的美,後面想要學習C++
的熱情高漲,故完成了一個小項目,來達到溫故知新的效果吧。
主要涉及知識點:類、STL等
需求
1.比賽規則
- 學校舉行一場演講比賽,共有12個人參加。比賽共兩輪,第一輪爲淘汰賽,第二輪爲決賽。
- 每次比賽共有10名裁判分別對每個選手進行打分。
- 比賽方式:分組比賽,每組6個人;選手每次要隨機分組,進行比賽
- 每名選手都有對應的編號,如 10001 ~ 10012
- 第一輪分爲兩個小組,每組6個人。 整體按照選手編號進行抽籤後順序演講。
- 當小組演講完後,淘汰組內排名最後的三個選手,前三名晉級,進入下一輪的比賽。
- 第二輪爲決賽,前三名勝出
- 每輪比賽過後需要顯示晉級選手的信息
2.程序功能
- 開始演講比賽:完成整屆比賽的流程,每個比賽階段需要給用戶一個提示,用戶按任意鍵後繼續下一個階段
- 查看往屆記錄:查看之前比賽前三名結果,每次比賽都會記錄到文件中,文件用.csv後綴名保存
- 清空比賽記錄:將文件中數據清空
- 退出比賽程序:可以退出當前程序
需求分析與功能設計
在這之前,我們首先創建兩個類:
第一個類表示比賽選手類,記錄了當前比賽的選手信息,如選手編號,以及分數等,記作speaker.h
,只是需要當作頭文件即可;
第二個類表示比賽流程類,記錄了整個比賽的信息,如比賽流程,記錄顯示等等,同樣的,我們將其聲明和其實現分開,分別記作speechmanager.h
和speechmanager.cpp
1.總體功能面板的設計
首先,觀察可知,比賽總共的可以分爲四個分支,那麼總體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;
}
2.退出系統功能設計
我們首先設計的是退出系統功能的設計,相對於來說,這個功能是比較好實現的,只需要一個方法即可。可以將其聲明在比賽的流程類的頭文件中,然後在cpp文件中去實現這個方法,方法的簡單實現如下:
void SpeechManager::systemExit() {
exit(0);
}
3.開始比賽功能設計
這一塊主要的功能是能夠分析清楚比賽的整體流程,按照項目的要求,來模擬整個比賽的流程:
可以分析如下:
首先是存在12名選手,然後隨機將其分爲2組,每組6個人(抽籤分組),完成了這個內容之後,那麼可以開始第一輪比賽,每個小組取前三名選手進入第二輪比賽,現在總共的就是存在6名選手,再次抽籤,進行第二輪比賽,然後等到第二輪比賽結束之後,記錄前三名選手的信息(編號,以及分數),保存到文件中。這個就是整體的流程,將上面的文字使用方法的流程體現出來如下:
// 比賽流程實現
void SpeechManager::startSpeech() {
//第一輪比賽
//1、抽籤
//2、比賽
//3、顯示晉級結果
//第二輪比賽
//1、抽籤
//2、比賽
//3、顯示最終結果
//4、保存分數
}
在這個裏面,我們就是需要設計四個方法,分別是抽籤、比賽、顯示晉級結果、保存分數,有了整體的流程,那麼可以開始來實現單個方法。
3.1 抽籤
模擬抽籤的話,就是將選手的編號信息即可。我們可以採用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;
}
3.2 比賽
對於比賽,整體的情況是分爲兩輪的,但是我們可以設計一個小小的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;
}
3.3 顯示晉級結果
這裏的顯示,只是需要將容器中的內容打印出來即可。
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;
}
3.4 保存分數
保存分數的話,主要是將其寫入到文件中,這裏只是需要記錄第二名比賽前三名的信息,在C++中,我們寫文件主要是包含以下五個步驟:
-
包含頭文件
#include <fstream>
-
創建流對象
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;
}
4.查看記錄功能設計
查看記錄的功能主要是將內容從文件中讀取出來,這樣就是可以看到每一屆選手的前三名的成績了。即分析清楚之後,我們就可以看到這是一個簡單的讀文件的操作:
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();
}
5.清空記錄功能設計
清空記錄的話,主要是將文件中的所有的信息刪除即可。
void SpeechManager::clearRecord(){
//打開模式 ios::trunc 如果存在刪除文件並重新創建
ofstream ofs("../speech.csv", ios::trunc);
ofs.close();
//初始化屬性
this->initSpeech();
//創建選手
this->createSpeaker();
//獲取往屆記錄
this->readRecord();
cout << "清空成功!" << endl;
}
至此,所有的小功能均已設計完畢。
當然,還有一些小bug的解決,在代碼中有詳細的體現哦.
Reference
C++ STL
bilibili 項目課(網址沒找到,待補充)
總結
通過這個小項目,可以重溫一下stl相關的內容,熟悉流程。通過分析流程,更加能夠對語言的認識更加的深刻。