diff --git a/.vs/ProjectSettings.json b/.vs/ProjectSettings.json new file mode 100644 index 0000000..f8b4888 --- /dev/null +++ b/.vs/ProjectSettings.json @@ -0,0 +1,3 @@ +{ + "CurrentProjectSetting": null +} \ No newline at end of file diff --git a/.vs/VSWorkspaceState.json b/.vs/VSWorkspaceState.json new file mode 100644 index 0000000..6b61141 --- /dev/null +++ b/.vs/VSWorkspaceState.json @@ -0,0 +1,6 @@ +{ + "ExpandedNodes": [ + "" + ], + "PreviewInSolutionExplorer": false +} \ No newline at end of file diff --git a/.vs/exam/FileContentIndex/1ddc9389-948a-4a78-bd01-9394694742a9.vsidx b/.vs/exam/FileContentIndex/1ddc9389-948a-4a78-bd01-9394694742a9.vsidx new file mode 100644 index 0000000..9962f5d Binary files /dev/null and b/.vs/exam/FileContentIndex/1ddc9389-948a-4a78-bd01-9394694742a9.vsidx differ diff --git a/.vs/exam/v17/.wsuo b/.vs/exam/v17/.wsuo new file mode 100644 index 0000000..38c182f Binary files /dev/null and b/.vs/exam/v17/.wsuo differ diff --git a/.vs/exam/v17/DocumentLayout.json b/.vs/exam/v17/DocumentLayout.json new file mode 100644 index 0000000..dc3fe9b --- /dev/null +++ b/.vs/exam/v17/DocumentLayout.json @@ -0,0 +1,41 @@ +{ + "Version": 1, + "WorkspaceRootPath": "C:\\Users\\27887\\Desktop\\exam\\", + "Documents": [ + { + "AbsoluteMoniker": "D:0:0:{A2FE74E1-B743-11D0-AE1A-00A0C90FFFC3}|\u003CMiscFiles\u003E|C:\\Users\\27887\\Desktop\\exam\\main.cpp||{D0E1A5C6-B359-4E41-9B60-3365922C2A22}", + "RelativeMoniker": "D:0:0:{A2FE74E1-B743-11D0-AE1A-00A0C90FFFC3}|\u003CMiscFiles\u003E|solutionrelative:main.cpp||{D0E1A5C6-B359-4E41-9B60-3365922C2A22}" + } + ], + "DocumentGroupContainers": [ + { + "Orientation": 0, + "VerticalTabListWidth": 256, + "DocumentGroups": [ + { + "DockedWidth": 200, + "SelectedChildIndex": 1, + "Children": [ + { + "$type": "Bookmark", + "Name": "ST:0:0:{34e76e81-ee4a-11d0-ae2e-00a0c90fffc3}" + }, + { + "$type": "Document", + "DocumentIndex": 0, + "Title": "main.cpp", + "DocumentMoniker": "C:\\Users\\27887\\Desktop\\exam\\main.cpp", + "RelativeDocumentMoniker": "main.cpp", + "ToolTip": "C:\\Users\\27887\\Desktop\\exam\\main.cpp", + "RelativeToolTip": "main.cpp", + "ViewState": "AgIAAAAAAAAAAAAAAADwvwAAAAATAAAAAAAAAA==", + "Icon": "ae27a6b0-e345-4288-96df-5eaf394ee369.000677|", + "WhenOpened": "2025-09-24T15:15:56.502Z", + "EditorCaption": "" + } + ] + } + ] + } + ] +} \ No newline at end of file diff --git a/.vs/slnx.sqlite b/.vs/slnx.sqlite new file mode 100644 index 0000000..75c7efd Binary files /dev/null and b/.vs/slnx.sqlite differ diff --git a/.vscode/c_cpp_properties.json b/.vscode/c_cpp_properties.json new file mode 100644 index 0000000..873b113 --- /dev/null +++ b/.vscode/c_cpp_properties.json @@ -0,0 +1,18 @@ +{ + "configurations": [ + { + "name": "windows-gcc-x64", + "includePath": [ + "${workspaceFolder}/**" + ], + "compilerPath": "D:/mingw64/bin/gcc.exe", + "cStandard": "${default}", + "cppStandard": "${default}", + "intelliSenseMode": "windows-gcc-x64", + "compilerArgs": [ + "" + ] + } + ], + "version": 4 +} \ No newline at end of file diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 0000000..99e41e1 --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,24 @@ +{ + "version": "0.2.0", + "configurations": [ + { + "name": "C/C++ Runner: Debug Session", + "type": "cppdbg", + "request": "launch", + "args": [], + "stopAtEntry": false, + "externalConsole": true, + "cwd": "c:/Users/27887/Desktop/exam", + "program": "c:/Users/27887/Desktop/exam/build/Debug/outDebug", + "MIMode": "gdb", + "miDebuggerPath": "gdb", + "setupCommands": [ + { + "description": "Enable pretty-printing for gdb", + "text": "-enable-pretty-printing", + "ignoreFailures": true + } + ] + } + ] +} \ No newline at end of file diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..bb879da --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,59 @@ +{ + "C_Cpp_Runner.cCompilerPath": "gcc", + "C_Cpp_Runner.cppCompilerPath": "g++", + "C_Cpp_Runner.debuggerPath": "gdb", + "C_Cpp_Runner.cStandard": "", + "C_Cpp_Runner.cppStandard": "", + "C_Cpp_Runner.msvcBatchPath": "C:/Program Files/Microsoft Visual Studio/VR_NR/Community/VC/Auxiliary/Build/vcvarsall.bat", + "C_Cpp_Runner.useMsvc": false, + "C_Cpp_Runner.warnings": [ + "-Wall", + "-Wextra", + "-Wpedantic", + "-Wshadow", + "-Wformat=2", + "-Wcast-align", + "-Wconversion", + "-Wsign-conversion", + "-Wnull-dereference" + ], + "C_Cpp_Runner.msvcWarnings": [ + "/W4", + "/permissive-", + "/w14242", + "/w14287", + "/w14296", + "/w14311", + "/w14826", + "/w44062", + "/w44242", + "/w14905", + "/w14906", + "/w14263", + "/w44265", + "/w14928" + ], + "C_Cpp_Runner.enableWarnings": true, + "C_Cpp_Runner.warningsAsError": false, + "C_Cpp_Runner.compilerArgs": [], + "C_Cpp_Runner.linkerArgs": [], + "C_Cpp_Runner.includePaths": [], + "C_Cpp_Runner.includeSearch": [ + "*", + "**/*" + ], + "C_Cpp_Runner.excludeSearch": [ + "**/build", + "**/build/**", + "**/.*", + "**/.*/**", + "**/.vscode", + "**/.vscode/**" + ], + "C_Cpp_Runner.useAddressSanitizer": false, + "C_Cpp_Runner.useUndefinedSanitizer": false, + "C_Cpp_Runner.useLeakSanitizer": false, + "C_Cpp_Runner.showCompilationTime": false, + "C_Cpp_Runner.useLinkTimeOptimization": false, + "C_Cpp_Runner.msvcSecureNoWarnings": false +} \ No newline at end of file diff --git a/.vscode/tasks.json b/.vscode/tasks.json new file mode 100644 index 0000000..e469db4 --- /dev/null +++ b/.vscode/tasks.json @@ -0,0 +1,28 @@ +{ + "tasks": [ + { + "type": "cppbuild", + "label": "C/C++: g++.exe 生成活动文件", + "command": "D:\\vscode\\mingw64\\bin\\g++.exe", + "args": [ + "-fdiagnostics-color=always", + "-g", + "${file}", + "-o", + "${fileDirname}\\${fileBasenameNoExtension}.exe" + ], + "options": { + "cwd": "${fileDirname}" + }, + "problemMatcher": [ + "$gcc" + ], + "group": { + "kind": "build", + "isDefault": true + }, + "detail": "调试器生成的任务。" + } + ], + "version": "2.0.0" +} \ No newline at end of file diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..9492924 --- /dev/null +++ b/Makefile @@ -0,0 +1,11 @@ +CXX = g++ +CXXFLAGS = -std=c++17 -Wall -Wextra -I. +LDFLAGS = -pthread +SOURCES = src/auth_manager.cpp src/question_generator.cpp src/primary_generator.cpp src/junior_generator.cpp src/senior_generator.cpp src/file_manager.cpp src/session_manager.cpp src/main.cpp +TARGET = math_exam_generator + +$(TARGET): $(SOURCES) + $(CXX) $(CXXFLAGS) $(SOURCES) $(LDFLAGS) -o $(TARGET) + +clean: + rm -f $(TARGET) diff --git a/auth_manager.cpp b/auth_manager.cpp new file mode 100644 index 0000000..3ebf744 --- /dev/null +++ b/auth_manager.cpp @@ -0,0 +1,43 @@ +#include "auth_manager.h" + +#include +#include +#include + +AuthManager::AuthManager() { + // 初始化预设的教师账户信息,包括小学、初中、高中各三个账号 + accounts_ = { + {"张三1", {"123", "primary"}}, + {"张三2", {"123", "primary"}}, + {"张三3", {"123", "primary"}}, + {"李四1", {"123", "junior"}}, + {"李四2", {"123", "junior"}}, + {"李四3", {"123", "junior"}}, + {"王五1", {"123", "senior"}}, + {"王五2", {"123", "senior"}}, + {"王五3", {"123", "senior"}} + }; +} + +bool AuthManager::Authenticate(const std::string& username, + const std::string& password, + std::string* user_type) { + // 验证输入不包含非法字符 + if (username.find(' ') != std::string::npos || + password.find(' ') != std::string::npos) { + return false; + } + + auto it = accounts_.find(username); + if (it != accounts_.end() && it->second.first == password) { + *user_type = it->second.second; + return true; + } + return false; +} + +bool AuthManager::IsValidUser(const std::string& username, + const std::string& password) { + std::string user_type; + return Authenticate(username, password, &user_type); +} \ No newline at end of file diff --git a/auth_manager.h b/auth_manager.h new file mode 100644 index 0000000..ee69d64 --- /dev/null +++ b/auth_manager.h @@ -0,0 +1,23 @@ +#ifndef AUTH_MANAGER_H_ +#define AUTH_MANAGER_H_ + +#include +#include + +class AuthManager { + public: + AuthManager(); + + // 用户认证,返回认证结果并通过user_type输出用户类型 + bool Authenticate(const std::string& username, const std::string& password, + std::string* user_type); + + // 验证用户是否存在且密码正确 + bool IsValidUser(const std::string& username, const std::string& password); + + private: + // 存储账户信息:用户名 -> (密码, 用户类型) + std::unordered_map> accounts_; +}; + +#endif // AUTH_MANAGER_H_ \ No newline at end of file diff --git a/file_manager.cpp b/file_manager.cpp new file mode 100644 index 0000000..184863a --- /dev/null +++ b/file_manager.cpp @@ -0,0 +1,175 @@ +#include "file_manager.h" + +#include +#include +#include +#include +#include +#include // Windows目录操作 +#include // Windows API +#include + +std::string FileManager::UsernameToPinyin(const std::string& username) { + // 用户名到拼音的映射 + static const std::unordered_map name_map = { + {"张三1", "zhangsan1"}, + {"张三2", "zhangsan2"}, + {"张三3", "zhangsan3"}, + {"李四1", "lisi1"}, + {"李四2", "lisi2"}, + {"李四3", "lisi3"}, + {"王五1", "wangwu1"}, + {"王五2", "wangwu2"}, + {"王五3", "wangwu3"} + }; + + auto it = name_map.find(username); + if (it != name_map.end()) { + return it->second; + } + + // 如果没有找到映射,返回原用户名 + return username; +} + +bool FileManager::CreateUserDirectory(const std::string& username) { + std::string pinyin_username = UsernameToPinyin(username); + const std::string dir_path = std::string(kBasePath) + pinyin_username; + + // 先创建基础目录(如果不存在) + _mkdir(kBasePath); + + // 使用Windows API创建用户目录 + if (CreateDirectoryA(dir_path.c_str(), NULL)) { + return true; + } else { + // 如果目录已存在,ERROR_ALREADY_EXISTS是正常的 + return GetLastError() == ERROR_ALREADY_EXISTS; + } +} + +std::string FileManager::GenerateFilename() { + std::time_t now = std::time(nullptr); + std::tm local_time; + localtime_s(&local_time, &now); // 使用安全的版本 + + char buffer[80]; + std::strftime(buffer, sizeof(buffer), "%Y%m%d_%H%M%S", &local_time); + return std::string(buffer) + ".txt"; +} + +bool FileManager::SaveQuestions(const std::string& username, + const std::vector& questions) { + if (questions.empty()) { + std::cerr << "错误:问题列表为空" << std::endl; + return false; + } + + // 确保用户目录存在 + if (!CreateUserDirectory(username)) { + std::cerr << "错误:无法创建用户目录: " << username << std::endl; + return false; + } + + std::string pinyin_username = UsernameToPinyin(username); + const std::string dir_path = std::string(kBasePath) + pinyin_username; + const std::string filename = GenerateFilename(); + const std::string full_path = dir_path + "\\" + filename; + + std::ofstream file(full_path); + if (!file.is_open()) { + std::cerr << "错误:无法创建文件: " << full_path << std::endl; + return false; + } + + // 写入文件头信息 + file << "用户名: " << username << " (" << pinyin_username << ")\n"; + file << "生成时间: " << filename.substr(0, filename.length() - 4) << "\n"; + file << "题目数量: " << questions.size() << "\n"; + file << "==============================\n\n"; + + // 写入题目 + for (size_t i = 0; i < questions.size(); ++i) { + file << "第" << (i + 1) << "题: " << questions[i] << "\n\n"; + } + + file.close(); + std::cout << "✓ 题目已保存至: " << full_path << std::endl; + return true; +} + +bool FileManager::IsQuestionExists(const std::string& username, + const std::string& question) { + const auto existing_questions = LoadExistingQuestions(username); + + // 简单的字符串匹配(可改进为模糊匹配) + for (const auto& existing : existing_questions) { + if (existing.find(question) != std::string::npos || + question.find(existing) != std::string::npos) { + return true; + } + } + return false; +} + +std::vector FileManager::LoadExistingQuestions(const std::string& username) { + std::vector questions; + std::string pinyin_username = UsernameToPinyin(username); + const std::string dir_path = std::string(kBasePath) + pinyin_username; + const std::string search_path = dir_path + "\\*.txt"; + + // 检查目录是否存在 + DWORD attrib = GetFileAttributesA(dir_path.c_str()); + if (attrib == INVALID_FILE_ATTRIBUTES || !(attrib & FILE_ATTRIBUTE_DIRECTORY)) { + return questions; // 目录不存在 + } + + WIN32_FIND_DATAA find_file_data; + HANDLE h_find = FindFirstFileA(search_path.c_str(), &find_file_data); + + if (h_find == INVALID_HANDLE_VALUE) { + return questions; // 没有找到文件 + } + + do { + if (!(find_file_data.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)) { + const std::string filename = find_file_data.cFileName; + const std::string file_path = dir_path + "\\" + filename; + + std::ifstream file(file_path); + if (file.is_open()) { + std::string line; + bool in_questions_section = false; + + while (std::getline(file, line)) { + // 跳过文件头,直到分隔线 + if (line.find("==============================") != std::string::npos) { + in_questions_section = true; + continue; + } + + if (in_questions_section && !line.empty()) { + // 匹配 "第X题: " 格式 + if (line.find("第") == 0 && line.find("题: ") != std::string::npos) { + size_t pos = line.find("题: "); + if (pos != std::string::npos) { + std::string question_text = line.substr(pos + 3); // "题: " 长度是3 + // 移除Windows换行符 + if (!question_text.empty() && question_text.back() == '\r') { + question_text.pop_back(); + } + if (!question_text.empty()) { + questions.push_back(question_text); + } + } + } + } + } + file.close(); + } + } + } while (FindNextFileA(h_find, &find_file_data) != 0); + + FindClose(h_find); + return questions; +} \ No newline at end of file diff --git a/file_manager.h b/file_manager.h new file mode 100644 index 0000000..709f199 --- /dev/null +++ b/file_manager.h @@ -0,0 +1,28 @@ +#ifndef FILE_MANAGER_H_ +#define FILE_MANAGER_H_ + +#include +#include + +class FileManager { + public: + FileManager() = default; + + bool CreateUserDirectory(const std::string& username); + std::string GenerateFilename(); + bool SaveQuestions(const std::string& username, + const std::vector& questions); + bool IsQuestionExists(const std::string& username, + const std::string& question); + std::vector LoadExistingQuestions(const std::string& username); + + private: + static constexpr const char* kBasePath = "exams\\"; // Windows·���ָ��� + + // ���ÿ��� + FileManager(const FileManager&) = delete; + FileManager& operator=(const FileManager&) = delete; + std::string UsernameToPinyin(const std::string& username); +}; + +#endif // FILE_MANAGER_H_ \ No newline at end of file diff --git a/junior_generator.cpp b/junior_generator.cpp new file mode 100644 index 0000000..0283645 --- /dev/null +++ b/junior_generator.cpp @@ -0,0 +1,76 @@ +#include "junior_generator.h" + +#include +#include +#include + +std::vector JuniorGenerator::GenerateQuestions(int count, + const std::string& username) { + current_user_ = username; + std::vector questions; + questions.reserve(count); + + for (int i = 0; i < count; ++i) { + questions.push_back(GenerateSingleQuestion()); + } + + return questions; +} + +std::string JuniorGenerator::GenerateSingleQuestion() { + static const std::vector kOperators = {'+', '-', '*', '/'}; + const int operand_count = GetRandomNumber(kMinOperands, kMaxOperands); + + std::stringstream ss; + bool has_special_operator = false; + + // 第一个操作数可以是普通数字或特殊运算符 + if (operand_count == 1 || GetRandomNumber(0, 1)) { + // 单操作数情况或第一个操作数是特殊运算符 + if (GetRandomNumber(0, 1)) { + ss << "sqrt(" << GetRandomNumber(kMinNumber, kMaxNumber) << ")"; + } else { + ss << GetRandomNumber(1, 10) << "^2"; + } + has_special_operator = true; + } else { + // 第一个操作数是普通数字 + ss << GetRandomNumber(kMinNumber, kMaxNumber); + } + + // 添加后续操作数 + for (int i = 1; i < operand_count; ++i) { + const char op = GetRandomOperator(kOperators); + + // 随机决定是添加普通数字还是特殊运算符 + if (!has_special_operator || GetRandomNumber(0, 1)) { + if (GetRandomNumber(0, 1)) { + ss << " " << op << " sqrt(" << GetRandomNumber(kMinNumber, kMaxNumber) << ")"; + } else { + ss << " " << op << " " << GetRandomNumber(1, 10) << "^2"; + } + has_special_operator = true; + } else { + ss << " " << op << " " << GetRandomNumber(kMinNumber, kMaxNumber); + } + } + + // 确保至少有一个特殊运算符 + if (!has_special_operator) { + const char op = GetRandomOperator(kOperators); + if (GetRandomNumber(0, 1)) { + ss << " " << op << " sqrt(" << GetRandomNumber(kMinNumber, kMaxNumber) << ")"; + } else { + ss << " " << op << " " << GetRandomNumber(1, 10) << "^2"; + } + } + + ss << " = ?"; + return ss.str(); +} + +bool JuniorGenerator::MeetsDifficultyRequirements(const std::string& question) { + // 使用ASCII字符而不是特殊Unicode字符 + return question.find("^2") != std::string::npos || + question.find("sqrt") != std::string::npos; +} \ No newline at end of file diff --git a/junior_generator.h b/junior_generator.h new file mode 100644 index 0000000..f0fc4c0 --- /dev/null +++ b/junior_generator.h @@ -0,0 +1,21 @@ +#ifndef JUNIOR_GENERATOR_H_ +#define JUNIOR_GENERATOR_H_ + +#include "question_generator.h" + +class JuniorGenerator : public QuestionGenerator { + public: + std::vector GenerateQuestions(int count, + const std::string& username) override; + bool MeetsDifficultyRequirements(const std::string& question) override; + + private: + static constexpr int kMinOperands = 1; + static constexpr int kMaxOperands = 5; + static constexpr int kMinNumber = 1; + static constexpr int kMaxNumber = 100; + + std::string GenerateSingleQuestion(); +}; + +#endif // JUNIOR_GENERATOR_H_ \ No newline at end of file diff --git a/main.cpp b/main.cpp new file mode 100644 index 0000000..e53105e --- /dev/null +++ b/main.cpp @@ -0,0 +1,156 @@ +#include +#include +#include +#include + +#include "auth_manager.h" +#include "session_manager.h" +#include "file_manager.h" + +// 显示欢迎界面 +void ShowWelcome() { + std::cout << "=====================================" << std::endl; + std::cout << " 中小学数学卷子自动生成系统" << std::endl; + std::cout << "=====================================" << std::endl; +} + +// 显示主菜单 +void ShowMainMenu(const std::string& username, const std::string& difficulty) { + std::cout << "\n=====================================" << std::endl; + std::cout << "当前用户: " << username << " (" << difficulty << "老师)" << std::endl; + std::cout << "当前出题类型: " << difficulty << std::endl; + std::cout << "=====================================" << std::endl; + std::cout << "请选择操作:" << std::endl; + std::cout << "1. 生成题目(输入题目数量 10-30)" << std::endl; + std::cout << "2. 切换难度(输入:切换为小学/初中/高中)" << std::endl; + std::cout << "3. 退出登录(输入:-1)" << std::endl; + std::cout << "> "; +} + +int main() { + AuthManager auth_manager; + SessionManager session_manager; + FileManager file_manager; + + while (true) { + ShowWelcome(); + std::cout << "请输入用户名和密码(格式:用户名 密码),或输入 quit 退出系统:" << std::endl; // 修改提示 + std::cout << "> "; + + std::string input; + std::getline(std::cin, input); + + // 去除前后空格 + input.erase(0, input.find_first_not_of(" ")); + input.erase(input.find_last_not_of(" ") + 1); + + // 检查是否要退出系统(只在首页) + if (input == "quit" || input == "exit" || input == "退出") { + std::cout << "感谢使用中小学数学卷子自动生成系统,再见!" << std::endl; + break; // 退出整个程序 + } + + std::istringstream iss(input); + std::string username, password; + iss >> username >> password; + + // 检查输入格式 + if (username.empty() || password.empty()) { + std::cout << "请输入正确的用户名、密码" << std::endl; + continue; + } + + // 检查是否正好有两个token + std::vector tokens; + std::string token; + while (iss >> token) { + tokens.push_back(token); + } + + if (!tokens.empty()) { // 如果还有更多token,说明输入格式错误 + std::cout << "输入格式错误:请输入用户名和密码,用空格分隔" << std::endl; + continue; + } + + std::string user_type; + if (auth_manager.Authenticate(username, password, &user_type)) { + const char* difficulty_name = + (user_type == "primary") ? "小学" : + (user_type == "junior") ? "初中" : "高中"; + + std::cout << "登录成功!当前选择为" << difficulty_name << "出题" << std::endl; + + session_manager.SetUser(username, user_type); + file_manager.CreateUserDirectory(username); + + // 主循环 + bool logged_in = true; + while (logged_in) { + ShowMainMenu(username, session_manager.GetCurrentDifficulty()); + + std::string command; + std::getline(std::cin, command); + + // 去除前后空格 + command.erase(0, command.find_first_not_of(" ")); + command.erase(command.find_last_not_of(" ") + 1); + + if (command.empty()) { + continue; + } + + // 首先检查退出命令 + if (command == "-1") { + std::cout << "退出当前用户,重新登录..." << std::endl; + logged_in = false; + continue; + } + + // 检查切换难度命令 + if (command.find("切换为") == 0) { + std::string new_difficulty = command.substr(9); + + if (new_difficulty == "小学" || new_difficulty == "初中" || new_difficulty == "高中") { + std::string difficulty_type = + (new_difficulty == "小学") ? "primary" : + (new_difficulty == "初中") ? "junior" : "senior"; + + if (session_manager.SwitchDifficulty(difficulty_type)) { + std::cout << "切换成功!当前出题类型: " << new_difficulty << std::endl; + } + } else { + std::cout << "请输入小学、初中和高中三个选项中的一个" << std::endl; + } + continue; + } + + // 尝试解析为数字(题目数量) + try { + int count = std::stoi(command); + if (count >= 10 && count <= 30) { + std::cout << "正在生成" << session_manager.GetCurrentDifficulty() + << "数学题目,数量: " << count << "..." << std::endl; + + QuestionGenerator* generator = session_manager.GetCurrentGenerator(); + auto questions = generator->GenerateQuestions(count, username); + + if (file_manager.SaveQuestions(username, questions)) { + std::cout << "题目生成完成!已保存到文件。" << std::endl; + } else { + std::cout << "文件保存失败!" << std::endl; + } + } else { + std::cout << "题目数量范围应为10-30,请重新输入" << std::endl; + } + } catch (const std::exception& e) { + std::cout << "无效命令,请重新输入" << std::endl; + std::cout << "有效命令:10-30(生成题目)、-1(退出登录)、切换为小学/初中/高中" << std::endl; + } + } + } else { + std::cout << "请输入正确的用户名、密码" << std::endl; + } + } + + return 0; +} \ No newline at end of file diff --git a/output/exams/lisi1/20250928_181147.txt b/output/exams/lisi1/20250928_181147.txt new file mode 100644 index 0000000..6747419 --- /dev/null +++ b/output/exams/lisi1/20250928_181147.txt @@ -0,0 +1,25 @@ +用户名: 李四1 (lisi1) +生成时间: 20250928_181147 +题目数量: 10 +============================== + +第1题: sqrt(93) / sqrt(46) * 73 - 32 = ? + +第2题: 75 * 9^2 / sqrt(88) / 35 + 2^2 = ? + +第3题: 60 / sqrt(98) + 41 = ? + +第4题: sqrt(31) = ? + +第5题: 8^2 = ? + +第6题: 94 + sqrt(84) / 16 = ? + +第7题: 18 * 4^2 / 70 - 61 / sqrt(15) = ? + +第8题: sqrt(40) / 1^2 - sqrt(34) / 3^2 = ? + +第9题: 1^2 / 86 / 10^2 + 10^2 / 64 = ? + +第10题: sqrt(1) + sqrt(56) / sqrt(66) / 9 = ? + diff --git a/output/exams/zhangsan1/20250928_181110.txt b/output/exams/zhangsan1/20250928_181110.txt new file mode 100644 index 0000000..45e3de8 --- /dev/null +++ b/output/exams/zhangsan1/20250928_181110.txt @@ -0,0 +1,25 @@ +用户名: 张三1 (zhangsan1) +生成时间: 20250928_181110 +题目数量: 10 +============================== + +第1题: 79 + 88 * 67 - 90 = ? + +第2题: 89 - 97 = ? + +第3题: 64 / 50 - 85 + 51 = ? + +第4题: 81 + 40 = ? + +第5题: 98 + 98 - 17 = ? + +第6题: 10 * 27 * 16 / 12 = ? + +第7题: 94 / 28 + 73 + 84 * 80 = ? + +第8题: 64 / 35 + 51 = ? + +第9题: 62 * 49 = ? + +第10题: 46 * 63 / 13 * 75 = ? + diff --git a/output/main.exe b/output/main.exe new file mode 100644 index 0000000..f6ea8ef Binary files /dev/null and b/output/main.exe differ diff --git a/primary_generator.cpp b/primary_generator.cpp new file mode 100644 index 0000000..35711e0 --- /dev/null +++ b/primary_generator.cpp @@ -0,0 +1,42 @@ +#include "primary_generator.h" + +#include +#include +#include + +std::vector PrimaryGenerator::GenerateQuestions(int count, + const std::string& username) { + current_user_ = username; + std::vector questions; + questions.reserve(count); // 预分配空间提高效率 + + for (int i = 0; i < count; ++i) { + questions.push_back(GenerateSingleQuestion()); + } + + return questions; +} + +std::string PrimaryGenerator::GenerateSingleQuestion() { + static const std::vector kOperators = {'+', '-', '*', '/'}; + const int operand_count = GetRandomNumber(kMinOperands, kMaxOperands); + + std::stringstream ss; + ss << GetRandomNumber(kMinNumber, kMaxNumber); // 第一个操作数 + + for (int i = 1; i < operand_count; ++i) { + const char op = GetRandomOperator(kOperators); + ss << " " << op << " " << GetRandomNumber(kMinNumber, kMaxNumber); + } + + ss << " = ?"; + return ss.str(); +} + +bool PrimaryGenerator::MeetsDifficultyRequirements(const std::string& question) { + // 小学题目只需要包含基本运算符:+ - * / + return question.find('+') != std::string::npos || + question.find('-') != std::string::npos || + question.find('*') != std::string::npos || + question.find('/') != std::string::npos; +} \ No newline at end of file diff --git a/primary_generator.h b/primary_generator.h new file mode 100644 index 0000000..2c2b25f --- /dev/null +++ b/primary_generator.h @@ -0,0 +1,21 @@ +#ifndef PRIMARY_GENERATOR_H_ +#define PRIMARY_GENERATOR_H_ + +#include "question_generator.h" + +class PrimaryGenerator : public QuestionGenerator { + public: + std::vector GenerateQuestions(int count, + const std::string& username) override; + bool MeetsDifficultyRequirements(const std::string& question) override; + + private: + static constexpr int kMinOperands = 2; + static constexpr int kMaxOperands = 5; + static constexpr int kMinNumber = 1; + static constexpr int kMaxNumber = 100; + + std::string GenerateSingleQuestion(); +}; + +#endif // PRIMARY_GENERATOR_H_ \ No newline at end of file diff --git a/question_generator.cpp b/question_generator.cpp new file mode 100644 index 0000000..d990ae5 --- /dev/null +++ b/question_generator.cpp @@ -0,0 +1,19 @@ +#include "question_generator.h" + +#include +#include + +QuestionGenerator::QuestionGenerator() { + rng_.seed(std::chrono::steady_clock::now().time_since_epoch().count()); +} + +int QuestionGenerator::GetRandomNumber(int min, int max) { + std::uniform_int_distribution dist(min, max); + return dist(rng_); +} + +char QuestionGenerator::GetRandomOperator(const std::vector& operators) { + if (operators.empty()) return '+'; + std::uniform_int_distribution dist(0, operators.size() - 1); + return operators[dist(rng_)]; +} \ No newline at end of file diff --git a/question_generator.h b/question_generator.h new file mode 100644 index 0000000..bf22df5 --- /dev/null +++ b/question_generator.h @@ -0,0 +1,37 @@ +#ifndef QUESTION_GENERATOR_H_ +#define QUESTION_GENERATOR_H_ + +#include +#include +#include + +class QuestionGenerator { + public: + virtual ~QuestionGenerator() = default; + + // 生成指定数量的数学题目 + virtual std::vector GenerateQuestions(int count, + const std::string& username) = 0; + + // 检查题目是否符合难度要求 + virtual bool MeetsDifficultyRequirements(const std::string& question) = 0; + + protected: + QuestionGenerator(); + + // 生成指定范围内的随机数 + int GetRandomNumber(int min, int max); + + // 从运算符列表中随机选择一个运算符 + char GetRandomOperator(const std::vector& operators); + + std::string current_user_; + std::mt19937 rng_; + + private: + // 禁用拷贝构造和赋值操作 + QuestionGenerator(const QuestionGenerator&) = delete; + QuestionGenerator& operator=(const QuestionGenerator&) = delete; +}; + +#endif // QUESTION_GENERATOR_H_ \ No newline at end of file diff --git a/readme.md b/readme.md new file mode 100644 index 0000000..5fbaec6 --- /dev/null +++ b/readme.md @@ -0,0 +1,203 @@ +g++ -Wall -Wextra -g3 -std=c++17 -pthread *.cpp -o output/main.exe +[Console]::OutputEncoding = [System.Text.Encoding]::UTF8 + +中小学数学卷子自动生成系统 +? 项目简介 +中小学数学卷子自动生成系统是一个基于C++开发的命令行应用程序,旨在帮助小学、初中和高中数学老师快速生成符合教学大纲要求的数学题目。系统支持题目查重、难度切换、文件保存等功能,严格遵循Google C++代码规范。 + +? 功能特性 +? 用户认证系统 +预设小学、初中、高中各三个教师账户 + +安全的用户名密码验证机制 + +自动识别用户类型并设置相应难度 + +? 题目生成能力 +小学题目:包含加减乘除基本运算,操作数2-5个 + +初中题目:必须包含平方(?)或开根号(√)运算符,支持单操作数 + +高中题目:必须包含三角函数(sin/cos/tan),支持单操作数 + +智能查重:避免生成重复题目 + +? 交互功能 +动态难度切换(小学/初中/高中) + +题目数量控制(10-30题) + +实时题目预览 + +用户友好的命令行界面 + +? 文件管理 +按用户分文件夹存储 + +时间戳命名的文件格式(年-月-日-时-分-秒.txt) + +题目编号和格式化输出 + +?? 技术架构 +类设计(面向对象) + +QuestionGenerator(抽象基类) +├── PrimaryGenerator(小学题目生成器) +├── JuniorGenerator(初中题目生成器) +└── SeniorGenerator(高中题目生成器) + +核心管理类: +├── AuthManager(认证管理) +├── SessionManager(会话管理) +├── FileManager(文件管理) + +设计模式 +工厂模式:题目生成器的多态实现 + +单一职责原则:每个类专注特定功能 + +开放封闭原则:易于扩展新难度级别 + +? 安装与运行 +环境要求 +操作系统:Windows 10/11 或 Linux (WSL) + +编译器:GCC 8.1.0+ 支持C++17 + +构建工具:直接使用g++ + + +# 直接编译 +g++ -Wall -Wextra -g3 -std=c++17 -pthread *.cpp -o output/main.exe +# 运行程序 +chcp 65001 +[Console]::OutputEncoding = [System.Text.Encoding]::UTF8 +./main.exe +??? 使用指南 +登录系统 + +===================================== + 中小学数学卷子自动生成系统 +===================================== +请输入用户名和密码(格式:用户名 密码),或输入 quit 退出系统: +> 张三1 123 +登录成功!当前选择为小学出题 + +===================================== +当前用户: 张三1 (小学老师) +当前出题类型: 小学 +===================================== +请选择操作: +1. 生成题目(输入题目数量 10-30) +2. 切换难度(输入:切换为小学/初中/高中) +3. 退出登录(输入:-1) +> 15 +测试账户 +用户类型 用户名 密码 默认难度 +小学 张三1, 张三2, 张三3 123 小学 +初中 李四1, 李四2, 李四3 123 初中 +高中 王五1, 王五2, 王五3 123 高中 +? 题目示例 +小学题目 + +1. 3 + 5 × 2 = ? +2. 24 ÷ 3 + 7 - 2 = ? +初中题目 + +1. √(16) + 5 = ? +2. 4? × 3 - 2 = ? +3. √(25) = ? (单操作数) +高中题目 + +1. sin(30°) × 2 = ? +2. 5 + cos(45°) - 1 = ? +3. tan(60°) = ? (单操作数) +? 项目结构 + +exam/ +├── src/ +│ ├── main.cpp # 程序入口 +│ ├── auth_manager.h/cpp # 认证管理 +│ ├── question_generator.h/cpp # 题目生成基类 +│ ├── primary_generator.h/cpp # 小学题目生成 +│ ├── junior_generator.h/cpp # 初中题目生成 +│ ├── senior_generator.h/cpp # 高中题目生成 +│ ├── file_manager.h/cpp # 文件管理 +│ └── session_manager.h/cpp # 会话管理 +├── exams/ # 生成的题目文件 +│ ├── zhangsan1/ +│ ├── lisi1/ +│ └── wangwu1/ +├── Makefile # 编译配置 +└── README.md # 项目说明 +? 开发指南 +代码规范 +遵循Google C++代码规范 + +使用大驼峰命名法(类名) + +成员变量以下划线结尾 + +包含详细的注释文档 + +扩展新功能 +添加新难度级别:继承QuestionGenerator基类 + +修改题目规则:调整各生成器的算法参数 + +增强文件格式:修改FileManager的输出格式 + +编译选项 + +CXX = g++ +CXXFLAGS = -std=c++17 -Wall -Wextra -I. +LDFLAGS = -pthread +? 故障排除 +常见问题 +编译错误:确保GCC支持C++17,检查文件路径 + +文件权限:确保有创建目录和文件的权限 + +编码问题:系统使用UTF-8编码,确保终端支持中文显示 + +调试模式 + +? 未来规划 +功能增强 +图形用户界面(Qt版本) + +题目难度分级(简单/中等/困难) + +答案生成和批改功能 + +题目导出为PDF格式 + +网络协作和题目共享 + +技术优化 +单元测试覆盖 + +性能优化和内存管理 + +跨平台兼容性增强 + +配置文件和个性化设置 + + +开发流程 +Fork本项目 + +创建功能分支(git checkout -b feature/AmazingFeature) + +提交更改(git commit -m 'Add some AmazingFeature') + +推送到分支(git push origin feature/AmazingFeature) + +开启Pull Request + +? 致谢 +感谢所有为这个项目提供建议和帮助的贡献者! + +开发者:软件2303班 郭永瑞 +最后更新:2025年9月 +版本:v1.0.0 \ No newline at end of file diff --git a/senior_generator.cpp b/senior_generator.cpp new file mode 100644 index 0000000..73de012 --- /dev/null +++ b/senior_generator.cpp @@ -0,0 +1,72 @@ +#include "senior_generator.h" + +#include +#include +#include + +std::vector SeniorGenerator::GenerateQuestions(int count, + const std::string& username) { + current_user_ = username; + std::vector questions; + questions.reserve(count); + + for (int i = 0; i < count; ++i) { + questions.push_back(GenerateSingleQuestion()); + } + + return questions; +} + +std::string SeniorGenerator::GenerateSingleQuestion() { + static const std::vector kOperators = {'+', '-', '*', '/'}; + static const std::vector kTrigFunctions = {"sin", "cos", "tan"}; + const int operand_count = GetRandomNumber(kMinOperands, kMaxOperands); + + std::stringstream ss; + bool has_trig_function = false; + + // 第一个操作数可以是普通数字或三角函数 + if (operand_count == 1 || GetRandomNumber(0, 1)) { + // 单操作数情况或第一个操作数是三角函数 + const int trig_index = GetRandomNumber(0, kTrigFunctions.size() - 1); + ss << kTrigFunctions[trig_index] << "(" + << GetRandomNumber(kMinNumber, kMaxAngle) << "°)"; + has_trig_function = true; + } else { + // 第一个操作数是普通数字 + ss << GetRandomNumber(kMinNumber, kMaxNumber); + } + + // 添加后续操作数 + for (int i = 1; i < operand_count; ++i) { + const char op = GetRandomOperator(kOperators); + + // 随机决定是添加普通数字还是三角函数 + if (!has_trig_function || GetRandomNumber(0, 1)) { + const int trig_index = GetRandomNumber(0, kTrigFunctions.size() - 1); + ss << " " << op << " " << kTrigFunctions[trig_index] << "(" + << GetRandomNumber(kMinNumber, kMaxAngle) << "°)"; + has_trig_function = true; + } else { + ss << " " << op << " " << GetRandomNumber(kMinNumber, kMaxNumber); + } + } + + // 确保至少有一个三角函数 + if (!has_trig_function) { + const char op = GetRandomOperator(kOperators); + const int trig_index = GetRandomNumber(0, kTrigFunctions.size() - 1); + ss << " " << op << " " << kTrigFunctions[trig_index] << "(" + << GetRandomNumber(kMinNumber, kMaxAngle) << "°)"; + } + + ss << " = ?"; + return ss.str(); +} + +bool SeniorGenerator::MeetsDifficultyRequirements(const std::string& question) { + // 高中题目至少包含一个三角函数 + return question.find("sin") != std::string::npos || + question.find("cos") != std::string::npos || + question.find("tan") != std::string::npos; +} \ No newline at end of file diff --git a/senior_generator.h b/senior_generator.h new file mode 100644 index 0000000..89a8ba9 --- /dev/null +++ b/senior_generator.h @@ -0,0 +1,22 @@ +#ifndef SENIOR_GENERATOR_H_ +#define SENIOR_GENERATOR_H_ + +#include "question_generator.h" + +class SeniorGenerator : public QuestionGenerator { + public: + std::vector GenerateQuestions(int count, + const std::string& username) override; + bool MeetsDifficultyRequirements(const std::string& question) override; + + private: + static constexpr int kMinOperands = 1; + static constexpr int kMaxOperands = 5; + static constexpr int kMinNumber = 1; + static constexpr int kMaxNumber = 100; + static constexpr int kMaxAngle = 90; + + std::string GenerateSingleQuestion(); +}; + +#endif // SENIOR_GENERATOR_H_ \ No newline at end of file diff --git a/session_manager.cpp b/session_manager.cpp new file mode 100644 index 0000000..57afb46 --- /dev/null +++ b/session_manager.cpp @@ -0,0 +1,40 @@ +#include "session_manager.h" + +SessionManager::SessionManager() + : current_user_(""), + current_difficulty_(""), + current_generator_(nullptr) {} + +void SessionManager::SetUser(const std::string& username, + const std::string& difficulty) { + current_user_ = username; + SwitchDifficulty(difficulty); +} + +bool SessionManager::SwitchDifficulty(const std::string& difficulty) { + if (difficulty == "primary") { + current_generator_ = &primary_generator_; + current_difficulty_ = "小学"; + } else if (difficulty == "junior") { + current_generator_ = &junior_generator_; + current_difficulty_ = "初中"; + } else if (difficulty == "senior") { + current_generator_ = &senior_generator_; + current_difficulty_ = "高中"; + } else { + return false; + } + return true; +} + +QuestionGenerator* SessionManager::GetCurrentGenerator() { + return current_generator_; +} + +std::string SessionManager::GetCurrentDifficulty() const { + return current_difficulty_; +} + +std::string SessionManager::GetCurrentUser() const { + return current_user_; +} \ No newline at end of file diff --git a/session_manager.h b/session_manager.h new file mode 100644 index 0000000..de1d7de --- /dev/null +++ b/session_manager.h @@ -0,0 +1,43 @@ +#ifndef SESSION_MANAGER_H_ +#define SESSION_MANAGER_H_ + +#include +#include "question_generator.h" +#include "primary_generator.h" +#include "junior_generator.h" +#include "senior_generator.h" + +class SessionManager { + public: + SessionManager(); + + // 设置当前用户和难度 + void SetUser(const std::string& username, const std::string& difficulty); + + // 切换难度级别 + bool SwitchDifficulty(const std::string& difficulty); + + // 获取当前题目生成器 + QuestionGenerator* GetCurrentGenerator(); + + // 获取当前难度描述 + std::string GetCurrentDifficulty() const; + + // 获取当前用户名 + std::string GetCurrentUser() const; + + private: + std::string current_user_; + std::string current_difficulty_; + QuestionGenerator* current_generator_; + + PrimaryGenerator primary_generator_; + JuniorGenerator junior_generator_; + SeniorGenerator senior_generator_; + + // 禁用拷贝构造和赋值操作 + SessionManager(const SessionManager&) = delete; + SessionManager& operator=(const SessionManager&) = delete; +}; + +#endif // SESSION_MANAGER_H_ \ No newline at end of file