C++样例自动生成器技术设计与源码解析
2026-07-11 22:10:28
发布于:四川
1. 项目概述
C++样例自动生成器初始版本,是一款基于 Python Tkinter 实现的桌面级代码测试用例生成工具。核心功能为解析 C++ 代码输入语句、动态生成随机测试数据、调用 MinGW GCC 编译器完成编译与运行,自动输出标准化输入输出样例。该版本主打功能落地,实现基础编译修复、常规输入识别能力,满足基础 OJ 代码自测需求。
注意:此代码只能解决日常常规自测,可能存在无法识别的输入类型,且必须同时有python编译器和c编译器才可执行,本文结尾附带源码,在使用时需注意将代码中c编译器的路径改为自己电脑的编译器路径并使用本地编译器测试参数,若有问题请及时提出,点个赞再走吧,非常感谢!
2. 核心技术栈与知识点详解
2.1 GUI 桌面开发技术
基于 Python 内置标准库 Tkinter 实现图形化界面,依托 ttk 组件库实现标准化控件布局,使用滚动文本组件完成代码编辑、结果展示、日志输出功能。采用界面分区布局思想,通过上下、左右面板拆分功能区域,实现控件自适应填充。
2.2 正则表达式语法解析技术
采用 RE 正则匹配 实现 C++ 输入语句静态语法分析,通过自定义正则规则匹配 cin 流式输入、scanf 格式化输入。核心实现逻辑为:统计代码内输入变量总数,根据变量数量批量生成随机数字,完成输入数据构造。
支持基础匹配规则:单行多变量 cin 连续输入、固定格式 scanf 占位符匹配、简单循环输入识别。
2.3 子进程调用与程序编译技术
基于 subprocess 子进程模块 调用外部 GCC 编译器,实现 C++ 代码动态编译与运行。解决 Windows 平台 MinGW 运行依赖缺失问题,通过临时注入系统环境变量 PATH,补全 libwinpthread-1.dll 依赖,规避动态链接库加载失败异常。
配置编译参数 -std=c++17,兼容 C++17 标准语法,设置超时机制防止死循环阻塞进程。
2.4 临时文件资源调度技术
依托 tempfile 临时文件模块 动态生成 cpp 源码文件与 exe 可执行文件,无需落地持久化文件。通过列表缓存所有临时文件路径,程序运行结束后统一批量销毁,实现资源自动回收。
2.5 随机数生成技术
基于 random 伪随机数模块生成整型测试数据,通过切片分块处理,将大批量随机数字格式化分行输出,适配 OJ 标准输入格式。
2.6 异常捕获与容错机制
覆盖文件不存在、进程超时、编译错误、参数非法四类核心异常,通过分层异常捕获机制,精准反馈运行日志,提升工具稳定性。
3. 初始版本架构缺陷分析
-
高耦合架构:所有解析逻辑、生成逻辑、编译逻辑耦合在单一函数,无模块化拆分,扩展性极差。
-
正则鲁棒性弱:仅支持单行简单输入匹配,无法识别多行循环结构体输入、带注释代码、格式混乱代码。
-
硬编码泛滥:编译器路径、超时时间、随机数范围全部硬编码,配置灵活性低。
-
资源管理简陋:临时文件分散管理,无统一目录调度,清理不彻底。
-
输入识别单一:仅统计输入变量总数,无法适配分段输入、循环批量输入、混合字符数字输入场景。
-
日志体系简陋:无日志分级,错误、警告、普通信息无视觉区分,调试效率低。
4. 初始版纯净无注释源码
import tkinter as tk
from tkinter import ttk, scrolledtext, messagebox, filedialog
import subprocess
import random
import tempfile
import os
import re
class CppSampleGeneratorGUI:
def __init__(self, root):
self.root = root
self.root.title("C++题目样例自动生成器(修复循环scanf分段输入)")
self.root.geometry("1000x750")
self.sample_count = tk.StringVar(value="5")
self.generated_samples = []
self.temp_files = []
self.create_widgets()
def create_widgets(self):
frame_top = ttk.Frame(self.root, padding=5)
frame_top.pack(fill="x")
ttk.Label(frame_top, text="生成样例数量:").pack(side="left")
ttk.Entry(frame_top, textvariable=self.sample_count, width=10).pack(side="left", padx=5)
ttk.Button(frame_top, text="自动生成样例", command=self.generate_samples).pack(side="left", padx=10)
ttk.Button(frame_top, text="保存样例到文件", command=self.save_samples).pack(side="left")
paned = ttk.PanedWindow(self.root, orient="horizontal")
paned.pack(fill="both", expand=True, padx=5, pady=5)
frame_code = ttk.Frame(paned)
paned.add(frame_code, weight=1)
ttk.Label(frame_code, text="粘贴C++ AC代码(cin/scanf/for循环/while终止输入均可):").pack(anchor="w")
self.code_text = scrolledtext.ScrolledText(frame_code, wrap="word", font=("Consolas", 10))
self.code_text.pack(fill="both", expand=True)
frame_out = ttk.Frame(paned)
paned.add(frame_out, weight=1)
ttk.Label(frame_out, text="生成输入输出样例:").pack(anchor="w")
self.result_text = scrolledtext.ScrolledText(frame_out, wrap="word", font=("Consolas", 10))
self.result_text.pack(fill="both", expand=True)
frame_log = ttk.Frame(self.root, padding=5)
frame_log.pack(fill="x")
ttk.Label(frame_log, text="运行日志:").pack(anchor="w")
self.log_text = scrolledtext.ScrolledText(frame_log, height=6, font=("Consolas", 9))
self.log_text.pack(fill="x")
def log(self, msg):
self.log_text.insert(tk.END, msg + "\n")
self.log_text.see(tk.END)
self.root.update_idletasks()
def build_random_input(self, code: str) -> str:
match_while_triple = re.search(r'scanf\s*\(\s*"%d%d%d"', code)
match_single_n_scanf = re.search(r'scanf\s*\(\s*"%d"\s*,[^&]*&n\)', code)
if match_while_triple and match_single_n_scanf:
self.log("【识别模式1】费用流分段输入:n + n组三元数 + 0 0 0")
n_val = random.randint(2, 8)
lines = [str(n_val)]
for _ in range(n_val):
x = random.randint(1, 1000)
y = random.randint(1, 1000)
z = random.randint(1, 500)
lines.append(f"{x} {y} {z}")
lines.append("0 0 0")
return "\n".join(lines) + "\n"
for_cin_pat = re.compile(r'for\s*\(.*i.*n.*\)\s*\{\s*cin\s*>>\s*\w+\[i\]\s*;')
has_for_cin = for_cin_pat.search(code)
single_n_cin = re.search(r'cin\s*>>\s*n\s*;', code)
single_n_scan = re.search(r'scanf\s*\("%d"\s*,[^&]*&n\)', code)
if has_for_cin and (single_n_cin or single_n_scan):
self.log("【识别模式2】for循环数组输入:首行n,后输出n个数字")
n_val = random.randint(3, 12)
lines = [str(n_val)]
arr = [str(random.randint(1, 200)) for _ in range(n_val)]
lines.append(" ".join(arr))
return "\n".join(lines) + "\n"
self.log("【识别模式3】普通零散输入,统计所有输入变量总数")
total_vars = 0
cin_lines = re.findall(r"cin\s*>>\s*([\w\s>>]+);", code)
for seg in cin_lines:
vs = re.split(r"\s*>>\s*", seg.strip())
total_vars += len([v for v in vs if v])
scanf_pairs = re.findall(r'scanf\s*\(\s*"([^"]+)"\s*,([^)]+)\)', code)
for fmt, _ in scanf_pairs:
cnt = re.findall(r"%[idlf]", fmt)
total_vars += len(cnt)
self.log(f"检测到总输入数字个数:{total_vars}")
if total_vars == 0:
self.log("警告:未检测到cin/scanf,使用默认单数字输入")
return f"{random.randint(1, 1000)}\n"
nums = [str(random.randint(1, 2000)) for _ in range(total_vars)]
chunks = []
for i in range(0, len(nums), 4):
chunks.append(" ".join(nums[i:i+4]))
return "\n".join(chunks) + "\n"
def run_cpp_program(self, code: str, stdin_input: str) -> str:
cpp_fd, cpp_path = tempfile.mkstemp(suffix=".cpp", text=True)
os.close(cpp_fd)
self.temp_files.append(cpp_path)
with open(cpp_path, "w", encoding="utf-8") as f:
f.write(code)
exe_path = cpp_path[:-4] + ".exe"
self.temp_files.append(exe_path)
gpp_path = "C:/Program Files/RedPanda-Cpp/mingw64/bin/g++.exe"
gcc_cmd = [gpp_path, cpp_path, "-o", exe_path, "-std=c++17"]
mingw_bin_dir = "C:/Program Files/RedPanda-Cpp/mingw64/bin"
env = os.environ.copy()
env["PATH"] = mingw_bin_dir + os.pathsep + env["PATH"]
try:
compile_proc = subprocess.run(gcc_cmd, capture_output=True, text=True, timeout=10, env=env)
if compile_proc.returncode != 0:
self.log(f"编译失败:\n{compile_proc.stderr}")
return None
if not os.path.exists(exe_path):
self.log("错误:编译完成但未生成exe文件!")
return None
run_proc = subprocess.run([exe_path], input=stdin_input, capture_output=True, text=True, timeout=5, env=env)
return run_proc.stdout.strip()
except FileNotFoundError as e:
self.log(f"致命错误:找不到g++!路径检查:{e}")
return None
except subprocess.TimeoutExpired:
self.log("程序运行超时!存在死循环可能")
return None
except Exception as e:
self.log(f"运行异常:{str(e)}")
return None
def clear_temp(self):
for fpath in self.temp_files:
try:
if os.path.exists(fpath):
os.remove(fpath)
except:
pass
self.temp_files.clear()
def generate_samples(self):
self.result_text.delete(1.0, tk.END)
self.log_text.delete(1.0, tk.END)
self.generated_samples.clear()
self.clear_temp()
try:
sample_cnt = int(self.sample_count.get())
if sample_cnt <= 0:
messagebox.showerror("参数错误", "样例数量必须为正整数")
return
except ValueError:
messagebox.showerror("参数错误", "请输入合法数字作为样例数量")
return
cpp_code = self.code_text.get(1.0, tk.END).strip()
if not cpp_code:
messagebox.showerror("提示", "请粘贴完整C++代码到左侧输入框")
return
self.log(f"开始生成 {sample_cnt} 组样例\n=====================================")
for idx in range(1, sample_cnt + 1):
self.log(f"第{idx}组生成中...")
input_data = self.build_random_input(cpp_code)
self.log(f"本次输入:\n{input_data}")
output_data = self.run_cpp_program(cpp_code, input_data)
if output_data is None:
messagebox.showerror("生成失败", f"第{idx}组出错,终止")
self.clear_temp()
return
block = f"""Sample {idx}:
Input:
{input_data}
Output:
{output_data}
----------------------------------------
"""
self.generated_samples.append(block)
self.result_text.insert(tk.END, block)
self.log(f"\n{sample_cnt} 组样例全部生成完毕!")
messagebox.showinfo("完成", f"成功生成 {sample_cnt} 组样例")
self.clear_temp()
def save_samples(self):
if not self.generated_samples:
messagebox.showwarning("提示", "无样例可保存")
return
save_path = filedialog.asksaveasfilename(
defaultextension=".txt",
filetypes=[("文本文件 *.txt", "*.txt"), ("所有文件", "*.*")]
)
if not save_path:
return
with open(save_path, "w", encoding="utf-8") as f:
f.write("".join(self.generated_samples))
messagebox.showinfo("保存成功", f"保存至:{save_path}")
if __name__ == "__main__":
root = tk.Tk()
app = CppSampleGeneratorGUI(root)
def close_event():
app.clear_temp()
root.destroy()
root.protocol("WM_DELETE_WINDOW", close_event)
root.mainloop()
这里空空如也















有帮助,赞一个