在这里插入图片描述

全文连载前置回顾

  1. 1-6篇:开发环境、基础数据类型、运算符全集
  2. 7-10篇:分支结构、三大循环、数组、字符串(字符数组)
  3. 11-12篇:指针基础与进阶、动态内存分配
  4. 13-14篇:基础输入输出、结构体
  5. 15-16篇:函数基础与进阶
  6. 17篇:动态内存管理
  7. 18-26篇:名称空间、类与对象、继承、多态、运算符重载、面向对象设计
  8. 27-29篇:异常处理、文件IO、string类与stringstream

第17篇(动态内存管理)我们学会了用new手动分配数组,但每次都要自己管理大小、扩容、释放。STL(Standard Template Library——标准模板库)把这些工作自动化了:它提供了一套模板化的容器类,用泛型编程的方式管理任意类型的数据,内部自动处理内存分配和释放。


前言

什么是STL? STL是C++标准库的核心组件之一,包含四大部分:

  1. 容器(Container):存储数据的结构(vector、list、map、set等)
  2. 算法(Algorithm):操作容器数据的通用算法(sort、find、copy等)
  3. 迭代器(Iterator):连接容器和算法的桥梁(像指针一样遍历容器)
  4. 函数对象/适配器:灵活扩展算法行为

STL的核心设计理念是模板化 + 泛型编程:写一次模板代码,可以用在任意类型上(如 vector<int> 存int、vector<string> 存字符串、vector<Device> 存自定义类)。

三大序列容器

  • vector(向量/动态数组):连续内存,支持O(1)随机访问,尾部插入O(1),中间插入O(n)
  • list(双向链表):节点不连续,任何位置插入/删除都是O(1),但随机访问需要O(n)
  • deque(双端队列):分段连续内存,头尾插入/删除都是O(1),支持O(1)随机访问

一、vector:最常用的动态数组

vector在内部维护一块连续内存,像数组一样用下标访问,但可以自动扩容。

#include <iostream>
#include <vector>   // vector头文件
#include <string>
using namespace std;

struct SensorData
{
    int id;
    string name;
    double temperature;
    SensorData(int i = 0, string n = "", double t = 0)
        : id(i), name(n), temperature(t) {}
};

