|
|
|
|
@ -0,0 +1,425 @@
|
|
|
|
|
# 中小学数学卷子自动生成程序
|
|
|
|
|
|
|
|
|
|
一个基于 C++ 的命令行应用程序,为小学、初中和高中教师自动生成数学试卷,确保题目唯一性。
|
|
|
|
|
|
|
|
|
|
## 一、项目概述
|
|
|
|
|
|
|
|
|
|
本项目采用面向对象设计,通过多级难度题目生成器和智能查重机制,为不同学段的数学教师提供便捷的试卷生成工具。
|
|
|
|
|
|
|
|
|
|
## 二、编译运行
|
|
|
|
|
|
|
|
|
|
### 环境要求
|
|
|
|
|
|
|
|
|
|
- C++17 或更高版本
|
|
|
|
|
|
|
|
|
|
- 支持标准库的 C++ 编译器(g++、clang++、MSVC)
|
|
|
|
|
|
|
|
|
|
### 运行
|
|
|
|
|
|
|
|
|
|
- **Windows操作系统在PowerShell中执行:chcp 65001; .\main.exe**
|
|
|
|
|
|
|
|
|
|
- **Windows操作系统在CMD中执行:chcp 65001 && main.exe**
|
|
|
|
|
|
|
|
|
|
- Windows采用本地语言编码(GBK),而程序用UTF-8输出,从而导致中文乱码。chcp 65001将设置Windows当前终端会话用UTF-8编码来显示文字,以此来解决中文乱码问题。
|
|
|
|
|
|
|
|
|
|
## 三、核心类设计
|
|
|
|
|
|
|
|
|
|
### 类关系图
|
|
|
|
|
Application
|
|
|
|
|
├── User (用户信息)
|
|
|
|
|
├── PaperManager (试卷管理)
|
|
|
|
|
└── QuestionGenerator (题目生成器接口)
|
|
|
|
|
├── PrimaryGenerator (小学)
|
|
|
|
|
├── MiddleGenerator (初中)
|
|
|
|
|
└── HighGenerator (高中)
|
|
|
|
|
|
|
|
|
|
### 类详细说明
|
|
|
|
|
|
|
|
|
|
#### Application - 应用程序主控制器
|
|
|
|
|
**职责**:程序入口,用户交互流程控制,协调各个模块协作
|
|
|
|
|
|
|
|
|
|
**关键方法**:
|
|
|
|
|
- `run()` - 程序主循环
|
|
|
|
|
- `loginPhase()` - 处理用户登录
|
|
|
|
|
- `mainOperationPhase()` - 主操作逻辑
|
|
|
|
|
- `createGenerator(Grade)` - 工厂方法创建题目生成器
|
|
|
|
|
- `switchGenerator(string)` - 动态切换生成器
|
|
|
|
|
|
|
|
|
|
#### User - 用户信息管理
|
|
|
|
|
**职责**:存储和管理用户身份信息及权限等级
|
|
|
|
|
|
|
|
|
|
**属性**:
|
|
|
|
|
- `username` - 用户名
|
|
|
|
|
- `password` - 密码
|
|
|
|
|
- `grade` - 学段等级(Primary/Middle/High)
|
|
|
|
|
|
|
|
|
|
**关键方法**:
|
|
|
|
|
- `userInital()` - 用户信息初始化
|
|
|
|
|
- `getGradString()` - 获取学段字符串表示
|
|
|
|
|
|
|
|
|
|
#### QuestionGenerator - 题目生成器抽象基类
|
|
|
|
|
**职责**:定义题目生成的统一接口,提供随机数工具
|
|
|
|
|
|
|
|
|
|
**设计特点**:
|
|
|
|
|
- 使用现代 C++ 随机数库 (`random_device`, `mt19937`)
|
|
|
|
|
- 纯虚函数 `generateQuestion()` 强制子类实现
|
|
|
|
|
- 模板方法模式提供 `getRand()` 工具函数
|
|
|
|
|
|
|
|
|
|
#### 具体生成器类
|
|
|
|
|
- **PrimaryGenerator** - 小学数学题目生成
|
|
|
|
|
- **MiddleGenerator** - 初中数学题目生成
|
|
|
|
|
- **HighGenerator** - 高中数学题目生成
|
|
|
|
|
|
|
|
|
|
#### PaperManager - 试卷管理器
|
|
|
|
|
**职责**:试卷生成、查重、文件保存的全生命周期管理
|
|
|
|
|
|
|
|
|
|
**属性**:
|
|
|
|
|
- `questions` - 当前已生成试卷
|
|
|
|
|
- `historys` - 历史试卷
|
|
|
|
|
|
|
|
|
|
**关键方法**:
|
|
|
|
|
- `void setGenerator(QuestionGenerator* newGenerator);` -生成题目
|
|
|
|
|
- `bool generatePaper(int num);` - 生成试卷
|
|
|
|
|
- `int loadHistoryQuestions();` - 加载用户历史试卷
|
|
|
|
|
- `bool isDulicate(const std::string& question);` - 查重逻辑
|
|
|
|
|
- `int savePaperToFile();` - 保存试卷
|
|
|
|
|
|
|
|
|
|
## 四、主要代码
|
|
|
|
|
|
|
|
|
|
### 1、程序入口
|
|
|
|
|
|
|
|
|
|
Application类负责调用其他类提供的对外接口,共有登陆和主要操作两个阶段
|
|
|
|
|
|
|
|
|
|
```cpp
|
|
|
|
|
void Application::run() {
|
|
|
|
|
std::cout << "=== 中小学数学试卷自动生成程序 ===" << std::endl;
|
|
|
|
|
|
|
|
|
|
while(true){
|
|
|
|
|
//登录界面
|
|
|
|
|
if(!loginPhase()){
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
//主要操作界面
|
|
|
|
|
mainOperationPhase();
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
### 2、用户登陆
|
|
|
|
|
|
|
|
|
|
登陆阶段主要负责接收用户的登录请求,请求成功后初始化一系列必要的资源。
|
|
|
|
|
|
|
|
|
|
```cpp
|
|
|
|
|
bool Application::loginPhase(){
|
|
|
|
|
std::cout << "0. 退出系统" << std::endl;
|
|
|
|
|
std::cout << "1. 登录" << std::endl;
|
|
|
|
|
int logresult;
|
|
|
|
|
std::string choice;
|
|
|
|
|
|
|
|
|
|
while(true){
|
|
|
|
|
std::cout << "在此处输入您的选择:";
|
|
|
|
|
std::getline(std::cin, choice);
|
|
|
|
|
if(choice == "0"){
|
|
|
|
|
return false;
|
|
|
|
|
} else if(choice == "1"){
|
|
|
|
|
logresult = handlelogin();
|
|
|
|
|
} else{
|
|
|
|
|
std::cout << "请根据提示输入正确的选项!!!" << std::endl;
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
if(logresult == 0){
|
|
|
|
|
QuestionGenerator* questiongenerator = createGenerator(myUser.getGrad());
|
|
|
|
|
myPaperManager = std::make_unique<PaperManager>(myUser.getUsername(), questiongenerator);
|
|
|
|
|
myPaperManager->loadHistoryQuestions();
|
|
|
|
|
return true;
|
|
|
|
|
} else if(logresult == -1){
|
|
|
|
|
std::cout << "文件连接出错,请等待技术人员修复..." << std::endl;
|
|
|
|
|
system("pause");
|
|
|
|
|
} else
|
|
|
|
|
std::cout << "账号或者密码错误!" << std::endl;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
int Application::handlelogin() {
|
|
|
|
|
std::string str, username, password;
|
|
|
|
|
std::vector<std::string> tokens, account;
|
|
|
|
|
account = split(getLoginInput());
|
|
|
|
|
if(account.size() == 2){
|
|
|
|
|
username = account[0];
|
|
|
|
|
password = account[1];
|
|
|
|
|
} else{
|
|
|
|
|
return 1;
|
|
|
|
|
}
|
|
|
|
|
std::ifstream infile("users.txt");
|
|
|
|
|
if (!infile) {
|
|
|
|
|
std::cerr << "Unable to open file users.txt";
|
|
|
|
|
return -1;
|
|
|
|
|
}
|
|
|
|
|
while (getline(infile, str)) {
|
|
|
|
|
tokens = split(str, ' ');
|
|
|
|
|
if (tokens.size() == 3 && tokens[1] == username && tokens[2] == password) {
|
|
|
|
|
std::string level = tokens[0];
|
|
|
|
|
Grade grade;
|
|
|
|
|
if (level == "小学") {
|
|
|
|
|
grade = Primary;
|
|
|
|
|
} else if (level == "初中") {
|
|
|
|
|
grade = Middle;
|
|
|
|
|
} else if (level == "高中") {
|
|
|
|
|
grade = High;
|
|
|
|
|
} else {
|
|
|
|
|
grade = Unknown;
|
|
|
|
|
}
|
|
|
|
|
myUser.userInital(username, password, grade);
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
infile.close();
|
|
|
|
|
return 1;
|
|
|
|
|
}
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
### 3、主要操作
|
|
|
|
|
|
|
|
|
|
这一阶段是主要是通过调用Application类的私有成员myPaperManager和myUser来完成的
|
|
|
|
|
|
|
|
|
|
myPaperManager会根据用户的年级以及需求调用对应的Generator来生成问题
|
|
|
|
|
|
|
|
|
|
myUser提供当前登陆用户的姓名、密码以及年纪信息
|
|
|
|
|
|
|
|
|
|
```cpp
|
|
|
|
|
bool Application::mainOperationPhase(){
|
|
|
|
|
std::cout << "登录成功!当前选择为" << myUser.getGradString() << "出题" << std::endl;
|
|
|
|
|
std::cout << "选项:" << std::endl;
|
|
|
|
|
std::cout << "1. 输入10-30的数字:生成对应数量的题目" << std::endl;
|
|
|
|
|
std::cout << "2. 输入'切换为小学/初中/高中':切换出题难度" << std::endl;
|
|
|
|
|
std::cout << "3. 输入-1:退出当前用户,重新登陆" << std::endl;
|
|
|
|
|
while (true) {
|
|
|
|
|
|
|
|
|
|
std::cout << "请输入指令: ";
|
|
|
|
|
std::string str ;
|
|
|
|
|
std::cin >> str;
|
|
|
|
|
std::cin.ignore(std::numeric_limits<std::streamsize>::max(), '\n');
|
|
|
|
|
if(str.find("切换为") == 0){
|
|
|
|
|
switchGenerator(str.substr(9));
|
|
|
|
|
} else if(isInteger(str)) {
|
|
|
|
|
int num = std::stoi(str);
|
|
|
|
|
if(num >= 10 && num <= 30){
|
|
|
|
|
if(myPaperManager->generatePaper(num))
|
|
|
|
|
std::cout << "试卷生成成功!" << std::endl;
|
|
|
|
|
} else if(num == -1){
|
|
|
|
|
myPaperManager.reset();
|
|
|
|
|
return false;
|
|
|
|
|
} else {
|
|
|
|
|
std::cout << "请输入正确的数字!!!" << std::endl;
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
std::cout << "输入无效,请重新输入" << std::endl;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
### 4、试卷生成逻辑
|
|
|
|
|
|
|
|
|
|
myPaperManager的成员变量currentGenerator调用generateQuestion()生成题目
|
|
|
|
|
|
|
|
|
|
```cpp
|
|
|
|
|
bool PaperManager::generatePaper(int num){
|
|
|
|
|
if(currentGenerator == nullptr)
|
|
|
|
|
return false;
|
|
|
|
|
|
|
|
|
|
questions.clear();
|
|
|
|
|
std::string newQuestion;
|
|
|
|
|
for(int i=0 ; i<num ; i++){
|
|
|
|
|
const int max_Count = 1000;
|
|
|
|
|
int current_Count = 0;
|
|
|
|
|
|
|
|
|
|
do {
|
|
|
|
|
current_Count++;
|
|
|
|
|
newQuestion = currentGenerator->generateQuestion();
|
|
|
|
|
if(current_Count >= max_Count)
|
|
|
|
|
break;
|
|
|
|
|
} while(isDulicate(newQuestion));
|
|
|
|
|
questions.push_back(newQuestion);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if((savePaperToFile() == 0)){
|
|
|
|
|
for(const auto& question : questions){
|
|
|
|
|
historys.insert(question);
|
|
|
|
|
}
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
#### 小学题目生成
|
|
|
|
|
|
|
|
|
|
```cpp
|
|
|
|
|
std::string PrimaryGenerator::generateQuestion() {
|
|
|
|
|
std::stringstream ss;
|
|
|
|
|
const std::vector<std::string> operators = {"+", "-", "*", "/"};
|
|
|
|
|
|
|
|
|
|
const int numCount = getRand(2, 5);
|
|
|
|
|
const bool useParentheses = (getRand(0, 1) == 1) && (numCount > 2);
|
|
|
|
|
|
|
|
|
|
if (useParentheses) {
|
|
|
|
|
int leftPos = getRand(0, numCount - 2);
|
|
|
|
|
int rightPos = getRand(leftPos + 1, numCount - 1);
|
|
|
|
|
for (int i = 0; i < numCount; i++) {
|
|
|
|
|
if (i == leftPos) {
|
|
|
|
|
ss << "(";
|
|
|
|
|
}
|
|
|
|
|
ss << getRand(1, 100);
|
|
|
|
|
if (i == rightPos) {
|
|
|
|
|
ss << ")";
|
|
|
|
|
}
|
|
|
|
|
if (i < numCount - 1) {
|
|
|
|
|
ss << " " << operators[getRand(0, 3)] << " ";
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
for (int i = 0; i < numCount; i++) {
|
|
|
|
|
ss << getRand(1, 100);
|
|
|
|
|
if (i < numCount - 1) {
|
|
|
|
|
ss << " " << operators[getRand(0, 3)] << " ";
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return ss.str() + " = ?";
|
|
|
|
|
}
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
#### 初中题目生成
|
|
|
|
|
|
|
|
|
|
```cpp
|
|
|
|
|
std::string MiddleGenerator::generateQuestion(){
|
|
|
|
|
const int numCount = getRand(1, 5);
|
|
|
|
|
std::stringstream ss;
|
|
|
|
|
std::vector<std::string> operators = {"+","-","*","/"};
|
|
|
|
|
std::vector<std::string> operands;
|
|
|
|
|
bool hasAdvanced = false;
|
|
|
|
|
for (int i = 0; i < numCount; i++) {
|
|
|
|
|
int num = getRand(1, 100);
|
|
|
|
|
std::string operand = std::to_string(num);
|
|
|
|
|
|
|
|
|
|
if (numCount == 1 || getRand(0, 1) == 1) {
|
|
|
|
|
hasAdvanced = true;
|
|
|
|
|
if (getRand(0, 1) == 0) {
|
|
|
|
|
operand = operand + "²";
|
|
|
|
|
} else {
|
|
|
|
|
operand = "√" + operand;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
operands.push_back(operand);
|
|
|
|
|
}
|
|
|
|
|
if (!hasAdvanced) {
|
|
|
|
|
if (getRand(0, 1) == 0) {
|
|
|
|
|
operands[0] = operands[0] + "²";
|
|
|
|
|
} else {
|
|
|
|
|
operands[0] = "√" + operands[0];
|
|
|
|
|
}
|
|
|
|
|
hasAdvanced = true;
|
|
|
|
|
}
|
|
|
|
|
for (int i = 0; i < numCount; i++) {
|
|
|
|
|
ss << operands[i];
|
|
|
|
|
if (i < numCount - 1) {
|
|
|
|
|
ss << " " << operators[getRand(0,3)] << " ";
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return ss.str() + " = ?";
|
|
|
|
|
}
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
#### 高中题目生成
|
|
|
|
|
|
|
|
|
|
```cpp
|
|
|
|
|
std::string HighGenerator::generateQuestion(){
|
|
|
|
|
const int numCount = getRand(1, 5);
|
|
|
|
|
std::stringstream ss;
|
|
|
|
|
std::vector<std::string> trigFunctions = {"sin", "cos", "tan"};
|
|
|
|
|
std::vector<std::string> operators = {"+","-","*","/"};
|
|
|
|
|
|
|
|
|
|
std::vector<std::string> operands;
|
|
|
|
|
bool hasTrig = false;
|
|
|
|
|
for (int i = 0; i < numCount; i++) {
|
|
|
|
|
int num = getRand(1, 100);
|
|
|
|
|
std::string operand = std::to_string(num);
|
|
|
|
|
if (numCount == 1 || getRand(0, 1) == 1) {
|
|
|
|
|
hasTrig = true;
|
|
|
|
|
std::string trigFunc = trigFunctions[getRand(0,2)];
|
|
|
|
|
operand = trigFunc + operand + "°";
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
operands.push_back(operand);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!hasTrig) {
|
|
|
|
|
operands[0] = trigFunctions[getRand(0,2)] + operands[0] + "°";
|
|
|
|
|
hasTrig = true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
for (int i = 0; i < numCount; i++) {
|
|
|
|
|
ss << operands[i];
|
|
|
|
|
if (i < numCount - 1) {
|
|
|
|
|
ss << " " << operators[getRand(0,3)] << " ";
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return ss.str() + " = ?";
|
|
|
|
|
}
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
### 5、题目查重逻辑
|
|
|
|
|
|
|
|
|
|
对当前生成的问题,isDulicate会遍历历史已经生成和当前正在生成的题目库,避免生成一样的题目
|
|
|
|
|
|
|
|
|
|
```cpp
|
|
|
|
|
bool PaperManager::isDulicate(const std::string& question){
|
|
|
|
|
if(historys.find(question) != historys.end())
|
|
|
|
|
return true;
|
|
|
|
|
if(std::find(questions.begin(), questions.end() ,question) != questions.end())
|
|
|
|
|
return true;
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
### 6、题目保存逻辑
|
|
|
|
|
|
|
|
|
|
```cpp
|
|
|
|
|
int PaperManager::savePaperToFile(){
|
|
|
|
|
std::string str = simpleChineseToPinyin(username);
|
|
|
|
|
std::string floderPath = "../paper/" + str + "/";
|
|
|
|
|
std::filesystem::create_directories(floderPath) ;
|
|
|
|
|
|
|
|
|
|
std::string filename = floderPath + getCurrntTimeString() + ".txt";
|
|
|
|
|
std::ofstream outfile(filename);
|
|
|
|
|
if (!outfile) {
|
|
|
|
|
std::cerr << "Unable to open file " << filename << std::endl;
|
|
|
|
|
return -1;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
for(size_t i = 0 ; i<questions.size() ; i++){
|
|
|
|
|
outfile << (i+1) << '.' << questions[i] << "\n\n";
|
|
|
|
|
}
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
## 五、核心特性
|
|
|
|
|
|
|
|
|
|
### 1、多态题目生成
|
|
|
|
|
|
|
|
|
|
- 使用抽象基类 QuestionGenerator 定义统一接口
|
|
|
|
|
|
|
|
|
|
- 具体生成器继承并实现不同难度的题目生成逻辑
|
|
|
|
|
|
|
|
|
|
- 支持运行时动态切换生成器
|
|
|
|
|
|
|
|
|
|
### 2、内存安全
|
|
|
|
|
|
|
|
|
|
std::unique_ptr 自动管理 PaperManager 生命周期,避免内存泄漏和悬空指针问题
|