C++ 第四阶段 STL 容器 - 第一讲:详解 std::vector

发布于:2025-06-30 ⋅ 阅读:(16) ⋅ 点赞:(0)

目录

📌 一、std::vector 核心特性

📌 二、常用函数详解(附代码示例)

1. 构造函数

2. 元素访问

3. 容量操作

4. 修改操作

📌 三、性能分析与优化建议

1. 动态扩容机制

2. 随机访问 vs. 插入/删除性能

3. 迭代器失效问题

📌 四、常见陷阱与解决方案

1. 未初始化元素访问

2. 不必要的拷贝

📌 五、典型应用场景

1. 动态数组

2. 算法实现

3. 二维数组模拟

🧠 总结

C++从入门到入土学习导航_c++学习进程-CSDN博客


📌 一、std::vector 核心特性

std::vector 是 C++ STL 中最常用的 序列式容器,其底层基于 动态数组 实现,具有以下核心特性:

特性 说明
动态扩容 内存自动增长(默认翻倍策略),无需手动管理内存。
连续存储 元素存储在连续内存块中,支持指针偏移和随机访问。
随机访问 时间复杂度为 O(1),可通过下标或 at() 访问任意元素。
尾部操作高效 push_back()/pop_back() 时间复杂度为 O(1)(扩容时为 O(n))。
迭代器支持 提供随机访问迭代器,兼容 STL 算法(如 sortfind)。

📌 二、常用函数详解(附代码示例)

1. 构造函数

#include <vector>
#include <iostream>

int main() {
    // 1. 默认构造空 vector
    std::vector<int> v1;

    // 2. 构造指定大小和初始值
    std::vector<int> v2(5, 100);  // 5 个 100

    // 3. 迭代器构造
    std::vector<int> v3(v2.begin(), v2.end());  // 复制 v2 的内容

    // 4. 初始化列表(C++11)
    std::vector<int> v4 = {1, 2, 3, 4, 5};

    // 打印所有 vector 内容
    for (int i : v4) {
        std::cout << i << " ";
    }
    return 0;
}

2. 元素访问

std::vector<int> v = {10, 20, 30, 40, 50};

// 1. 下标访问(不检查越界)
std::cout << "v[2] = " << v[2] << "\n";  // 输出 30

// 2. at()(带边界检查)
std::cout << "v.at(3) = " << v.at(3) << "\n";  // 输出 40

// 3. front() / back()
std::cout << "front: " << v.front() << "\n";  // 输出 10
std::cout << "back: " << v.back() << "\n";    // 输出 50

// 4. data()(C++11,返回指针)
int* ptr = v.data();
std::cout << "*ptr = " << *ptr << "\n";  // 输出 10

3. 容量操作

std::vector<int> v = {1, 2, 3};

// 1. size() / capacity()
std::cout << "size: " << v.size() << ", capacity: " << v.capacity() << "\n";

// 2. reserve() 预分配内存
v.reserve(10);  // 扩容到至少 10 个元素的空间
std::cout << "capacity after reserve: " << v.capacity() << "\n";

// 3. resize() 调整元素个数
v.resize(5, 0);  // 调整为 5 个元素,不足用 0 填充
for (int i : v) {
    std::cout << i << " ";  // 输出 1 2 3 0 0
}
std::cout << "\n";

// 4. shrink_to_fit()(C++11)减少多余容量
v.shrink_to_fit();
std::cout << "capacity after shrink_to_fit: " << v.capacity() << "\n";

4. 修改操作

std::vector<int> v = {1, 2, 3, 4, 5};

// 1. 尾部插入/删除
v.push_back(6);           // 插入到末尾
v.pop_back();             // 删除末尾元素

// 2. emplace_back()(C++11,原地构造对象)
v.emplace_back(7);        // 更高效的插入(避免临时对象)

// 3. 插入/删除任意位置
auto it = v.insert(v.begin() + 2, 99);  // 在第 3 个位置插入 99
std::cout << "Inserted value: " << *it << "\n";  // 输出 99

v.erase(v.begin() + 1);   // 删除第 2 个元素

// 4. 清空
v.clear();
std::cout << "size after clear: " << v.size() << "\n";  // 输出 0

📌 三、性能分析与优化建议

