C++ std::vector: vector中元素的存储方式 size、capacity、reserve、resize的使用

本文详细解读vector对象的内存管理,包括元素连续存储、size()和capacity()的区别,reserve()预分配空间与resize()调整大小的作用。通过实例演示如何正确运用这些函数进行内存管理和容器操作。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

1. vector的对象的存储方式

为了支持随机访问,vector将元素连续存储–每个元素紧挨着前一个元素存储。容器中元素是连续存储的,且容器的大小是可变的。

在容器中增加元素时。vector根据存储元素的大小,在内存上申请一个空间,用于存储数据,空间的大小通常会大于所存储元素的实际大小,并且预留出一部分预留的空间,以便再次增加数据时,可以不用重新开辟空间。

当容器再次增加新的元素后,首先判断预留的空间是否够用,如果够用直接在预留空间中存储。如果预留的空间不够,需要在内存中开辟一整块新的更大的空间,并将vector原来的存储的数据拷贝过来,存储到新的内存中,然后在新的内存中增加需要增加的元素,这样保证存储的空间是连续的。所开的空间会预留出一部分空间,以便后续增加数据。

理解了vector的对象的存储方式,很轻松理解size、capacity、reserve、resize的使用。

2. size()和capacity()

size(): 返回vector中存放元素的实际数量( 实际存储元素的个数)。返回值为size_type(无符号整数类型)

capacity(): 返回vector在内存中,开辟空间的容量(最多能放所少个元素)。返回值为size_type(无符号整数类型)

例子:

#include <iostream>
#include <vector>
int main()
{
    std::vector<int> vecInt;
    
    std::cout << vecInt.size() << std::endl;	// 输出的结果为0
    std::cout << vecInt.capacity() << std::endl;	// 输出的结果为0 

    for(int i = 0; i < 10 ; ++i)
    {
        vecInt.push_back(i);
    }

    std::cout << vecInt.size() << std::endl;	// 输出的结果为10,容器中存了10个元素
    std::cout << vecInt.capacity() << std::endl; // 输出的结果为16,容器在内存开辟空间的容量(包含预留空间)
}

3. reserve()和resize()

reserve(): 在内存上指定容器开辟空间的容量大小。

  • 如果容器目前在内存上开辟空间的容量大于指定的容量,则不做处理直接返回。
  • 如果容器目前在内存上开辟空间的容量小于指定的容量,则在内存上开辟指定容量大小的空间,并将原来的数据拷贝到新的内存中。(相当于容器扩容)

resize():设置容器中的元素。有两个参数,一个参数表示指定元素的数量。另一个可选参数表示,指定元素初始化的值。如果不传入第二个参数,则使用默认值初始化。

  • 如果容器中的元素数量大于resize中设置的值,则将后面多出的元素删除。
  • 如果容器中的元素数量小于resize中设置的值,则在原来的元素后面新增新的元素,如果没有指定值,则按照默认值初始化。

例子:

#include <iostream>
#include <vector>
int main()
{
    std::vector<int> vecInt;

    std::cout << vecInt.size() << std::endl;	// 输出的结果为0
    std::cout << vecInt.capacity() << std::endl;	// 输出的结果为0 

    vecInt.reserve(10);     // 在内存上开辟容量为10的空间
     
    std::cout << vecInt.size() << std::endl;	// 输出的结果为0
    std::cout << vecInt.capacity() << std::endl;	// 输出的结果为10

    vecInt.resize(20,1);    //  将容器初始化20个元素,每个元素的默认值为1

    std::cout << vecInt.size() << std::endl;	// 输出的结果为20,容器中存了20个元素
    std::cout << vecInt.capacity() << std::endl; // 输出的结果为20,容器在内存开辟空间的容量
}

注意: 在执行resize时,如果容器中在内存中,所开的空间不够,会自动扩容。不一定非要执行reserve(),提前开辟空间。在容器中使用其他函数添加元素也是如此。

