这个学生管理系统涵盖了 C++ 的多个核心知识点:
1. 面向对象编程 (OOP)
• 类与对象:Student 和 StudentManager 类
• 封装:私有成员变量和公共接口
• 构造函数:默认构造和参数化构造
• 成员函数:getter/setter 方法
• 操作符重载:operator< 用于排序
2. 标准模板库 (STL)
• 容器:vector 存储学生对象
• 算法:sort 排序算法
• 迭代器:遍历容器元素
• 字符串:string 操作
3. 输入输出系统
• 标准 I/O:cin, cout, cerr
• 文件 I/O:ifstream, ofstream
• 格式化输出:<iomanip> 中的 setw, left, fixed, setprecision
4. 内存管理
• 智能指针:未显式使用,但通过 vector 自动管理
• 引用:const auto& 避免复制
• 指针:查找学生返回指针
5. 异常处理
• 输入验证:cin.fail() 检查
• 流状态恢复:cin.clear(), cin.ignore()
• 基本异常处理:try-catch 在加载函数中
6. 函数与 Lambda
• 成员函数:各种管理功能
• Lambda 表达式:自定义排序规则
• 函数封装:独立功能模块化
7. 类型转换
• stoi:字符串转整数
• stod:字符串转双精度浮点数
8. 字符串处理
• 查找:find(), find(',', pos)
• 子串:substr()
• 空值检查:npos
9. 控制结构
• 循环:while, for (范围 for)
• 条件:if-else, switch-case
• 跳转:break, continue, return
10. 其他特性
• 命名空间:using namespace std
• 常量:const 成员函数和参数
• 头文件:合理包含标准库头文件
源代码:
//提供基本的输入输出功能
#include <iostream>
//提供动态数组容器
#include <vector>
//提供文件操作功能
#include <fstream>
//包含各种算法函数
#include <algorithm>
//<iomanip> 是 C++ 标准库中的一个重要头文件,
// 全称是 input/output manipulators(输入/输出操纵器)。
// 它提供了一系列用于格式化输入输出的工具函数,
// 主要配合 iostream 库中的 cin 和 cout 等流对象使用。
// 用于格式化输出
#include <iomanip>
// 用于输入验证
#include <limits>
//提供字符串操作功能
#include <string>
//可以直接使用cout而不必写std::cout
using namespace std;
// ==================== 学生类 ====================
class Student {
private:
string name;
int id;
double score;
public:
// 构造函数(默认构造函数、带参数构造函数)
// 默认构造函数 :开始成员初始化列表
Student() : name(""), id(0), score(0.0) {}
// 参数化构造函数
Student(string n, int i, double s)
: name(n), id(i), score(s) {
}
// Getter方法(封装思想)
// 常量成员函数
string getName() const { return name; }
int getID() const { return id; }
double getScore() const { return score; }
// Setter方法(封装思想)
void setName(string n) { name = n; }
void setID(int i) { id = i; }
void setScore(double s) { score = s; }
// 显示学生信息
void display() const {
// 格式化输出
//std::left: 设置输出左对齐
//std::setw() : 设置下一个字段的输出宽度
cout << left << setw(10) << id
<< left << setw(20) << name
//endl 插入换行符并刷新输出缓冲区
<< left << setw(10) << score << endl;
}
// 操作符重载 - 用于排序
// 按成绩升序排序
bool operator<(const Student& other) const {
return score < other.score;
}
};
// ==================== 学生管理系统类 ====================
class StudentManager {
//访问修饰符(private, public, protected)后面必须跟冒号
private:
// 使用vector容器存储学生对象
vector<Student> students;
public:
// ======== 核心功能 ========
// 添加学生(参数验证)
void addStudent() {
string name;
int id;
double score;
cout << "\n=== 添加学生 ===" << endl;
// 输入姓名
cout << "请输入学生姓名: ";
// 清除输入缓冲区
cin.ignore();
/*
getline(cin, name); 是:
读取整行文本的标准方法
特别适合需要包含空格的输入
正确处理换行符和输入缓冲区
在学生管理系统中用于读取完整学生姓名
需要与 cin.ignore() 配合使用以避免输入问题
*/
getline(cin, name);
// 输入ID(验证唯一性)
bool idExists;
do {
idExists = false;
cout << "请输入学生ID: ";
//cin >> id;
// 检查输入是否为有效整数
if (!(cin >> id)) {
// 清除错误状态
cin.clear();
// 忽略无效输入
cin.ignore(numeric_limits<streamsize>::max(), '\n');
cout << "错误:请输入有效的数字ID!" << endl;
return; // 重新开始循环
}
// 检查ID是否已存在
/*
auto:自动类型推导(编译器推断为 Student)
&:引用(避免复制对象)
const:保证不修改对象
*/
for (const auto& student : students) {
if (student.getID() == id) {
cout << "错误:该ID已存在!请重新输入。" << endl;
idExists = true;
break;
}
}
} while (idExists);
// 输入成绩(验证范围)
do {
cout << "请输入学生成绩 (0-100): ";
//cin >> score;
if (!(cin >> score)) {
// 清除错误状态
cin.clear();
// 忽略无效输入
cin.ignore(numeric_limits<streamsize>::max(), '\n');
cout << "错误:请输入有效的数字ID!" << endl;
return; // 重新开始循环
}
if (score < 0 || score > 100) {
cout << "错误:成绩必须在0-100之间!" << endl;
}
} while (score < 0 || score > 100);
// 创建学生对象并添加到vector
//将新创建的学生对象添加到 vector 的末尾
students.push_back(Student(name, id, score));
cout << "学生添加成功!" << endl;
}
// 显示所有学生
void displayAll() const {
if (students.empty()) {
cout << "\n没有学生记录!" << endl;
return;
}
cout << "\n=== 所有学生 ===" << endl;
cout << left << setw(10) << "ID"
<< left << setw(20) << "姓名"
<< left << setw(10) << "成绩" << endl;
//输出一行由40个连字符(-)组成的字符串,然后换行
cout << string(40, '-') << endl;
for (const auto& student : students) {
student.display(); // 调用对象方法
}
}
// 根据ID查找学生(使用const引用提高效率)
Student* findStudent(int id) {
for (auto& student : students) {
if (student.getID() == id) {
return &student; // 返回指针
}
}
return nullptr; // 未找到返回空指针
}
// 更新学生信息
void updateStudent() {
int id;
cout << "\n请输入要更新的学生ID: ";
/* cin >> id;*/
if (!(cin >> id)) {
// 清除错误状态
cin.clear();
// 忽略无效输入
cin.ignore(numeric_limits<streamsize>::max(), '\n');
cout << "错误:请输入有效的数字ID!" << endl;
return;
}
Student* student = findStudent(id);
//!student 在 student 为空时成立
if (!student) {
cout << "找不到该学生!" << endl;
return;
}
string name;
double score;
cout << "请输入新的姓名: ";
cin.ignore();
getline(cin, name);
do {
cout << "请输入新的成绩 (0-100): ";
//cin >> score;
if (!(cin >> score)) {
// 清除错误状态
cin.clear();
// 忽略无效输入
cin.ignore(numeric_limits<streamsize>::max(), '\n');
cout << "错误:请输入有效的数字ID!" << endl;
return;
}
if (score < 0 || score > 100) {
cout << "错误:成绩必须在0-100之间!" << endl;
}
} while (score < 0 || score > 100);
//通过指针调用成员函数
//等价于 (*student).setName(name)
student->setName(name);
//通过指针调用成员函数
//等价于 (*student).setScore(score)
student->setScore(score);
cout << "学生信息更新成功!" << endl;
}
// 删除学生(使用迭代器)
void deleteStudent() {
int id;
cout << "\n请输入要删除的学生ID: ";
cin >> id;
//返回指向容器第一个元素的迭代器
//等价于 指向第一个学生对象的指针
auto it = students.begin();
while (it != students.end()) {
if (it->getID() == id) {
// 删除元素并返回下一个位置
it = students.erase(it);
cout << "学生删除成功!" << endl;
return;
}
else {
++it;
}
}
cout << "找不到该学生!" << endl;
}
// ======== 扩展功能 ========
// 按成绩排序(使用STL算法和lambda表达式)
void sortStudents() {
if (students.empty()) {
cout << "\n没有学生记录!" << endl;
return;
}
// 使用sort算法和自定义排序规则
/* sort(students.begin(), students.end());*/
// 使用lambda表达式反转排序(降序)
//sort(students.begin(), students.end(), ...);
/* []:捕获列表(空,表示不捕获外部变量)
(const Student & a, const Student & b):参数列表
{
...
}:函数体
无显式返回类型声明(编译器自动推导为 bool)*/
sort(students.begin(), students.end(),
[](const Student& a, const Student& b) {
return a.getScore() > b.getScore(); // 降序排序
});
cout << "按成绩降序排序完成!" << endl;
}
// 计算平均分
void calculateAverage() const {
if (students.empty()) {
cout << "\n没有学生记录!" << endl;
return;
}
double total = 0.0;
for (const auto& student : students) {
total += student.getScore();
}
// 设置输出精度
/* 设置后所有后续浮点数输出:
固定小数格式:不使用科学计数法
两位小数:即使整数也显示两位小数(如 85.00)*/
cout << fixed << setprecision(2);
cout << "\n班级平均分: " << total / students.size() << endl;
}
// ======== 文件操作 ========
// 保存数据到CSV文件(文件I/O)
void saveToFile(const string& filename) {
//创建输出文件流
ofstream outFile(filename);
//检查文件是否成功打开
if (!outFile) {
cerr << "无法打开文件进行写入!" << endl;
return;
}
// 写入CSV表头
outFile << "ID,姓名,成绩\n";
// 写入每个学生数据
for (const auto& student : students) {
outFile << student.getID() << ","
<< student.getName() << ","
<< student.getScore() << "\n";
}
//关闭文件
outFile.close();
cout << "\n数据已保存到 " << filename << endl;
}
// 从CSV文件加载数据(文件I/O)
void loadFromFile(const string& filename) {
//创建输入文件流
ifstream inFile(filename);
// 检查文件是否成功打开
if (!inFile) {
cerr << "错误:无法打开文件 '" << filename << "' 进行读取!" << endl;
return;
}
// 清空当前学生记录
students.clear();
string line;
int lineNumber = 0;
int successCount = 0;
int errorCount = 0;
// 跳过CSV文件头行
if (getline(inFile, line)) {
lineNumber++;
// 验证标题行格式
if (line != "ID,姓名,成绩") {
cerr << "警告:文件标题行不匹配,可能不是标准格式" << endl;
}
}
// 逐行读取文件内容
while (getline(inFile, line)) {
lineNumber++;
// 跳过空行
if (line.empty()) {
continue;
}
//解析CSV行
//查找第一个逗号
//在字符串 line 中查找第一个逗号 ','
//从位置 0(开头)开始搜索
size_t pos1 = line.find(',');
//从第一个逗号后查找第二个逗号
//从位置 pos1 + 1 开始搜索(跳过第一个逗号)
size_t pos2 = line.find(',', pos1 + 1);
// 验证找到两个逗号
if (pos1 == string::npos) {
//throw runtime_error("未找到第一个逗号分隔符");
//cerr << "未找到第一个逗号分隔符" << endl;
cerr << "警告:行 " << lineNumber << " 未找到第一个逗号分隔符" << endl;
return;
}
if (pos2 == string::npos) {
//throw runtime_error("未找到第二个逗号分隔符");
//cerr << "未找到第二个逗号分隔符" << endl;
cerr << "警告:行 " << lineNumber << " 未找到第二个逗号分隔符" << endl;
return;
}
// 提取ID字段
string idStr = line.substr(0, pos1);
if (idStr.empty()) {
//throw runtime_error("ID字段为空");
cerr << "警告:行 " << lineNumber << " ID字段为空" << endl;
return;
}
// 验证找到两个逗号
/* pos1 != string::npos
字面意思:位置 pos1 不等于"未找到"值
实际含义:成功找到了逗号
等价于:find() 操作成功*/
if (pos1 != string::npos && pos2 != string::npos) {
// 提取ID
/*
line.substr(0, pos1)
作用:从字符串 line 中提取子字符串
参数:
0:起始位置(从索引0开始)
pos1:子字符串的长度(字符数)
返回值:一个新字符串,包含 line[0] 到 line[pos1 - 1] 的字符
示例:如果 line = "101,张三,85.5" 且 pos1 = 3,则返回 "101"
*/
//stoi 函数详解:字符串到整数的转换
//stoi 是 C++ 标准库中的一个函数,用于将字符串转换为整数。
//它的名称是 "string to integer" 的缩写,是 C++11 引入的一组字符串转换函数之一
int id = 0;
try {
id = stoi(idStr);
}
catch (const invalid_argument& e) {
cerr << "警告:行 " << lineNumber << " ID格式无效: " + idStr << endl;
return;
/*throw runtime_error("ID格式无效: " + idStr);*/
}
catch (const out_of_range& e) {
/*throw runtime_error("ID超出范围: " + idStr);*/
cerr << "警告:行 " << lineNumber << " ID超出范围: " + idStr << endl;
return;
}
// 验证ID
if (id <= 0) {
//throw runtime_error("ID必须是正整数: " + to_string(id));
cerr << "警告:行 " << lineNumber << " ID必须是正整数: " + to_string(id) << endl;
return;
}
// 提取姓名
// pos2 - pos1 - 1
//确保只获取姓名内容
//不包括分隔符逗号
string name = line.substr(pos1 + 1, pos2 - pos1 - 1);
if (name.empty()) {
cerr << "警告:行 " << lineNumber << " 姓名字段为空" << endl;
return;
}
// 提取成绩
//line.substr(pos2 + 1)
/* 作用:从字符串 line 中提取子字符串
参数:
pos2 + 1:起始位置(第二个逗号之后)
无长度参数:提取到字符串末尾
返回值:包含从 pos2 + 1 到行尾的所有字符
示例:如果 line = "101,张三,85.5" 且 pos2 = 6
返回 "85.5"
*/
double score =0;
try
{
score = stod(line.substr(pos2 + 1));
if (score < 0 || score > 100) {
cerr << "警告:行 " << lineNumber << " 成绩:"<<score <<" \n错误:成绩必须在0 - 100之间!" << endl;
return;
}
}
catch (const std::exception&)
{
cerr << "警告:行 " << lineNumber << " 成绩字段为空" << endl;
return;
}
// 创建学生对象并添加到容器
students.push_back(Student(name, id, score));
}
}
// 关闭文件
inFile.close();
cout << "\n从 " << filename << " 加载数据成功!" << endl;
}
};
// ==================== 用户界面 ====================
void displayMenu() {
cout << "\n===== 学生管理系统 =====" << endl;
cout << "1. 添加学生" << endl;
cout << "2. 显示所有学生" << endl;
cout << "3. 查找学生" << endl;
cout << "4. 更新学生信息" << endl;
cout << "5. 删除学生" << endl;
cout << "6. 按成绩排序(降序)" << endl;
cout << "7. 计算平均分" << endl;
cout << "8. 保存数据到文件" << endl;
cout << "9. 从文件加载数据" << endl;
cout << "0. 退出" << endl;
cout << "=======================" << endl;
cout << "请选择操作: ";
}
// ==================== 主函数 ====================
int main() {
StudentManager manager;
int choice;
const string FILENAME = "students.csv";
cout << "欢迎使用学生管理系统!" << endl;
// 尝试加载数据文件(如果存在)
manager.loadFromFile(FILENAME);
do {
while (true) {
displayMenu();
cin >> choice;
// 检查输入流是否处于失败状态
//输入数据类型不匹配(如期望整数但输入字母)
if (cin.fail()) {
// 清除错误标志
cin.clear();
//从缓冲区中移除所有字符,直到遇到换行符
//等价于 清空当前行的所有输入
cin.ignore(numeric_limits<streamsize>::max(), '\n'); // 忽略无效输入
cout << "无效输入,请重新选择!" << endl;
continue;
}
else {
//退出while循环
break;
}
}
switch (choice) {
case 1:
manager.addStudent();
break;
case 2:
manager.displayAll();
break;
case 3: {
int id;
cout << "请输入要查找的学生ID: ";
//cin >> id;
if (!(cin >> id)) {
// 清除错误状态
cin.clear();
// 忽略无效输入
cin.ignore(numeric_limits<streamsize>::max(), '\n');
cout << "错误:请输入有效的数字ID!" << endl;
break; // 重新开始循环
}
Student* student = manager.findStudent(id);
if (student) {
cout << "\n找到学生:" << endl;
cout << left << setw(10) << "ID"
<< left << setw(20) << "姓名"
<< left << setw(10) << "成绩" << endl;
cout << string(40, '-') << endl;
student->display();
}
else {
cout << "找不到该学生!" << endl;
}
break;
}
case 4:
manager.updateStudent();
break;
case 5:
manager.deleteStudent();
break;
case 6:
manager.sortStudents();
manager.displayAll();
break;
case 7:
manager.calculateAverage();
break;
case 8:
manager.saveToFile(FILENAME);
break;
case 9:
manager.loadFromFile(FILENAME);
break;
case 0:
cout << "感谢使用学生管理系统!" << endl;
break;
default:
cout << "无效选择,请重新输入!" << endl;
}
} while (choice != 0);
return 0;
}