1. 课程设计概述

1.1. 设计背景

在教育教学管理中,学生信息与成绩的高效管理是提升工作效率的关键。传统手工记录、Excel 表格管理等方式存在数据查询繁琐、修改不便、统计分析困难等问题,难以满足批量学生数据的规范化管理需求。因此,设计一款功能完善、操作便捷、数据安全的学生信息管理系统,对简化学生管理流程、提高教学管理效率具有重要现实意义。

1.2. 设计目标

本课程设计旨在开发一款基于 C++ 语言的学生信息管理系统,实现学生基本信息与成绩的一体化管理,具体目标如下:

  1. 支持学生基本信息(学号、姓名、学院、专业)和成绩信息(语文、数学、英语、总分)的添加、删除、修改、查询操作;
  2. 提供多样化排序功能,可按学号升序、各科目成绩降序、总分降序排列学生数据;
  3. 实现成绩统计分析功能,包括各科目及总分的平均分、众数、最高分、最低分计算,并关联对应学生信息;
  4. 采用 CSV 文件存储数据,保证数据持久化,支持特殊字符处理,确保数据读写准确性;
  5. 设计友好的用户交互界面,包含输入验证机制,提升系统容错性和使用体验。

    1.3. 开发环境

  • 编程语言:C语言
  • 开发工具:Visual Studio Code + MinGW
  • 数据存储:CSV 文件

    2. 系统总体设计

2.1. 系统架构

本系统采用面向对象的设计思想,分为数据模型层和业务逻辑层两层架构:

  1. 数据模型层:通过Student类封装单个学生的信息,作为系统数据的基本载体;
  2. 业务逻辑层:通过StudentManager类封装所有业务操作,包括数据的增删改查、排序、统计分析及 CSV 文件读写,实现数据与操作的分离,提高代码的可维护性和复用性。
    系统整体架构图如下:
    image.png
graph TD
A["控制逻辑层"] -->|"核心管理类"| A1["StudentManager类"]
A1 -->|"封装功能"| A2["增删改查<br>排序<br>统计分析<br>CSV读写"]

B["数据模型层"] -->|"数据载体类"| B1["Student类"]
B1 -->|"存储信息"| B2["学号、姓名、学院、专业<br>语文/数学/英语成绩<br>总分"]

C["数据存储层"] -->|"持久化文件"| C1["student_info.csv文件"]

%% 层级关联
A1 --> B1
B1 --> C1

%% 样式优化(可选,使结构更清晰)
classDef layerStyle fill:#f0f8ff,stroke:#2c3e50,stroke-width:2px,rounded:10px
classDef classStyle fill:#e8f4f8,stroke:#3498db,stroke-width:1px,rounded:8px
class A,B,C layerStyle
class A1,B1,C1 classStyle

2.2. 核心类设计

2.2.1. Student

功能:存储单个学生的完整信息,提供成员变量的访问接口。

成员变量 类型 说明
id string 学生学号(唯一标识)
name string 学生姓名
college string 所属学院
major string 所属专业
chinese string 语文成绩(字符串存储,便于 CSV 读写)
math string 数学成绩
english string 英语成绩
total string 总分(自动计算)

成员方法

  • 构造函数:默认构造函数、带参数构造函数(用于快速创建学生对象);
  • 访问器(getter)和修改器(setter):用于安全访问和修改成员变量。

    2.2.2. StudentManager

    功能:封装系统所有业务逻辑,是系统的核心管理类。
    核心成员方法
方法分类 方法名 功能描述
文件操作 loadFromCSV() 从 CSV 文件加载数据到内存
saveToCSV() 将内存中的数据保存到 CSV 文件
辅助工具 escapeCSVField() CSV 字段特殊字符转义(逗号、引号)
unescapeCSVField() CSV 字段特殊字符解析
parseCSVLine() 解析 CSV 文件中的一行数据
isValidScore() 验证成绩是否为 0-100 的数字
calculateTotal() 计算学生总分
findStudentIndex() 根据学号 / 姓名查找学生索引
增删改查 addStudent() 添加新学生信息
deleteStudent() 删除指定学生信息
modifyStudent() 修改指定学生信息
searchStudentById() 按学号查询学生信息
searchStudentByName() 按姓名查询学生信息
displayAllStudents() 显示所有学生信息
排序功能 sortByid() 按学号升序排序
sortByChinese() 按语文成绩降序排序
sortByMath() 按数学成绩降序排序
sortByEnglish() 按英语成绩降序排序
sortByTotal() 按总分降序排序
统计分析 calculateAverage() 计算指定科目 / 总分的平均分
findMode() 查找指定科目 / 总分的众数
findMaxScore() 查找指定科目 / 总分的最高分及对应学生
findMinScore() 查找指定科目 / 总分的最低分及对应学生

2.3. 数据存储设计

系统采用 CSV(Comma-Separated Values)文件作为数据存储格式,具体设计如下:

  1. 文件名:student_info.csv,存储在程序运行目录下;
  2. 文件格式:第一行为表头(学号,姓名,学院,专业,语文,数学,英语,总分),后续每行存储一个学生的完整信息;
  3. 特殊字符处理:当字段中包含逗号(,)或双引号(”)时,使用双引号包裹该字段,并将字段内的双引号替换为两个连续双引号(””),确保 CSV 文件解析的准确性;
  4. 数据持久化:每次添加、删除、修改、排序操作后,系统自动调用saveToCSV()方法将数据写入文件,保证数据不丢失。

    3. 系统详细实现

3.1. CSV 文件读写实现

3.1.1. 特殊字符转义与解析

为解决 CSV 文件中特殊字符导致的解析错误问题,实现了escapeCSVField()unescapeCSVField()方法:

// CSV特殊字符转义(处理包含逗号、引号的字段)
string escapeCSVField(const string& field) {
if (field.find(',') != string::npos || field.find('"') != string::npos) {
string escaped = field;
// 替换双引号为两个双引号(CSV标准转义)
replace(escaped.begin(), escaped.end(), '"', '"');
return "\"" + escaped + "\""; // 用双引号包裹字段
}
return field;
}