int main()
{
    // ===== 1. 初始化 =====
    cout << "=== 1. 初始化 ===" << endl;
    vector<int> v1;                // 空vector,存int
    vector<int> v2(10, 0);         // 10个0
    vector<int> v3 = {1, 2, 3, 4, 5}; // 初始化列表
    vector<string> vs(3, "Hello");  // 3个"Hello"字符串

    cout << "v1大小: " << v1.size() << endl;
    cout << "v3内容: ";
    for (size_t i = 0; i < v3.size(); i++)
        cout << v3[i] << " ";
    cout << endl;

    // ===== 2. 尾部插入:push_back =====
    cout << "\n=== 2. push_back尾部插入 ===" << endl;
    vector<SensorData> sensors;
    sensors.push_back(SensorData(1001, "加热炉A", 78.5));
    sensors.push_back(SensorData(1002, "冷却塔B", 28.3));
    sensors.push_back(SensorData(2001, "反应釜C", 125.8));
    cout << "插入后大小: " << sensors.size() << endl;

    // ===== 3. 访问元素 =====
    cout << "\n=== 3. 访问元素 ===" << endl;
    cout << "sensors[0]: ID=" << sensors[0].id
         << " " << sensors[0].name
         << " T=" << sensors[0].temperature << "度" << endl;

    // at()是安全访问(越界抛异常)
    try
    {
        cout << "sensors.at(10): " << sensors.at(10).name << endl;
    }
    catch (const out_of_range& e)
    {
        cout << "at(10)越界: " << e.what() << endl;
    }

    // front()/back()访问首尾
    cout << "第一个: " << sensors.front().name << endl;
    cout << "最后一个: " << sensors.back().name << endl;

    // ===== 4. 遍历 =====
    cout << "\n=== 4. 遍历vector ===" << endl;
    cout << "方式1: 用索引遍历" << endl;
    for (size_t i = 0; i < sensors.size(); i++)
    {
        cout << "  [" << i << "] ID=" << sensors[i].id
             << " " << sensors[i].name
             << " T=" << sensors[i].temperature << "度" << endl;
    }

    cout << "方式2: 用迭代器遍历(后续详解,这里先展示)" << endl;
    for (vector<SensorData>::iterator it = sensors.begin();
         it != sensors.end();
         ++it)
    {
        cout << "  ID=" << it->id << " " << it->name << endl;
    }

    // ===== 5. 删除元素 =====
    cout << "\n=== 5. 删除元素 ===" << endl;
    sensors.pop_back();  // 删除最后一个元素
    cout << "pop_back后大小: " << sensors.size() << endl;

    // 清空所有元素
    sensors.clear();
    cout << "clear后大小: " << sensors.size()
         << " 是否为空: " << (sensors.empty() ? "是" : "否") << endl;

    // ===== 6. 容量与预留 =====
    cout << "\n=== 6. 容量管理 ===" << endl;
    vector<int> data;
    cout << "初始: size=" << data.size() << " capacity=" << data.capacity() << endl;

    data.reserve(1000);  // 预留1000个元素的空间,避免多次重新分配
    cout << "reserve(1000)后: size=" << data.size()
         << " capacity=" << data.capacity() << endl;

    // 插入大量数据(因为预留了空间,不会触发重新分配)
    for (int i = 0; i < 500; i++) data.push_back(i);
    cout << "插入500个后: size=" << data.size()
         << " capacity=" << data.capacity() << endl;

    // resize改变元素个数(增大会构造新元素,减小会销毁多余元素)
    data.resize(100);
    cout << "resize(100)后: size=" << data.size()
         << " capacity=" << data.capacity() << endl;

    // ===== 7. 嵌套vector =====
    cout << "\n=== 7. 嵌套vector(二维数组) ===" << endl;
    vector<vector<int>> matrix(3, vector<int>(4, 0));  // 3x4矩阵,全0
    matrix[0][0] = 1;
    matrix[1][2] = 5;
    matrix[2][3] = 9;
    for (size_t row = 0; row < matrix.size(); row++)
    {
        for (size_t col = 0; col < matrix[row].size(); col++)
        {
            cout << matrix[row][col] << " ";
        }
        cout << endl;
    }

    return 0;
}

vector的内存模型

  • size:当前有多少个元素
  • capacity:实际分配的内存可以容纳多少个元素(>=size)
  • push_back时如果size达到capacity,会重新分配一块更大的内存(通常2倍),把旧数据拷贝过去,释放旧内存——这是vector偶尔"卡"的原因
  • reserve(n):手动预留容量,减少重新分配
  • resize(n):改变元素个数(实际大小)

二、list:双向链表

list由节点组成,每个节点包含数据和两个指针(指向前一个和后一个节点)。

#include <iostream>
#include <list>     // list头文件
#include <string>
using namespace std;

struct Task
{
    int id;
    string name;
    int priority;
    Task(int i, string n, int p) : id(i), name(n), priority(p) {}
};

int main()
{
    list<Task> tasks;

    // 1. 插入元素(push_back/push_front都很快,O(1))
    tasks.push_back(Task(1, "数据采集", 2));
    tasks.push_back(Task(2, "数据处理", 1));
    tasks.push_back(Task(3, "数据存储", 3));
    tasks.push_front(Task(0, "初始化", 1));  // 前端插入

    cout << "任务列表(共" << tasks.size() << "个):" << endl;
    for (list<Task>::iterator it = tasks.begin(); it != tasks.end(); ++it)
    {
        cout << "  ID=" << it->id << " [" << it->priority << "] " << it->name << endl;
    }

    // 2. 访问首尾元素
    cout << "\n第一个任务: " << tasks.front().name << endl;
    cout << "最后一个任务: " << tasks.back().name << endl;

    // 3. list不支持[]随机访问(因为节点不连续)
    // tasks[1] 编译错误!

    // 4. 插入/删除任意位置(找到迭代器后都是O(1))
    list<Task>::iterator it = tasks.begin();
    ++it;  // 跳到第二个元素
    tasks.insert(it, Task(99, "紧急任务", 0));  // 在第二个位置插入
    cout << "\n插入紧急任务后:" << endl;
    for (list<Task>::iterator it2 = tasks.begin(); it2 != tasks.end(); ++it2)
        cout << "  " << it2->name << " (优先级" << it2->priority << ")" << endl;

    // 5. 删除元素
    tasks.pop_front();  // 删除第一个
    tasks.pop_back();   // 删除最后一个
    cout << "\n删除首尾后剩 " << tasks.size() << " 个任务" << endl;

    // 6. list特有操作:合并/排序
    list<int> list1, list2;
    list1.push_back(1); list1.push_back(3); list1.push_back(5);
    list2.push_back(2); list2.push_back(4); list2.push_back(6);

    list1.merge(list2);  // 合并两个有序list(list2变为空)
    cout << "\n合并后:";
    for (list<int>::iterator it3 = list1.begin(); it3 != list1.end(); ++it3)
        cout << " " << *it3;
    cout << endl;

    // 7. 反转
    list1.reverse();
    cout << "反转后:";
    for (list<int>::iterator it4 = list1.begin(); it4 != list1.end(); ++it4)
        cout << " " << *it4;
    cout << endl;

    return 0;
}

