☰
C++实现教师管理系统
2026/11/2 6:44:14 网站建设 项目流程

前言


教师管理系统是课程设计里的经典题目,需求通常包括:录入教师、按工号删除、修改工资、按工号查询、列出全部、统计平均工资、按工资排序。功能本身不难,难的是把「数据」和「操作」分开,以及把输入处理写稳——很多系统在正常输入下没问题,一遇到非数字、空行、重复工号就陷入死循环。


本文用std::vector存储教师记录,用标准算法完成查找、删除和排序,并给出一个可交互的菜单。相比手写链表,vector的连续内存更适合「遍历 + 排序」这类主要操作,也免去了手动管理节点的负担。示例以 C++17 为标准,只使用标准库,不依赖平台 API。


需要先说明:下面的代码没有在本机编译或运行过,输入输出行为是按标准库语义推演的。实际使用时请根据自己的编译器检查警告,并补充持久化(文件读写)等需求。


一、数据模型与职责划分


一条教师记录包含工号、姓名、职称、月薪。工号是唯一键,用int存储;姓名和职称用std::string;工资用double。真实系统里金额更适宜用「分」为单位的整数或定点类型,避免浮点误差,本文为演示从简。


管理类只暴露业务操作,不把内部容器交出去:




操作接口复杂度



添加add(...)查重 O(n),追加均摊 O(1)

删除remove(id)O(n)

修改工资updateSalary(id, salary)O(n)

查询findById(id)O(n)

遍历printAll()O(n)

平均工资averageSalary()O(n)

排序sortedBySalary()O(n log n)



findById返回const Teacher*,找不到返回nullptr。返回指针而不是引用,是因为「可能不存在」需要用空值表达;调用方在使用前必须检查。若想避免裸指针,也可以返回std::optional<Teacher>,但那会复制一份记录。


sortedBySalary返回排序后的副本,不修改内部顺序。这样「查看排名」不会改变「录入顺序」,两个视图互不干扰。如果业务要求永久排序,可以另提供sortInPlace(),但要在接口名上区分清楚。


二、查找、删除与排序


查找用std::find_if,谓词比较工号。删除先拿到迭代器,再调用vector::erase。注意erase会使被删位置及其后的迭代器失效,因此删除后不要继续使用旧迭代器;本类的删除操作一次只删一个,返回后即结束,不存在这个问题。


排序用std::sort,比较器必须满足严格弱序(strict weak ordering)。先比工资降序,工资相同再按工号升序:


// 适用于 C++17
std::sort(copy.begin(), copy.end(),
[](const Teacher& lhs, const Teacher& rhs) {
if (lhs.salary != rhs.salary) {
return lhs.salary > rhs.salary;
}
return lhs.id < rhs.id;
});

不要写lhs.salary >= rhs.salary。相等时返回true会破坏严格弱序,std::sort的行为是未定义行为(undefined behavior, UB),可能越界访问,而不只是「顺序不对」。


平均工资要先判断容器是否为空,否则除以 0。对浮点数来说,除以 0.0 得到的是无穷大或 NaN(IEEE 754 语义),不是整数除零那种 UB,但结果显然不是业务想要的。


三、完整实现


// 适用于 C++17
#include <algorithm>
#include <cstddef>
#include <iomanip>
#include <iostream>
#include <limits>
#include <string>
#include <vector>

struct Teacher {
int id = 0;
std::string name;
std::string title;
double salary = 0.0;
};

