You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

425 lines
12 KiB

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

# 中小学数学卷子自动生成程序
一个基于 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 生命周期,避免内存泄漏和悬空指针问题