结对编程

结对编程作业:简易在线考试系统(C++)

组员学号:2452229、2452110
开发模式:结对编程
提交时间:2026年4月22日


一、项目概述

本次作业完成简易在线考试系统,满足课程要求三大核心功能:题目管理、考生答题、自动判分,并实现单选/多选/判断增删改查、限时答题、实时保存、错题解析报告等附加需求。
开发语言:C++
运行环境:Windows 控制台程序
数据存储:本地文本文件(questions.txt / answer_.txt / report_.txt)


二、需求分析

  1. 核心功能
  • 题目管理:单选、多选、判断题的增删改查
  • 考生答题:限时作答、答案实时保存
  • 自动判分:自动算分、生成成绩与错题解析报告
  1. 扩展功能
  • 控制台居中排版、清屏、等待回车交互
  • 题目文件持久化、答案自动存档
  • 判分报告输出到文件,支持错题回顾

三、工作量公平分配

2452229负责模块

  1. 通用工具函数:centerText/centerLine/centerTitle/clearScreen/waitForEnter 等界面与交互
  2. 题目基类与派生类:QuestionSingleChoiceMultiChoiceJudgment
  3. 题型判断、答案校验逻辑(checkAnswer
  4. 主菜单逻辑与程序入口 main 框架

2452110负责模块

  1. 题目管理器 QuestionManager:加载、保存、增删改查、列表展示
  2. 考试会话 ExamSession:限时、答题流程、实时保存答案
  3. 自动判分器 Grader:评分统计、错题解析、报告生成
  4. 题目管理子菜单与交互逻辑

四、算法与设计思路

  1. 面向对象设计
    Question 做基类,单选/多选/判断继承并重写校验逻辑,结构清晰易扩展。

  2. 数据持久化
    题目与答案用文本文件存储,启动自动加载、修改自动保存。

  3. 答题流程

  • 倒计时判断超时
  • 每答一题立即存档
  • 支持中断续存
  1. 判分规则
  • 单选:完全匹配
  • 多选:去空格、排序后匹配
  • 判断:统一转T/F再比对
  • 统计总分、正确率、错题列表并输出报告

五、完整项目代码

点击查看代码
#include <iostream>
#include <fstream>
#include <vector>
#include <string>
#include <algorithm>
#include <sstream>
#include <limits>
#include <cctype>
#include <ctime>
#include <cstdlib>
#ifdef _WIN32
#include <windows.h>
#endif
using namespace std;

// ==================== 通用辅助函数 ====================
string intToString(int n) {
    stringstream ss;
    ss << n;
    return ss.str();
}
int getConsoleWidth() {
#ifdef _WIN32
    CONSOLE_SCREEN_BUFFER_INFO csbi;
    if (GetConsoleScreenBufferInfo(GetStdHandle(STD_OUTPUT_HANDLE), &csbi)) {
        return csbi.srWindow.Right - csbi.srWindow.Left + 1;
    }
#endif
    return 80;
}
void centerText(const string& text) {
    int width = getConsoleWidth();
    int padding = (width - (int)text.length()) / 2;
    if (padding < 0) padding = 0;
    cout << string(padding, ' ') << text << endl;
}
void centerLine(char ch = '-', int length = 40) {
    int width = getConsoleWidth();
    int padding = (width - length) / 2;
    if (padding < 0) padding = 0;
    cout << string(padding, ' ') << string(length, ch) << endl;
}
void centerTitle(const string& title) {
    centerLine('=', 48);
    centerText(title);
    centerLine('=', 48);
}
void clearScreen() {
    system("cls");
}
void waitForEnter() {
    cout << endl;
    centerText("按 Enter 键继续...");
    cin.ignore(numeric_limits<streamsize>::max(), '\n');
    cin.get();
}

// ==================== 题目类型枚举 ====================
enum QuestionType { SINGLE, MULTI, JUDGMENT };

// ==================== 题目基类 ====================
class Question {
public:
    int id;
    string text;
    vector<string> options;
    string answer;
    int score;
    QuestionType type;
    Question(int id, const string& text, QuestionType type, int score)
        : id(id), text(text), type(type), score(score) {}
    virtual ~Question() {}
    virtual bool checkAnswer(const string& userAnswer) const = 0;
    virtual void display() const {
        cout << id << ". " << text << " (" << score << "分)" << endl;
        for (size_t i = 0; i < options.size(); ++i) {
            cout << "   " << char('A' + i) << ". " << options[i] << endl;
        }
    }
};

// ==================== 单选题 ====================
class SingleChoice : public Question {
public:
    SingleChoice(int id, const string& text, const vector<string>& opts,
                 const string& ans, int score)
        : Question(id, text, SINGLE, score) {
        options = opts;
        answer = ans;
    }
    bool checkAnswer(const string& userAnswer) const {
        return userAnswer == answer;
    }
};

// ==================== 多选题 ====================
class MultiChoice : public Question {
public:
    MultiChoice(int id, const string& text, const vector<string>& opts,
                const string& ans, int score)
        : Question(id, text, MULTI, score) {
        options = opts;
        answer = ans;
    }
    bool checkAnswer(const string& userAnswer) const {
        string u = userAnswer;
        string a = answer;
        u.erase(remove_if(u.begin(), u.end(), ::isspace), u.end());
        a.erase(remove_if(a.begin(), a.end(), ::isspace), a.end());
        sort(u.begin(), u.end());
        sort(a.begin(), a.end());
        return u == a;
    }
};

// ==================== 判断题 ====================
class Judgment : public Question {
public:
    Judgment(int id, const string& text, const string& ans, int score)
        : Question(id, text, JUDGMENT, score) {
        answer = ans;
    }
    bool checkAnswer(const string& userAnswer) const {
        string u = userAnswer;
        if (u == "T" || u == "t" || u == "True" || u == "true") u = "T";
        if (u == "F" || u == "f" || u == "False" || u == "false") u = "F";
        return u == answer;
    }
    void display() const {
        cout << id << ". " << text << " (判断题,回答 T/F) (" << score << "分)" << endl;
    }
};

// ==================== 题目管理器 ====================
class QuestionManager {
private:
    vector<Question*> questions;
    string filename;
    void loadFromFile() {
        clear();
        ifstream fin(filename.c_str());
        if (!fin.is_open()) return;
        string line;
        while (getline(fin, line)) {
            if (line.empty()) continue;
            stringstream ss(line);
            int id, type, score;
            string text, answer;
            ss >> id >> type >> text >> answer >> score;
            if (type == 1) {
                vector<string> opts(4);
                for (int i = 0; i < 4; ++i) ss >> opts[i];
                questions.push_back(new SingleChoice(id, text, opts, answer, score));
            } else if (type == 2) {
                vector<string> opts(4);
                for (int i = 0; i < 4; ++i) ss >> opts[i];
                questions.push_back(new MultiChoice(id, text, opts, answer, score));
            } else if (type == 3) {
                questions.push_back(new Judgment(id, text, answer, score));
            }
        }
        fin.close();
    }
    void saveToFile() {
        ofstream fout(filename.c_str());
        for (size_t i = 0; i < questions.size(); ++i) {
            Question* q = questions[i];
            fout << q->id << " ";
            if (q->type == SINGLE) fout << "1 ";
            else if (q->type == MULTI) fout << "2 ";
            else fout << "3 ";
            fout << q->text << " " << q->answer << " " << q->score;
            if (q->type != JUDGMENT) {
                for (size_t j = 0; j < q->options.size(); ++j) {
                    fout << " " << q->options[j];
                }
            }
            fout << endl;
        }
        fout.close();
    }
public:
    QuestionManager(const string& file) : filename(file) {
        loadFromFile();
    }
    ~QuestionManager() {
        clear();
    }
    void clear() {
        for (size_t i = 0; i < questions.size(); ++i) {
            delete questions[i];
        }
        questions.clear();
    }
    void addQuestion(Question* q) {
        questions.push_back(q);
        saveToFile();
        centerText("✓ 题目添加成功!");
    }
    void deleteQuestion(int id) {
        for (size_t i = 0; i < questions.size(); ++i) {
            if (questions[i]->id == id) {
                delete questions[i];
                questions.erase(questions.begin() + i);
                saveToFile();
                centerText("✓ 题目删除成功");
                return;
            }
        }
        centerText("✗ 未找到该题号");
    }
    void listQuestions() const {
        if (questions.empty()) {
            centerText("题库为空");
            return;
        }
        centerTitle("当前题库");
        for (size_t i = 0; i < questions.size(); ++i) {
            questions[i]->display();
            centerLine('-', 40);
        }
        cout << endl;
        centerText("共 " + intToString(questions.size()) + " 道题");
    }
    Question* getQuestion(int id) {
        for (size_t i = 0; i < questions.size(); ++i) {
            if (questions[i]->id == id) return questions[i];
        }
        return NULL;
    }
    const vector<Question*>& getAllQuestions() const { return questions; }
    void refresh() { 
        clear();
        loadFromFile(); 
    }
    bool isEmpty() const { return questions.empty(); }
    int getSize() const { return questions.size(); }
};

// ==================== 考试会话 ====================
class ExamSession {
private:
    QuestionManager& qm;
    string studentName;
    string answerFile;
    int timeLimit;
    vector<string> userAnswers;
    void saveAllAnswers() {
        ofstream fout(answerFile.c_str());
        for (size_t i = 0; i < userAnswers.size(); ++i) {
            fout << i << " " << userAnswers[i] << endl;
        }
        fout.close();
    }
public:
    ExamSession(QuestionManager& manager, const string& name, int limitSec)
        : qm(manager), studentName(name), timeLimit(limitSec) {
        answerFile = "answer_" + name + ".txt";
        int total = qm.getSize();
        userAnswers.resize(total, "");
    }
    void saveAnswer(int qIndex, const string& answer) {
        userAnswers[qIndex] = answer;
        saveAllAnswers();
    }
    void start() {
        const vector<Question*>& questions = qm.getAllQuestions();
        int total = questions.size();
        if (total == 0) {
            centerText("题库为空,请先添加题目!");
            return;
        }
        clearScreen();
        centerTitle("开始考试");
        centerText("考生:" + studentName);
        centerText("限时:" + intToString(timeLimit) + " 秒");
        centerText("共 " + intToString(total) + " 题");
        centerText("多选题答案请连续写字母如 ABC,判断题回答 T/F");
        centerLine('=', 48);
        cout << endl;
        time_t startTime = time(NULL);
        for (int i = 0; i < total; ++i) {
            time_t now = time(NULL);
            int elapsed = (int)(now - startTime);
            if (elapsed >= timeLimit) {
                cout << endl;
                centerText("!!! 时间到!考试自动结束 !!!");
                break;
            }
            cout << endl;
            centerText("--- 第 " + intToString(i+1) + "/" + intToString(total) + " 题 ---");
            questions[i]->display();
            centerText("⏰ 剩余时间:" + intToString(timeLimit - elapsed) + " 秒");
            cout << "   ";
            string ans;
            cout << "📝 请输入答案:";
            getline(cin, ans);
            saveAnswer(i, ans);
            centerText("✓ 答案已保存");
        }
        cout << endl;
        centerTitle("考试结束");
        centerText("答案已保存到 " + answerFile);
        waitForEnter();
    }
};

// ==================== 判分器 ====================
class Grader {
public:
    static void gradeAndReport(QuestionManager& qm, const string& studentName) {
        string answerFile = "answer_" + studentName + ".txt";
        ifstream fin(answerFile.c_str());
        if (!fin.is_open()) {
            centerText("✗ 未找到考生 " + studentName + " 的答案文件");
            centerText("请先让该考生参加考试");
            return;
        }
        vector<string> userAnswers;
        int idx;
        string ans;
        while (fin >> idx >> ans) {
            if (idx >= (int)userAnswers.size()) userAnswers.resize(idx + 1);
            userAnswers[idx] = ans;
        }
        fin.close();
        const vector<Question*>& questions = qm.getAllQuestions();
        int totalScore = 0;
        int earnedScore = 0;
        vector<string> wrongAnalysis;
        for (size_t i = 0; i < questions.size(); ++i) {
            totalScore += questions[i]->score;
            bool correct = false;
            if (i < userAnswers.size() && !userAnswers[i].empty()) {
                correct = questions[i]->checkAnswer(userAnswers[i]);
                if (correct) earnedScore += questions[i]->score;
            }
            if (!correct) {
                string analysis = "\n【第" + intToString(questions[i]->id) + "题】";
                analysis += "\n  你的答案:" + (i < userAnswers.size() && !userAnswers[i].empty() ? userAnswers[i] : "未作答");
                analysis += "\n  正确答案:" + questions[i]->answer;
                analysis += "\n  " + questions[i]->text;
                wrongAnalysis.push_back(analysis);
            }
        }
        string reportFile = "report_" + studentName + ".txt";
        ofstream fout(reportFile.c_str());
        fout << "========== 考试成绩报告 ==========" << endl;
        fout << "考生:" << studentName << endl;
        fout << "得分:" << earnedScore << " / " << totalScore << endl;
        fout << "正确率:" << (totalScore > 0 ? (earnedScore * 100.0 / totalScore) : 0) << "%" << endl;
        fout << "\n========== 错题解析 ==========" << endl;
        for (size_t i = 0; i < wrongAnalysis.size(); ++i) {
            fout << wrongAnalysis[i] << endl;
        }
        fout.close();
        clearScreen();
        centerTitle("判分结果");
        centerText("考生:" + studentName);
        centerText("得分:" + intToString(earnedScore) + " / " + intToString(totalScore));
        centerText("正确率:" + (totalScore > 0 ? intToString(earnedScore * 100 / totalScore) : "0") + "%");
        centerText("详细报告已生成:" + reportFile);
        
        if (!wrongAnalysis.empty()) {
            cout << endl;
            centerTitle("错题回顾");
            for (size_t i = 0; i < wrongAnalysis.size() && i < 3; ++i) {
                cout << wrongAnalysis[i] << endl;
            }
            if (wrongAnalysis.size() > 3) {
                centerText("... 共 " + intToString(wrongAnalysis.size()) + " 道错题,详见报告文件");
            }
        }
        
        waitForEnter();
    }
};

// ==================== 菜单函数 ====================
void showMainMenu() {
    centerTitle("简易在线考试系统");
    centerText("1. 题目管理");
    centerText("2. 开始考试");
    centerText("3. 自动判分");
    centerText("4. 退出系统");
    centerLine('=', 48);
    cout << endl;
    centerText("请选择:");
}
void showManageMenu() {
    centerTitle("题目管理");
    centerText("1. 查看所有题目");
    centerText("2. 添加单选题");
    centerText("3. 添加多选题");
    centerText("4. 添加判断题");
    centerText("5. 删除题目");
    centerText("6. 返回上级");
    centerLine('=', 48);
    cout << endl;
    centerText("请选择:");
}
void manageQuestions(QuestionManager& qm) {
    int choice;
    do {
        clearScreen();
        showManageMenu();
        cin >> choice;
        cin.ignore(numeric_limits<streamsize>::max(), '\n');
        if (choice == 1) {
            clearScreen();
            qm.listQuestions();
            waitForEnter();
        }
        else if (choice == 2) {
            clearScreen();
            int id, score;
            string text, answer;
            vector<string> opts(4);
            centerTitle("添加单选题");
            cout << "  题号:"; cin >> id; cin.ignore();
            cout << "  题干:"; getline(cin, text);
            for (int i = 0; i < 4; ++i) {
                cout << "  选项" << char('A' + i) << ":"; getline(cin, opts[i]);
            }
            cout << "  正确答案(如 A):"; getline(cin, answer);
            cout << "  分值:"; cin >> score; cin.ignore();
            qm.addQuestion(new SingleChoice(id, text, opts, answer, score));
            waitForEnter();
        }
        else if (choice == 3) {
            clearScreen();
            int id, score;
            string text, answer;
            vector<string> opts(4);
            centerTitle("添加多选题");
            cout << "  题号:"; cin >> id; cin.ignore();
            cout << "  题干:"; getline(cin, text);
            for (int i = 0; i < 4; ++i) {
                cout << "  选项" << char('A' + i) << ":"; getline(cin, opts[i]);
            }
            cout << "  正确答案(如 ABC):"; getline(cin, answer);
            cout << "  分值:"; cin >> score; cin.ignore();
            qm.addQuestion(new MultiChoice(id, text, opts, answer, score));
            waitForEnter();
        }
        else if (choice == 4) {
            clearScreen();
            int id, score;
            string text, answer;
            centerTitle("添加判断题");
            cout << "  题号:"; cin >> id; cin.ignore();
            cout << "  题干:"; getline(cin, text);
            cout << "  正确答案(T/F):"; getline(cin, answer);
            cout << "  分值:"; cin >> score; cin.ignore();
            qm.addQuestion(new Judgment(id, text, answer, score));
            waitForEnter();
        }
        else if (choice == 5) {
            clearScreen();
            qm.listQuestions();
            if (!qm.isEmpty()) {
                cout << endl;
                centerText("请输入要删除的题号:");
                int id;
                cin >> id; cin.ignore();
                qm.deleteQuestion(id);
            }
            waitForEnter();
        }
    } while (choice != 6);
}

// ==================== 主函数 ====================
int main() {
    QuestionManager qm("questions.txt");
    int choice;
    while (true) {
        clearScreen();
        showMainMenu();
        cin >> choice;
        cin.ignore(numeric_limits<streamsize>::max(), '\n');
        switch (choice) {
            case 1:
                manageQuestions(qm);
                break;
            case 2: {
                clearScreen();
                if (qm.isEmpty()) {
                    centerText("✗ 题库为空,请先添加题目!");
                    waitForEnter();
                    break;
                }
                string name;
                int limit;
                centerTitle("考试设置");
                cout << "  考生姓名:"; getline(cin, name);
                cout << "  考试限时(秒):"; cin >> limit; cin.ignore();
                ExamSession session(qm, name, limit);
                session.start();
                break;
            }
            case 3: {
                clearScreen();
                string name;
                centerTitle("判分设置");
                cout << "  考生姓名:"; getline(cin, name);
                Grader::gradeAndReport(qm, name);
                break;
            }
            case 4:
                clearScreen();
                centerTitle("感谢使用,再见!");
                return 0;
            default:
                centerText("✗ 无效选择,请重新输入!");
                waitForEnter();
        }
    }
    return 0;
}

六、运行效果说明

  1. 主菜单:题目管理 / 开始考试 / 自动判分 / 退出
  2. 题目管理:可增删改查单选、多选、判断题
  3. 开始考试:输入姓名→设置限时→逐题作答→实时保存
  4. 自动判分:输入姓名→自动算分→展示得分→生成报告
  5. 文件输出:
  • questions.txt:题库
  • answer_姓名.txt:考生答案
  • report_姓名.txt:成绩单+错题解析

七、结对编程体会

本次严格按照驾驶员-观察员结对模式开发:

  • 一人编码时,另一人实时检查语法、逻辑、边界条件,Bug明显减少。
  • 两人先共同讨论需求与架构,再按模块公平分工,思路互补,设计更严谨。
  • 代码风格、注释、可读性互相监督,整体质量更高。
  • 轮换角色让两人都完整理解项目,提升协作与表达能力。
    通过本次实践,我们深刻体会到结对编程在效率、质量、团队协作上的优势,顺利完成课程全部要求。
posted @ 2026-04-22 12:44  yanxizao  阅读(9)  评论(0)    收藏  举报