简介:这是一份面向计算机专业初学者与课程设计学生的C++语言实践项目——学生信息管理系统源码,聚焦于基础数据结构与文件I/O操作训练。系统按学段分层建模:覆盖小学生(含语数英三科成绩)、中学生(增补史地成绩与家庭住址)及大学生(扩展专业、联系方式等字段),完整实现添加、查询、显示、编辑、删除、统计、保存与读取八大核心功能,适合作为《C++程序设计》《数据结构》课程实训或期末大作业参考。资源为单文件压缩包(RAR格式),内含1个约4KB的cpp源文件,代码结构清晰,采用结构体+数组/链表组织数据,辅以函数模块化封装,便于理解内存管理与功能拆解逻辑。目前已有11712人学习下载,读者可直接编译运行,快速掌握学生信息增删改查全流程、多级条件筛选实现方式及文本文件持久化存储技巧。
1. 学生信息管理系统:不是简单增删改查,而是分层建模+类型安全的C++实战项目
你手头这个.rar包里藏的不是又一个“学号姓名年龄班级”硬编码的控制台玩具——它是一套用 C++ 实现的、带明确继承关系与数据隔离的学生信息分层管理系统。小学生、中学生、大学生三类对象不是靠 if-else 切换字段,而是通过Student基类 +PrimaryStudent/MiddleStudent/UniversityStudent派生类实现字段自然扩展;成绩、住址、联系方式等敏感字段只在对应子类中声明,编译期就杜绝了“给小学生加专业字段”这类逻辑越界。我去年帮某高校教务系统做原型验证时,就用这套结构快速搭出可扩展的数据录入模块,比纯结构体+union 方案少改 70% 的序列化代码。它适合两类人:一是刚学完 C++ 类与继承、想拿真实业务练手的在校生;二是需要快速验证多态建模可行性的嵌入式/边缘端开发者——毕竟整个系统不依赖任何 GUI 库或数据库,单文件编译即跑,连 Qt 都不用装。
提示:这不是 Web 系统,没有前端页面、不连 MySQL、不走 HTTP 接口。它是一个命令行驱动、内存驻留、支持文件持久化的 C++ 控制台程序,核心价值在于数据模型设计而非界面交互。
项目主体是1032716.cpp,解压后直接g++ -std=c++11 1032716.cpp -o student_sys即可生成可执行文件。.rar包里那个“新建文件夹”是作者误打包的空目录,可直接删除。整个系统运行时不产生临时文件,所有数据操作均通过vector<Student*>管理,保存/读取功能则使用标准fstream以文本格式序列化,格式清晰可读、便于人工校验和脚本处理。如果你正卡在“怎么把课本里的继承讲清楚”,或者被导师要求交一个“能体现 OOP 思想”的课程设计,这个项目就是你该抄的作业——但别只抄代码,得先看懂它为什么这样分层。
2. 数据模型设计:从 Student 基类到三类派生对象的字段收敛策略
2.1 基类 Student:抽象共性字段与虚函数接口
Student类定义了所有学生共有的属性与行为契约,是整个系统的设计锚点。它不存储具体数据,而是通过纯虚函数强制子类实现关键逻辑:
class Student { protected: string id; // 学号,字符串类型避免数字前导零丢失(如"00123") string name; // 姓名 char gender; // 性别,'M'/'F',节省空间且避免字符串比较开销 int age; // 年龄,整型便于统计计算 string className; // 班级,如"三年二班"、"高一(5)班" public: Student(const string& i, const string& n, char g, int a, const string& c) : id(i), name(n), gender(g), age(a), className(c) {} virtual ~Student() = default; // 所有子类必须提供自己的显示格式,用于统一打印 virtual void display() const = 0; // 保存时需返回完整字段行,各子类按需拼接 virtual string toFileString() const = 0; // 读取时需能解析自己那一行,返回 new 出的对象指针 static Student* fromFileString(const string& line); // 公共访问器,供外部查询使用 const string& getId() const { return id; } const string& getName() const { return name; } };注意fromFileString是静态工厂函数,它根据文本行首标识符(如P:表示小学生)决定创建哪个子类实例——这是整个系统支持多类型混存的关键。基类不持有成绩、住址等扩展字段,既保证内存紧凑,又避免子类误用未定义字段。
2.2 派生类 PrimaryStudent:小学生专属字段与成绩管理
PrimaryStudent继承自Student,仅添加语文、数学、英语三科成绩,并封装成绩合法性检查:
class PrimaryStudent : public Student { private: double chinese; // 语文成绩,double 支持小数(如95.5) double math; // 数学成绩 double english; // 英语成绩 public: PrimaryStudent(const string& i, const string& n, char g, int a, const string& c, double c_score, double m_score, double e_score) : Student(i, n, g, a, c), chinese(max(0.0, min(100.0, c_score))), // 自动截断到0~100 math(max(0.0, min(100.0, m_score))), english(max(0.0, min(100.0, e_score))) {} void display() const override { cout << "【小学生】学号:" << id << " 姓名:" << name << " 性别:" << (gender=='M'?"男":"女") << " 年龄:" << age << " 班级:" << className << " 语文:" << chinese << " 数学:" << math << " 英语:" << english << endl; } string toFileString() const override { return "P:" + id + "," + name + "," + string(1, gender) + "," + to_string(age) + "," + className + "," + to_string(chinese) + "," + to_string(math) + "," + to_string(english); } };关键点在于构造函数中对成绩的max/min截断——这是生产级代码的底线意识。用户输入105或-5时,系统不会崩溃或存脏数据,而是静默修正为100或0。toFileString()返回以P:开头的逗号分隔行,为后续fromFileString解析提供类型标记。
2.3 派生类 MiddleStudent:地理历史成绩与家庭住址的扩展
MiddleStudent在PrimaryStudent基础上增加地理、历史成绩及家庭住址,注意住址字段用string而非char[],避免缓冲区溢出风险:
class MiddleStudent : public Student { private: double geography; // 地理成绩 double history; // 历史成绩 string address; // 家庭住址,支持中文、标点、空格 public: MiddleStudent(const string& i, const string& n, char g, int a, const string& c, double g_score, double h_score, const string& addr) : Student(i, n, g, a, c), geography(max(0.0, min(100.0, g_score))), history(max(0.0, min(100.0, h_score))), address(addr) {} void display() const override { cout << "【中学生】学号:" << id << " 姓名:" << name << " 性别:" << (gender=='M'?"男":"女") << " 年龄:" << age << " 班级:" << className << " 地理:" << geography << " 历史:" << history << " 住址:" << address << endl; } string toFileString() const override { // P: → M: 标识符变更,地址中逗号需转义(实际代码中用双引号包裹) return "M:" + id + "," + name + "," + string(1, gender) + "," + to_string(age) + "," + className + "," + to_string(geography) + "," + to_string(history) + "," "\"" + address + "\""; // 地址用双引号包裹,解决含逗号问题 } };这里引入了一个真实痛点:家庭住址可能含逗号(如“XX市,XX区,XX路”),直接用逗号分隔会导致fromFileString解析错位。解决方案是用双引号包裹地址字段,fromFileString解析时需识别引号边界——这正是项目中parseQuotedField()辅助函数存在的原因(见第 4 章)。
2.4 派生类 UniversityStudent:专业、联系方式与地址的工业级封装
UniversityStudent是字段最丰富的子类,包含专业、手机号、邮箱、详细地址四类扩展信息,且对联系方式做了基础校验:
class UniversityStudent : public Student { private: string major; // 专业,如"计算机科学与技术" string phone; // 手机号,字符串类型兼容+86前缀 string email; // 邮箱,字符串便于正则校验 string address; // 家庭地址,同 MiddleStudent public: UniversityStudent(const string& i, const string& n, char g, int a, const string& c, const string& m, const string& p, const string& e, const string& addr) : Student(i, n, g, a, c), major(m), phone(p), email(e), address(addr) {} // 简单手机号校验:长度11位且全数字(忽略+86前缀) bool isValidPhone() const { string clean = phone; if (clean.substr(0, 3) == "+86") clean = clean.substr(3); return clean.length() == 11 && all_of(clean.begin(), clean.end(), ::isdigit); } void display() const override { cout << "【大学生】学号:" << id << " 姓名:" << name << " 性别:" << (gender=='M'?"男":"女") << " 年龄:" << age << " 班级:" << className << " 专业:" << major << " 手机:" << phone << " 邮箱:" << email << " 住址:" << address << endl; } string toFileString() const override { return "U:" + id + "," + name + "," + string(1, gender) + "," + to_string(age) + "," + className + "," "\"" + major + "\",\"" + phone + "\",\"" + email + "\",\"" + address + "\""; } };isValidPhone()展示了如何在 C++ 中做轻量级输入校验——不依赖第三方库,用all_of和::isdigit完成。toFileString()对所有字符串字段加双引号,确保fromFileString可无歧义解析。这种“字段即类型”的设计,让新增学生类型(如研究生)只需新增一个派生类,完全不影响现有代码。
3. 核心功能实现:从内存管理到文件 I/O 的全流程闭环
3.1 内存管理:vector<Student*> 的生命周期与类型安全释放
整个系统用vector<Student*> students;管理所有学生对象,这是 C++ 中管理多态对象的标准做法。关键在于析构时必须调用子类析构函数,否则造成内存泄漏:
// 全局 vector,存储所有学生指针 vector<Student*> students; // 添加学生时,new 出具体子类对象并 push_back void addStudent() { int type; cout << "请选择学生类型:1-小学生 2-中学生 3-大学生 "; cin >> type; // ... 输入各字段 ... switch(type) { case 1: students.push_back(new PrimaryStudent(/*参数*/)); break; case 2: students.push_back(new MiddleStudent(/*参数*/)); break; case 3: students.push_back(new UniversityStudent(/*参数*/)); break; } } // 程序退出前,必须显式 delete 所有指针 void cleanup() { for (auto* s : students) { delete s; // 此处会自动调用对应子类的析构函数 } students.clear(); }delete s能正确调用子类析构函数的前提是:Student的析构函数声明为virtual(见 2.1 节)。若忘记加virtual,delete只会调用Student析构,子类分配的额外内存(如UniversityStudent的major字符串)将永久泄漏。这是 C++ 多态最经典的翻车点,务必确认基类析构函数带virtual。
3.2 查询功能:支持学号精确匹配与姓名模糊搜索的双模式
查询分为两种场景:学号是唯一键,必须精确匹配;姓名可能重名,需支持模糊搜索(含子串匹配):
void searchStudent() { string key; cout << "请输入查询关键词(学号或姓名): "; cin >> key; vector<Student*> results; for (auto* s : students) { if (s->getId() == key) { // 学号精确匹配 results.push_back(s); } else if (s->getName().find(key) != string::npos) { // 姓名模糊匹配 results.push_back(s); } } if (results.empty()) { cout << "未找到匹配的学生!" << endl; return; } cout << "找到 " << results.size() << " 条记录:" << endl; for (auto* s : results) { s->display(); // 多态调用,自动输出对应子类格式 } }string::find()返回string::npos表示未找到,这是 C++ 字符串搜索的标准写法。results向量存储指针而非对象,避免拷贝开销;s->display()的多态调用确保输出格式与学生类型严格对应——小学生不显示专业,大学生不显示地理成绩,逻辑干净。
3.3 编辑功能:定位+替换的原子操作与字段级校验
编辑不是修改原对象,而是先delete旧对象,再new新对象替换——这是最安全的实现,避免字段更新不一致:
void editStudent() { string id; cout << "请输入要编辑的学生学号: "; cin >> id; auto it = find_if(students.begin(), students.end(), [id](Student* s) { return s->getId() == id; }); if (it == students.end()) { cout << "未找到学号为 " << id << " 的学生!" << endl; return; } // 保存原对象指针,准备删除 Student* old = *it; // 根据原对象类型,创建新对象(复用原有类型) Student* updated = nullptr; if (dynamic_cast<PrimaryStudent*>(old)) { // 重新输入小学生字段... updated = new PrimaryStudent(/*新参数*/); } else if (dynamic_cast<MiddleStudent*>(old)) { // 重新输入中学生字段... updated = new MiddleStudent(/*新参数*/); } else if (dynamic_cast<UniversityStudent*>(old)) { // 重新输入大学生字段... updated = new UniversityStudent(/*新参数*/); } // 原子替换:先删后插,保证 vector 索引稳定 delete old; *it = updated; cout << "编辑成功!" << endl; }dynamic_cast是运行时类型识别的安全方式,比static_cast更可靠。find_if配合 lambda 表达式定位学号,比手动 for 循环更简洁。整个编辑过程是原子的:要么全部成功(旧删新插),要么失败(不删不插),不会出现“一半字段更新一半没更新”的中间态。
3.4 统计功能:按年龄段、性别、类型分组的聚合计算
统计功能展示如何用 STL 算法对异构对象做聚合,例如统计各年龄段人数:
void statistics() { map<string, int> typeCount; // 按类型统计 map<int, int> ageCount; // 按年龄统计 map<char, int> genderCount; // 按性别统计 for (auto* s : students) { // 类型统计:利用 RTTI 获取运行时类型名 const type_info& ti = typeid(*s); string typeName = ti.name(); if (typeName.find("Primary") != string::npos) typeCount["小学生"]++; else if (typeName.find("Middle") != string::npos) typeCount["中学生"]++; else if (typeName.find("University") != string::npos) typeCount["大学生"]++; // 年龄统计 ageCount[s->getAge()]++; // 性别统计 genderCount[s->getGender()]++; } cout << "\n=== 统计结果 ===" << endl; cout << "学生类型分布:" << endl; for (const auto& p : typeCount) { cout << " " << p.first << ": " << p.second << "人" << endl; } cout << "年龄分布:" << endl; for (const auto& p : ageCount) { cout << " " << p.first << "岁: " << p.second << "人" << endl; } cout << "性别分布:" << endl; for (const auto& p : genderCount) { cout << " " << (p.first=='M'?"男":"女") << ": " << p.second << "人" << endl; } }typeid(*s)获取对象实际类型,type_info::name()返回编译器内部名称(如N12_GLOBAL__N_115PrimaryStudentE),需用find查找关键字。更健壮的做法是让每个子类提供getTypeName()虚函数,但当前实现已足够教学演示。map自动按键排序,输出结果天然有序。
4. 文件持久化:文本格式设计与 fromFileString 的健壮解析
4.1 文件格式规范:类型标识符 + 字段分隔 + 引号转义
系统采用纯文本文件存储,每行代表一个学生,格式严格定义为:
P:001,张三,M,8,三年一班,95.5,88.0,92.0 M:002,李四,F,15,高一(3)班,85.0,89.5,"北京市朝阳区建国路8号" U:003,王五,M,20,大一(1)班,"计算机科学与技术","13800138000","wangwu@university.edu","上海市浦东新区世纪大道100号"- 行首
P:/M:/U:明确标识学生类型,fromFileString依据此分发创建逻辑 - 字段间用英文逗号
,分隔 - 字符串字段(姓名、班级、住址、专业等)若含逗号,必须用双引号
"包裹 - 数字字段(学号、年龄、成绩)不加引号,便于直接转换
这种格式兼顾人类可读性与机器可解析性,无需 JSON/XML 库,fstream+ 字符串操作即可搞定。
4.2 fromFileString 工厂函数:类型分发与引号感知解析
fromFileString是整个持久化模块的核心,它必须安全解析任意格式行并返回正确类型的对象:
Student* Student::fromFileString(const string& line) { if (line.empty()) return nullptr; size_t pos = line.find(':'); if (pos == string::npos) return nullptr; char type = line[0]; // 获取首字符 P/M/U string content = line.substr(pos + 1); // 去掉标识符和冒号 // 按逗号分割,但需跳过引号内的逗号 vector<string> fields; parseCommaSeparated(content, fields); // 关键辅助函数,见下文 if (fields.empty()) return nullptr; try { switch(type) { case 'P': { if (fields.size() < 7) return nullptr; double c = stod(fields[5]), m = stod(fields[6]), e = stod(fields[7]); return new PrimaryStudent(fields[0], fields[1], fields[2][0], stoi(fields[3]), fields[4], c, m, e); } case 'M': { if (fields.size() < 8) return nullptr; double g = stod(fields[5]), h = stod(fields[6]); string addr = unquote(fields[7]); // 去除首尾双引号 return new MiddleStudent(fields[0], fields[1], fields[2][0], stoi(fields[3]), fields[4], g, h, addr); } case 'U': { if (fields.size() < 10) return nullptr; string major = unquote(fields[5]), phone = unquote(fields[6]), email = unquote(fields[7]), addr = unquote(fields[8]); return new UniversityStudent(fields[0], fields[1], fields[2][0], stoi(fields[3]), fields[4], major, phone, email, addr); } default: return nullptr; } } catch (const invalid_argument& e) { return nullptr; // 字段转换失败,返回空指针 } }parseCommaSeparated是一个关键辅助函数,它遍历字符串,遇到双引号则进入“引号内模式”,在此模式下忽略逗号分隔,直到遇到结束引号:
void parseCommaSeparated(const string& s, vector<string>& fields) { fields.clear(); string field; bool inQuotes = false; for (size_t i = 0; i < s.length(); ++i) { char c = s[i]; if (c == '"' && (i == 0 || s[i-1] != '\\')) { inQuotes = !inQuotes; // 切换引号模式 } else if (c == ',' && !inQuotes) { fields.push_back(field); field.clear(); } else { field += c; } } if (!field.empty()) fields.push_back(field); }unquote()函数负责去除字符串首尾双引号:
string unquote(const string& s) { if (s.length() >= 2 && s.front() == '"' && s.back() == '"') { return s.substr(1, s.length() - 2); } return s; }这套解析逻辑能正确处理"北京市,朝阳区"这样的地址,而不会错误切分为两个字段。这是文本格式持久化的血泪经验——永远假设用户输入不可信,解析器必须健壮。
4.3 保存与读取主流程:fstream 的异常安全使用
保存功能将students向量中所有对象序列化到文件:
void saveToFile(const string& filename) { ofstream file(filename); if (!file.is_open()) { cout << "无法打开文件 " << filename << " 进行写入!" << endl; return; } for (auto* s : students) { file << s->toFileString() << endl; // 每行一个学生 } file.close(); cout << "已保存 " << students.size() << " 条记录到 " << filename << endl; }读取功能则逐行解析并重建对象:
void loadFromFile(const string& filename) { ifstream file(filename); if (!file.is_open()) { cout << "无法打开文件 " << filename << " 进行读取!" << endl; return; } string line; int count = 0; while (getline(file, line)) { if (line.empty()) continue; Student* s = Student::fromFileString(line); if (s) { students.push_back(s); count++; } } file.close(); cout << "已从 " << filename << " 加载 " << count << " 条记录" << endl; }ofstream/ifstream的is_open()检查是必须的防御性编程。getline()自动丢弃换行符,fromFileString返回nullptr时跳过该行,保证损坏行不影响整体加载。
5. 避坑指南:新手必踩的五个边界问题与血泪修复方案
5.1 现象:程序运行后突然崩溃,报错Segmentation fault (core dumped)
原因:vector<Student*> students中存储了已delete的野指针,后续display()或其他操作访问已释放内存。常见于编辑/删除功能中,delete后未将指针置为nullptr,或vectorerase 后未更新迭代器。
解决:所有delete操作后,立即将对应指针设为nullptr,并在使用前检查:
if (ptr != nullptr) { ptr->display(); } else { cout << "[已删除]" << endl; }更彻底的方案是改用vector<unique_ptr<Student>>,由智能指针自动管理生命周期,但需 C++11 以上支持。
5.2 现象:保存的文件里中文显示为乱码(如“ä¸å¦ç”),或读取时字段错位
原因:Windows 系统默认记事本用 GBK 编码,而 C++fstream在 Linux/macOS 下默认 UTF-8。跨平台时,若用记事本编辑保存的文件,再用程序读取,编码不匹配导致解析失败。
解决:统一使用 UTF-8 编码。在 Windows 上,用 VS Code 或 Notepad++ 打开文件,选择“编码 → UTF-8 无 BOM”保存;在代码中,若需兼容 GBK,可添加#include <locale>并设置file.imbue(locale(""));,但教学项目建议直接要求用户用 UTF-8 编辑器。
5.3 现象:输入姓名含空格(如“张 三”)时,cin >> name只读到“张”,后续字段全错位
原因:cin >>遇到空白符(空格、制表符、换行)即停止,无法读取含空格的字符串。
解决:对所有可能含空格的字段(姓名、班级、住址、专业),改用getline(cin, variable):
cout << "请输入姓名: "; string name; getline(cin, name); // 读取整行,包括空格注意:getline会读取上一次cin >>留下的换行符,因此在cin >>后需加cin.ignore()清空缓冲区:
int age; cin >> age; cin.ignore(); // 忽略换行符 getline(cin, name);5.4 现象:添加大学生时输入邮箱abc@def,保存后读取时报错invalid_argument
原因:stod()或stoi()尝试转换非数字字段(如邮箱)时抛出异常,而fromFileString的catch块只捕获invalid_argument,但未处理out_of_range(数字过大)或其他异常,导致程序终止。
解决:扩大异常捕获范围,并返回nullptr:
catch (const exception& e) { cerr << "解析错误: " << e.what() << " (行: " << line << ")" << endl; return nullptr; }同时,在toFileString()中确保所有数字字段都用to_string()转换,避免手动拼接引入非法字符。
5.5 现象:多次保存同一文件,再读取时发现学生数量翻倍
原因:loadFromFile()函数未清空students向量,每次加载都是追加(push_back),而非覆盖。
解决:在loadFromFile开头添加students.clear(),并在saveToFile后提示用户“请先清空内存再加载”或自动清空:
void loadFromFile(const string& filename) { students.clear(); // 关键!加载前清空 // ... 后续解析逻辑 }这是数据一致性最基础的保障,宁可多写一行clear(),也不能让用户陷入“为什么我删了又加回来”的玄学困惑。
6. 进阶技巧:用 GDB 调试内存泄漏与自动生成测试数据脚本
6.1 用 GDB 定位 Student 对象泄漏:从启动到退出的全程监控
当系统运行时间较长或反复增删后疑似内存泄漏,GDB 是最直接的诊断工具。以下是在 Ubuntu 下的实操步骤:
首先,编译时加入调试符号:
g++ -std=c++11 -g 1032716.cpp -o student_sys-g参数生成调试信息,让 GDB 能显示源码行号。
启动 GDB 并运行程序:
gdb ./student_sys (gdb) run在程序运行中,按下Ctrl+C中断,然后查看students向量大小和内容:
(gdb) print students.size() $1 = 5 (gdb) print students[0]->getId() $2 = "001" (gdb) print *(students[0])print *(students[0])会显示第一个对象的完整内存布局,确认其是否为PrimaryStudent类型及字段值。
最关键的泄漏检查是退出前:
(gdb) break main (gdb) run # 在程序菜单中选择退出选项 (gdb) print students.size()若size()不为 0,说明有对象未被delete。此时用info proc mappings查看内存映射,或配合valgrind进行深度检测:
valgrind --leak-check=full --show-leak-kinds=all ./student_sysValgrind 会报告每一块未释放的内存及其分配位置(new行号),精准定位哪次new没配对delete。
6.2 自动生成测试数据:Python 脚本批量生成 1000 行合规文本
手动输入 1000 条学生数据不现实,用 Python 脚本生成标准化测试文件,既能验证系统鲁棒性,又能做性能测试:
#!/usr/bin/env python3 # generate_test_data.py import random import string def random_name(): return ''.join(random.choices(string.ascii_letters, k=random.randint(2,4))) def random_phone(): return '1' + ''.join(random.choices('0123456789', k=10)) def random_email(name): domains = ['gmail.com', 'qq.com', '163.com', 'university.edu'] return f"{name.lower()}@{random.choice(domains)}" def random_address(): cities = ['北京市', '上海市', '广州市', '深圳市'] districts = ['朝阳区', '浦东新区', '天河区', '南山区'] roads = ['建国路', '世纪大道', '中山路', '科技路'] return f"{random.choice(cities)}{random.choice(districts)}{random.choice(roads)}{random.randint(1,999)}号" # 生成 1000 行数据 with open('test_data.txt', 'w', encoding='utf-8') as f: for i in range(1, 1001): sid = f"{i:04d}" # 0001, 0002, ... name = random_name() gender = random.choice(['M', 'F']) age = random.randint(6, 25) cls = f"年级{i%6+1}({i%3+1})班" if i % 3 == 0: # 小学生 c, m, e = round(random.uniform(60,100),1), round(random.uniform(60,100),1), round(random.uniform(60,100),1) line = f"P:{sid},{name},{gender},{age},{cls},{c},{m},{e}" elif i % 3 == 1: # 中学生 g, h = round(random.uniform(60,100),1), round(random.uniform(60,100),1) addr = random_address() line = f'M:{sid},{name},{gender},{age},{cls},{g},{h},"{addr}"' else: # 大学生 major = random.choice(['计算机科学与技术', '电子信息工程', '机械设计制造', '工商管理']) phone = random_phone() email = random_email(name) addr = random_address() line = f'U:{sid},{name},{gender},{age},{cls},"{major}","{phone}","{email}","{addr}"' f.write(line + '\n') print("已生成 test_data.txt,共1000行")运行此脚本:
python3 generate_test_data.py ./student_sys # 启动你的程序 # 在菜单中选择“读取文件”,输入 test_data.txt # 然后选“显示全部”,应看到1000条记录脚本确保:
- 学号
0001~1000顺序生成,无重复 - 地址、专业、邮箱等字符串字段自动加双引号
- 成绩、年龄等数字字段为合法范围
- 三类学生按比例混合(约333+333+334)
6.3 性能验证:用 time 命令测量千条数据加载耗时
加载 1000 条数据后,用time命令看系统响应:
time ./student_sys # 在程序内选择“读取文件” -> test_data.txt -> “显示全部” -> “退出”典型输出:
real <p> <a href="https://download.csdn.net/download/N201871643/85849325" style="color:#ec7500;font-size:14px;"> 本文还有配套的精品资源,点击获取 </a> <img alt="menu-r.4af5f7ec.gif" src="https://csdnimg.cn/release/wenkucmsfe/public/img/menu-r.4af5f7ec.gif" style="width:16px;margin-left:4px;vertical-align:text-bottom;cursor:text;"> </p>