C++项目之演讲比赛模拟

演讲系统(SpeechContext)小结

前记

最近把C++ 的知识点过了一遍,虽然说不能完全的了解C++语言的特性,但是也发现了它的美,后面想要学习C++的热情高涨,故完成了一个小项目,来达到温故知新的效果吧。

主要涉及知识点:类、STL等

需求

1.比赛规则

  • 学校举行一场演讲比赛,共有12个人参加。比赛共两轮,第一轮为淘汰赛,第二轮为决赛。
  • 每次比赛共有10名裁判分别对每个选手进行打分。
  • 比赛方式:分组比赛,每组6个人;选手每次要随机分组,进行比赛
  • 每名选手都有对应的编号,如 10001 ~ 10012
  • 第一轮分为两个小组,每组6个人。 整体按照选手编号进行抽签后顺序演讲。
  • 当小组演讲完后,淘汰组内排名最后的三个选手,前三名晋级,进入下一轮的比赛。
  • 第二轮为决赛,前三名胜出
  • 每轮比赛过后需要显示晋级选手的信息

2.程序功能

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

需求分析与功能设计

在这之前,我们首先创建两个类:

第一个类表示比赛选手类,记录了当前比赛的选手信息,如选手编号,以及分数等,记作speaker.h,只是需要当作头文件即可;

第二个类表示比赛流程类,记录了整个比赛的信息,如比赛流程,记录显示等等,同样的,我们将其声明和其实现分开,分别记作speechmanager.hspeechmanager.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++中,我们写文件主要是包含以下五个步骤:

  1. 包含头文件

    #include

  2. 创建流对象

    ofstream ofs;

  3. 打开文件

    ofs.open(“文件路径”,打开方式);

  4. 写数据

    ofs << “写入的数据”;

  5. 关闭文件

    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的解决,在代码中有详细的体现哦.

欢迎到我的github上观看完整内容

Reference

C++ STL
bilibili 项目课(网址没找到,待补充)

总结

通过这个小项目,可以重温一下stl相关的内容,熟悉流程。通过分析流程,更加能够对语言的认识更加的深刻。

你可能感兴趣的:(program,C++,STL,项目)