三、deque:双端队列

deque(Double-Ended QUEue)是vector和list的折中方案:分段连续内存,支持随机访问,头尾插入都很快。

#include <iostream>
#include <deque>
#include <string>
using namespace std;

int main()
{
    deque<string> msgQueue;  // 消息队列

    // 1. 首尾都可高效插入
    msgQueue.push_back("消息1: 正常数据");      // 后端插入
    msgQueue.push_back("消息2: 正常数据");
    msgQueue.push_front("优先级消息: 紧急报警");  // 前端插入(高优先级)
    msgQueue.push_front("优先级消息: 系统重启");

    // 2. 支持随机访问(像vector一样用[])
    cout << "队列大小: " << msgQueue.size() << endl;
    for (size_t i = 0; i < msgQueue.size(); i++)
        cout << "  [" << i << "] " << msgQueue[i] << endl;

    // 3. 首尾都可删除
    cout << "\n取出第一个: " << msgQueue.front() << endl;
    msgQueue.pop_front();  // 取出后删除
    cout << "取出最后一个: " << msgQueue.back() << endl;
    msgQueue.pop_back();

    cout << "剩余 " << msgQueue.size() << " 条:" << endl;
    for (size_t i = 0; i < msgQueue.size(); i++)
        cout << "  " << msgQueue[i] << endl;

    return 0;
}

四、容器对比与选择

特性 vector deque list
内存模型 连续内存 分段连续 节点+指针
随机访问([]) O(1) O(1) 不支持(O(n))
尾部插入/删除 O(1)(平均) O(1) O(1)
前端插入/删除 O(n)(很慢) O(1) O(1)
中间插入/删除 O(n) O(n) O(1)(已知迭代器)
遍历速度 最快(CPU缓存友好) 较慢(指针跳转,缓存不命中)
元素迭代器失效 插入/删除后可能全部失效 仅被删除元素失效 仅被删除元素失效

选择建议

  • 大多数场景用vector——默认首选,随机访问快,CPU缓存命中率高
  • 需要两端都频繁插入/删除(如队列、任务池)→ deque
  • 需要中间频繁插入/删除且不需要随机访问→ list
  • 数据量很小(几十个元素)时差异不明显,按习惯选即可

五、工业场景实战:设备数据用vector管理

#include <iostream>
#include <fstream>
#include <sstream>
#include <vector>
#include <string>
#include <cstdlib>
#include <ctime>
#include <cstring>
using namespace std;

struct DeviceReading
{
    int deviceId;
    string name;
    double temperature;
    double pressure;
    int timestamp;
};

// 生成模拟数据
vector<DeviceReading> generateReadings(int count)
{
    vector<DeviceReading> readings;
    readings.reserve(count);  // 预留容量,避免多次重新分配

    string names[] = {"加热炉A", "加热炉B", "冷却塔", "反应釜", "阀门控制器"};
    for (int i = 0; i < count; i++)
    {
        DeviceReading r;
        r.deviceId = 1000 + (i % 5) * 10;
        r.name = names[i % 5];
        r.temperature = 25.0 + (rand() % 1000) / 10.0;
        r.pressure = 0.5 + (rand() % 150) / 100.0;
        r.timestamp = time(0) + i;
        readings.push_back(r);
    }
    return readings;
}

