0
点赞
收藏
分享

微信扫一扫

C++ SLT Vector


C++ 中的 std::vector

一、底层结构与内存管理

1. 核心数据结构
  • 连续内存块:元素在内存中连续存储,支持指针算术运算,实现 O(1) 随机访问。
  • 三指针管理(简化模型):
  • _start:指向首个元素的地址(begin() 迭代器)。
  • _finish:指向最后一个有效元素的下一个位置(end() 迭代器),size = _finish - _start
  • _end_of_storage:指向已分配内存的末尾,capacity = _end_of_storage - _start
2. 动态扩容机制
  • size == capacity 时插入元素会触发扩容:
  • 分配新内存:通常扩容至原容量的 2 倍(不同编译器实现可能不同)。
  • 迁移数据:将旧元素拷贝/移动到新内存(涉及构造与析构)。
  • 释放旧内存:更新三指针指向新空间。
  • 均摊时间复杂度push_back() 的均摊复杂度为 O(1),因扩容频率随元素增长降低。
3. 迭代器与失效问题
  • 迭代器类型:随机访问迭代器(支持 +-[] 操作)。
  • 失效场景
  • 扩容后:所有迭代器、指针、引用失效(地址变更)。
  • 中间插入/删除:操作位置之后的迭代器失效。

二、常用函数详解

1. 构造与初始化

方法

示例

说明

默认构造

vector<int> v1;

创建空 vector

指定大小和初值

vector<int> v2(5, 10);

5 个元素,值均为 10

列表初始化

vector<int> v3 = {1, 2, 3};

C++11 初始化列表

范围构造

vector<int> v4(v3.begin(), v3.end());

复制另一容器的区间

2. 元素访问

函数

示例

特性

operator[]

int a = v[0];

不检查边界,效率高

at()

int b = v.at(1);

边界检查,越界抛 out_of_range

front()/back()

int f = v.front();

访问首/尾元素

data()

int* p = v.data();

返回底层数组指针(C++11)

3. 容量操作

函数

说明

size()

返回当前元素个数

capacity()

返回当前分配的内存容量

reserve(n)