class TeacherManager {
public:
bool add(int id, const std::string& name,
const std::string& title, double salary) {
if (findById(id) != nullptr) {
return false; // 工号唯一
}
teachers_.push_back(Teacher{id, name, title, salary});
return true;
}

bool remove(int id) {
auto it = std::find_if(
teachers_.begin(), teachers_.end(),
[id](const Teacher& t) { return t.id == id; });
if (it == teachers_.end()) {
return false;
}
teachers_.erase(it);
return true;
}

bool updateSalary(int id, double salary) {
auto it = std::find_if(
teachers_.begin(), teachers_.end(),
[id](const Teacher& t) { return t.id == id; });
if (it == teachers_.end()) {
return false;
}
it->salary = salary;
return true;
}

const Teacher* findById(int id) const {
auto it = std::find_if(
teachers_.begin(), teachers_.end(),
[id](const Teacher& t) { return t.id == id; });
return it == teachers_.end() ? nullptr : &*it;
}

void printAll() const {
std::cout << std::left << std::setw(8) << "ID"
<< std::setw(12) << "Name"
<< std::setw(12) << "Title"
<< "Salary\n";
for (const Teacher& t : teachers_) {
std::cout << std::left << std::setw(8) << t.id
<< std::setw(12) << t.name
<< std::setw(12) << t.title
<< t.salary << '\n';
}
}

double averageSalary() const {
if (teachers_.empty()) {
return 0.0;
}
double sum = 0.0;
for (const Teacher& t : teachers_) {
sum += t.salary;
}
return sum / static_cast<double>(teachers_.size());
}

std::vector<Teacher> sortedBySalary() const {
std::vector<Teacher> copy = teachers_;
std::sort(copy.begin(), copy.end(),
[](const Teacher& lhs, const Teacher& rhs) {
if (lhs.salary != rhs.salary) {
return lhs.salary > rhs.salary;
}
return lhs.id < rhs.id;
});
return copy;
}

std::size_t size() const { return teachers_.size(); }

private:
std::vector<Teacher> teachers_;
};

int readInt(const std::string& prompt) {
for (;;) {
std::cout << prompt;
int value = 0;
if (std::cin >> value) {
std::cin.ignore(std::numeric_limits<std::streamsize>::max(), '\n');
return value;
}
std::cin.clear();
std::cin.ignore(std::numeric_limits<std::streamsize>::max(), '\n');
std::cout << "请输入整数。\n";
}
}

double readDouble(const std::string& prompt) {
for (;;) {
std::cout << prompt;
double value = 0.0;
if (std::cin >> value) {
std::cin.ignore(std::numeric_limits<std::streamsize>::max(), '\n');
return value;
}
std::cin.clear();
std::cin.ignore(std::numeric_limits<std::streamsize>::max(), '\n');
std::cout << "请输入数字。\n";
}
}

std::string readLine(const std::string& prompt) {
std::cout << prompt;
std::string line;
std::getline(std::cin, line);
return line;
}

int main() {
TeacherManager manager;
for (;;) {
std::cout << "\n1 添加 2 删除 3 改工资 4 查询 "
"5 列表 6 平均 7 排序 0 退出\n";
const int choice = readInt("请选择: ");
if (choice == 0) {
break;
}
switch (choice) {
case 1: {
const int id = readInt("工号: ");
const std::string name = readLine("姓名: ");
const std::string title = readLine("职称: ");
const double salary = readDouble("月薪: ");
std::cout << (manager.add(id, name, title, salary)
? "添加成功\n"
: "工号重复\n");
break;
}
case 2: {
const int id = readInt("要删除的工号: ");
std::cout << (manager.remove(id) ? "已删除\n" : "未找到\n");
break;
}
case 3: {
const int id = readInt("工号: ");
const double salary = readDouble("新工资: ");
std::cout << (manager.updateSalary(id, salary)
? "已更新\n"
: "未找到\n");
break;
}
case 4: {
const int id = readInt("工号: ");
if (const Teacher* t = manager.findById(id)) {
std::cout << t->id << ' ' << t->name << ' '
<< t->title << ' ' << t->salary << '\n';
} else {
std::cout << "未找到\n";
}
break;
}
case 5:
manager.printAll();
break;
case 6:
std::cout << "平均工资: " << manager.averageSalary() << '\n';
break;
case 7: {
const std::vector<Teacher> ranked = manager.sortedBySalary();
for (const Teacher& t : ranked) {
std::cout << t.id << ' ' << t.name << ' '
<< t.salary << '\n';
}
break;
}
default:
std::cout << "无效选项\n";
break;
}
}
return 0;
}

