第10周:STL容器 - 理论与实战
学习目标
- 理解STL容器的设计思想和分类
- 掌握序列容器的特性和使用场景
- 理解关联容器的内部机制
- 学会根据需求选择合适的容器
- 掌握容器性能优化的实用技巧
10.1 容器设计哲学
10.1.1 容器的抽象思维
想象你在整理不同类型的物品:
- 书架:连续摆放书籍,方便直接拿取第N本(类似vector)
- 文件柜:每个抽屉独立,可以从两端添加文件(类似deque)
- 链条:每个环节连接,插入新环节不会影响其他部分(类似list)
- 索引卡片:按字母顺序排列,快速找到特定卡片(类似map/set)
- 哈希表:通过标签直接定位物品(类似unordered_map/set)
STL容器正是这种生活智慧的程序化体现,每种容器都有其最适合的使用场景。
10.1.2 容器的分类与选择
序列容器:元素按插入顺序排列
vector:动态数组,支持快速随机访问deque:双端队列,两端操作都高效list:双向链表,任意位置插入删除forward_list:单向链表,内存更节省
关联容器:元素按键排序存储
set/multiset:唯一/可重复的有序集合map/multimap:唯一/可重复的有序键值对unordered_*:哈希表实现,平均常数时间访问
容器适配器:基于其他容器实现
stack:后进先出queue:先进先出priority_queue:优先级队列
10.2 序列容器深度解析
10.2.1 vector:动态数组的智慧
vector的工作原理:
想象vector就像一个可伸缩的背包。当你往背包里放东西时:
- 如果背包还有空间,直接放入
- 如果背包满了,换一个大一号的背包,把所有东西转移过去
- 为了下次不用频繁换背包,新背包通常是旧的两倍大小
cpp
#include <vector>
#include <iostream>
class Backpack {
std::vector<Item> items;
public:
void addItem(const Item& item) {
items.push_back(item);
std::cout << "背包现在有 " << items.size() << " 个物品";
std::cout << ",容量为 " << items.capacity() << std::endl;
}
Item& getItem(size_t index) {
return items[index]; // 常数时间访问
}
};
vector的内存管理:
vector在内存中就像一排连续的停车位:
- 每个车位大小相同(元素类型决定)
- 可以快速找到第N个车位(随机访问)
- 如果车位不够,需要重新找一块更大的连续区域
- 为了避免频繁搬迁,会预留一些空车位(capacity)
cpp
void demonstrateVectorGrowth() {
std::vector<int> vec;
std::cout << "观察vector的增长模式:\n";
for (int i = 0; i < 20; ++i) {
vec.push_back(i);
std::cout << "元素: " << vec.size()
<< ", 容量: " << vec.capacity() << std::endl;
}
}
vector的最佳实践:
- 预分配容量:如果你知道大概需要多少元素
cpp
std::vector<int> vec;
vec.reserve(1000); // 预先分配空间,避免多次重新分配
- 使用emplace_back:直接在vector内部构造对象
cpp
struct Person {
std::string name;
int age;
Person(const std::string& n, int a) : name(n), age(a) {}
};
std::vector<Person> people;
people.emplace_back("张三", 25); // 比push_back更高效
- 避免频繁插入删除:vector适合尾部操作,中间操作代价高
cpp
// 好的做法:尾部操作
std::vector<int> data;
data.push_back(1);
data.push_back(2);
// 避免:频繁中间插入
data.insert(data.begin() + 1, 3); // O(n)复杂度
10.2.2 deque:双端队列的巧妙设计
deque的内部结构:
deque就像一个环形停车场,有多个独立的停车区域:
- 每个区域可以独立扩展
- 可以从两端同时停车(双端操作)
- 不需要像vector那样整体搬迁
- 通过索引表快速定位到具体区域
cpp
#include <deque>
#include <iostream>
class CircularParking {
std::deque<Car> cars;
public:
void parkFront(const Car& car) {
cars.push_front(car); // 前端停车
std::cout << "前端停车,总数:" << cars.size() << std::endl;
}
void parkBack(const Car& car) {
cars.push_back(car); // 后端停车
std::cout << "后端停车,总数:" << cars.size() << std::endl;
}
Car leaveFront() {
Car car = cars.front();
cars.pop_front(); // 前端离开
return car;
}
Car leaveBack() {
Car car = cars.back();
cars.pop_back(); // 后端离开
return car;
}
};
deque vs vector对比:
| 操作 | vector | deque | 生活类比 |
|---|---|---|---|
| 随机访问 | ⚡ 超快 | ⚡ 超快 | 直接拿第N本书 |
| 尾部插入 | ⚡ 超快 | ⚡ 超快 | 背包后面加东西 |
| 头部插入 | 🐌 很慢 | ⚡ 超快 | 背包前面加东西 |
| 内存连续性 | ✅ 是 | ❌ 否 | 停车位 vs 多个区域 |
| 重新分配 | 需要 | 不需要 | 整体搬迁 vs 局部扩展 |
deque的实用场景:
- 双端队列:需要两端操作的场景
cpp
std::deque<std::string> history; // 浏览历史
history.push_back(currentPage); // 新页面
if (history.size() > 100) {
history.pop_front(); // 保持最近100个
}
- 滑动窗口:固定大小的数据窗口
cpp
template<typename T>
class SlidingWindow {
std::deque<T> window;
size_t max_size;
public:
void add(const T& value) {
window.push_back(value);
if (window.size() > max_size) {
window.pop_front();
}
}
double average() const {
if (window.empty()) return 0;
double sum = 0;
for (const auto& val : window) {
sum += val;
}
return sum / window.size();
}
};
10.2.3 list:双向链表的灵活性
list的内部结构:
list就像一串珍珠项链:
- 每个珍珠独立存在(节点)
- 通过线连接前后珍珠(指针)
- 可以在任意位置插入新珍珠
- 插入删除不会影响其他珍珠
cpp
#include <list>
#include <iostream>
class PearlNecklace {
struct Pearl {
std::string color;
Pearl* prev;
Pearl* next;
};
std::list<Pearl> pearls;
public:
void addPearl(const std::string& color, size_t position) {
auto it = pearls.begin();
std::advance(it, position); // 移动到指定位置
pearls.insert(it, Pearl{color, nullptr, nullptr});
std::cout << "在位置 " << position << " 插入 " << color << " 珍珠\n";
}
void removePearl(size_t position) {
auto it = pearls.begin();
std::advance(it, position);
pearls.erase(it);
std::cout << "移除位置 " << position << " 的珍珠\n";
}
void display() const {
std::cout << "项链:";
for (const auto& pearl : pearls) {
std::cout << pearl.color << " ";
}
std::cout << std::endl;
}
};
list的独特优势:
- 稳定的迭代器:插入删除不会使迭代器失效
cpp
std::list<int> numbers = {1, 2, 3, 4, 5};
auto it = std::find(numbers.begin(), numbers.end(), 3);
numbers.push_back(6); // ✅ 不会使it失效
numbers.push_front(0); // ✅ 不会使it失效
numbers.erase(it); // ✅ 可以安全删除
- 拼接操作:高效地合并两个list
cpp
std::list<int> list1 = {1, 2, 3};
std::list<int> list2 = {4, 5, 6};
// 把list2的所有元素移到list1的末尾
list1.splice(list1.end(), list2);
// list1: {1, 2, 3, 4, 5, 6}
// list2: {} (变为空)
- 排序稳定性:保持相等元素的相对顺序
cpp
struct Student {
std::string name;
int score;
int id;
};
std::list<Student> students = {
{"张三", 85, 1},
{"李四", 90, 2},
{"王五", 85, 3}
};
// 按分数排序,相同分数保持原有顺序
students.sort([](const Student& a, const Student& b) {
return a.score < b.score;
});
10.3 关联容器深度解析
10.3.1 map:有序字典的实现
map的工作原理:
map就像一本按字母顺序排列的通讯录:
- 每个名字对应一个电话号码(键值对)
- 新联系人自动插入到正确位置
- 可以快速查找特定联系人
- 可以遍历所有联系人(按字母顺序)
cpp
#include <map>
#include <string>
#include <iostream>
class PhoneBook {
std::map<std::string, std::string> contacts; // 名字 -> 电话
public:
void addContact(const std::string& name, const std::string& phone) {
contacts[name] = phone; // 自动排序插入
std::cout << "添加联系人:" << name << " -> " << phone << std::endl;
}
std::string findPhone(const std::string& name) const {
auto it = contacts.find(name);
if (it != contacts.end()) {
return it->second;
}
return "未找到";
}
void displayAll() const {
std::cout << "通讯录(按名字排序):\n";
for (const auto& [name, phone] : contacts) {
std::cout << name << ": " << phone << std::endl;
}
}
void removeContact(const std::string& name) {
contacts.erase(name);
std::cout << "删除联系人:" << name << std::endl;
}
};
map的内部结构:
map使用红黑树(自平衡二叉搜索树)实现:
- 每个节点有颜色标记(红或黑)
- 通过旋转保持树的平衡
- 保证查找、插入、删除都是O(log n)复杂度
- 最坏情况下也不会退化成链表
cpp
// 演示map的有序性和高效查找
void demonstrateMapOrdering() {
std::map<int, std::string> scoreMap;
// 插入一些分数(不按顺序)
scoreMap[85] = "良好";
scoreMap[95] = "优秀";
scoreMap[75] = "中等";
scoreMap[65] = "及格";
std::cout << "分数映射(自动排序):\n";
for (const auto& [score, level] : scoreMap) {
std::cout << score << "分: " << level << std::endl;
}
// 查找特定分数
auto it = scoreMap.lower_bound(80); // 第一个>=80的分数
if (it != scoreMap.end()) {
std::cout << "80分对应的最低等级:" << it->second << std::endl;
}
}
map的高级用法:
- 自定义排序:使用不同的比较函数
cpp
struct CaseInsensitiveCompare {
bool operator()(const std::string& a, const std::string& b) const {
return std::lexicographical_compare(
a.begin(), a.end(), b.begin(), b.end(),
[](char a, char b) { return tolower(a) < tolower(b); }
);
}
};
std::map<std::string, int, CaseInsensitiveCompare> caseInsensitiveMap;
caseInsensitiveMap["Apple"] = 1;
caseInsensitiveMap["apple"] = 2; // 会覆盖,因为认为相同
- multimap:允许重复键
cpp
std::multimap<std::string, std::string> synonymDict;
synonymDict.insert({"happy", "joyful"});
synonymDict.insert({"happy", "cheerful"});
synonymDict.insert({"happy", "content"});
// 查找所有同义词
auto range = synonymDict.equal_range("happy");
for (auto it = range.first; it != range.second; ++it) {
std::cout << it->second << " ";
}
10.3.2 unordered_map:哈希表的威力
unordered_map的工作原理:
unordered_map就像一个图书馆的书架系统:
- 每本书按主题分类(哈希函数)
- 同类书籍放在同一个书架(桶)
- 通过分类号快速找到书架
- 不需要按字母顺序排列
cpp
#include <unordered_map>
#include <string>
#include <iostream>
class LibrarySystem {
struct Book {
std::string title;
std::string author;
int year;
};
std::unordered_map<std::string, Book> books; // ISBN -> Book信息
public:
void addBook(const std::string& isbn, const Book& book) {
books[isbn] = book;
std::cout << "添加图书:" << book.title << std::endl;
}
Book* findBook(const std::string& isbn) {
auto it = books.find(isbn);
return (it != books.end()) ? &it->second : nullptr;
}
void displayStats() const {
std::cout << "图书馆统计:\n";
std::cout << "总藏书:" << books.size() << std::endl;
std::cout << "桶数量:" << books.bucket_count() << std::endl;
std::cout << "负载因子:" << books.load_factor() << std::endl;
}
};
哈希函数的重要性:
好的哈希函数就像好的分类系统:
- 均匀分布(避免某个书架太满)
- 计算快速(快速找到分类号)
- 冲突最少(同类书籍不要太多)
cpp
// 自定义哈希函数:按字符串长度分类
struct StringLengthHash {
size_t operator()(const std::string& str) const {
return str.length(); // 按长度哈希
}
};
// 自定义相等函数:长度相等认为相等
struct StringLengthEqual {
bool operator()(const std::string& a, const std::string& b) const {
return a.length() == b.length();
}
};
std::unordered_map<std::string, int, StringLengthHash, StringLengthEqual>
lengthBasedMap;
性能优化技巧:
- 预分配桶数量:减少重新哈希
cpp
std::unordered_map<int, std::string> map;
map.reserve(1000); // 预分配1000个桶
- 选择合适的负载因子:平衡空间和时间
cpp
map.max_load_factor(0.75); // 默认负载因子,可以调整
- 处理哈希冲突:使用开放寻址或链地址法
cpp
// 检测冲突情况
void analyzeCollisions(const std::unordered_map<int, std::string>& map) {
std::cout << "哈希冲突分析:\n";
for (size_t i = 0; i < map.bucket_count(); ++i) {
size_t bucket_size = map.bucket_size(i);
if (bucket_size > 1) {
std::cout << "桶 " << i << " 有 " << bucket_size << " 个元素\n";
}
}
}
10.4 容器适配器
10.4.1 stack:后进先出的智慧
stack的工作原理:
stack就像一叠盘子:
- 新盘子只能放在最上面
- 拿盘子只能从最上面拿
- 最后放的盘子最先被拿走
cpp
#include <stack>
#include <iostream>
#include <string>
class PlateStack {
std::stack<std::string> plates;
public:
void washPlate(const std::string& plate) {
plates.push(plate);
std::cout << "洗净盘子:" << plate << ",叠在最上面\n";
}
std::string usePlate() {
if (plates.empty()) {
throw std::runtime_error("没有干净的盘子了!");
}
std::string plate = plates.top();
plates.pop();
std::cout << "使用盘子:" << plate << std::endl;
return plate;
}
size_t cleanPlates() const {
return plates.size();
}
void displayStack() const {
std::cout << "盘子堆叠情况(从上到下):\n";
std::stack<std::string> temp = plates; // 复制一份
while (!temp.empty()) {
std::cout << temp.top() << std::endl;
temp.pop();
}
}
};
stack的实用场景:
- 表达式求值:处理数学表达式
cpp
class ExpressionEvaluator {
std::stack<double> operands;
std::stack<char> operators;
public:
double evaluate(const std::string& expression) {
// 简化的表达式求值
for (char c : expression) {
if (isdigit(c)) {
operands.push(c - '0');
} else if (c == '+' || c == '-') {
while (!operators.empty() && operators.top() != '(') {
calculate();
}
operators.push(c);
}
}
while (!operators.empty()) {
calculate();
}
return operands.top();
}
private:
void calculate() {
double b = operands.top(); operands.pop();
double a = operands.top(); operands.pop();
char op = operators.top(); operators.pop();
switch (op) {
case '+': operands.push(a + b); break;
case '-': operands.push(a - b); break;
}
}
};
- 括号匹配:检查代码中的括号是否匹配
cpp
bool checkBrackets(const std::string& code) {
std::stack<char> brackets;
for (char c : code) {
if (c == '(' || c == '[' || c == '{') {
brackets.push(c);
} else if (c == ')' || c == ']' || c == '}') {
if (brackets.empty()) return false;
char top = brackets.top();
brackets.pop();
if ((c == ')' && top != '(') ||
(c == ']' && top != '[') ||
(c == '}' && top != '{')) {
return false;
}
}
}
return brackets.empty(); // 所有括号都应该匹配
}
10.4.2 queue:先进先出的公平
queue的工作原理:
queue就像排队买票:
- 先来的人排在前面
- 后来的人排在后面
- 前面的人先买到票离开
- 保证公平,不会有人插队
cpp
#include <queue>
#include <iostream>
#include <string>
class TicketQueue {
struct Customer {
std::string name;
int ticketNumber;
};
std::queue<Customer> customers;
int nextTicket;
public:
TicketQueue() : nextTicket(1) {}
void joinQueue(const std::string& name) {
Customer customer{name, nextTicket++};
customers.push(customer);
std::cout << name << " 取票号:" << customer.ticketNumber << std::endl;
}
Customer serveNext() {
if (customers.empty()) {
throw std::runtime_error("队列空,没有顾客等待");
}
Customer customer = customers.front();
customers.pop();
std::cout << "服务顾客:" << customer.name
<< ",票号:" << customer.ticketNumber << std::endl;
return customer;
}
size_t waitingCount() const {
return customers.size();
}
void displayQueue() const {
std::cout << "等待队列(从前到后):\n";
std::queue<Customer> temp = customers;
while (!temp.empty()) {
std::cout << temp.front().name
<< " (票号:" << temp.front().ticketNumber << ")\n";
temp.pop();
}
}
};
queue的实用场景:
- 任务调度:处理后台任务
cpp
class TaskScheduler {
struct Task {
int priority;
std::string description;
std::chrono::system_clock::time_point created;
};
std::queue<Task> tasks;
public:
void addTask(int priority, const std::string& description) {
Task task{priority, description, std::chrono::system_clock::now()};
tasks.push(task);
std::cout << "添加任务:" << description << std::endl;
}
Task getNextTask() {
if (tasks.empty()) {
throw std::runtime_error("没有待处理任务");
}
Task task = tasks.front();
tasks.pop();
return task;
}
bool hasTasks() const {
return !tasks.empty();
}
};
- 消息队列:处理网络消息
cpp
class MessageQueue {
struct Message {
std::string sender;
std::string content;
std::chrono::system_clock::time_point timestamp;
};
std::queue<Message> messages;
std::mutex mtx;
public:
void sendMessage(const std::string& sender, const std::string& content) {
std::lock_guard<std::mutex> lock(mtx);
Message msg{sender, content, std::chrono::system_clock::now()};
messages.push(msg);
}
Message receiveMessage() {
std::lock_guard<std::mutex> lock(mtx);
if (messages.empty()) {
throw std::runtime_error("没有消息");
}
Message msg = messages.front();
messages.pop();
return msg;
}
size_t messageCount() const {
std::lock_guard<std::mutex> lock(mtx);
return messages.size();
}
};
10.4.3 priority_queue:优先级队列的效率
priority_queue的工作原理:
priority_queue就像医院的急诊室:
- 病情最严重的病人优先治疗
- 新来的病人按病情严重程度排队
- 医生总是治疗当前最严重的病人
- 保证医疗资源的最优分配
cpp
#include <queue>
#include <iostream>
#include <string>
class EmergencyRoom {
struct Patient {
std::string name;
int severity; // 严重程度:1-10,10最严重
std::chrono::system_clock::time_point arrival;
// 优先级:严重程度高者优先,相同时先到先服务
bool operator<(const Patient& other) const {
if (severity != other.severity) {
return severity < other.severity; // 严重程度高优先级高
}
return arrival > other.arrival; // 相同时先到先服务
}
};
std::priority_queue<Patient> patients;
public:
void addPatient(const std::string& name, int severity) {
Patient patient{name, severity, std::chrono::system_clock::now()};
patients.push(patient);
std::cout << "患者 " << name << " 到达,严重程度:" << severity << std::endl;
}
Patient treatNext() {
if (patients.empty()) {
throw std::runtime_error("没有患者等待");
}
Patient patient = patients.top();
patients.pop();
std::cout << "治疗患者:" << patient.name
<< ",严重程度:" << patient.severity << std::endl;
return patient;
}
size_t waitingCount() const {
return patients.size();
}
void displayQueue() const {
std::cout << "等待患者(按优先级):\n";
std::priority_queue<Patient> temp = patients;
while (!temp.empty()) {
Patient patient = temp.top();
temp.pop();
std::cout << patient.name << " (严重程度:" << patient.severity << ")\n";
}
}
};
priority_queue的实用场景:
- 任务调度:按优先级处理任务
cpp
class PriorityTaskScheduler {
struct Task {
int id;
std::string description;
int priority; // 优先级:1-10,10最高
bool operator<(const Task& other) const {
return priority < other.priority; // 优先级高的先处理
}
};
std::priority_queue<Task> tasks;
int nextId;
public:
PriorityTaskScheduler() : nextId(1) {}
void addTask(const std::string& description, int priority) {
Task task{nextId++, description, priority};
tasks.push(task);
std::cout << "添加任务:" << description
<< ",优先级:" << priority << std::endl;
}
Task getNextTask() {
if (tasks.empty()) {
throw std::runtime_error("没有待处理任务");
}
Task task = tasks.top();
tasks.pop();
return task;
}
bool hasTasks() const {
return !tasks.empty();
}
};
- Dijkstra算法:最短路径计算
cpp
class Graph {
struct Edge {
int to;
int weight;
};
struct Node {
int id;
int distance;
bool operator<(const Node& other) const {
return distance > other.distance; // 距离小的优先级高
}
};
std::vector<std::vector<Edge>> adj;
public:
Graph(int n) : adj(n) {}
void addEdge(int from, int to, int weight) {
adj[from].push_back({to, weight});
}
std::vector<int> dijkstra(int start) {
std::vector<int> dist(adj.size(), INT_MAX);
std::priority_queue<Node> pq;
dist[start] = 0;
pq.push({start, 0});
while (!pq.empty()) {
Node node = pq.top();
pq.pop();
if (node.distance > dist[node.id]) continue;
for (const Edge& edge : adj[node.id]) {
int newDist = node.distance + edge.weight;
if (newDist < dist[edge.to]) {
dist[edge.to] = newDist;
pq.push({edge.to, newDist});
}
}
}
return dist;
}
};
10.5 容器性能优化
10.5.1 内存管理优化
预分配策略:
cpp
// vector预分配
std::vector<int> vec;
vec.reserve(1000); // 预分配空间,避免重新分配
// unordered_map预分配
std::unordered_map<int, std::string> map;
map.reserve(1000); // 预分配桶数量
// string预分配
std::string str;
str.reserve(1000); // 预分配字符空间
内存池技术:
cpp
template<typename T>
class MemoryPool {
std::vector<T> pool;
std::stack<T*> available;
public:
MemoryPool(size_t size) {
pool.reserve(size);
for (size_t i = 0; i < size; ++i) {
pool.emplace_back();
available.push(&pool[i]);
}
}
T* acquire() {
if (available.empty()) {
return nullptr;
}
T* obj = available.top();
available.pop();
return obj;
}
void release(T* obj) {
available.push(obj);
}
};
10.5.2 算法复杂度优化
选择合适的容器:
cpp
// 需要频繁随机访问?用vector
std::vector<int> data(1000);
for (size_t i = 0; i < data.size(); ++i) {
process(data[i]); // O(1)随机访问
}
// 需要频繁插入删除?用list
std::list<int> data;
auto it = data.begin();
std::advance(it, 500); // O(n)移动到位置
data.insert(it, 42); // O(1)插入
// 需要快速查找?用unordered_map
std::unordered_map<std::string, int> lookup;
int value = lookup["key"]; // 平均O(1)查找
批量操作:
cpp
// 批量插入比逐个插入更高效
std::vector<int> vec;
std::vector<int> batch = {1, 2, 3, 4, 5};
vec.insert(vec.end(), batch.begin(), batch.end()); // 批量插入
// 使用移动语义避免复制
std::vector<std::string> createData() {
std::vector<std::string> data;
data.push_back("large string data");
return data; // 移动返回,避免复制
}
10.5.3 缓存友好优化
数据局部性:
cpp
// 缓存友好的遍历
std::vector<std::vector<int>> matrix(1000, std::vector<int>(1000));
// ✅ 行优先遍历(缓存友好)
for (size_t i = 0; i < matrix.size(); ++i) {
for (size_t j = 0; j < matrix[i].size(); ++j) {
matrix[i][j] *= 2;
}
}
// ❌ 列优先遍历(缓存不友好)
for (size_t j = 0; j < matrix[0].size(); ++j) {
for (size_t i = 0; i < matrix.size(); ++i) {
matrix[i][j] *= 2;
}
}
结构体数组 vs 数组结构体:
cpp
// ❌ 数组结构体(缓存不友好)
struct GameObject {
float x, y, z; // 位置
float vx, vy, vz; // 速度
int health;
bool active;
};
std::vector<GameObject> objects;
// 只更新位置时,会加载不需要的数据
for (auto& obj : objects) {
obj.x += obj.vx; // 加载了整个GameObject
}
// ✅ 结构体数组(缓存友好)
struct GameObjects {
std::vector<float> x, y, z;
std::vector<float> vx, vy, vz;
std::vector<int> health;
std::vector<bool> active;
};
// 只加载需要的数据
for (size_t i = 0; i < objects.x.size(); ++i) {
objects.x[i] += objects.vx[i];
}
10.6 实战项目:学生成绩管理系统
10.6.1 需求分析
设计一个学生成绩管理系统,要求:
- 存储学生信息(姓名、学号、成绩)
- 支持快速查找学生
- 按成绩排序
- 统计班级成绩分布
- 支持成绩修改
10.6.2 系统设计
cpp
#include <iostream>
#include <map>
#include <unordered_map>
#include <vector>
#include <algorithm>
#include <iomanip>
#include <string>
class StudentGradeSystem {
struct Student {
std::string name;
std::string id;
double score;
Student(const std::string& n, const std::string& i, double s)
: name(n), id(i), score(s) {}
};
// 按学号索引(主索引)
std::unordered_map<std::string, Student> students_by_id;
// 按姓名索引(辅助索引)
std::multimap<std::string, std::string> students_by_name;
// 按成绩排序(用于排名)
std::multimap<double, std::string, std::greater<double>> students_by_score;
public:
// 添加学生
void addStudent(const std::string& name, const std::string& id, double score) {
if (students_by_id.find(id) != students_by_id.end()) {
std::cout << "学号 " << id << " 已存在!\n";
return;
}
Student student(name, id, score);
students_by_id[id] = student;
students_by_name.insert({name, id});
students_by_score.insert({score, id});
std::cout << "添加学生:" << name << " (" << id << "),成绩:" << score << std::endl;
}
// 查找学生(按学号)
Student* findById(const std::string& id) {
auto it = students_by_id.find(id);
return (it != students_by_id.end()) ? &it->second : nullptr;
}
// 查找学生(按姓名)
std::vector<Student> findByName(const std::string& name) {
std::vector<Student> result;
auto range = students_by_name.equal_range(name);
for (auto it = range.first; it != range.second; ++it) {
const std::string& id = it->second;
result.push_back(students_by_id[id]);
}
return result;
}
// 修改成绩
void updateScore(const std::string& id, double newScore) {
auto it = students_by_id.find(id);
if (it == students_by_id.end()) {
std::cout << "学号 " << id << " 不存在!\n";
return;
}
Student& student = it->second;
double oldScore = student.score;
student.score = newScore;
// 更新成绩索引
auto score_range = students_by_score.equal_range(oldScore);
for (auto it = score_range.first; it != score_range.second; ++it) {
if (it->second == id) {
students_by_score.erase(it);
break;
}
}
students_by_score.insert({newScore, id});
std::cout << "更新成绩:" << student.name
<< " 从 " << oldScore << " 到 " << newScore << std::endl;
}
// 显示排名
void displayRanking(int top_n = 10) {
std::cout << "\n=== 成绩排名 ===\n";
std::cout << std::setw(4) << "排名"
<< std::setw(10) << "学号"
<< std::setw(12) << "姓名"
<< std::setw(8) << "成绩" << std::endl;
int rank = 1;
int count = 0;
double prev_score = -1;
for (const auto& [score, id] : students_by_score) {
if (score != prev_score) {
rank = count + 1;
prev_score = score;
}
const Student& student = students_by_id[id];
std::cout << std::setw(4) << rank
<< std::setw(10) << student.id
<< std::setw(12) << student.name
<< std::setw(8) << std::fixed << std::setprecision(1)
<< student.score << std::endl;
if (++count >= top_n) break;
}
}
// 统计成绩分布
void displayScoreDistribution() {
std::map<std::string, int> distribution = {
{"90-100", 0}, {"80-89", 0}, {"70-79", 0},
{"60-69", 0}, {"0-59", 0}
};
for (const auto& [id, student] : students_by_id) {
if (student.score >= 90) distribution["90-100"]++;
else if (student.score >= 80) distribution["80-89"]++;
else if (student.score >= 70) distribution["70-79"]++;
else if (student.score >= 60) distribution["60-69"]++;
n else distribution["0-59"]++;
n }
std::cout << "\n=== 成绩分布 ===\n";
for (const auto& [range, count] : distribution) {
std::cout << range << ": " << count << " 人\n";
}
}
// 显示所有学生
void displayAll() const {
std::cout << "\n=== 所有学生 ===\n";
std::cout << std::setw(10) << "学号"
<< std::setw(12) << "姓名"
<< std::setw(8) << "成绩" << std::endl;
for (const auto& [id, student] : students_by_id) {
std::cout << std::setw(10) << student.id
<< std::setw(12) << student.name
<< std::setw(8) << std::fixed << std::setprecision(1)
<< student.score << std::endl;
}
}
};
10.6.3 使用示例
cpp
int main() {
StudentGradeSystem system;
// 添加学生
system.addStudent("张三", "2021001", 85.5);
system.addStudent("李四", "2021002", 92.0);
system.addStudent("王五", "2021003", 78.5);
system.addStudent("赵六", "2021004", 95.5);
system.addStudent("钱七", "2021005", 68.0);
// 查找学生
std::cout << "\n=== 查找学生 ===\n";
if (auto student = system.findById("2021002")) {
std::cout << "找到学生:" << student->name << ",成绩:" << student->score << std::endl;
}
auto same_name = system.findByName("张三");
std::cout << "找到 " << same_name.size() << " 个叫张三的学生\n";
// 更新成绩
system.updateScore("2021001", 88.0);
// 显示排名和分布
system.displayRanking(3);
system.displayScoreDistribution();
// 显示所有学生
system.displayAll();
return 0;
}
10.7 最佳实践总结
10.7.1 容器选择指南
根据访问模式选择:
- 随机访问:vector、deque、array
- 顺序访问:所有容器都适合
- 前端/后端操作:deque、stack、queue
- 任意位置插入删除:list、forward_list
根据数据特性选择:
- 需要排序:map、set、multimap、multiset
- 需要快速查找:unordered_map、unordered_set
- 允许重复:multimap、multiset、unordered_multimap
- 需要范围查询:map、set(有序容器)
10.7.2 性能优化技巧
- 预分配内存:reserve()、resize()
- 使用emplace:避免不必要的复制
- 批量操作:insert()、erase()批量版本
- 移动语义:std::move()避免复制
- 缓存友好:注意数据局部性
10.7.3 常见陷阱避免
- 迭代器失效:容器修改后重新获取迭代器
- 异常安全:使用RAII和智能指针
- 线程安全:STL容器不是线程安全的
- 内存泄漏:注意循环引用
- 性能假设:实际测试而不是假设
10.8 动手实践
练习1:容器性能对比
编写程序比较vector、list、deque在不同操作下的性能:
- 随机访问性能
- 头部/尾部插入性能
- 中间插入删除性能
练习2:自定义容器
实现一个简单的环形缓冲区(circular buffer):
- 固定大小的缓冲区
- 支持push和pop操作
- 当缓冲区满时,新元素覆盖最旧的元素
练习3:文本分析器
使用合适的容器实现文本分析功能:
- 统计单词频率(unordered_map)
- 找出最常出现的单词(priority_queue)
- 按字母顺序显示单词(set)
- 显示单词的上下文(multimap)
练习4:内存池分配器
为vector实现一个简单的内存池分配器:
- 预分配大块内存
- 按需提供小块内存
- 减少内存分配开销
- 支持自定义对齐
通过这些实践,你将深入理解STL容器的设计哲学和使用技巧,为编写高效的C++程序打下坚实基础。记住,选择合适的容器是程序设计的重要决策,需要根据具体需求和性能特征做出明智的选择。