预分配内存,避免多次扩容(如 reserve(1000)

shrink_to_fit()

释放多余内存(容量降至 size

empty()

判断容器是否为空

4. 修改操作

函数

示例

时间复杂度

push_back(val)

v.push_back(10);

尾部插入,均摊 O(1)

emplace_back(args)

v.emplace_back(10);

直接构造,避免拷贝(更高效)

pop_back()

v.pop_back();

尾部删除,O(1)

insert(pos, val)

v.insert(v.begin() + 1, 20);

中间插入,O(n)

erase(pos)

v.erase(v.begin());

中间删除,O(n)

clear()

v.clear();

清空元素(不释放内存)

resize(n)

v.resize(10);

调整元素个数

5. 迭代器与遍历

// 1. 下标遍历
for (size_t i = 0; i < v.size(); ++i) { 
    cout << v[i]; 
}

// 2. 迭代器遍历
for (auto it = v.begin(); it != v.end(); ++it) { 
    cout << *it; 
}

// 3. 范围 for 循环(C++11)
for (int num : v) { 
    cout << num; 
}

三、关键特性与性能优化

  1. 与数组/链表对比
  • 优势:随机访问 O(1)、缓存友好(连续内存)、自动内存管理。
  • 劣势:中间插入/删除效率低(需移动元素)。
  1. 性能优化技巧
  • 预分配内存reserve() 减少扩容开销。
  • 使用 emplace_back:避免临时对象构造(尤其对复杂类型)。
  • 避免中间修改:频繁插入/删除时考虑 std::liststd::deque
  1. 高级用法
  • 二维 vectorvector<vector<int>> matrix(5, vector<int>(5, 0));
  • 排序与查找:结合 <algorithm> 中的 sort()find()

四、总结

  • 适用场景:需频繁随机访问、尾部操作居多的场景(如数据缓存、矩阵运算)。
  • 慎用场景:频繁在中间位置插入/删除数据(链表更优)。
  • 设计哲学:以空间换时间,通过动态扩容和连续内存实现高效访问,是现代 C++ 高性能容器的代表。

vector 之键值对

在 C++ 标准库中,std::vector 本身不直接支持键值对(Key-Value)结构,但可以通过组合其他类型(如 std::pair 或自定义结构)间接实现类似功能。以下是详细分析:

1. std::vector 的默认设计

  • 本质std::vector 是一个动态数组容器,存储单一类型的元素(如 intstring 等)。
  • 内存布局:元素在内存中连续存储,支持通过索引(vec[i])高效随机访问(时间复杂度 O(1))。
  • 无键值概念:原生不支持通过“键”(Key)查找或管理数据,仅依赖下标索引。

2. 如何实现键值对功能?

虽然 std::vector 不直接支持键值对,但可通过以下方式模拟:

方法一:使用 std::pair 存储键值对

#include <vector>
#include <utility> // for std::pair

std::vector<std::pair<std::string, int>> vec;
vec.push_back(std::make_pair("apple", 10)); // 添加键值对
vec.push_back(std::make_pair("banana", 20));

// 遍历查找(线性搜索,O(n))
for (const auto& kv : vec) {
    if (kv.first == "apple") {
        std::cout << "Found: " << kv.second << std::endl;
    }
}

  • 特点
  • 键值对作为整体存储在连续内存中。
  • 查找需遍历,效率较低(O(n)),适合少量数据或频繁遍历场景。
方法二:自定义结构体

struct KeyValue {
    std::string key;
    int value;
};
std::vector<KeyValue> vec;
vec.push_back({"apple", 10});

  • 适用场景:需扩展更多字段(如时间戳、状态等)。

3. 与专用键值容器的对比

以下对比 std::vector<std::pair>std::map/std::unordered_map

特性

vector<std::pair>

std::map

std::unordered_map

内存连续性

连续存储,缓存友好

红黑树节点分散

哈希表桶分散

查找复杂度

O(n)(无序)/ O(log n)(有序+二分查找)

O(log n)(自动排序)

O(1) 平均(哈希表)

插入效率

尾部插入 O(1),中间插入 O(n)

O(log n)(需调整红黑树)

O(1) 平均(可能触发 rehash)

内存占用

⭐️ 低(仅需存储数据)

⭐️⭐️ 高(每个节点含指针)

⭐️⭐️ 高(桶+链表)

是否自动去重/排序

需手动维护

按键排序且去重

去重,无序

4. 适用场景分析

  • 推荐用 vector<std::pair> 的情况
  • 数据量小(≤1000),需频繁遍历或按索引访问(如渲染数据列表)。
  • 键值对一次性加载,后续仅需遍历(如配置文件读取)。
  • 内存敏感场景(嵌入式系统)。
  • 推荐用 map/unordered_map 的情况
  • 需高频按键查找、插入或删除(如缓存、字典)。
  • 需自动去重或排序(如词频统计)。
  • 数据量较大(>1000)且对查找效率敏感。

5. 注意事项

  1. 查找效率vector 的线性查找效率低,若需高效查找,需先排序再用 std::binary_search(复杂度 O(log n))。
  2. 插入开销:在 vector 中间插入键值对需移动后续元素,避免频繁操作。
  3. 线程安全:与所有 STL 容器一样,多线程环境下需手动加锁。

6.总结

std::vector 本身不原生支持键值对,但可通过 std::pair 或自定义结构间接实现。其优势在于内存紧凑和遍历高效,劣势是查找效率低。若需高频按键操作,应优先选择 std::map(有序)或 std::unordered_map(无序哈希表)。
简单来说:用索引访问选 vector,用键名查找选 map


举报

相关推荐

0 条评论