Vector 增长变化

VS 2019 亲测 ,绝对详细。

    std::vector<char> v6;
    auto v6_capacity = v6.capacity();
    std::cout << "[V6]:" << std::endl;
    std::cout << "Size:" << v6.size() << "  MaxSize" << v6.max_size() << std::endl;
    std::cout << "Capacity:" << v6.capacity() << std::endl;
    for (int i = 0; i < 1000; i++)
    {
        v6.push_back('1'); //capacity 1, size 1, add allocator 1
        if (v6.capacity() - v6_capacity)
        {
            std::cout << "Size:" << v6.size() << std::endl;
            std::cout << "Capacity:" << v6.capacity() << std::endl;
            std::cout << "add allocated apacity:" << v6.capacity() - v6_capacity << std::endl << std::endl;
        }
        v6_capacity = v6.capacity();
    }
[V6]:
Size:0  MaxSize2147483647
Capacity:0
Size:1
Capacity:1
add allocated apacity:1

Size:2
Capacity:2
add allocated apacity:1

Size:3
Capacity:3
add allocated apacity:1

Size:4
Capacity:4
add allocated apacity:1

Size:5
Capacity:6
add allocated apacity:2

Size:7
Capacity:9
add allocated apacity:3

Size:10
Capacity:13
add allocated apacity:4

Size:14
Capacity:19
add allocated apacity:6

Size:20
Capacity:28
add allocated apacity:9

Size:29
Capacity:42
add allocated apacity:14

Size:43
Capacity:63
add allocated apacity:21

Size:64
Capacity:94
add allocated apacity:31

Size:95
Capacity:141
add allocated apacity:47

Size:142
Capacity:211
add allocated apacity:70

Size:212
Capacity:316
add allocated apacity:105

Size:317
Capacity:474
add allocated apacity:158

Size:475
Capacity:711
add allocated apacity:237

Size:712
Capacity:1066
add allocated apacity:355

刚刚做了一个实验,有人说是增长50%。但是!!!重点来了。具体多少可以自己算,毕竟标准库没有规定,VS2019的capacity增长结果来看:
+1(0.5进位 多计算0.5)A
+1(0.5舍去不进位 补回0.5)B
+1(0.5进位 多计算0.5)A
+1(0.5舍去不进位 补回0.5)B
+2(1整除) 0
+3(1.5进位 多计算0.5) A
+4(整除)
+6(整除)
+9(不进位 补回0.5) B
+14(整除)
+21(进位 多计算0.5) A
+31(不进位 补回0.5) B
+47(进位 多计算0.5) A
+70(整除)
+105(不进位 补回0.5) B
+158(整除)
+237(进位 多计算0.5) A
+355(不进位 补回0.5) B
. 如果前一次4舍5入进位后,下次就4舍5不入,不进位。

为什么要在意这个增长速度呢?

当Vector里面放的东西足够多,足够大时。就会影响效率。
因为Vector 每次增长,会创建一个新的 Vector也就是增长大小以后的Vector,然后会把所有东西都copy过去。然后清除原来的。这样就会造成很多很多次copy,如果是对象会进入多次拷贝构造。
当然如果自定义类型是支持移动构造函数的化,vector的copy会优先使用移动构造来交换。