// 解析CSV字段(处理带引号的字段)
string unescapeCSVField(const string& field) {
if (field.size() >= 2 && field.front() == '"' && field.back() == '"') {
string unescaped = field.substr(1, field.size() - 2);
// 还原双引号(将两个双引号替换为一个)
size_t pos = 0;
while ((pos = unescaped.find("\"\"", pos)) != string::npos) {
unescaped.replace(pos, 2, "\"");
pos += 1;
}
return unescaped;
}
return field;
}

3.1.2. CSV 文件加载与保存

  • 加载数据:loadFromCSV()方法打开 CSV 文件,逐行读取数据,通过parseCSVLine()方法解析每行字段,创建Student对象并添加到students容器中;
  • 保存数据:saveToCSV()方法遍历students容器,将每个学生的信息转换为 CSV 格式的一行,写入文件并处理特殊字符。

    3.2. 核心功能实现

3.2.1. 学生信息添加

添加学生时需进行多重验证:学号唯一性、姓名 / 学院 / 专业非空、成绩为 0-100 的数字,验证通过后计算总分并保存数据:

void addStudent() {
Student s;
cout << "\n===================== 添加学生 =====================" << endl;

// 学号验证(唯一)
while (true) {
cout << "请输入学号:";
cin >> s.id;
if (findById(s.id) != -1) {
cout << "错误:该学号已存在!请重新输入。" << endl;
}
else if (s.id.empty()) {
cout << "错误:学号不能为空!请重新输入。" << endl;
}
else {
break;
}
}

// 姓名
cout << "请输入姓名:";
cin.ignore();
getline(cin, s.name);
while (s.name.empty()) {
cout << "错误:姓名不能为空!请重新输入:";
getline(cin, s.name);
}

// 学院
cout << "请输入学院:";
getline(cin, s.college);
while (s.college.empty()) {
cout << "错误:学院不能为空!请重新输入:";
getline(cin, s.college);
}

// 专业
cout << "请输入专业:";
getline(cin, s.major);
while (s.major.empty()) {
cout << "错误:专业不能为空!请重新输入:";
getline(cin, s.major);
}

// 成绩输入与验证
while (true) {
cout << "请输入语文成绩:";
cin >> s.chinese;
if (validateScore(s.chinese, "语文")) break;
}

while (true) {
cout << "请输入数学成绩:";
cin >> s.math;
if (validateScore(s.math, "数学")) break;
}

while (true) {
cout << "请输入英语成绩:";
cin >> s.english;
if (validateScore(s.english, "英语")) break;
}

// 计算总分
updateTotal(s);

students.push_back(s);
autoSaveToCSV(); // 保存到CSV
cout << "\n✅ 学生添加成功!数据已保存到 " << CSV_FILENAME << endl;
}

3.2.2. 排序功能实现

以按总分降序排序为例,利用 C++ STL 的sort()函数结合 lambda 表达式实现:

// 按学号排序(升序)
void sortById() {
if (students.empty()) {
cout << "提示:当前没有学生数据可排序!" << endl;
return;
}

sort(students.begin(), students.end(), [](const Student& a, const Student& b) {
// 先按长度排序,再按字典序排序
if (a.id.size() != b.id.size())
return a.id.size() < b.id.size();
return a.id < b.id;
});
autoSaveToCSV(); // 保存到CSV
cout << "\n✅ 已按学号升序排序!数据已保存到 " << CSV_FILENAME << endl;
}

// 按语文成绩排序(降序)
void sortByChinese() {
if (students.empty()) {
cout << "提示:当前没有学生数据可排序!" << endl;
return;
}

sort(students.begin(), students.end(), [](const Student& a, const Student& b) {
try {
return stof(a.chinese) > stof(b.chinese);
}
catch (...) {
return false;
}
});
autoSaveToCSV(); // 保存到CSV
cout << "\n✅ 已按语文成绩降序排序!数据已保存到 " << CSV_FILENAME << endl;
}

// 按数学成绩排序(降序)
void sortByMath() {
if (students.empty()) {
cout << "提示:当前没有学生数据可排序!" << endl;
return;
}

sort(students.begin(), students.end(), [](const Student& a, const Student& b) {
try {
return stof(a.math) > stof(b.math);
}
catch (...) {
return false;
}
});
autoSaveToCSV(); // 保存到CSV
cout << "\n✅ 已按数学成绩降序排序!数据已保存到 " << CSV_FILENAME << endl;
}

// 按英语成绩排序(降序)
void sortByEnglish() {
if (students.empty()) {
cout << "提示:当前没有学生数据可排序!" << endl;
return;
}

sort(students.begin(), students.end(), [](const Student& a, const Student& b) {
try {
return stof(a.english) > stof(b.english);
}
catch (...) {
return false;
}
});
autoSaveToCSV(); // 保存到CSV
cout << "\n✅ 已按英语成绩降序排序!数据已保存到 " << CSV_FILENAME << endl;
}

// 按总分排序(降序)
void sortByTotal() {
if (students.empty()) {
cout << "提示:当前没有学生数据可排序!" << endl;
return;
}

sort(students.begin(), students.end(), [](const Student& a, const Student& b) {
try {
return stof(a.total) > stof(b.total);
}
catch (...) {
return false;
}
});
autoSaveToCSV(); // 保存到CSV
cout << "\n✅ 已按总分降序排序!数据已保存到 " << CSV_FILENAME << endl;
}