<think>我们正在讨论`std::vector`的`resize`方法是否会引发内存拷贝。根据引用内容,我们可以分析`vector`的内部实现和`resize`的行为。 首先,从引用[1]和引用[3]中可以看到,`vector`通常有三个成员指针:`_start`(指向内存块的起始)、`_finish`(指向最后一个有效元素的下一个位置)和`_end_of_storage`(指向内存块末尾的下一个位置)。当我们调用`resize`时,可能会改变`vector`的大小(即`size()`),这可能会涉及内存的重新分配和元素的拷贝。 具体来说,`resize`操作可能有两种情况: 1. 当新的大小小于或等于当前容量(`capacity()`)时,`resize`只需要调整`_finish`指针的位置,并可能销毁多余的元素(如果缩小)或构造新元素(如果扩大且新元素需要初始化)。这种情况下,不会重新分配内存,因此不会发生整个内存块的拷贝。 2. 当新的大小大于当前容量时,`vector`需要重新分配一块更大的内存,然后将原有元素拷贝(或移动)到新的内存中,并添加新的元素(如果需要)。这个过程会引发内存拷贝(或移动),因为原有元素需要被复制到新的内存位置。 引用[3]中给出了一个拷贝构造函数的示例,其中在构造时如果容量不足,会先调用`reserve`来分配足够的内存,然后逐个拷贝元素。类似地,`resize`在需要扩容时也会触发类似的过程。 引用[5]提到了使用`resize`来初始化二维向量的例子,其中每一行都调用`resize`来设置列数。这里虽然讨论的是二维向量,但同样适用于一维:`resize`会改变大小,并可能涉及内存分配和元素初始化。 因此,我们可以得出结论: - 如果`resize`导致`vector`的容量增加(即新大小大于当前容量),那么会发生内存重新分配,并引发原有元素的拷贝(或移动)以及新元素的构造。 - 如果`resize`没有导致容量增加(即新大小小于或等于当前容量),则不会发生内存重新分配,因此不会引发整个内存块的拷贝。但是,如果新大小大于当前大小,那么新添加的元素会被构造(可能使用默认值或指定值);如果新大小小于当前大小,那么多余的元素会被销毁。 注意:在C++11以后,如果元素类型支持移动语义,那么重新分配内存时可能会使用移动构造而不是拷贝构造,这通常更高效。 **总结**: `std::vector::resize`在需要扩容时会引发内存重新分配和元素拷贝(或移动),而在不需要扩容时不会引发整个内存块的拷贝。 §§相关问题§§ 1. 如何避免`std::vector`在`resize`时发生内存拷贝? 2. `std::vector`的`resize`和`reserve`方法有什么区别? 3. 在C++中,`std::vector`扩容时内存重新分配的具体过程是怎样的? 4. 如何减少`std::vector`扩容带来的性能开销? 5. `std::vector`的移动构造函数是如何工作的?</think>### C++ `std::vector::resize()` 内存拷贝行为分析 `std::vector::resize()` 的内存行为取决于操作类型和元素特性,具体分析如下: #### **1. 缩小操作(新大小 < 当前大小)** ```cpp std::vector<int> vec = {1, 2, 3, 4, 5}; vec.resize(3); // 缩小到前3个元素 ``` - **内存行为**:不会重新分配内存或拷贝元素 - **实现原理**: - 仅调整 `_finish` 指针位置(如引用[1]所述) - 销毁多余元素(调用析构函数) - `_start` 和 `_end_of_storage` 指针不变[^1][^3] - ✅ **无内存拷贝** #### **2. 扩大操作(新大小 ≤ 当前容量)** ```cpp vec.reserve(10); // 预分配容量 vec.resize(8); // 在预分配空间内扩大 ``` - **内存行为**: - 不会重新分配内存 - 在现有内存空间构造新元素 - **实现原理**: - 移动 `_finish` 指针(引用[1]的 `_finish = _start + new_size`) - 调用新元素的构造函数(如 `int` 默认初始化为0) - ✅ **无内存拷贝**(仅构造新元素) #### **3. 扩容操作(新大小 > 当前容量)** ```cpp std::vector<int> vec = {1, 2, 3}; vec.resize(10); // 超出当前容量 ``` - **内存行为**:**触发内存拷贝** - **实现过程**(如引用[3]的拷贝构造函数流程): 1. 分配新内存块(大小通常为 `2*capacity`) 2. 拷贝原元素到新内存: ```cpp // 伪代码(参考引用[3]) T* new_start = allocator.allocate(new_capacity); std::memcpy(new_start, _start, sizeof(T)*old_size); // 平凡类型 // 或 for循环拷贝构造(非平凡类型) ``` 3. 构造新增元素 4. 释放旧内存 5. 更新指针:`_start`/`_finish`/`_end_of_storage`[^1][^3] - ⚠️ **触发全量内存拷贝** #### **关键影响因素** 1. **元素类型特性**: - 平凡可拷贝类型(如 `int`):可能用 `memcpy`(引用[3]) - 非平凡类型(如 `std::string`):必须调用拷贝构造函数 2. **扩容策略**: - 常见策略:容量翻倍(避免频繁扩容) - 具体实现依赖标准库版本(如 GCC 的 `libstdc++` 或 LLVM 的 `libc++`) #### **内存操作验证示例** ```cpp #include <vector> #include <iostream> struct Tracker { static int copies; Tracker() {} Tracker(const Tracker&) { copies++; } // 拷贝时计数 }; int Tracker::copies = 0; int main() { std::vector<Tracker> vec(3); std::cout << "Before resize: " << Tracker::copies << " copies\n"; // 0 vec.resize(10); // 触发扩容 std::cout << "After resize: " << Tracker::copies << " copies\n"; // 3次拷贝 } ``` 输出: ``` Before resize: 0 copies After resize: 3 copies // 原元素被拷贝 ``` #### **最佳实践建议** 1. **预分配避免扩容拷贝**: ```cpp std::vector<int> vec; vec.reserve(100); // 单次分配 vec.resize(100); // 无内存拷贝 ``` 2. **优先使用 `reserve()` + `push_back()`**: ```cpp vec.reserve(100); for(int i=0; i<100; ++i) vec.push_back(i); // 避免resize的初始化开销 ``` 3. **移动语义优化(C++11+)**: ```cpp std::vector<std::string> vec; vec.reserve(10); vec.push_back(std::move(str)); // 避免字符串拷贝 ``` > **总结**:`resize()` **仅在需要扩容时触发内存拷贝**。通过预分配(`reserve()`)可完全避免拷贝,对平凡类型优化后可能使用 `memcpy`[^3],非平凡类型则调用拷贝构造。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值