// 统计:返回报警记录数
int analyzeAndReport(const vector<DeviceReading>& data)
{
    int alertCount = 0;
    double sumT = 0, sumP = 0;
    double maxT = 0;
    string maxTDevice;

    // const引用遍历(只读,避免拷贝)
    for (size_t i = 0; i < data.size(); i++)
    {
        const DeviceReading& r = data[i];  // 引用,不拷贝
        sumT += r.temperature;
        sumP += r.pressure;
        if (r.temperature > maxT)
        {
            maxT = r.temperature;
            maxTDevice = r.name;
        }
        if (r.temperature > 80.0 || r.pressure > 1.5)
        {
            alertCount++;
        }
    }

    cout << "\n=== 统计报告 ===" << endl;
    cout << "总记录数: " << data.size() << endl;
    cout << "平均温度: " << sumT / data.size() << "度" << endl;
    cout << "平均压力: " << sumP / data.size() << "MPa" << endl;
    cout << "最高温度: " << maxT << "度 (" << maxTDevice << ")" << endl;
    cout << "报警记录: " << alertCount << "条" << endl;
    return alertCount;
}

// 过滤:返回温度高于阈值的记录
vector<DeviceReading> filterHighTemp(const vector<DeviceReading>& data, double threshold)
{
    vector<DeviceReading> result;
    for (size_t i = 0; i < data.size(); i++)
    {
        if (data[i].temperature > threshold)
        {
            result.push_back(data[i]);
        }
    }
    return result;
}

// 导出到CSV
bool exportToCSV(const vector<DeviceReading>& data, const string& filename)
{
    ofstream out(filename.c_str());
    if (!out.is_open()) return false;
    out << "DeviceID,Name,Temperature,Pressure,Timestamp" << endl;
    for (size_t i = 0; i < data.size(); i++)
    {
        const DeviceReading& r = data[i];
        out << r.deviceId << "," << r.name << ","
            << r.temperature << "," << r.pressure << ","
            << r.timestamp << endl;
    }
    return true;
}

int main()
{
    srand(time(0));

    // 1. 生成数据
    cout << "=== 生成模拟数据 ===" << endl;
    vector<DeviceReading> data = generateReadings(100);
    cout << "已生成 " << data.size() << " 条记录" << endl;

    // 2. 分析全部数据
    analyzeAndReport(data);

    // 3. 过滤高温记录
    cout << "\n=== 过滤温度>80度的记录 ===" << endl;
    vector<DeviceReading> highTemp = filterHighTemp(data, 80.0);
    cout << "共有 " << highTemp.size() << " 条高温记录" << endl;
    for (size_t i = 0; i < highTemp.size() && i < 10; i++)
    {
        cout << "  ID=" << highTemp[i].deviceId
             << " " << highTemp[i].name
             << " T=" << highTemp[i].temperature << "度" << endl;
    }

    // 4. 导出
    cout << "\n=== 导出数据 ===" << endl;
    if (exportToCSV(data, "readings.csv"))
        cout << "已导出到 readings.csv" << endl;

    // 5. vector的一些其他操作
    cout << "\n=== vector进阶操作 ===" << endl;
    cout << "当前容量: " << data.capacity() << endl;

    // 在第5个位置插入一条(比较慢,O(n))
    data.insert(data.begin() + 5, DeviceReading{9999, "手动插入", 99.9, 2.0, time(0)});
    cout << "insert后大小: " << data.size() << endl;

    // 删除第3条
    data.erase(data.begin() + 2);
    cout << "erase后大小: " << data.size() << endl;

    // 缩小容量(把空着的内存释放掉,C++11)
    data.shrink_to_fit();
    cout << "shrink_to_fit后容量: " << data.capacity() << endl;

    return 0;
}

六、独家C#语法对照

对比维度 C++ C# 工业开发差异
动态数组 std::vector<T> List<T> 两者核心特性几乎一致
声明方式 vector<int> v(10); List<int> v = new List<int>(10); C#需要显式new
添加元素 v.push_back(x); v.Add(x); API类似
元素访问 v[i]v.at(i) v[i](越界抛异常) C#默认行为类似at()
容量管理 v.capacity()/v.reserve(n)/v.resize(n) v.Capacity/v.Capacity=n/v.Count 类似,C#属性代替方法
链表 std::list<T> LinkedList<T> API差异较大,C#ListNode独立操作
双端队列 std::deque<T> C#无直接对应,可用LinkedList<T>模拟 C#原生没有双端队列类型
元素类型 任意类型(模板实例化) 任意类型(泛型) 泛型概念类似,实现机制不同