3.2.3. 成绩统计分析实现

  • 平均分计算:过滤无效成绩后,求所有有效成绩的均值;
  • 众数查找:通过map统计成绩出现频率,找出频率最高的成绩;
  • 极值查找:使用min_element()max_element()函数查找最高分和最低分,并返回对应学生信息。
    示例:众数查找实现
    // 计算平均分
    void calculateAverage() {
    if (students.empty()) {
    cout << "提示:当前没有学生数据!" << endl;
    return;
    }

    float chiTotal = 0.0f, mathTotal = 0.0f, engTotal = 0.0f, totalTotal = 0.0f;
    int validCount = 0;

    for (const auto& s : students) {
    try {
    chiTotal += stof(s.chinese);
    mathTotal += stof(s.math);
    engTotal += stof(s.english);
    totalTotal += stof(s.total);
    validCount++;
    }
    catch (...) {
    continue;
    }
    }

    if (validCount == 0) {
    cout << "错误:没有有效的成绩数据可计算!" << endl;
    return;
    }

    cout << fixed << setprecision(2) << "\n===================== 成绩平均分 =====================" << endl;
    cout << "语文平均分:" << chiTotal / validCount << endl;
    cout << "数学平均分:" << mathTotal / validCount << endl;
    cout << "英语平均分:" << engTotal / validCount << endl;
    cout << "总分平均分:" << totalTotal / validCount << endl;
    }

    // 查找众数(出现次数最多的成绩)
    void findMode() {
    if (students.empty()) {
    cout << "提示:当前没有学生数据!" << endl;
    return;
    }

    // 辅助函数:查找map中的众数
    auto findMapMode = [](const map<float, int>& freqMap) -> pair<float, int> {
    if (freqMap.empty()) return { 0.0f, 0 };
    auto modeIt = max_element(freqMap.begin(), freqMap.end(),
    [](const pair<float, int>& a, const pair<float, int>& b) {
    return a.second < b.second;
    });
    return *modeIt;
    };

    // 语文成绩众数
    map<float, int> chiFreq;
    for (const auto& s : students) {
    try {
    chiFreq[stof(s.chinese)]++;
    }
    catch (...) { continue; }
    }
    auto chiMode = findMapMode(chiFreq);

    // 数学成绩众数
    map<float, int> mathFreq;
    for (const auto& s : students) {
    try {
    mathFreq[stof(s.math)]++;
    }
    catch (...) { continue; }
    }
    auto mathMode = findMapMode(mathFreq);

    // 英语成绩众数
    map<float, int> engFreq;
    for (const auto& s : students) {
    try {
    engFreq[stof(s.english)]++;
    }
    catch (...) { continue; }
    }
    auto engMode = findMapMode(engFreq);

    // 总分众数
    map<float, int> totalFreq;
    for (const auto& s : students) {
    try {
    totalFreq[stof(s.total)]++;
    }
    catch (...) { continue; }
    }
    auto totalMode = findMapMode(totalFreq);

    cout << fixed << setprecision(1) << "\n===================== 成绩众数 =====================" << endl;
    cout << "语文众数:" << chiMode.first << " (出现次数:" << chiMode.second << ")" << endl;
    cout << "数学众数:" << mathMode.first << " (出现次数:" << mathMode.second << ")" << endl;
    cout << "英语众数:" << engMode.first << " (出现次数:" << engMode.second << ")" << endl;
    cout << "总分众数:" << totalMode.first << " (出现次数:" << totalMode.second << ")" << endl;
    }

    // 查找极值(最高分和最低分)
    void findExtremeValues() {
    if (students.empty()) {
    cout << "提示:当前没有学生数据!" << endl;
    return;
    }

    // 过滤掉成绩无效的学生
    vector<Student> validStudents;
    for (const auto& s : students) {
    try {
    stof(s.chinese);
    stof(s.math);
    stof(s.english);
    stof(s.total);
    validStudents.push_back(s);
    }
    catch (...) {
    continue;
    }
    }

    if (validStudents.empty()) {
    cout << "错误:没有有效的成绩数据可分析!" << endl;
    return;
    }

    // 语文极值
    auto chiMin = min_element(validStudents.begin(), validStudents.end(),
    [](const Student& a, const Student& b) {
    return stof(a.chinese) < stof(b.chinese);
    });
    auto chiMax = max_element(validStudents.begin(), validStudents.end(),
    [](const Student& a, const Student& b) {
    return stof(a.chinese) < stof(b.chinese);
    });

    // 数学极值
    auto mathMin = min_element(validStudents.begin(), validStudents.end(),
    [](const Student& a, const Student& b) {
    return stof(a.math) < stof(b.math);
    });
    auto mathMax = max_element(validStudents.begin(), validStudents.end(),
    [](const Student& a, const Student& b) {
    return stof(a.math) < stof(b.math);
    });

    // 英语极值
    auto engMin = min_element(validStudents.begin(), validStudents.end(),
    [](const Student& a, const Student& b) {
    return stof(a.english) < stof(b.english);
    });
    auto engMax = max_element(validStudents.begin(), validStudents.end(),
    [](const Student& a, const Student& b) {
    return stof(a.english) < stof(b.english);
    });

    // 总分极值
    auto totalMin = min_element(validStudents.begin(), validStudents.end(),
    [](const Student& a, const Student& b) {
    return stof(a.total) < stof(b.total);
    });
    auto totalMax = max_element(validStudents.begin(), validStudents.end(),
    [](const Student& a, const Student& b) {
    return stof(a.total) < stof(b.total);
    });

    cout << fixed << setprecision(1) << "\n===================== 成绩极值 =====================" << endl;
    cout << "语文最低分:" << chiMin->chinese << " (学号:" << chiMin->id << ",姓名:" << chiMin->name << ")" << endl;
    cout << "语文最高分:" << chiMax->chinese << " (学号:" << chiMax->id << ",姓名:" << chiMax->name << ")" << endl;
    cout << "数学最低分:" << mathMin->math << " (学号:" << mathMin->id << ",姓名:" << mathMin->name << ")" << endl;
    cout << "数学最高分:" << mathMax->math << " (学号:" << mathMax->id << ",姓名:" << mathMax->name << ")" << endl;
    cout << "英语最低分:" << engMin->english << " (学号:" << engMin->id << ",姓名:" << engMin->name << ")" << endl;
    cout << "英语最高分:" << engMax->english << " (学号:" << engMax->id << ",姓名:" << engMax->name << ")" << endl;
    cout << "总分最低分:" << totalMin->total << " (学号:" << totalMin->id << ",姓名:" << totalMin->name << ")" << endl;
    cout << "总分最高分:" << totalMax->total << " (学号:" << totalMax->id << ",姓名:" << totalMax->name << ")" << endl;
    }
    };

    3.2.4. 用户交互实现

系统提供清晰的菜单界面,用户通过输入数字选择功能,包含输入验证机制:

// 显示主菜单
void displayMenu() {
cout << "\n==========================================================" << endl;
cout << " 学生信息管理系统 " << endl;
cout << "==========================================================" << endl;
cout << "1. 添加学生 2. 删除学生 3. 修改学生" << endl;
cout << "4. 查找学生 5. 显示所有 6. 按学号排序" << endl;
cout << "7. 按语文排序 8. 按数学排序 9. 按英语排序" << endl;
cout << "10. 按总分排序 11. 计算平均分 12. 查找众数" << endl;
cout << "13. 查找极值 14. 退出系统 " << endl;
cout << "==========================================================" << endl;
cout << "提示:数据存储在 " << CSV_FILENAME << ",支持Excel直接打开" << endl;
cout << "请选择功能(1-14):";
}


// 清空输入缓冲区
void clearInputBuffer() {
cin.clear();
cin.ignore(numeric_limits<streamsize>::max(), '\n');
}

int main() {
// 设置控制台输出为UTF-8(Windows系统)
#ifdef _WIN32
system("chcp 65001 > nul");
#endif

StudentManager manager;
cout << "正在加载CSV学生数据..." << endl;
manager.loadFromCSV(); // 从CSV加载

int choice = 0;
do {
displayMenu();

// 输入验证
if (!(cin >> choice)) {
clearInputBuffer();
cout << "\n❌ 错误:输入无效!请输入数字1-14。" << endl;
continue;
}

clearInputBuffer(); // 清空缓冲区中的多余字符

switch (choice) {
case 1: manager.addStudent(); break;
case 2: manager.deleteStudent(); break;
case 3: manager.modifyStudent(); break;
case 4: manager.searchStudent(); break;
case 5: manager.displayAll(); break;
case 6: manager.sortById(); break;
case 7: manager.sortByChinese(); break;
case 8: manager.sortByMath(); break;
case 9: manager.sortByEnglish(); break;
case 10: manager.sortByTotal(); break;
case 11: manager.calculateAverage(); break;
case 12: manager.findMode(); break;
case 13: manager.findExtremeValues(); break;
case 14:
cout << "\n✅ 感谢使用学生信息管理系统!数据已保存到 " << CSV_FILENAME << endl;
break;
default:
cout << "\n❌ 错误:选择无效!请输入1-14之间的数字。" << endl;
}

// 每次操作后暂停,方便用户查看结果
if (choice != 14) {
cout << "\n按Enter键继续...";
cin.get();
}

} while (choice != 14);
system("pause");
return 0;
}

4. 系统测试

4.1. 功能测试

测试用例 预期结果 测试结果
添加学号重复的学生 提示 “学号已存在”,添加失败 符合预期
添加成绩为 101 的学生 提示 “成绩必须是 0-100 之间的数字”,添加失败 符合预期
按学号查询不存在的学生 提示 “未找到该学生” 符合预期
修改学生姓名后保存 CSV 文件中对应学生姓名更新 符合预期
按总分排序后显示 学生按总分从高到低排列 符合预期
计算语文成绩平均分 正确输出所有有效语文成绩的均值 符合预期
查找数学成绩众数 正确输出出现频率最高的数学成绩 符合预期

4.2. 运行截图

4.2.1. 显示所有学生信息

image.png

4.2.2. 计算平均分

image.png

4.2.3. 插入学生

image.png

4.2.4. 排序功能

image.png

4.3. 边界测试

测试用例 预期结果 测试结果
系统无学生数据时执行删除操作 提示 “无学生数据” 符合预期
字段包含逗号(如姓名 “张,三”) 正确存储和读取该字段 符合预期
成绩为 0 或 100 正常添加和统计 符合预期
批量添加 100 条学生数据 数据正常存储,排序和查询功能正常 符合预期

4.4. 用户体验测试

  • 输入错误时,系统给出明确提示并引导重新输入,容错性良好;
  • 每次操作后自动保存数据,无需手动干预;
  • 菜单界面清晰,操作流程简单,无需专业知识即可使用;
  • CSV 文件可直接用 Excel 打开编辑,兼容性良好。

    5. 总结与展望

    5.1. 课程设计总结

    本课程设计基于 C++ 语言实现了一款功能完善的学生信息管理系统,完成了学生信息的增删改查、排序、统计分析及 CSV 文件存储等核心功能。系统采用面向对象设计思想,结构清晰,代码复用性高;通过严格的输入验证和特殊字符处理,提升了系统的稳定性和可靠性;友好的用户交互界面降低了使用门槛,满足了小型教育机构或班级的学生管理需求。
    在开发过程中,重点解决了以下问题:
  1. CSV 文件中特殊字符的处理,确保数据读写的准确性;
  2. 多样化排序和统计分析功能的实现,满足不同管理需求;
  3. 输入验证机制的设计,提升系统容错性;
  4. 数据持久化的实现,保证数据不丢失。
    同时,也认识到系统存在的不足:
  5. 未实现用户权限管理,所有用户拥有相同操作权限;
  6. 数据查询功能仅支持按学号和姓名查询,不支持多条件组合查询;
  7. 未提供数据备份和恢复功能;
  8. 界面为控制台界面,视觉体验有待提升。

5.2. 未来展望

为进一步完善系统功能,提升用户体验,未来可进行以下改进:

  1. 增加用户权限管理模块,区分管理员和普通用户,设置不同操作权限;
  2. 扩展查询功能,支持按学院、专业、成绩区间等多条件组合查询;
  3. 增加数据备份和恢复功能,支持手动备份和自动定时备份;
  4. 采用 Qt 或 MFC 等框架开发图形化用户界面(GUI),提升视觉体验和操作便捷性;
  5. 引入数据库(如 MySQL、SQLite)替代 CSV 文件存储,支持更大规模数据管理和更复杂的查询操作;
  6. 增加成绩图表分析功能(如柱状图、折线图),直观展示成绩分布情况。
    通过本次课程设计,不仅巩固了 C++ 语言基础、面向对象编程思想、STL 容器使用等专业知识,还提升了问题分析与解决能力、代码编写与调试能力。该学生信息管理系统虽存在一定不足,但基本满足小型场景的使用需求,具有一定的实用价值。