1. 动态扩容机制

  • 默认策略:扩容时分配 2 倍当前容量 的新内存,拷贝旧数据,释放旧内存。
  • 性能影响:频繁扩容会导致:
    • 内存碎片:频繁分配/释放内存。
    • 拷贝开销:大对象拷贝成本高。
  • 优化方案
    • 预分配内存:使用 reserve() 避免多次扩容。
    • 批量插入:优先使用 emplace_back() 而非 push_back()
// 示例:预分配内存减少扩容次数
std::vector<int> v;
v.reserve(1000);  // 一次性分配 1000 个元素的空间
for (int i = 0; i < 1000; ++i) {
    v.push_back(i);  // 无需扩容
}

2. 随机访问 vs. 插入/删除性能

操作类型 时间复杂度 说明
随机访问 O(1) 利用指针偏移直接访问元素。
尾部插入/删除 O(1)(平均) 默认扩容时为 O(n)。
中间插入/删除 O(n) 需移动元素,性能较差。
查找(find) O(n) 无序数据需遍历;有序数据可用二分法。
// 示例:查找特定值
int target = 3;
auto it = std::find(v.begin(), v.end(), target);
if (it != v.end()) {
    std::cout << "Found at index: " << it - v.begin() << "\n";
} else {
    std::cout << "Not found\n";
}

3. 迭代器失效问题

  • 扩容导致迭代器失效push_back() 触发扩容后,所有迭代器失效。
  • 插入/删除导致迭代器失效insert()/erase() 后,迭代器需重新获取。
// 错误示例:扩容后迭代器失效
std::vector<int> v = {1, 2, 3};
auto it = v.begin();
v.push_back(4);  // 可能触发扩容,it 失效
std::cout << *it << "\n";  // 未定义行为!

// 正确做法:重新获取迭代器
it = v.begin();
std::cout << *it << "\n";  // 安全

📌 四、常见陷阱与解决方案

1. 未初始化元素访问

  • 错误示例reserve() 不初始化元素,直接访问会导致未定义行为。
  • 正确做法:使用 resize() 初始化元素。
std::vector<int> v;
v.reserve(10);          // 仅分配内存,未初始化元素
std::cout << v[5];      // 未定义行为!

v.resize(10);           // 初始化 10 个元素为 0
std::cout << v[5];      // 合法,输出 0

2. 不必要的拷贝

  • 推荐使用 emplace_back():直接构造对象,避免临时对象拷贝。
struct MyStruct {
    MyStruct(int a, int b) : x(a), y(b) {}
    int x, y;
};

std::vector<MyStruct> v;

// push_back 会创建临时对象再拷贝
v.push_back(MyStruct(1, 2));  // 两次构造(一次临时,一次拷贝)

// emplace_back 原地构造,避免拷贝
v.emplace_back(1, 2);  // 一次构造

📌 五、典型应用场景

1. 动态数组

  • 替代静态数组,支持运行时调整大小。
  • 适用于数据量不确定的场景(如读取用户输入、动态数据收集)。
std::vector<int> nums;
int n;
while (std::cin >> n) {
    nums.push_back(n);  // 动态添加元素
}

2. 算法实现

  • 与 STL 算法无缝兼容(如 sortfindtransform)。
#include <algorithm>

std::vector<int> v = {5, 2, 4, 1, 3};
std::sort(v.begin(), v.end());  // 排序
std::reverse(v.begin(), v.end());  // 反转

3. 二维数组模拟

  • 用于动态二维数组(如杨辉三角)。
std::vector<std::vector<int>> pascal(5);
for (int i = 0; i < 5; ++i) {
    pascal[i].resize(i + 1, 1);
    for (int j = 1; j < i; ++j) {
        pascal[i][j] = pascal[i-1][j] + pascal[i-1][j-1];
    }
}

🧠 总结

特性 适用场景 优势 注意事项
动态扩容 数据量不确定 自动管理内存 频繁扩容影响性能
连续存储 需要随机访问 缓存命中率高 中间插入/删除慢
尾部操作高效 栈/队列模拟 插入/删除 O(1) 扩容时迭代器失效
迭代器支持 STL 算法兼容 与 sortfind 等无缝对接 插入/删除后需更新迭代器

网站公告

今日签到

点亮在社区的每一天
去签到