【Linux C/C++开发】第10周:STL容器 - 理论与实战

第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就像一个可伸缩的背包。当你往背包里放东西时:

  1. 如果背包还有空间,直接放入
  2. 如果背包满了,换一个大一号的背包,把所有东西转移过去
  3. 为了下次不用频繁换背包,新背包通常是旧的两倍大小
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的最佳实践

  1. 预分配容量:如果你知道大概需要多少元素
cpp 复制代码
std::vector<int> vec;
vec.reserve(1000); // 预先分配空间,避免多次重新分配
  1. 使用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更高效
  1. 避免频繁插入删除: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的实用场景

  1. 双端队列:需要两端操作的场景
cpp 复制代码
std::deque<std::string> history; // 浏览历史
history.push_back(currentPage); // 新页面
if (history.size() > 100) {
    history.pop_front(); // 保持最近100个
}
  1. 滑动窗口:固定大小的数据窗口
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的独特优势

  1. 稳定的迭代器:插入删除不会使迭代器失效
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);       // ✅ 可以安全删除
  1. 拼接操作:高效地合并两个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: {} (变为空)
  1. 排序稳定性:保持相等元素的相对顺序
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的高级用法

  1. 自定义排序:使用不同的比较函数
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; // 会覆盖,因为认为相同
  1. 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;

性能优化技巧

  1. 预分配桶数量:减少重新哈希
cpp 复制代码
std::unordered_map<int, std::string> map;
map.reserve(1000); // 预分配1000个桶
  1. 选择合适的负载因子:平衡空间和时间
cpp 复制代码
map.max_load_factor(0.75); // 默认负载因子,可以调整
  1. 处理哈希冲突:使用开放寻址或链地址法
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的实用场景

  1. 表达式求值:处理数学表达式
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;
        }
    }
};
  1. 括号匹配:检查代码中的括号是否匹配
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的实用场景

  1. 任务调度:处理后台任务
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();
    }
};
  1. 消息队列:处理网络消息
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的实用场景

  1. 任务调度:按优先级处理任务
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();
    }
};
  1. 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 需求分析

设计一个学生成绩管理系统,要求:

  1. 存储学生信息(姓名、学号、成绩)
  2. 支持快速查找学生
  3. 按成绩排序
  4. 统计班级成绩分布
  5. 支持成绩修改

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 性能优化技巧

  1. 预分配内存:reserve()、resize()
  2. 使用emplace:避免不必要的复制
  3. 批量操作:insert()、erase()批量版本
  4. 移动语义:std::move()避免复制
  5. 缓存友好:注意数据局部性

10.7.3 常见陷阱避免

  1. 迭代器失效:容器修改后重新获取迭代器
  2. 异常安全:使用RAII和智能指针
  3. 线程安全:STL容器不是线程安全的
  4. 内存泄漏:注意循环引用
  5. 性能假设:实际测试而不是假设

10.8 动手实践

练习1:容器性能对比

编写程序比较vector、list、deque在不同操作下的性能:

  • 随机访问性能
  • 头部/尾部插入性能
  • 中间插入删除性能

练习2:自定义容器

实现一个简单的环形缓冲区(circular buffer):

  • 固定大小的缓冲区
  • 支持push和pop操作
  • 当缓冲区满时,新元素覆盖最旧的元素

练习3:文本分析器

使用合适的容器实现文本分析功能:

  • 统计单词频率(unordered_map)
  • 找出最常出现的单词(priority_queue)
  • 按字母顺序显示单词(set)
  • 显示单词的上下文(multimap)

练习4:内存池分配器

为vector实现一个简单的内存池分配器:

  • 预分配大块内存
  • 按需提供小块内存
  • 减少内存分配开销
  • 支持自定义对齐

通过这些实践,你将深入理解STL容器的设计哲学和使用技巧,为编写高效的C++程序打下坚实基础。记住,选择合适的容器是程序设计的重要决策,需要根据具体需求和性能特征做出明智的选择。

相关推荐
云泽8082 小时前
C++ List 容器详解:迭代器失效、排序与高效操作
开发语言·c++·list
xlq223222 小时前
15.list(上)
数据结构·c++·list
a123560mh2 小时前
国产信创操作系统银河麒麟常见软件适配(MongoDB、 Redis、Nginx、Tomcat)
linux·redis·nginx·mongodb·tomcat·kylin
赖small强2 小时前
【Linux驱动开发】Linux MMC子系统技术分析报告 - 第二部分:协议实现与性能优化
linux·驱动开发·mmc
我不会插花弄玉3 小时前
排序【由浅入深-数据结构】
c语言·数据结构
guygg883 小时前
Linux服务器上安装配置GitLab
linux·运维·gitlab
Elias不吃糖3 小时前
总结我的小项目里现在用到的Redis
c++·redis·学习
百***35513 小时前
Linux(CentOS)安装 Nginx
linux·nginx·centos
tzhou644523 小时前
Linux文本处理工具:cut、sort、uniq、tr
linux·运维·服务器
AA陈超3 小时前
使用UnrealEngine引擎,实现鼠标点击移动
c++·笔记·学习·ue5·虚幻引擎