6. 代码

#include <iostream>
#include <fstream>
#include <vector>
#include <string>
#include <algorithm>
#include <cctype>
#include <iomanip>
#include <limits>
#include <map>
#include <numeric>
#include <stdexcept>
using namespace std;

// 表头定义(学号、姓名、学院、专业、语文、数学、英语、总分)
const vector<string> HEADERS = { "学号", "姓名", "学院", "专业", "语文", "数学", "英语", "总分" };
const string CSV_FILENAME = "student_info.csv"; // 标准CSV文件名

class Student {
public:
string id;
string name;
string college;
string major;
string chinese;
string math;
string english;
string total;

Student(string i = "", string n = "", string c = "", string m = "",
string chi = "", string ma = "", string eng = "", string tot = "")
: id(i), name(n), college(c), major(m),
chinese(chi), math(ma), english(eng), total(tot) {
}
};

class StudentManager {
private:
vector<Student> students;

// 检查字符串是否为数字(支持整数和小数)
bool isNumeric(const string& str) {
if (str.empty()) return false;
int dotCount = 0;
for (char c : str) {
if (c == '.') {
dotCount++;
if (dotCount > 1) return false; // 只能有一个小数点
}
else if (!isdigit(c)) {
return false;
}
}
return true;
}

// 显示单个学生信息
void displayStudent(const Student& s) {
cout << setiosflags(ios::left) << setw(10) << s.id
<< setiosflags(ios::left)<< setw(20) << s.name
<< setiosflags(ios::left)<< setw(30) << s.college
<< setiosflags(ios::left)<< setw(30) << s.major
<< setiosflags(ios::left)<< setw(10) << fixed << setprecision(1) << stof(s.chinese)
<< setiosflags(ios::left)<< setw(10) << fixed << setprecision(1) << stof(s.math)
<< setiosflags(ios::left)<< setw(10) << fixed << setprecision(1) << stof(s.english)
<< setiosflags(ios::left)<< setw(10) << fixed << setprecision(1) << stof(s.total) << endl;
}

// 根据学号查找学生索引
int findById(const string& id) {
for (int i = 0; i < students.size(); i++) {
if (students[i].id == id) return i;
}
return -1;
}

// 根据姓名查找学生索引(找到第一个匹配的)
int findByName(const string& name) {
for (int i = 0; i < students.size(); i++) {
if (students[i].name == name) return i;
}
return -1;
}

// CSV特殊字符转义(处理包含逗号、引号的字段)
string escapeCSVField(const string& field) {
if (field.find(',') != string::npos || field.find('"') != string::npos) {
string escaped = field;
// 替换双引号为两个双引号(CSV标准转义)
replace(escaped.begin(), escaped.end(), '"', '"');
return "\"" + escaped + "\""; // 用双引号包裹字段
}
return field;
}

// 解析CSV字段(处理带引号的字段)
string unescapeCSVField(const string& field) {
if (field.size() >= 2 && field.front() == '"' && field.back() == '"') {
string unescaped = field.substr(1, field.size() - 2);
// 还原双引号(将两个双引号替换为一个)
size_t pos = 0;
while ((pos = unescaped.find("\"\"", pos)) != string::npos) {
unescaped.replace(pos, 2, "\"");
pos += 1;
}
return unescaped;
}
return field;
}

// 从CSV行解析字段(支持带引号的字段)
vector<string> parseCSVLine(const string& line) {
vector<string> fields;
string currentField;
bool inQuotes = false;

for (char c : line) {
if (c == '"') {
inQuotes = !inQuotes;
}
else if (c == ',' && !inQuotes) {
// 遇到逗号且不在引号中,字段结束
fields.push_back(unescapeCSVField(currentField));
currentField.clear();
}
else {
currentField += c;
}
}
// 添加最后一个字段
fields.push_back(unescapeCSVField(currentField));
return fields;
}

// 自动保存到CSV文件(标准CSV格式)
void autoSaveToCSV() {
ofstream file(CSV_FILENAME, ios::trunc);
if (!file.is_open()) {
cout << "CSV文件保存失败!" << endl;
return;
}

// 写入CSV表头(转义特殊字符)
for (int i = 0; i < HEADERS.size(); i++) {
file << escapeCSVField(HEADERS[i]);
if (i < HEADERS.size() - 1) file << ",";
}
file << "\n"; // CSV标准换行符

// 写入学生数据(每个字段都转义)
for (const auto& s : students) {
file << escapeCSVField(s.id) << ","
<< escapeCSVField(s.name) << ","
<< escapeCSVField(s.college) << ","
<< escapeCSVField(s.major) << ","
<< escapeCSVField(s.chinese) << ","
<< escapeCSVField(s.math) << ","
<< escapeCSVField(s.english) << ","
<< escapeCSVField(s.total) << "\n";
}
file.close();
}

// 验证成绩合法性
bool validateScore(const string& score, const string& subject) {
if (!isNumeric(score)) {
cout << "错误:" << subject << "成绩必须是数字!请重新输入。" << endl;
return false;
}
try {
float scoreValue = stof(score);
if (scoreValue < 0 || scoreValue > 100) {
cout << "错误:" << subject << "成绩必须在0-100之间!请重新输入。" << endl;
return false;
}
}
catch (...) {
cout << "错误:" << subject << "成绩格式错误!请重新输入。" << endl;
return false;
}
return true;
}

// 更新总分
void updateTotal(Student& s) {
try {
float chi = stof(s.chinese);
float math = stof(s.math);
float eng = stof(s.english);
float total = chi + math + eng;
s.total = to_string(total);
}
catch (...) {
s.total = "0.0";
}
}

public:
// 从CSV文件加载数据
void loadFromCSV() {
ifstream file(CSV_FILENAME);
if (!file.is_open()) {
cout << "未找到CSV数据文件,将创建新文件。" << endl;
return;
}

string line;
getline(file, line); // 跳过CSV表头

while (getline(file, line)) {
if (line.empty()) continue;

// 解析CSV行
vector<string> tokens = parseCSVLine(line);

if (tokens.size() >= 8) {
Student s;
s.id = tokens[0];
s.name = tokens[1];
s.college = tokens[2];
s.major = tokens[3];
s.chinese = tokens[4];
s.math = tokens[5];
s.english = tokens[6];
s.total = tokens[7];

// 验证加载的成绩是否合法,不合法则设为0
if (!validateScore(s.chinese, "语文")) s.chinese = "0.0";
if (!validateScore(s.math, "数学")) s.math = "0.0";
if (!validateScore(s.english, "英语")) s.english = "0.0";
updateTotal(s);
students.push_back(s);
}
}
file.close();
cout << "CSV数据加载完成,共 " << students.size() << " 名学生。" << endl;
}

// 添加学生(保存到CSV)
void addStudent() {
Student s;
cout << "\n===================== 添加学生 =====================" << endl;

// 学号验证(唯一)
while (true) {
cout << "请输入学号:";
cin >> s.id;
if (findById(s.id) != -1) {
cout << "错误:该学号已存在!请重新输入。" << endl;
}
else if (s.id.empty()) {
cout << "错误:学号不能为空!请重新输入。" << endl;
}
else {
break;
}
}

// 姓名
cout << "请输入姓名:";
cin.ignore();
getline(cin, s.name);
while (s.name.empty()) {
cout << "错误:姓名不能为空!请重新输入:";
getline(cin, s.name);
}

// 学院
cout << "请输入学院:";
getline(cin, s.college);
while (s.college.empty()) {
cout << "错误:学院不能为空!请重新输入:";
getline(cin, s.college);
}

// 专业
cout << "请输入专业:";
getline(cin, s.major);
while (s.major.empty()) {
cout << "错误:专业不能为空!请重新输入:";
getline(cin, s.major);
}

// 成绩输入与验证
while (true) {
cout << "请输入语文成绩:";
cin >> s.chinese;
if (validateScore(s.chinese, "语文")) break;
}

while (true) {
cout << "请输入数学成绩:";
cin >> s.math;
if (validateScore(s.math, "数学")) break;
}

while (true) {
cout << "请输入英语成绩:";
cin >> s.english;
if (validateScore(s.english, "英语")) break;
}

// 计算总分
updateTotal(s);

students.push_back(s);
autoSaveToCSV(); // 保存到CSV
cout << "\n✅ 学生添加成功!数据已保存到 " << CSV_FILENAME << endl;
}

// 删除学生(保存到CSV)
void deleteStudent() {
cout << "\n===================== 删除学生 =====================" << endl;
if (students.empty()) {
cout << "提示:当前没有学生数据!" << endl;
return;
}

string input;
cout << "请输入学号或姓名:";
cin >> input;

int index = isNumeric(input) ? findById(input) : findByName(input);

if (index == -1) {
cout << "错误:未找到该学生!" << endl;
return;
}

cout << "找到学生:" << endl;
displayHeader();
displayStudent(students[index]);

char confirm;
cout << "确定要删除吗?(y/n):";
cin >> confirm;

if (confirm == 'y' || confirm == 'Y') {
students.erase(students.begin() + index);
autoSaveToCSV(); // 保存到CSV
cout << "\n✅ 学生删除成功!数据已保存到 " << CSV_FILENAME << endl;
}
else {
cout << "❌ 删除操作已取消" << endl;
}
}

// 修改学生信息(保存到CSV)
void modifyStudent() {
cout << "\n===================== 修改学生 =====================" << endl;
if (students.empty()) {
cout << "提示:当前没有学生数据!" << endl;
return;
}

string input;
cout << "请输入学号或姓名:";
cin >> input;

int index = isNumeric(input) ? findById(input) : findByName(input);

if (index == -1) {
cout << "错误:未找到该学生!" << endl;
return;
}

cout << "当前学生信息:" << endl;
displayHeader();
displayStudent(students[index]);

cout << "\n请选择要修改的项目:" << endl;
cout << "1. 学院" << endl;
cout << "2. 专业" << endl;
cout << "3. 语文成绩" << endl;
cout << "4. 数学成绩" << endl;
cout << "5. 英语成绩" << endl;
cout << "6. 取消修改" << endl;
cout << "请选择(1-6):";

int choice;
while (!(cin >> choice) || choice < 1 || choice > 6) {
cin.clear();
cin.ignore(numeric_limits<streamsize>::max(), '\n');
cout << "错误:输入无效!请重新选择(1-6):";
}

cin.ignore(); // 清空缓冲区
switch (choice) {
case 1:
cout << "请输入新的学院:";
getline(cin, students[index].college);
while (students[index].college.empty()) {
cout << "错误:学院不能为空!请重新输入:";
getline(cin, students[index].college);
}
break;
case 2:
cout << "请输入新的专业:";
getline(cin, students[index].major);
while (students[index].major.empty()) {
cout << "错误:专业不能为空!请重新输入:";
getline(cin, students[index].major);
}
break;
case 3:
while (true) {
cout << "请输入新的语文成绩:";
cin >> students[index].chinese;
if (validateScore(students[index].chinese, "语文")) {
updateTotal(students[index]);
break;
}
}
break;
case 4:
while (true) {
cout << "请输入新的数学成绩:";
cin >> students[index].math;
if (validateScore(students[index].math, "数学")) {
updateTotal(students[index]);
break;
}
}
break;
case 5:
while (true) {
cout << "请输入新的英语成绩:";
cin >> students[index].english;
if (validateScore(students[index].english, "英语")) {
updateTotal(students[index]);
break;
}
}
break;
case 6:
cout << "❌ 修改操作已取消" << endl;
return;
}

autoSaveToCSV(); // 保存到CSV
cout << "\n✅ 学生信息修改成功!数据已保存到 " << CSV_FILENAME << endl;
cout << "修改后的信息:" << endl;
displayHeader();
displayStudent(students[index]);
}

// 查找学生
void searchStudent() {
cout << "\n===================== 查找学生 =====================" << endl;
if (students.empty()) {
cout << "提示:当前没有学生数据!" << endl;
return;
}

string input;
cout << "请输入学号或姓名:";
cin >> input;

int index = isNumeric(input) ? findById(input) : findByName(input);

if (index == -1) {
cout << "错误:未找到该学生!" << endl;
return;
}

cout << "✅ 找到学生:" << endl;
displayHeader();
displayStudent(students[index]);
}

// 显示所有学生
void displayAll() {
if (students.empty()) {
cout << "提示:当前没有学生数据!" << endl;
return;
}

cout << "\n===================== 所有学生信息 (" << students.size() << " 名) =====================" << endl;
displayHeader();
for (const auto& s : students) {
displayStudent(s);
}
cout << "==========================================================================" << endl;
}

// 按学号排序(升序)
void sortById() {
if (students.empty()) {
cout << "提示:当前没有学生数据可排序!" << endl;
return;
}

sort(students.begin(), students.end(), [](const Student& a, const Student& b) {
// 先按长度排序,再按字典序排序
if (a.id.size() != b.id.size())
return a.id.size() < b.id.size();
return a.id < b.id;
});
autoSaveToCSV(); // 保存到CSV
cout << "\n✅ 已按学号升序排序!数据已保存到 " << CSV_FILENAME << endl;
}

// 按语文成绩排序(降序)
void sortByChinese() {
if (students.empty()) {
cout << "提示:当前没有学生数据可排序!" << endl;
return;
}

sort(students.begin(), students.end(), [](const Student& a, const Student& b) {
try {
return stof(a.chinese) > stof(b.chinese);
}
catch (...) {
return false;
}
});
autoSaveToCSV(); // 保存到CSV
cout << "\n✅ 已按语文成绩降序排序!数据已保存到 " << CSV_FILENAME << endl;
}

// 按数学成绩排序(降序)
void sortByMath() {
if (students.empty()) {
cout << "提示:当前没有学生数据可排序!" << endl;
return;
}

sort(students.begin(), students.end(), [](const Student& a, const Student& b) {
try {
return stof(a.math) > stof(b.math);
}
catch (...) {
return false;
}
});
autoSaveToCSV(); // 保存到CSV
cout << "\n✅ 已按数学成绩降序排序!数据已保存到 " << CSV_FILENAME << endl;
}

// 按英语成绩排序(降序)
void sortByEnglish() {
if (students.empty()) {
cout << "提示:当前没有学生数据可排序!" << endl;
return;
}

sort(students.begin(), students.end(), [](const Student& a, const Student& b) {
try {
return stof(a.english) > stof(b.english);
}
catch (...) {
return false;
}
});
autoSaveToCSV(); // 保存到CSV
cout << "\n✅ 已按英语成绩降序排序!数据已保存到 " << CSV_FILENAME << endl;
}

// 按总分排序(降序)
void sortByTotal() {
if (students.empty()) {
cout << "提示:当前没有学生数据可排序!" << endl;
return;
}

sort(students.begin(), students.end(), [](const Student& a, const Student& b) {
try {
return stof(a.total) > stof(b.total);
}
catch (...) {
return false;
}
});
autoSaveToCSV(); // 保存到CSV
cout << "\n✅ 已按总分降序排序!数据已保存到 " << CSV_FILENAME << endl;
}

// 显示表头
void displayHeader() {
cout << setiosflags(ios::left) << setw(10) << HEADERS[0]
<< setiosflags(ios::left)<< setw(20) << HEADERS[1]
<< setiosflags(ios::left)<< setw(30) << HEADERS[2]
<< setiosflags(ios::left)<< setw(30) << HEADERS[3]
<< setiosflags(ios::left)<< setw(10) << HEADERS[4]
<< setiosflags(ios::left)<< setw(10) << HEADERS[5]
<< setiosflags(ios::left)<< setw(10) << HEADERS[6]
<< setiosflags(ios::left)<< setw(10) << HEADERS[7] << endl;
cout << string(105, '-') << endl; // 分隔线(105个减号)
}

// 计算平均分
void calculateAverage() {
if (students.empty()) {
cout << "提示:当前没有学生数据!" << endl;
return;
}

float chiTotal = 0.0f, mathTotal = 0.0f, engTotal = 0.0f, totalTotal = 0.0f;
int validCount = 0;

for (const auto& s : students) {
try {
chiTotal += stof(s.chinese);
mathTotal += stof(s.math);
engTotal += stof(s.english);
totalTotal += stof(s.total);
validCount++;
}
catch (...) {
continue;
}
}

if (validCount == 0) {
cout << "错误:没有有效的成绩数据可计算!" << endl;
return;
}

cout << fixed << setprecision(2) << "\n===================== 成绩平均分 =====================" << endl;
cout << "语文平均分:" << chiTotal / validCount << endl;
cout << "数学平均分:" << mathTotal / validCount << endl;
cout << "英语平均分:" << engTotal / validCount << endl;
cout << "总分平均分:" << totalTotal / validCount << endl;
}

// 查找众数(出现次数最多的成绩)
void findMode() {
if (students.empty()) {
cout << "提示:当前没有学生数据!" << endl;
return;
}

// 辅助函数:查找map中的众数
auto findMapMode = [](const map<float, int>& freqMap) -> pair<float, int> {
if (freqMap.empty()) return { 0.0f, 0 };
auto modeIt = max_element(freqMap.begin(), freqMap.end(),
[](const pair<float, int>& a, const pair<float, int>& b) {
return a.second < b.second;
});
return *modeIt;
};

// 语文成绩众数
map<float, int> chiFreq;
for (const auto& s : students) {
try {
chiFreq[stof(s.chinese)]++;
}
catch (...) { continue; }
}
auto chiMode = findMapMode(chiFreq);

// 数学成绩众数
map<float, int> mathFreq;
for (const auto& s : students) {
try {
mathFreq[stof(s.math)]++;
}
catch (...) { continue; }
}
auto mathMode = findMapMode(mathFreq);

// 英语成绩众数
map<float, int> engFreq;
for (const auto& s : students) {
try {
engFreq[stof(s.english)]++;
}
catch (...) { continue; }
}
auto engMode = findMapMode(engFreq);

// 总分众数
map<float, int> totalFreq;
for (const auto& s : students) {
try {
totalFreq[stof(s.total)]++;
}
catch (...) { continue; }
}
auto totalMode = findMapMode(totalFreq);

cout << fixed << setprecision(1) << "\n===================== 成绩众数 =====================" << endl;
cout << "语文众数:" << chiMode.first << " (出现次数:" << chiMode.second << ")" << endl;
cout << "数学众数:" << mathMode.first << " (出现次数:" << mathMode.second << ")" << endl;
cout << "英语众数:" << engMode.first << " (出现次数:" << engMode.second << ")" << endl;
cout << "总分众数:" << totalMode.first << " (出现次数:" << totalMode.second << ")" << endl;
}

// 查找极值(最高分和最低分)
void findExtremeValues() {
if (students.empty()) {
cout << "提示:当前没有学生数据!" << endl;
return;
}

// 过滤掉成绩无效的学生
vector<Student> validStudents;
for (const auto& s : students) {
try {
stof(s.chinese);
stof(s.math);
stof(s.english);
stof(s.total);
validStudents.push_back(s);
}
catch (...) {
continue;
}
}

if (validStudents.empty()) {
cout << "错误:没有有效的成绩数据可分析!" << endl;
return;
}

// 语文极值
auto chiMin = min_element(validStudents.begin(), validStudents.end(),
[](const Student& a, const Student& b) {
return stof(a.chinese) < stof(b.chinese);
});
auto chiMax = max_element(validStudents.begin(), validStudents.end(),
[](const Student& a, const Student& b) {
return stof(a.chinese) < stof(b.chinese);
});

// 数学极值
auto mathMin = min_element(validStudents.begin(), validStudents.end(),
[](const Student& a, const Student& b) {
return stof(a.math) < stof(b.math);
});
auto mathMax = max_element(validStudents.begin(), validStudents.end(),
[](const Student& a, const Student& b) {
return stof(a.math) < stof(b.math);
});

// 英语极值
auto engMin = min_element(validStudents.begin(), validStudents.end(),
[](const Student& a, const Student& b) {
return stof(a.english) < stof(b.english);
});
auto engMax = max_element(validStudents.begin(), validStudents.end(),
[](const Student& a, const Student& b) {
return stof(a.english) < stof(b.english);
});

// 总分极值
auto totalMin = min_element(validStudents.begin(), validStudents.end(),
[](const Student& a, const Student& b) {
return stof(a.total) < stof(b.total);
});
auto totalMax = max_element(validStudents.begin(), validStudents.end(),
[](const Student& a, const Student& b) {
return stof(a.total) < stof(b.total);
});

cout << fixed << setprecision(1) << "\n===================== 成绩极值 =====================" << endl;
cout << "语文最低分:" << chiMin->chinese << " (学号:" << chiMin->id << ",姓名:" << chiMin->name << ")" << endl;
cout << "语文最高分:" << chiMax->chinese << " (学号:" << chiMax->id << ",姓名:" << chiMax->name << ")" << endl;
cout << "数学最低分:" << mathMin->math << " (学号:" << mathMin->id << ",姓名:" << mathMin->name << ")" << endl;
cout << "数学最高分:" << mathMax->math << " (学号:" << mathMax->id << ",姓名:" << mathMax->name << ")" << endl;
cout << "英语最低分:" << engMin->english << " (学号:" << engMin->id << ",姓名:" << engMin->name << ")" << endl;
cout << "英语最高分:" << engMax->english << " (学号:" << engMax->id << ",姓名:" << engMax->name << ")" << endl;
cout << "总分最低分:" << totalMin->total << " (学号:" << totalMin->id << ",姓名:" << totalMin->name << ")" << endl;
cout << "总分最高分:" << totalMax->total << " (学号:" << totalMax->id << ",姓名:" << totalMax->name << ")" << endl;
}
};

