结对编程
结对编程作业:简易在线考试系统(C++)
组员学号:2452229、2452110
开发模式:结对编程
提交时间:2026年4月22日
一、项目概述
本次作业完成简易在线考试系统,满足课程要求三大核心功能:题目管理、考生答题、自动判分,并实现单选/多选/判断增删改查、限时答题、实时保存、错题解析报告等附加需求。
开发语言:C++
运行环境:Windows 控制台程序
数据存储:本地文本文件(questions.txt / answer_.txt / report_.txt)
二、需求分析
- 核心功能
- 题目管理:单选、多选、判断题的增删改查
- 考生答题:限时作答、答案实时保存
- 自动判分:自动算分、生成成绩与错题解析报告
- 扩展功能
- 控制台居中排版、清屏、等待回车交互
- 题目文件持久化、答案自动存档
- 判分报告输出到文件,支持错题回顾
三、工作量公平分配
2452229负责模块
- 通用工具函数:
centerText/centerLine/centerTitle/clearScreen/waitForEnter等界面与交互 - 题目基类与派生类:
Question、SingleChoice、MultiChoice、Judgment - 题型判断、答案校验逻辑(
checkAnswer) - 主菜单逻辑与程序入口
main框架
2452110负责模块
- 题目管理器
QuestionManager:加载、保存、增删改查、列表展示 - 考试会话
ExamSession:限时、答题流程、实时保存答案 - 自动判分器
Grader:评分统计、错题解析、报告生成 - 题目管理子菜单与交互逻辑
四、算法与设计思路
-
面向对象设计
用Question做基类,单选/多选/判断继承并重写校验逻辑,结构清晰易扩展。 -
数据持久化
题目与答案用文本文件存储,启动自动加载、修改自动保存。 -
答题流程
- 倒计时判断超时
- 每答一题立即存档
- 支持中断续存
- 判分规则
- 单选:完全匹配
- 多选:去空格、排序后匹配
- 判断:统一转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;
}
六、运行效果说明
- 主菜单:题目管理 / 开始考试 / 自动判分 / 退出
- 题目管理:可增删改查单选、多选、判断题
- 开始考试:输入姓名→设置限时→逐题作答→实时保存
- 自动判分:输入姓名→自动算分→展示得分→生成报告
- 文件输出:
- questions.txt:题库
- answer_姓名.txt:考生答案
- report_姓名.txt:成绩单+错题解析
七、结对编程体会
本次严格按照驾驶员-观察员结对模式开发:
- 一人编码时,另一人实时检查语法、逻辑、边界条件,Bug明显减少。
- 两人先共同讨论需求与架构,再按模块公平分工,思路互补,设计更严谨。
- 代码风格、注释、可读性互相监督,整体质量更高。
- 轮换角色让两人都完整理解项目,提升协作与表达能力。
通过本次实践,我们深刻体会到结对编程在效率、质量、团队协作上的优势,顺利完成课程全部要求。
浙公网安备 33010602011771号