输入处理是这段代码里最容易出问题的部分。readInt和readDouble在读取成功后调用ignore吃掉行尾换行,否则紧接着的getline会读到空行。读取失败时先clear()恢复流状态,再ignore丢弃当前行的剩余字符,否则下一次读取仍会失败,形成死循环。


四、扩展与持久化


真实系统通常需要把数据保存到文件。简单的文本格式可以用std::ofstream每行写一条记录,字段之间用分隔符;读取时用std::getline拆行,再用std::istringstream解析。要注意姓名中可能包含分隔符,生产环境应使用 CSV 转义或 JSON 库,并校验字段数量。


如果教师数量很大,按工号查找可以改用std::unordered_map<int, Teacher>,平均 O(1);但遍历和按工资排序仍需要额外的顺序结构。选择容器前先明确哪类操作最频繁,不要为了「看起来高级」而牺牲简单性。


常见坑点



  1. ❌ 允许重复工号。


// 适用于 C++17
teachers_.push_back(Teacher{id, name, title, salary}); // 不查重

✅ 插入前检查唯一性。


// 适用于 C++17
if (findById(id) != nullptr) { return false; }


  1. ❌ 删除后继续使用旧迭代器。


// 适用于 C++17
auto it = std::find_if(...);
teachers_.erase(it);
std::cout << it->name; // it 已失效,UB

✅ 删除后不再使用该迭代器。


// 适用于 C++17
teachers_.erase(it);
return true;


  1. ❌ 排序比较器使用>=。


// 适用于 C++17
return lhs.salary >= rhs.salary; // 不满足严格弱序,UB

✅ 相等时走次级键。


// 适用于 C++17
if (lhs.salary != rhs.salary) { return lhs.salary > rhs.salary; }
return lhs.id < rhs.id;


  1. ❌ 空容器求平均时直接除。


// 适用于 C++17
return sum / teachers_.size(); // size 为 0,结果为 NaN/无穷

✅ 先判断空。


// 适用于 C++17
if (teachers_.empty()) { return 0.0; }


  1. ❌cin >>后直接getline。


// 适用于 C++17
int id; std::cin >> id;
std::string name; std::getline(std::cin, name); // 读到空行

✅ 读取后丢弃行尾换行。


// 适用于 C++17
std::cin.ignore(std::numeric_limits<std::streamsize>::max(), '\n');


  1. ❌ 输入失败后不清理流状态。


// 适用于 C++17
int x;
std::cin >> x; // 输入字母,流进入 fail 状态
std::cin >> x; // 仍然失败,死循环

✅clear()加ignore()。


// 适用于 C++17
std::cin.clear();
std::cin.ignore(std::numeric_limits<std::streamsize>::max(), '\n');


  1. ❌ 让findById返回内部元素的引用。


// 适用于 C++17
Teacher& findById(int id); // 调用方可能长期持有,容器扩容后悬垂

✅ 返回指针并说明生命周期,或按值返回副本。


// 适用于 C++17
const Teacher* findById(int id) const;


  1. ❌ 用system("pause")暂停。


// 适用于 C++17
system("pause"); // 平台相关,且有命令注入风险

✅ 用标准输入等待,或直接返回。


// 适用于 C++17
std::cin.get();

总结




模块实现要点



数据std::vector<Teacher>,工号唯一

查询std::find_if,返回const Teacher*

删除erase后不复用迭代器

排序返回副本,比较器满足严格弱序

统计空容器单独处理

输入失败后clear+ignore

扩展文件持久化、哈希索引按需引入



教师管理系统的功能列表可以很长,但骨架始终是「容器 + 查找 + 排序 + 输入校验」。把这四件事写扎实,增加字段或换存储方式都只是局部修改,不会牵动整体结构。




需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询