// 显示主菜单
void displayMenu() {
cout << "\n==========================================================" << endl;
cout << " 学生信息管理系统 " << endl;
cout << "==========================================================" << endl;
cout << "1. 添加学生 2. 删除学生 3. 修改学生" << endl;
cout << "4. 查找学生 5. 显示所有 6. 按学号排序" << endl;
cout << "7. 按语文排序 8. 按数学排序 9. 按英语排序" << endl;
cout << "10. 按总分排序 11. 计算平均分 12. 查找众数" << endl;
cout << "13. 查找极值 14. 退出系统 " << endl;
cout << "==========================================================" << endl;
cout << "提示:数据存储在 " << CSV_FILENAME << ",支持Excel直接打开" << endl;
cout << "请选择功能(1-14):";
}


// 清空输入缓冲区
void clearInputBuffer() {
cin.clear();
cin.ignore(numeric_limits<streamsize>::max(), '\n');
}

int main() {
// 设置控制台输出为UTF-8(Windows系统)
#ifdef _WIN32
system("chcp 65001 > nul");
#endif

StudentManager manager;
cout << "正在加载CSV学生数据..." << endl;
manager.loadFromCSV(); // 从CSV加载

int choice = 0;
do {
displayMenu();

// 输入验证
if (!(cin >> choice)) {
clearInputBuffer();
cout << "\n❌ 错误:输入无效!请输入数字1-14。" << endl;
continue;
}

clearInputBuffer(); // 清空缓冲区中的多余字符

switch (choice) {
case 1: manager.addStudent(); break;
case 2: manager.deleteStudent(); break;
case 3: manager.modifyStudent(); break;
case 4: manager.searchStudent(); break;
case 5: manager.displayAll(); break;
case 6: manager.sortById(); break;
case 7: manager.sortByChinese(); break;
case 8: manager.sortByMath(); break;
case 9: manager.sortByEnglish(); break;
case 10: manager.sortByTotal(); break;
case 11: manager.calculateAverage(); break;
case 12: manager.findMode(); break;
case 13: manager.findExtremeValues(); break;
case 14:
cout << "\n✅ 感谢使用学生信息管理系统!数据已保存到 " << CSV_FILENAME << endl;
break;
default:
cout << "\n❌ 错误:选择无效!请输入1-14之间的数字。" << endl;
}

// 每次操作后暂停,方便用户查看结果
if (choice != 14) {
cout << "\n按Enter键继续...";
cin.get();
}

} while (choice != 14);
system("pause");
return 0;
}