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.
pn9spckfw d2b47b3387
Update README.md
3 months ago
.vscode 添加README.md说明;修改中文文件名为拼英;优化终端显示 4 months ago
doc 添加README.md说明;修改中文文件名为拼英;优化终端显示 4 months ago
src 添加README.md说明;修改中文文件名为拼英;优化终端显示 4 months ago
README.md Update README.md 3 months ago

README.md

中小学数学卷子自动生成程序

一个基于 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类负责调用其他类提供的对外接口共有登陆和主要操作两个阶段

void Application::run() {
    std::cout << "=== 中小学数学试卷自动生成程序 ===" << std::endl;

    while(true){
        //登录界面
        if(!loginPhase()){ 
            break;  
        }

        //主要操作界面
        mainOperationPhase();
    
    }
}

2、用户登陆

登陆阶段主要负责接收用户的登录请求,请求成功后初始化一系列必要的资源。

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提供当前登陆用户的姓名、密码以及年纪信息

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生成题目

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;
}

小学题目生成

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() + " = ?";
}

初中题目生成

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() + " = ?";
}

高中题目生成

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会遍历历史已经生成和当前正在生成的题目库避免生成一样的题目

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、题目保存逻辑

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 生命周期,避免内存泄漏和悬空指针问题