一、报告概述
本次结对编程围绕“简易在线考试系统”展开,采用C++语言开发,实现题目增删改查、文件持久化、随机出题考试、成绩统计等核心功能。结对编程两人协同合作,遵循“分工明确、优势互补、高效协作”的原则,完成项目从需求分析到测试部署的全流程开发,既保证了代码质量,又提升了开发效率,同时积累了结对协作的实践经验。本报告将详细记录结对编程的成员分工、代码、遇到的问题及解决方法、项目成果与总结展望。
二、结对成员及分工
本次结对编程共2名成员,基于项目模块低耦合、高内聚的原则,结合两人技术特长进行合理分工,明确各自职责与协作边界,确保开发过程有序推进。具体分工如下:
成员1 (2452330 崔珈铭 博客园地址)
核心职责:负责项目基础数据结构、工具函数、题目管理模块及文件持久化模块的开发与优化,搭建系统的数据层与基础工具支撑,确保题目数据的安全存储与高效管理。
①定义Question结构体,封装题目ID、题干、题型、选项、正确答案索引等核心字段,实现typeStr()成员函数,用于返回题型描述,保障数据结构的合理性与易用性。
②开发文件持久化相关函数,包括saveQuestions()(将题库数据写入questions.txt文件)和loadQuestions()(从文件加载题库数据),处理文件打开失败等异常情况,保证数据持久化的可靠性。
③实现基础工具函数,包括waitForEnter()(等待用户按回车继续)、clearInput()(清空输入缓冲区)、displayQuestion()(展示题目详情,用于管理界面),提升用户交互体验与代码复用性。
④开发题目管理核心功能,包括addQuestion()(新增题目,区分单选题与判断题的差异化逻辑)、listQuestions()(查看所有题目)、updateQuestion()(修改题目信息)、deleteQuestion()(删除指定ID题目),完善输入合法性校验,确保功能的健壮性。
⑤配合成员2完成模块联调,提供清晰的题目数据调用接口,验证题目数据能被考试模块正确读取与使用。函数,用于返回题型描述,保障数据结构的合理性与易用性。
成员2 (2452419 宗雨辰 博客园地址)
核心职责:负责考试核心逻辑、用户交互流程、主程序整合及系统测试调试,搭建系统的业务流程层与交互层,确保考试流程顺畅、用户体验良好。
①开发shuffleOptions()函数,实现题目选项的随机打乱功能,同时匹配打乱后正确答案的索引,确保考试的公平性。
②实现startExam()函数,完成考试全流程开发,包括题目展示(打乱后选项)、用户答案输入校验、计分规则、错误答案提示、成绩统计与展示,处理题库为空等边界场景。
③负责main()函数的开发与优化,设计系统主菜单,实现菜单循环展示、用户选择接收、对应功能调用的逻辑,优化菜单提示语与错误输入提示,提升用户交互体验。
④完成系统联调,整合成员1开发的题目管理模块,验证“新增题目→加载题目→开始考试”的端到端流程,确保各模块衔接顺畅。
⑤负责系统测试与调试,排查功能漏洞(如输入非法字符、题型修改后数据异常等),优化代码容错性与运行效率。
三、代码模块
文件持久化saveQuestions()
void saveQuestions()
{
ofstream out("questions.txt");
if (!out)
{
cerr << "警告:无法保存题目文件!\n";
return;
}
out << questions.size() << "\n";
for (const auto &q : questions)
{
out << q.id << "|" << q.type << "|" << q.text << "|";
// 保存选项,用逗号分隔
for (size_t i = 0; i < q.options.size(); ++i)
{
if (i > 0)
out << ",";
out << q.options[i];
}
out << "|" << q.correctIndex << "\n";
}
out.close();
}
|
加载问题loadQuestions()
void loadQuestions()
{
ifstream in("questions.txt");
if (!in)
return; // 没有旧文件,直接开始
questions.clear();
size_t count;
in >> count;
in.ignore(); // 忽略换行
for (size_t i = 0; i < count; ++i)
{
string line;
getline(in, line);
if (line.empty())
continue;
stringstream ss(line);
string idStr, typeStr, text, optionsStr, correctStr;
getline(ss, idStr, '|');
getline(ss, typeStr, '|');
getline(ss, text, '|');
getline(ss, optionsStr, '|');
getline(ss, correctStr, '|');
Question q;
q.id = stoi(idStr);
q.type = stoi(typeStr);
q.text = text;
// 解析选项
stringstream optStream(optionsStr);
string opt;
while (getline(optStream, opt, ','))
{
q.options.push_back(opt);
}
q.correctIndex = stoi(correctStr);
questions.push_back(q);
if (q.id >= nextId)
nextId = q.id + 1;
}
in.close();
}
|
清除缓冲区clearInput()
void clearInput()
{
cin.clear();
cin.ignore(numeric_limits<streamsize>::max(), '\n');
}
|
显示单个题目displayQuestion()
void displayQuestion(const Question &q, int index = -1)
{
if (index >= 0)
cout << "[" << index << "] ";
cout << "ID:" << q.id << " | " << q.typeStr() << " | " << q.text << "\n";
char optLetter = 'A';
for (size_t i = 0; i < q.options.size(); ++i)
{
cout << " " << char(optLetter + i) << ". " << q.options[i] << "\n";
}
cout << " 正确答案: " << char('A' + q.correctIndex) << "\n";
}
|
添加题目addQuestion()
void addQuestion()
{
Question q;
q.id = nextId++;
cout << "\n--- 新增题目 ---\n";
cout << "请输入题干:(先输入回车再输入题目) \n";
clearInput();
getline(cin, q.text);
cout << "题型 (0-单选题, 1-判断题): ";
int t;
cin >> t;
while (t != 0 && t != 1)
{
cout << "输入错误,请重新输入 (0/1): ";
cin >> t;
}
q.type = t;
clearInput();
if (q.type == 0)
{ // 单选题
int optCount;
cout << "请输入选项个数 (2-6): ";
cin >> optCount;
while (optCount < 2 || optCount > 6)
{
cout << "选项个数应为2~6,请重新输入: ";
cin >> optCount;
}
clearInput();
q.options.resize(optCount);
for (int i = 0; i < optCount; ++i)
{
cout << "请输入选项 " << char('A' + i) << ": ";
getline(cin, q.options[i]);
}
char correct;
cout << "请输入正确选项 (A-" << char('A' + optCount - 1) << "): ";
cin >> correct;
correct = toupper(correct);
int idx = correct - 'A';
while (idx < 0 || idx >= optCount)
{
cout << "无效选项,请重新输入: ";
cin >> correct;
correct = toupper(correct);
idx = correct - 'A';
}
q.correctIndex = idx;
}
else
{ // 判断题
q.options = {"正确", "错误"};
int correctVal;
cout << "请输入正确答案 (1-正确, 2-错误): ";
cin >> correctVal;
while (correctVal != 1 && correctVal != 2)
{
cout << "请输入1或2: ";
cin >> correctVal;
}
q.correctIndex = (correctVal == 1) ? 0 : 1;
}
questions.push_back(q);
saveQuestions();
cout << "题目添加成功!\n";
waitForEnter();
}
|
查看所有题目listQuestions()
void listQuestions()
{
if (questions.empty())
{
cout << "\n题库为空。\n";
waitForEnter();
return;
}
cout << "\n========== 所有题目 ==========\n";
for (size_t i = 0; i < questions.size(); ++i)
{
displayQuestion(questions[i], i + 1);
cout << "----------------------------\n";
}
waitForEnter();
}
|
修改题目updateQuestion()
void updateQuestion()
{
if (questions.empty())
{
cout << "\n题库为空,无法修改。\n";
waitForEnter();
return;
}
int id;
cout << "\n请输入要修改的题目ID: ";
cin >> id;
clearInput();
auto it = find_if(questions.begin(), questions.end(), [id](const Question &q)
{ return q.id == id; });
if (it == questions.end())
{
cout << "未找到该ID的题目。\n";
waitForEnter();
return;
}
cout << "当前题目信息:\n";
displayQuestion(*it);
cout << "\n开始修改(直接回车保留原值)\n";
string newText;
cout << "新题干(原:" << it->text << "): ";
getline(cin, newText);
if (!newText.empty())
it->text = newText;
int newType;
cout << "新题型(0-单选 1-判断,原:" << it->type << "): ";
string typeStr;
getline(cin, typeStr);
if (!typeStr.empty())
{
newType = stoi(typeStr);
if (newType != it->type)
{
// 题型改变,需要重置选项
it->type = newType;
if (newType == 1)
{ // 改成判断题
it->options = {"正确", "错误"};
int correctVal;
cout << "请输入正确答案 (1-正确, 2-错误): ";
cin >> correctVal;
clearInput();
it->correctIndex = (correctVal == 1) ? 0 : 1;
}
else
{ // 改成单选题
int optCount;
cout << "请输入选项个数 (2-6): ";
cin >> optCount;
clearInput();
it->options.resize(optCount);
for (int i = 0; i < optCount; ++i)
{
cout << "选项 " << char('A' + i) << ": ";
getline(cin, it->options[i]);
}
char correct;
cout << "正确选项 (A-" << char('A' + optCount - 1) << "): ";
cin >> correct;
clearInput();
it->correctIndex = toupper(correct) - 'A';
}
}
}
// 如果题型未变且是单选题,可以修改选项和答案
if (it->type == 0 && typeStr.empty())
{
cout << "是否修改选项?(y/n): ";
char modifyOpt;
cin >> modifyOpt;
clearInput();
if (modifyOpt == 'y' || modifyOpt == 'Y')
{
int optCount;
cout << "新选项个数 (2-6): ";
cin >> optCount;
clearInput();
it->options.resize(optCount);
for (int i = 0; i < optCount; ++i)
{
cout << "选项 " << char('A' + i) << ": ";
getline(cin, it->options[i]);
}
char correct;
cout << "新正确选项: ";
cin >> correct;
clearInput();
it->correctIndex = toupper(correct) - 'A';
}
}
saveQuestions();
cout << "修改成功!\n";
waitForEnter();
}
|
删除题目deleteQuestion()
void deleteQuestion()
{
if (questions.empty())
{
cout << "\n题库为空。\n";
waitForEnter();
return;
}
int id;
cout << "\n请输入要删除的题目ID: ";
cin >> id;
clearInput();
auto it = find_if(questions.begin(), questions.end(), [id](const Question &q)
{ return q.id == id; });
if (it == questions.end())
{
cout << "未找到该ID的题目。\n";
}
else
{
questions.erase(it);
saveQuestions();
cout << "题目已删除。\n";
}
waitForEnter();
}
|
考试模块startExam()
void startExam()
{
if (questions.empty())
{
cout << "\n题库为空,无法开始考试。\n";
waitForEnter();
return;
}
cout << "\n========== 开始考试 ==========\n";
cout << "共 " << questions.size() << " 题,每题1分。\n";
cout << "注意:选项顺序已经随机打乱,请仔细选择!\n";
cout << "输入对应选项字母(A/B/C...)后按回车。\n\n";
int score = 0;
int total = questions.size();
// 逐题作答
for (size_t idx = 0; idx < questions.size(); ++idx)
{
const Question &q = questions[idx];
// 随机打乱选项顺序(对单选题和判断题均生效)
vector<string> shuffledOpts;
int newCorrectIdx;
shuffleOptions(q, shuffledOpts, newCorrectIdx);
// 显示题目(打乱后的选项)
cout << "第 " << idx + 1 << " 题:" << q.text << "\n";
char optLetter = 'A';
for (size_t i = 0; i < shuffledOpts.size(); ++i)
{
cout << " " << char(optLetter + i) << ". " << shuffledOpts[i] << "\n";
}
// 获取用户输入
char userChoice;
cout << "你的答案: ";
cin >> userChoice;
userChoice = toupper(userChoice);
int userIdx = userChoice - 'A';
// 验证输入合法性
while (userIdx < 0 || userIdx >= (int)shuffledOpts.size())
{
cout << "无效输入,请输入 A-" << char('A' + shuffledOpts.size() - 1) << ": ";
cin >> userChoice;
userChoice = toupper(userChoice);
userIdx = userChoice - 'A';
}
// 判分
if (userIdx == newCorrectIdx)
{
cout << " 正确\n\n";
score++;
}
else
{
// 显示正确答案(选项文本)
string correctText = shuffledOpts[newCorrectIdx];
cout << " 错误。正确答案是 " << char('A' + newCorrectIdx) << ". " << correctText << "\n\n";
}
}
// 输出最终成绩
cout << "========== 考试结束 ==========\n";
cout << "你的得分: " << score << " / " << total << "\n";
double percent = (total > 0) ? (score * 100.0 / total) : 0;
cout << "正确率: " << fixed << setprecision(1) << percent << "%\n";
waitForEnter();
}
|
主菜单main()
int main()
{
loadQuestions(); // 加载已有题目
int choice;
do
{
cout << "\n====== 简易在线考试系统 ======\n";
cout << "1. 添加题目\n";
cout << "2. 查看所有题目\n";
cout << "3. 修改题目\n";
cout << "4. 删除题目\n";
cout << "5. 开始考试\n";
cout << "6. 退出系统\n";
cout << "请选择 (1-6): ";
cin >> choice;
clearInput();
switch (choice)
{
case 1:
addQuestion();
break;
case 2:
listQuestions();
break;
case 3:
updateQuestion();
break;
case 4:
deleteQuestion();
break;
case 5:
startExam();
break;
case 6:
cout << "谢谢使用!\n";
break;
default:
cout << "无效选项,请重新输入。\n";
waitForEnter();
break;
}
} while (choice != 6);
return 0;
}
|
四、项目成果
经过结对编程协作,成功完成简易在线考试系统的开发,实现了所有核心功能,达到了预期需求,具体成果如下:
功能完整:实现了题目增删改查、文件持久化、随机打乱选项考试、成绩统计与展示等核心功能,支持单选题与判断题两种题型,满足基本的在线考试需求。
代码规范:代码命名、注释符合约定规范,结构清晰,逻辑严谨,无语法错误与明显漏洞,可维护性强。
用户体验良好:具备完善的输入校验与友好的提示信息,考试过程中选项随机打乱,保证公平性,成绩展示清晰,操作流程简洁易懂。
运行稳定:经过多轮测试,系统能够稳定运行,无崩溃、数据丢失等问题,文件持久化功能可靠,题目数据能够正常保存与加载。
项目最终交付文件包括:project1.cpp(核心源代码)、questions.txt(题库数据文件),以及本结对编程报告,完整呈现了项目开发的全过程。
五、问题及解决方法
问题1:题目选项打乱后,正确答案索引无法匹配,导致考试判分错误。
解决方法:两人共同分析逻辑,确定通过保存原正确选项的文本,在打乱后的选项列表中查找该文本的位置,从而获取新的正确答案索引,修改shuffleOptions()函数的逻辑,测试后问题解决。
void shuffleOptions(const Question &q, vector<string> &shuffledOpts, int &newCorrectIdx)
{
shuffledOpts = q.options;
// 使用随机洗牌
shuffle(shuffledOpts.begin(), shuffledOpts.end(), rng);
// 找到原正确选项的文本,在新列表中的位置
string correctText = q.options[q.correctIndex];
for (size_t i = 0; i < shuffledOpts.size(); ++i)
{
if (shuffledOpts[i] == correctText)
{
newCorrectIdx = i;
break;
}
}
}
问题2:修改题目题型时,若从单选题改为判断题,选项已重置为“正确”“错误”,但正确答案索引未同步更新,导致后续考试判分异常。
解决方法:成员1修改updateQuestion()函数,在题型切换为判断题时,强制要求用户重新输入正确答案,同步更新正确答案索引,确保数据一致性。
if (!typeStr.empty())
{
newType = stoi(typeStr);
if (newType != it->type)
{
// 题型改变,需要重置选项
it->type = newType;
if (newType == 1)
{ // 改成判断题
it->options = {"正确", "错误"};
int correctVal;
cout << "请输入正确答案 (1-正确, 2-错误): ";
cin >> correctVal;
clearInput();
it->correctIndex = (correctVal == 1) ? 0 : 1;
}
else
{ // 改成单选题
int optCount;
cout << "请输入选项个数 (2-6): ";
cin >> optCount;
clearInput();
it->options.resize(optCount);
for (int i = 0; i < optCount; ++i)
{
cout << "选项 " << char('A' + i) << ": ";
getline(cin, it->options[i]);
}
char correct;
cout << "正确选项 (A-" << char('A' + optCount - 1) << "): ";
cin >> correct;
clearInput();
it->correctIndex = toupper(correct) - 'A';
}
}
}
// 如果题型未变且是单选题,可以修改选项和答案
if (it->type == 0 && typeStr.empty())
{
cout << "是否修改选项?(y/n): ";
char modifyOpt;
cin >> modifyOpt;
clearInput();
if (modifyOpt == 'y' || modifyOpt == 'Y')
{
int optCount;
cout << "新选项个数 (2-6): ";
cin >> optCount;
clearInput();
it->options.resize(optCount);
for (int i = 0; i < optCount; ++i)
{
cout << "选项 " << char('A' + i) << ": ";
getline(cin, it->options[i]);
}
char correct;
cout << "新正确选项: ";
cin >> correct;
clearInput();
it->correctIndex = toupper(correct) - 'A';
}
}
问题3:文件加载时,若questions.txt文件中存在空行,会导致getline读取空行后解析失败,程序异常。
解决方法:成员1在loadQuestions()函数中添加空行判断,遇到空行直接跳过,避免解析异常,同时优化文件写入逻辑,确保写入的文件无多余空行。
六、总结与展望
本次结对编程圆满完成了简易在线考试系统的开发任务,通过两人的协同合作,不仅高效完成了项目开发,还积累了宝贵的结对协作经验。相比单人开发,结对编程具有明显优势:导航员的审核机制有效降低了代码错误率,实时沟通减少了问题积累,分工协作提升了开发效率,互相学习促进了个人技术提升。
同时,也认识到存在的不足:一是协作初期节奏把控不够好,导致部分代码规范不统一;二是对边界场景的考虑不够全面,测试阶段才发现部分异常问题;三是代码的扩展性有待提升,后续难以快速添加新功能(如多选题、批量导入题目等)。