七、重读专属:STL容器六大常见错误

  • 错误1:迭代器失效后继续使用 — vector插入/删除后,旧迭代器可能指向已释放的内存。操作后应重新获取迭代器
  • 错误2:用int接收size()返回值 — size()返回size_t(无符号整数)。for (int i = 0; i < v.size(); i++) 没问题,但 v.size() - 1 在空容器时是极大值。最好用 size_t 做索引
  • 错误3:在循环中用[]越界访问vector[i] 越界不抛异常(类似C数组),是未定义行为。想安全访问用 at(i) 或自己判断边界
  • 错误4:频繁在vector中间插入/删除 — 这是O(n)操作,大量元素时非常慢。如果频繁在中间操作,应考虑用list(但list不支持随机访问)
  • 错误5:传vector到函数不用引用void func(vector<int> v) 会拷贝整个vector,效率极低。应该用 void func(const vector<int>& v)(只读)或 void func(vector<int>& v)(需修改)
  • 错误6:在循环中push_back导致迭代器失效for (auto it = v.begin(); it != v.end(); ++it) { v.push_back(x); } 第一次push_back后it可能失效,导致无限循环或崩溃。循环修改容器要特别小心

八、原书课后习题解析

习题:读取一行整数,用vector存储并反转输出
#include <iostream>
#include <vector>
#include <sstream>
#include <string>
using namespace std;

int main()
{
    cout << "请输入一行整数(空格分隔): ";
    string line;
    getline(cin, line);

    // 用stringstream解析
    istringstream iss(line);
    vector<int> numbers;
    int num;
    while (iss >> num)  // 逐个读入整数
        numbers.push_back(num);

    cout << "共读取 " << numbers.size() << " 个整数" << endl;

    // 正向输出
    cout << "正向: ";
    for (size_t i = 0; i < numbers.size(); i++)
        cout << numbers[i] << " ";
    cout << endl;

    // 反向输出
    cout << "反向: ";
    for (int i = (int)numbers.size() - 1; i >= 0; i--)
        cout << numbers[i] << " ";
    cout << endl;

    // 求最大值、最小值、平均值
    if (!numbers.empty())
    {
        int minVal = numbers[0], maxVal = numbers[0];
        double sum = 0;
        for (size_t i = 0; i < numbers.size(); i++)
        {
            if (numbers[i] < minVal) minVal = numbers[i];
            if (numbers[i] > maxVal) maxVal = numbers[i];
            sum += numbers[i];
        }
        cout << "最小: " << minVal << ", 最大: " << maxVal
             << ", 平均: " << sum / numbers.size() << endl;
    }

    return 0;
}

核心考点:vector作为动态数组使用、push_back动态增长、size_t索引遍历、stringstream解析输入。注意反向遍历时size_t是无符号,直接i = size-1i>=0会变成死循环(无符号永远>=0),所以示例中用了int。后续学了STL迭代器和反向迭代器后,这类操作会更简洁。

本篇总结

  • vector:连续内存、O(1)随机访问、尾部操作快、中间操作慢。默认首选
  • list:节点链表、O(1)任意位置插入删除(已知迭代器)、不支持随机访问。特殊场景使用
  • deque:分段连续、两端O(1)插入删除、支持随机访问。队列/任务池场景
  • 通用操作push_back尾部插入、pop_back尾部删除、size()元素个数、empty()是否为空、clear()清空、front()/back()访问首尾
  • 容量管理reserve(n)预留空间减少重新分配、capacity()实际容量、shrink_to_fit()释放闲置空间
  • 传参约定总是用引用传vector,只读加const——避免昂贵的深拷贝
  • 迭代器注意:vector插入/删除后旧迭代器可能失效(后续详细讨论)

下篇预告

下一篇第三十一篇:STL算法(Algorithm)。学习STL中几十种通用算法——排序sort、查找find、遍历for_each、拷贝copy、计数count、累加accumulate等——配合容器使用,让数据处理代码更简洁高效。

Logo

汇聚全球AI编程工具,助力开发者即刻编程。

更多推荐