“软件开发与创新课程设计”第七周结对编程作业及感想
数学考试系统开发总结:从零到一构建C++控制台应用
📌 项目概述
本项目实现了一个数学考试系统,支持单选题、多选题和判断题的题库管理、在线考试、自动判分与成绩报告导出。系统采用纯C++控制台界面,适合初学者理解面向对象设计、文件I/O、随机算法等核心概念。
本项目的合作伙伴学号为:2452814
下附完整代码
点击查看代码
#include <iostream>
#include <vector>
#include <string>
#include <algorithm>
#include <ctime>
#include <map>
#include <cstdlib> // for rand, srand
#include <cstdio> // for getchar
#include <fstream> // 文件操作
using namespace std;
// 题目结构体
struct Question {
int id;
int type; // 0单选 1多选 2判断
string content;
vector<string> options;
vector<int> answer; // 正确答案索引
string explanation;
};
// 全局题库
vector<Question> bank;
int nextId = 1;
// 初始化数学题库
void initBank() {
Question q1;
q1.id = nextId++; q1.type = 0; q1.content = "3^2 + 4^2 = ?^2";
q1.options.push_back("5"); q1.options.push_back("6"); q1.options.push_back("7"); q1.options.push_back("8");
q1.answer.push_back(0); q1.explanation = "勾股定理";
bank.push_back(q1);
Question q2;
q2.id = nextId++; q2.type = 0; q2.content = "√2是?";
q2.options.push_back("有理数"); q2.options.push_back("无理数"); q2.options.push_back("整数"); q2.options.push_back("分数");
q2.answer.push_back(1); q2.explanation = "不能表示为分数";
bank.push_back(q2);
Question q3;
q3.id = nextId++; q3.type = 1; q3.content = "哪些是质数?";
q3.options.push_back("2"); q3.options.push_back("4"); q3.options.push_back("7"); q3.options.push_back("9");
q3.answer.push_back(0); q3.answer.push_back(2); q3.explanation = "2和7是质数";
bank.push_back(q3);
Question q4;
q4.id = nextId++; q4.type = 2; q4.content = "可导必连续";
q4.options.push_back("正确"); q4.options.push_back("错误");
q4.answer.push_back(0); q4.explanation = "可导一定连续";
bank.push_back(q4);
}
// 清屏
void cls() { system("clear||cls"); }
// 显示题目
void showQuestion(const Question& q) {
cout << "ID:" << q.id << " [" << (q.type==0?"单选":q.type==1?"多选":"判断") << "] ";
cout << q.content << endl;
for (size_t i = 0; i < q.options.size(); ++i)
cout << " " << char('A'+i) << ". " << q.options[i] << endl;
}
// 导出所有题目到文件
void exportQuestions() {
string filename;
cout << "请输入导出文件名(如 questions.txt): ";
cin >> filename;
ofstream fout(filename.c_str());
if (!fout) {
cout << "错误:无法创建文件!" << endl;
return;
}
for (size_t i = 0; i < bank.size(); ++i) {
const Question& q = bank[i];
fout << q.type << endl;
fout << q.content << endl;
fout << q.options.size() << endl;
for (size_t j = 0; j < q.options.size(); ++j) {
fout << q.options[j] << endl;
}
string ansStr;
for (size_t j = 0; j < q.answer.size(); ++j) {
ansStr += char('0' + q.answer[j]);
}
fout << ansStr << endl;
fout << q.explanation << endl;
fout << endl;
}
fout.close();
cout << "成功导出 " << bank.size() << " 道题目到文件 " << filename << endl;
}
// 考试判分
double check(const Question& q, vector<int> stuAns) {
if (stuAns.empty()) return 0;
if (q.type == 2) return stuAns[0] == q.answer[0] ? 1 : 0;
if (q.type == 0) return stuAns == q.answer ? 1 : 0;
// 多选判分
for (size_t i = 0; i < stuAns.size(); ++i) {
int a = stuAns[i];
bool found = false;
for (size_t j = 0; j < q.answer.size(); ++j) {
if (q.answer[j] == a) { found = true; break; }
}
if (!found) return 0;
}
return stuAns.size() == q.answer.size() ? 1 : 0.5;
}
// 开始考试(增加答题输入验证)
void exam() {
if (bank.empty()) {
cout << "错误:题库为空,请先添加题目!" << endl;
return;
}
int limit, cnt;
cout << "考试时长(分钟): ";
cin >> limit;
if (limit <= 0) {
cout << "时长必须为正数,已设为默认5分钟。" << endl;
limit = 5;
}
cout << "题目数(最多" << bank.size() << "): ";
cin >> cnt;
if (cnt <= 0) {
cout << "题目数至少为1,已设为1。" << endl;
cnt = 1;
}
if (cnt > (int)bank.size()) cnt = bank.size();
vector<Question> paper;
for (int i = 0; i < cnt; ++i) paper.push_back(bank[i]);
// 随机打乱
for (int i = 0; i < (int)paper.size(); ++i) {
int r = i + rand() % (paper.size() - i);
swap(paper[i], paper[r]);
}
map<int, vector<int> > answers;
time_t start = time(0);
cls();
cout << "=== 考试开始 时长" << limit << "分钟 ===\n";
for (size_t i = 0; i < paper.size(); ++i) {
int remain = limit * 60 - (time(0) - start);
if (remain <= 0) { cout << "\n时间到!\n"; break; }
Question& q = paper[i];
cout << "\n[" << i+1 << "/" << paper.size() << "] ";
cout << "剩余" << remain/60 << "分" << remain%60 << "秒\n";
showQuestion(q);
// 答案输入验证
string ans;
bool valid = false;
while (!valid) {
cout << "答案(0跳过): ";
cin >> ans;
if (ans == "0") {
valid = true;
break;
}
bool allValid = true;
for (size_t j = 0; j < ans.size(); ++j) {
char c = toupper(ans[j]);
if (c < 'A' || c > char('A' + q.options.size() - 1)) {
cout << "错误:答案 " << c << " 超出选项范围(A-" << char('A'+q.options.size()-1) << ")!请重新输入。" << endl;
allValid = false;
break;
}
}
if (allValid) valid = true;
}
vector<int> stuAns;
if (ans != "0") {
for (size_t j = 0; j < ans.size(); ++j) {
stuAns.push_back(toupper(ans[j]) - 'A');
}
}
answers[q.id] = stuAns;
}
// 判分并显示报告
cls();
cout << "========== 成绩报告 ==========\n";
double total = 0;
vector<double> scores(paper.size(), 0);
for (size_t i = 0; i < paper.size(); ++i) {
Question& q = paper[i];
double score = check(q, answers[q.id]);
scores[i] = score;
total += score;
cout << "\n" << q.content << "\n你的答案:";
vector<int>& stu = answers[q.id];
for (size_t j = 0; j < stu.size(); ++j)
cout << char('A' + stu[j]) << " ";
cout << "| 正确答案:";
for (size_t j = 0; j < q.answer.size(); ++j)
cout << char('A' + q.answer[j]) << " ";
cout << "| 得分:" << score;
if (score < 1) cout << "\n解析:" << q.explanation;
cout << endl;
}
cout << "\n总分:" << total << "/" << paper.size();
cout << " 正确率:" << total/paper.size()*100 << "%\n";
// 询问导出报告
cout << "\n是否导出本次考试报告?(y/n): ";
char ch;
cin >> ch;
if (ch == 'y' || ch == 'Y') {
string filename;
cout << "请输入导出文件名(如 report.txt): ";
cin >> filename;
ofstream fout(filename.c_str());
if (!fout) {
cout << "错误:无法创建文件!" << endl;
return;
}
fout << "========== 考试报告 ==========\n";
fout << "考试时间: " << ctime(&start);
fout << "题目数量: " << paper.size() << "\n";
fout << "总分: " << total << " / " << paper.size() << "\n";
fout << "正确率: " << total/paper.size()*100 << "%\n\n";
fout << "详细答题情况:\n";
for (size_t i = 0; i < paper.size(); ++i) {
Question& q = paper[i];
fout << "----------------------------------------\n";
fout << "题目" << i+1 << ": " << q.content << "\n";
fout << "类型: " << (q.type==0?"单选":q.type==1?"多选":"判断") << "\n";
fout << "选项:\n";
for (size_t j = 0; j < q.options.size(); ++j) {
fout << " " << char('A'+j) << ". " << q.options[j] << "\n";
}
fout << "正确答案: ";
for (size_t j = 0; j < q.answer.size(); ++j) {
fout << char('A' + q.answer[j]) << " ";
}
fout << "\n你的答案: ";
vector<int>& stu = answers[q.id];
if (stu.empty()) fout << "(未作答)";
else {
for (size_t j = 0; j < stu.size(); ++j) {
fout << char('A' + stu[j]) << " ";
}
}
fout << "\n得分: " << scores[i] << "\n";
if (scores[i] < 1) {
fout << "解析: " << q.explanation << "\n";
}
fout << "\n";
}
fout << "========== 报告结束 ==========\n";
fout.close();
cout << "考试报告已保存到 " << filename << endl;
}
}
// 题目管理(修改:单选题强制4个选项,题目文本不能为纯数字)
void manage() {
int opt;
cout << "1.添加 2.删除 3.查看 0.返回\n选择: ";
cin >> opt;
if (opt == 1) {
Question q;
q.id = nextId++;
// 验证题目类型
int type;
while (true) {
cout << "类型(0单选 1多选 2判断): ";
cin >> type;
if (type >= 0 && type <= 2) break;
cout << "错误:类型只能是0、1或2,请重新输入。" << endl;
}
q.type = type;
cin.ignore();
// 验证题目文本不能为纯数字
bool contentValid = false;
while (!contentValid) {
cout << "题目: ";
getline(cin, q.content);
if (q.content.empty()) {
cout << "错误:题目内容不能为空,请重新输入。" << endl;
continue;
}
// 检查是否为纯数字(只包含0-9,允许负号和小数点?用户要求“不可以是数字”,简单判断全数字即可)
bool allDigits = true;
for (size_t i = 0; i < q.content.size(); ++i) {
if (q.content[i] < '0' || q.content[i] > '9') {
allDigits = false;
break;
}
}
if (allDigits) {
cout << "错误:题目内容不能为纯数字,请重新输入。" << endl;
} else {
contentValid = true;
}
}
// 处理选项
if (q.type == 0) {
// 单选题:固定4个选项
int n = 4;
cout << "请输入4个选项(每行一个):" << endl;
for (int i = 0; i < n; ++i) {
string s;
cout << char('A'+i) << ": ";
getline(cin, s);
if (s.empty()) {
cout << "警告:选项内容为空,将自动填入默认文本。" << endl;
s = "选项" + char('A'+i);
}
q.options.push_back(s);
}
} else if (q.type == 1) {
// 多选题:用户输入选项数(2-10)
int n;
while (true) {
cout << "选项数: ";
cin >> n;
if (n >= 2 && n <= 10) break;
cout << "错误:选项数应为2~10之间的整数,请重新输入。" << endl;
}
cin.ignore();
for (int i = 0; i < n; ++i) {
string s;
cout << char('A'+i) << ": ";
getline(cin, s);
if (s.empty()) {
cout << "警告:选项内容为空,将自动填入默认文本。" << endl;
s = "选项" + char('A'+i);
}
q.options.push_back(s);
}
} else { // type == 2 判断
q.options.push_back("正确");
q.options.push_back("错误");
}
// 验证答案格式
string ans;
bool ansValid = false;
while (!ansValid) {
cout << "答案(字母,如A、AB等): ";
cin >> ans;
if (ans.empty()) {
cout << "错误:答案不能为空,请重新输入。" << endl;
continue;
}
// 检查每个字符是否合法
bool legal = true;
for (size_t i = 0; i < ans.size(); ++i) {
char c = toupper(ans[i]);
if (c < 'A' || c > char('A' + q.options.size() - 1)) {
cout << "错误:字母 " << c << " 超出选项范围(A-" << char('A'+q.options.size()-1) << ")!" << endl;
legal = false;
break;
}
}
if (!legal) continue;
if (q.type == 0 && ans.size() != 1) {
cout << "错误:单选题只能有一个答案,请重新输入。" << endl;
continue;
}
if (q.type == 2 && ans.size() != 1) {
cout << "错误:判断题只能有一个答案(A或B),请重新输入。" << endl;
continue;
}
ansValid = true;
}
q.answer.clear();
for (size_t i = 0; i < ans.size(); ++i) {
q.answer.push_back(toupper(ans[i]) - 'A');
}
cout << "解析: ";
cin.ignore();
getline(cin, q.explanation);
if (q.explanation.empty()) {
q.explanation = "无解析";
}
bank.push_back(q);
cout << "题目添加成功!新ID=" << q.id << endl;
} else if (opt == 2) {
if (bank.empty()) {
cout << "题库为空,无法删除。" << endl;
return;
}
int id;
cout << "输入要删除的题目ID: ";
cin >> id;
vector<Question>::iterator it;
for (it = bank.begin(); it != bank.end(); ++it) {
if (it->id == id) break;
}
if (it != bank.end()) {
bank.erase(it);
cout << "题目ID " << id << " 已删除。" << endl;
} else {
cout << "错误:未找到ID为 " << id << " 的题目。" << endl;
}
} else if (opt == 3) {
if (bank.empty()) {
cout << "题库为空。" << endl;
} else {
for (size_t i = 0; i < bank.size(); ++i) {
showQuestion(bank[i]);
cout << "---\n";
}
}
} else if (opt != 0) {
cout << "无效选项,请重新输入。" << endl;
}
}
// 主菜单
int main() {
srand(time(0));
initBank();
while (true) {
cls();
cout << "===== 数学考试系统 =====\n";
cout << "1.题目管理 2.开始考试 3.导出所有题目 0.退出\n选择: ";
int opt;
cin >> opt;
if (opt == 1) {
cls();
manage();
cin.ignore();
cin.get();
} else if (opt == 2) {
cls();
exam();
cin.ignore();
cin.get();
} else if (opt == 3) {
cls();
exportQuestions();
cin.ignore();
cin.get();
} else if (opt == 0) {
break;
} else {
cout << "无效选项,按回车继续..." << endl;
cin.ignore();
cin.get();
}
}
return 0;
}
算法设计思路
1. 数据结构设计
题目(Question)使用结构体存储:
struct Question {
int id; // 唯一标识
int type; // 0单选 1多选 2判断
string content; // 题干
vector<string> options; // 选项列表
vector<int> answer; // 正确答案索引(支持多选)
string explanation; // 解析
};
- 设计考量:用
vector<int>存储答案索引,兼容单选(1个索引)和多选(多个索引);判断视为选项只有“正确/错误”的特殊单选。
2. 考试判分算法
判分函数 check() 实现了三种题型的差异化评分规则:
double check(const Question& q, vector<int> stuAns) {
if (stuAns.empty()) return 0;
if (q.type == 2) return stuAns[0] == q.answer[0] ? 1 : 0; // 判断
if (q.type == 0) return stuAns == q.answer ? 1 : 0; // 单选
// 多选:部分正确得0.5,全对得1,有错误答案得0
for (int a : stuAns)
if (find(q.answer.begin(), q.answer.end(), a) == q.answer.end()) return 0;
return stuAns.size() == q.answer.size() ? 1 : 0.5;
}
- 亮点:多选题支持半对半给分,更贴近真实考试场景。
- 时间复杂度:O(n×m),n为学生答案数,m为标准答案数(实际n和m很小,可忽略)。
3. 随机抽题与时间控制
考试开始前,系统会:
- 从题库中随机打乱题目顺序(Fisher-Yates洗牌算法)
- 记录起始时间戳
start = time(0) - 每道题作答前计算剩余时间:
remain = limit*60 - (time(0)-start)
for (int i = 0; i < paper.size(); ++i) {
int remain = limit * 60 - (time(0) - start);
if (remain <= 0) break;
// 显示倒计时并接收答案
}
4. 文件导出设计
- 题目导出:逐行存储题目的类型、题干、选项数、选项内容、答案(如
"02"表示A和C)、解析,用空行分隔。 - 成绩报告导出:包含考试时间、总分、正确率、每道题的作答情况与得分。使用
ctime()生成时间戳,增强报告可读性。
5. 输入验证机制
为防止用户输入非法数据,代码在以下环节做了严格校验:
- 题目文本不能为纯数字
- 单选题答案只能是一个字母
- 答案字母不能超出选项范围(A~D等)
- 考试时长和题目数为正数
// 单选题强制4个选项,选项内容不能为空
if (q.type == 0) {
for (int i = 0; i < 4; ++i) {
string s; getline(cin, s);
if (s.empty()) s = "选项" + char('A'+i);
q.options.push_back(s);
}
}
运行示例
1. 初始界面
2.考试功能
3.导出考试报告
4.查看题目
5.添加题目
6.删除题目
7.添加题目时的错误提示(主要功能均有错误提示机制,在此以添加题目功能为例)
结对编程感想
本次项目采用 结对编程 模式,两人轮流来 编写代码 / 检查代码 。以下是我们合作中的真实体会:
结对编程的优点
-
错误率显著降低
一个人写代码时容易忽略边界条件(比如数组越界、文件打开失败),而另一个人实时审查能及时发现。 -
设计思路更开阔
关于“是否允许考试中跳过题目”,两人产生了不同看法。经过讨论,我们决定增加输入“0”表示跳过,但跳过题目之后该题不得分,并且不中断考试。 -
知识互补
一位成员对C++文件流更熟悉,另一位对随机算法和字符串处理更擅长。分工后,文件导出模块和洗牌算法模块都完成得很快,且代码质量高于单人编写。
结对编程的挑战
- 1.沟通成本:开发初期,成员对函数命名规范有不同意见,即存 使用英语进行标注 和 使用拼音进行标注 的分歧,后来约定参考现有代码风格。
- 2.轮换机制问题:成员在开发时往往由于投入或疲劳,导致开发时间 多于 / 少于 约定的开发时间,导致每次轮换的时间点都不固定,工作占比也不统一。
该系统的改进方向
- 持久化题库:目前每次启动初始化固定题目,可改为从文件加载。
- 图形界面:控制台在美观和交互上有限,可考虑Qt或Web版本。
- 防作弊:增加随机选项顺序、题目乱序、倒计时锁屏等。
总结
本次数学考试系统开发项目,全程采用结对编程模式推进,最终完成了功能较为完整、逻辑较为清晰的C++控制台应用。该项目覆盖题库管理、在线考试、自动判分、报告导出四大核心功能,而结对编程作为本次开发的核心合作模式,贯穿项目全流程,不仅直接影响了项目的开发效率与代码质量,更让我们在协作中获得了远超技术本身的成长。
结对编程是本次项目顺利推进的关键支撑,其优势在开发过程中得到了充分体现。不同于单人开发的局限性,两人轮流担任编写者与审查者,实时把控代码质量,有效降低了边界条件遗漏、语法错误、逻辑漏洞等问题的出现概率,比如在编写文件导出模块和输入验证机制时,审查者及时发现了文件流关闭不及时、非法输入校验不全面的问题,避免了后续测试中的大量返工。
。











浙公网安备 33010602011771号