<think>我们正在讨论C++ vector的动态扩容机制。根据引用内容,vector的扩容机制主要涉及以下几个方面: 1. **扩容触发条件**:当向vector中添加元素(如push_back)导致当前大小(size)等于容量(capacity)时,就会触发扩容[^3]。 2. **扩容策略**:通常采用固定倍数扩容(如2倍)[^2][^3],但具体实现可能因编译器和版本而异。 3. **扩容过程**: - 分配新的内存空间(大小为原容量的固定倍数) - 将原有元素移动到新空间(逐个拷贝或移动) - 释放原有内存空间 - 更新内部指针(起始位置、结束位置和容量指针)[^3] 4. **扩容对迭代器的影响**:扩容后元素的内存地址发生变化,导致原有迭代器失效[^5]。 ### 扩容机制详解 #### 1. 扩容倍数 - **常见策略**:大多数标准库实现(如GCC的libstdc++、Clang的libc++)采用**2倍扩容**(geometric growth)[^2][^3]。 - **数学原理**:假设初始容量为$c$,经过$k$次扩容后总容量为$c \times 2^k$。这种策略的均摊时间复杂度为$O(1)$(详见下文分析)。 #### 2. 扩容过程示例 参考用户提供的代码[^1]: ```cpp #include <iostream> #include <vector> int main() { std::vector<int> vec; int capacity = -1; std::cout << "size: " << vec.size() << " capacity: " << vec.capacity() << std::endl; for (int i = 0; i < 500; i++) { vec.push_back(i); if (capacity != vec.capacity()) { std::cout << "size: " << vec.size() << " capacity: " << vec.capacity() << std::endl; capacity = vec.capacity(); } } return 0; } ``` 运行结果可能如下(不同编译器输出不同): ``` size: 0 capacity: 0 size: 1 capacity: 1 // 插入第一个元素,扩容至1 size: 2 capacity: 2 // 插入第二个元素,扩容至2 size: 3 capacity: 4 // 插入第三个元素,扩容至4 size: 5 capacity: 8 // 插入第五个元素,扩容至8 size: 9 capacity: 16 // ... ... size: 17 capacity: 32 size: 33 capacity: 64 size: 65 capacity: 128 size: 129 capacity: 256 size: 257 capacity: 512 ``` **规律**:每次扩容时,新容量 = max(当前容量×2, 所需最小容量)[^3]。 #### 3. 时间复杂度分析 - **单次插入的最坏情况**:$O(n)$(需要移动所有元素) - **均摊时间复杂度**:$O(1)$(使用均摊分析法) **证明**(简化版): 设初始容量为1,插入$n$个元素的总操作次数(包括复制元素)为: $$ T(n) = 1 + 2 + 4 + ... + 2^{\lfloor \log_2 n \rfloor} + n \approx 2n $$ > 解释: > - 扩容时的复制操作:第$i$次扩容复制$2^i$个元素($i=0,1,2,...,k$,其中$2^k < n \leq 2^{k+1}$) > - 总复制量:$\sum_{i=0}^{k} 2^i = 2^{k+1} - 1 < 2n$ > - 总操作量 = 复制操作 + 插入操作 < $2n + n = 3n$ > 因此每次插入的均摊成本为$O(1)$[^4]。 #### 4. 空间复杂度 - 最坏情况:$O(n)$(与元素数量线性相关) - 实际占用空间:$O(n)$(容量最多为$2n$) #### 5. 迭代器失效问题 扩容后元素被移动到新内存地址,导致: - 所有迭代器(包括begin/end)、引用和指针失效[^5] - 示例: ```cpp std::vector<int> v = {1,2,3}; auto it = v.begin(); v.push_back(4); // 可能触发扩容,导致it失效 // 错误!不能使用失效的迭代器 std::cout << *it << std::endl; ``` ### 不同实现的扩容策略 | 标准库实现 | 扩容倍数 | 特殊规则 | |------------------|----------|------------------------------| | GCC (libstdc++) | 2倍 | 初始空容量为0 | | Clang (libc++) | 2倍 | 初始空容量为0 | | MSVC (VS 2022) | 1.5倍 | 初始空容量为0,之后按1.5倍增 | > 注:C++标准未规定具体倍数,由实现决定[^3]。 ### 性能优化建议 1. **预分配空间**:若已知元素数量$n$,提前调用`reserve(n)`避免多次扩容[^3]。 2. **避免在循环中插入**:多次`push_back`可能触发多次扩容。 3. **使用移动语义**:对于复杂对象,实现移动构造函数减少复制开销。 ### 手动实现动态数组(简化版) 参考C语言实现思路[^1]: ```c typedef struct { int* data; size_t size; size_t capacity; } IntVector; void push_back(IntVector* vec, int value) { if (vec->size == vec->capacity) { size_t new_cap = (vec->capacity == 0) ? 1 : vec->capacity * 2; int* new_data = realloc(vec->data, new_cap * sizeof(int)); if (!new_data) abort(); vec->data = new_data; vec->capacity = new_cap; } vec->data[vec->size++] = value; } ``` ### 总结 | 关键特性 | 说明 | |------------------|----------------------------------------------------------------------| | 触发条件 | size == capacity 时插入新元素 | | 扩容倍数 | 通常为2倍(GCC/Clang)或1.5倍(MSVC) | | 时间复杂度 | 单次插入最坏$O(n)$,均摊$O(1)$ | | 空间复杂度 | $O(n)$ | | 迭代器失效 | 扩容后所有迭代器、引用、指针失效 | | 优化方法 | 使用`reserve()`预分配空间 | --- §§相关问题§§ 1. 为什么C++ vector选择2倍(或1.5倍)扩容而不是固定增量扩容? 2. 如何验证当前编译器使用的vector扩容策略? 3. vector的`resize()`和`reserve()`方法有什么区别? 4. 在频繁插入元素的场景下,如何避免vector扩容导致的性能下降? 5. 其他STL容器(如deque、list)的存储机制与vector有何不同?</think>### C++ vector 动态扩容机制详解 C++ 中的 `std::vector` 采用动态数组实现,其核心特性是**自动扩容**机制。当元素数量超过当前容量时,vector 会自动分配更大的内存空间并迁移数据。以下是详细机制分析: --- #### 1. **扩容触发条件** - 当调用 `push_back()`、`insert()` 或 `emplace_back()` 等方法添加新元素时,如果当前元素数量(`size()`)等于当前容量(`capacity()`),则会触发扩容[^3]。 - **示例**: ```cpp std::vector<int> vec; vec.push_back(1); // size=1, capacity=1(首次触发) vec.push_back(2); // size=2 > capacity=1 → 扩容 ``` --- #### 2. **扩容策略:固定倍数增长** vector 采用**几何增长(Geometric Growth)**策略,通常按当前容量的固定倍数扩容: - **常见倍数**: - GCC/Clang:**2 倍扩容**(主流实现)[^2][^3] - MSVC:**1.5 倍扩容**(Visual Studio 实现) - **扩容过程**: 1. 分配新内存(大小为旧容量的 $k$ 倍) 2. 将旧元素**逐个移动或拷贝**到新内存 3. 释放旧内存 4. 更新容量指针[^3] --- #### 3. **扩容过程示例 通过代码观察扩容行为[^1]: ```cpp #include <iostream> #include <vector> int main() { std::vector<int> vec; int prev_cap = -1; for (int i = 0; i < 10; i++) { vec.push_back(i); if (prev_cap != vec.capacity()) { std::cout << "Size: " << vec.size() << " | Capacity: " << vec.capacity() << std::endl; prev_cap = vec.capacity(); } } } ``` **输出示例(GCC)**: ``` Size: 1 | Capacity: 1 // 初始 Size: 2 | Capacity: 2 // 2倍扩容 Size: 3 | Capacity: 4 // 2倍扩容 Size: 5 | Capacity: 8 // 2倍扩容 Size: 9 | Capacity: 16 // 2倍扩容 ``` --- #### 4. **时间复杂度分析** - **单次插入最坏情况**:$O(n)$(需移动所有元素) - **均摊时间复杂度**:$O(1)$(通过均摊分析证明) **数学证明**(以2倍扩容为例): 设初始容量为 $1$,插入 $n$ 个元素的总操作次数为: $$ T(n) = 1 + 2 + 4 + \cdots + 2^{\lfloor \log_2 n \rfloor} + n \leq 2n $$ 均摊到每次插入:$\frac{T(n)}{n} \leq 2 = O(1)$[^4]。 --- #### 5. **关键特性** - **迭代器失效**:扩容后元素地址改变,所有迭代器、指针、引用失效[^5]。 - **空间利用率**:容量总是 $2^k$ 形式,可能造成最多 **50% 的空间浪费**(如 capacity=8 时实际只用 5 个位置)。 - **默认初始容量**:空 vector 的 `capacity()` 通常为 0[^1]。 --- #### 6. **优化建议** 1. **预分配空间**:若已知元素数量 $n$,提前调用 `reserve(n)` 避免多次扩容[^3]。 ```cpp std::vector<int> vec; vec.reserve(100); // 一次性分配100个元素空间 ``` 2. **避免频繁插入**:批量插入优于单元素多次插入。 3. **移动语义**:对复杂对象使用 `std::move` 减少拷贝开销。 --- #### 不同编译器实现对比 | 编译器 | 扩容倍数 | 初始容量 | 扩容触发条件 | |-------------|----------|----------|--------------------| | GCC/Clang | 2.0× | 0 | size == capacity | | MSVC | 1.5× | 0 | size == capacity | ---
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值