云计算百科
云计算领域专业知识百科平台

对于C++中vector的详细介绍

开篇介绍:

hello 大家,很高兴又和大家见面了,那么在前面两篇博客中,我们成功的做到了对C++中string类的模拟实现,不得不说,确实是很复杂,也很麻烦,但是呢,大家,授人以鱼不如授人以渔,光会使用string而不会去实现string或者说是去熟悉string的各个成员变量、成员函数的底层,是大大错误的,是无法令我们达到更高的高度,所以希望大家,永远不要觉得去实现一个已有的结构或者是算法的底层,是无用功,是浪费时间,要永远相信自己,所做的一切,都是有意义的,都是有回报的,希望大家加油。

那么在我们了解了string类之后,我们就再来了解了解C++中,真正牛波一的一个类,vector,它,是STL的一个很经典的数据结构,是非常重要的,无论是算法竞赛,还是日常工作,都是不可或缺的。

而且,vector的使用,和string的使用,还是有不同的,所以大家不要想着会了string的使用之后,就能对vector手拿把掐了。

那么接下来,就让我们先来了解一下vector。

说明一下,我们这一篇文章先带大家了解一下vector的使用,下一篇博客中,我们才会进行对vector的模拟实现。

vector的介绍:

vector 是 C++ 标准模板库(STL)中最常用、最核心的容器之一,本质是动态数组—— 既保留了静态数组的随机访问优势,又解决了静态数组长度固定的缺陷,支持自动扩容、动态增删元素,是日常开发中替代原始数组的首选工具。本文将从核心特性、定义初始化、成员函数、元素访问、内存管理、高级用法、常见坑等维度,全面拆解 vector 的用法。

一、vector 核心特性(本质 + 优势 + 局限)

1. 本质

vector 底层基于连续的内存空间实现(与静态数组一致),内部维护三个关键指针(C++11 后为迭代器):

  • begin():指向数组首元素的指针;
  • end():指向数组尾元素的下一个位置的指针(即 “尾后迭代器”);
  • capacity() 对应的指针:指向当前内存块的末尾(即已分配内存的最后位置)。

2. 核心优势

  • 随机访问高效:支持 [] 下标访问和 at() 访问,时间复杂度 O (1)(与数组一致);
  • 动态扩容:无需手动管理内存,元素超过当前容量时自动分配更大的内存块;
  • 接口丰富:STL 提供了完整的增删改查、容量控制、迭代器等接口,易用性强;
  • 与原始数组兼容:可通过 data() 方法获取底层连续内存的指针,直接兼容 C 风格函数(如 memcpy、printf)。

3. 局限(适用场景边界)

  • 中间插入 / 删除低效:由于内存连续,在中间位置插入 / 删除元素时,需要移动后续所有元素(时间复杂度 O (n));
  • 扩容有开销:自动扩容时会经历 “分配新内存→拷贝旧元素→释放旧内存” 的过程,频繁扩容可能影响性能;
  • 不支持在尾部以外的位置高效插入:若需频繁在头部 / 中间插入元素,建议用 list 或 deque。

4. 适用场景

  • 需频繁通过下标随机访问元素;
  • 元素增删主要在尾部(插入 / 删除效率 O (1) amortized,即 “均摊 O (1)”);
  • 已知元素数量范围,可提前预留容量(reserve())以避免频繁扩容。

二、vector 定义与初始化(全场景覆盖)

vector 是模板类,定义时需指定元素类型(如 int、string、自定义结构体),语法为 std::vector<T>(需包含头文件 <vector>,且建议使用 std 命名空间,或 using std::vector;)。

1. 头文件与命名空间

#include <vector> // 必须包含的头文件
using namespace std; // 简化写法,否则需写 std::vector

2. 6 种常见初始化方式

初始化方式语法示例说明
默认初始化 vector<int> v; 空 vector,size=0,capacity 由编译器实现决定(通常为 0 或较小值)
指定大小(默认值初始化) vector<int> v(5); 初始化为 5 个元素,每个元素默认值为 0(内置类型)/ 默认构造函数(自定义类型)
指定大小 + 初始值 vector<int> v(5, 10); 5 个元素,每个元素值为 10(内置类型 / 自定义类型均适用,需支持赋值)
列表初始化(C++11+) vector<int> v = {1,2,3,4,5}; 或 vector<int> v{1,2,3,4,5}; 直接用初始化列表赋值,size=5,capacity=5(多数编译器)
拷贝初始化(复制其他 vector) vector<int> v1{1,2,3}; vector<int> v2(v1); 或 vector<int> v2 = v1; 拷贝 v1 的所有元素,v2 与 v1 完全独立(深拷贝)
迭代器范围初始化 vector<int> v1{1,2,3,4,5}; vector<int> v2(v1.begin()+1, v1.end()-2); 拷贝 v1 中 [begin+1, end-2) 区间的元素(示例中 v2 = {2,3}),支持其他容器迭代器(如 array、string)
移动初始化(C++11+) vector<int> v1{1,2,3}; vector<int> v2 = move(v1); 将 v1 的资源 “移动” 给 v2,v1 变为空 vector(无拷贝开销,效率高)
从 C 风格数组初始化 int arr[] = {1,2,3}; vector<int> v(arr, arr+sizeof(arr)/sizeof(arr[0])); 利用数组指针作为迭代器,拷贝数组所有元素

3. 特殊类型的 vector 初始化(如自定义结构体、指针)

// 1. 自定义结构体
struct Person {
string name;
int age;
Person(string n, int a) : name(n), age(a) {} // 构造函数
};
vector<Person> people = {{"Alice", 20}, {"Bob", 25}}; // 列表初始化(需 C++11+)

// 2. 指针类型(注意:需手动管理内存,避免内存泄漏)
vector<int*> v;
int a = 10;
v.push_back(&a); // 存储栈指针(安全)
v.push_back(new int(20)); // 存储堆指针(需手动 delete)

三、vector 核心成员函数(按功能分类,含示例)

vector 的成员函数是其核心用法,按 “迭代器、容量、元素操作” 分类整理,每个函数含功能、语法、示例。

1. 迭代器相关函数(遍历 / 区间操作基础)

迭代器是 vector 的 “遍历工具”,类似指针,支持 ++/– 移动,兼容 STL 算法(如 sort、find)。

函数功能描述示例代码
begin() 返回指向首元素的非 const 迭代器 vector<int> v{1,2,3}; auto it = v.begin(); *it = 10;(修改首元素为 10)
end() 返回指向尾后位置的非 const 迭代器 for (auto it = v.begin(); it != v.end(); ++it) cout << *it;(遍历所有元素)
cbegin() 返回指向首元素的const 迭代器(不可修改元素) const vector<int> v{1,2,3}; auto it = v.cbegin(); // *it = 10; 报错
cend() 返回指向尾后位置的const 迭代器 用于遍历 const vector,避免意外修改元素
rbegin() 返回指向尾元素的反向迭代器(从后往前遍历) for (auto it = v.rbegin(); it != v.rend(); ++it) cout << *it;(3,2,1)
rend() 返回指向首元素前位置的反向迭代器 反向遍历的结束条件

注意:迭代器可能失效(如扩容、删除元素后),失效后不可再使用(会触发未定义行为)。

2. 容量与大小操作(内存管理核心)

vector 有两个关键概念:

  • size():当前已存储的元素个数(用户实际使用的数量);
  • capacity():当前已分配的内存可容纳的最大元素个数(未扩容前最多能存多少)。
函数功能描述示例代码
size() 返回当前元素个数(O (1)) vector<int> v{1,2,3}; cout << v.size(); // 输出 3
capacity() 返回当前容量(O (1)) vector<int> v(5,10); cout << v.capacity(); // 输出 5(多数编译器)
empty() 判断是否为空(size()==0),O (1),比直接判断 size() 更高效 if (v.empty()) cout << "空vector";
reserve(n) 预分配内存,使容量至少为 n(仅扩容,不缩容;不改变 size) vector<int> v; v.reserve(100); cout << v.capacity(); // 100,size=0
resize(n) 调整 size 为 n:- n > 当前 size:补默认值(内置类型 0,自定义类型默认构造)- n < 当前 size:删除末尾元素 vector<int> v{1,2,3}; v.resize(5); // size=5,元素为 [1,2,3,0,0];v.resize(2); // size=2,元素为 [1,2]
resize(n, val) 调整 size 为 n,补值为 val(替代默认值) v.resize(5, 6); // 元素为 [1,2,3,6,6]
shrink_to_fit() 缩减容量至 size(C++11+),释放多余内存(仅为建议,编译器可能忽略) vector<int> v(100); v.resize(10); v.shrink_to_fit(); cout << v.capacity(); // 多数编译器输出 10
max_size() 返回 vector 理论上可容纳的最大元素个数(受系统内存限制) cout << v.max_size(); // 输出如 2147483647(32位系统)

关键区别:reserve 只改容量、不改大小;resize 既改大小、可能改容量(当 n > 原容量时)。

3. 元素增删改查操作(最常用)

(1)尾部操作(高效,O (1) 均摊)

函数功能描述示例代码
push_back(val) 在尾部插入元素 val(容量不足时自动扩容) vector<int> v; v.push_back(1); v.push_back(2); // v = [1,2]
emplace_back(args…) 在尾部直接构造元素(C++11+),避免拷贝开销(比 push_back 高效) vector<Person> people; people.emplace_back("Charlie", 30); // 直接调用 Person 构造函数,无临时对象
pop_back() 删除尾部元素(不返回值,O (1)),size 减 1,容量不变 v.pop_back(); // v = [1]

核心对比:push_back 先构造临时对象再拷贝 / 移动到 vector;emplace_back 直接在 vector 的内存中构造对象,效率更高(尤其自定义类型)。

(2)中间 / 头部插入(低效,O (n))

函数功能描述示例代码
insert(pos, val) 在迭代器 pos 位置插入 val,返回指向插入元素的迭代器 vector<int> v{1,3}; auto it = v.insert(v.begin()+1, 2); // v = [1,2,3],it 指向 2
insert(pos, n, val) 在 pos 位置插入 n 个 val v.insert(v.begin(), 2, 0); // v = [0,0,1,2,3]
insert(pos, first, last) 在 pos 位置插入 [first, last) 区间的元素(支持其他容器迭代器) vector<int> v1{4,5}; v.insert(v.end(), v1.begin(), v1.end()); // v = [0,0,1,2,3,4,5]
emplace(pos, args…) 在 pos 位置直接构造元素(C++11+),比 insert 高效 v.emplace(v.begin()+3, 6); // v = [0,0,1,6,2,3,4,5]

(3)删除元素(低效,O (n))

函数功能描述示例代码
erase(pos) 删除迭代器 pos 指向的元素,返回指向删除后下一个元素的迭代器 vector<int> v{1,2,3,4}; auto it = v.erase(v.begin()+1); // v = [1,3,4],it 指向 3
erase(first, last) 删除 [first, last) 区间的元素,返回指向删除后下一个元素的迭代器 v.erase(v.begin()+1, v.begin()+3); // v = [1]
clear() 删除所有元素(size=0),容量不变(不释放内存) v.clear(); cout << v.size(); // 0;cout << v.capacity(); // 仍为之前的容量

注意:erase 会导致删除位置后的迭代器失效,需用其返回值更新迭代器(如遍历删除时)。

(4)其他操作

函数功能描述示例代码
assign(n, val) 清空原有元素,重新赋值为 n 个 val(size 和 capacity 可能变化) vector<int> v{1,2,3}; v.assign(4, 5); // v = [5,5,5,5]
assign(first, last) 清空原有元素,赋值为 [first, last) 区间的元素 int arr[] = {6,7}; v.assign(arr, arr+2); // v = [6,7]
swap(v2) 交换当前 vector 与 v2 的内容(O (1),仅交换内部指针,无元素拷贝) vector<int> v1{1,2}, v2{3,4}; v1.swap(v2); // v1=[3,4], v2=[1,2]

4. 元素访问函数(4 种方式,各有优劣)

vector 支持 4 种元素访问方式,核心差异在越界检查和易用性:

访问方式语法示例越界处理适用场景
下标运算符 [] v[i] 不检查越界(触发未定义行为,如崩溃) 确定下标合法时(高效)
at(i) v.at(i) 越界时抛出 out_of_range 异常 下标可能非法时(安全)
front() v.front() 空 vector 访问触发未定义行为 获取首元素(比 v[0] 直观)
back() v.back() 空 vector 访问触发未定义行为 获取尾元素(比 v[v.size()-1] 简洁)
data() v.data() 空 vector 返回 nullptr 获取底层数组指针(兼容 C 函数)

示例:

vector<int> v{10,20,30};
cout << v[1]; // 20(无越界检查)
cout << v.at(1); // 20(越界抛异常)
cout << v.front(); // 10
cout << v.back(); // 30

int* p = v.data();
cout << p[2]; // 30(直接操作底层数组)

// 越界示例
// cout << v[5]; // 未定义行为(可能崩溃)
// cout << v.at(5); // 抛出 std::out_of_range 异常,可捕获

四、vector 内存管理深度解析(扩容机制 + 优化技巧)

内存管理是 vector 的核心原理,理解其扩容机制能避免性能问题和内存浪费。

1. 扩容机制(关键!)

当 push_back/emplace_back 插入元素时,若 size() == capacity()(当前容量已满),vector 会自动扩容,步骤如下:

  • 分配新内存:新容量通常是原容量的 2 倍(不同编译器可能不同,如 GCC 是 2 倍,VS 是 1.5 倍);
  • 拷贝旧元素:将旧内存中的所有元素拷贝(或移动,C++11+)到新内存;
  • 释放旧内存:销毁旧内存中的元素并释放空间;
  • 更新指针:将 begin()/end() 等指针指向新内存。
  • 示例(GCC 环境):

    vector<int> v;
    cout << v.capacity() << endl; // 0(初始容量)
    v.push_back(1);
    cout << v.capacity() << endl; // 1(第一次扩容到 1)
    v.push_back(2);
    cout << v.capacity() << endl; // 2(扩容到 2 倍)
    v.push_back(3);
    cout << v.capacity() << endl; // 4(扩容到 2 倍)
    v.push_back(4);
    cout << v.capacity() << endl; // 4(未满,不扩容)
    v.push_back(5);
    cout << v.capacity() << endl; // 8(扩容到 2 倍)

    2. 扩容的问题

    • 性能开销:频繁扩容会导致多次 “分配 – 拷贝 – 释放”,尤其是元素较多时(如 100 万元素,扩容时需拷贝 100 万次);
    • 迭代器失效:扩容后旧内存被释放,指向旧内存的迭代器、指针、引用全部失效(后续使用会触发未定义行为)。

    3. 内存优化技巧

    (1)提前预留容量(reserve())

    若已知元素数量范围,提前用 reserve(n) 预分配容量,避免多次扩容:

    // 优化前:频繁扩容
    vector<int> v;
    for (int i = 0; i < 1000000; ++i) {
    v.push_back(i); // 会扩容约 20 次(2^0 → 2^1 → … → 2^19)
    }

    // 优化后:一次扩容
    vector<int> v;
    v.reserve(1000000); // 预分配 100 万容量
    for (int i = 0; i < 1000000; ++i) {
    v.push_back(i); // 无扩容,性能提升明显
    }

    (2)用 emplace_back 替代 push_back(减少拷贝)

    自定义类型元素优先用 emplace_back,直接在 vector 内存中构造对象,避免临时对象的拷贝 / 移动开销:

    struct BigObj { // 大对象(拷贝开销大)
    int data[1000];
    BigObj(int x) { data[0] = x; }
    };

    vector<BigObj> v;
    v.reserve(1000);
    // 高效:直接构造
    v.emplace_back(10);
    // 低效:先构造临时对象,再移动(或拷贝)
    v.push_back(BigObj(20));

    (3)释放多余内存(shrink_to_fit() 或 swap 技巧)

    clear() 仅清空元素(size=0),但容量不变,若需释放多余内存:

    vector<int> v(1000);
    v.resize(10); // size=10,capacity=1000(仍占用 1000 个 int 内存)

    // 方法 1:C++11+ shrink_to_fit()
    v.shrink_to_fit(); // capacity 变为 10(编译器可能忽略)

    // 方法 2:swap 技巧(兼容所有 C++ 版本,强制释放)
    vector<int>().swap(v); // 用临时空 vector 交换,v 变为空(size=0,capacity=0)
    // 保留 size,仅释放多余容量:
    vector<int>(v).swap(v); // 临时 vector 拷贝 v 的元素(size=10),交换后 v 的 capacity=10

    (4)避免不必要的拷贝

    • 传递 vector 时,优先用引用(const vector<T>&)或移动语义(vector<T>&&),避免值传递(深拷贝所有元素);
    • 返回 vector 时,依赖 C++ 的 NRVO(具名返回值优化),无需手动 move(编译器会自动优化为无拷贝):

      // 优化前:手动 move(多余)
      vector<int> func() {
      vector<int> v{1,2,3};
      return move(v);
      }

      // 优化后:NRVO 自动优化(无拷贝)
      vector<int> func() {
      vector<int> v{1,2,3};
      return v;
      }

    五、vector 高级用法(STL 算法 + 遍历方式 + 特殊场景)

    1. 结合 STL 算法(排序、查找、去重等)

    vector 支持所有 STL 算法(需包含 <algorithm> 头文件),常用场景:

    #include <algorithm>

    vector<int> v{3,1,4,1,5,9,2,6};

    // 1. 排序(默认升序)
    sort(v.begin(), v.end()); // v = [1,1,2,3,4,5,6,9]
    // 降序排序
    sort(v.begin(), v.end(), greater<int>()); // [9,6,5,4,3,2,1,1]

    // 2. 查找元素(返回迭代器)
    auto it = find(v.begin(), v.end(), 5);
    if (it != v.end()) {
    cout << "找到元素:" << *it << endl;
    }

    // 3. 去重(需先排序)
    v.erase(unique(v.begin(), v.end()), v.end()); // 去重后 v = [9,6,5,4,3,2,1]

    // 4. 反转
    reverse(v.begin(), v.end()); // v = [1,2,3,4,5,6,9]

    // 5. 计数
    int cnt = count(v.begin(), v.end(), 1); // 统计 1 出现的次数(1 次)

    // 6. 替换
    replace(v.begin(), v.end(), 3, 30); // 将所有 3 替换为 30(v = [1,2,30,4,5,6,9])

    2. 4 种遍历方式(优劣对比)

    遍历方式语法示例优势劣势
    普通 for 循环(下标) for (int i=0; i<v.size(); ++i) cout << v[i]; 直观,支持随机访问 仅适用于支持下标的容器,遍历中不能增删元素
    范围 for 循环(C++11+) for (int x : v) cout << x; 或 for (int& x : v) x *= 2;(修改元素) 简洁,无需关心下标 / 迭代器 不能获取当前下标(需手动维护),遍历中不能增删元素
    迭代器遍历 for (auto it = v.begin(); it != v.end(); ++it) cout << *it; 兼容所有 STL 容器,支持区间操作 语法稍繁琐,需注意迭代器失效
    std::for_each(算法) for_each(v.begin(), v.end(), [](int x){ cout << x; });(lambda 表达式) 支持复杂逻辑,可复用函数对象 可读性稍差,适合复杂遍历场景

    示例(修改元素的遍历):

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

    // 范围 for 循环修改(需用引用)
    for (int& x : v) x *= 2; // v = [2,4,6]

    // 迭代器修改
    for (auto it = v.begin(); it != v.end(); ++it) *it += 1; // v = [3,5,7]

    3. 特殊场景用法

    (1)多维 vector(模拟二维 / 三维数组)

    vector 可嵌套定义多维数组(底层仍为连续内存?不!二维 vector 是 “vector 的 vector”,外层 vector 的每个元素是一个独立的 vector,内存不连续):

    // 二维 vector 初始化(3 行 4 列,初始值 0)
    vector<vector<int>> mat(3, vector<int>(4, 0));

    // 赋值
    mat[0][1] = 1;
    mat[2][3] = 5;

    // 遍历
    for (int i=0; i<mat.size(); ++i) {
    for (int j=0; j<mat[i].size(); ++j) {
    cout << mat[i][j] << " ";
    }
    cout << endl;
    }

    // 动态添加行
    mat.push_back({6,7,8,9}); // 变为 4 行 4 列

    注意:二维 vector 内存不连续,若需连续内存的二维数组(如高性能场景),可手动管理:vector<int> mat(3*4, 0);(用 mat[i*4 + j] 访问第 i 行第 j 列)。

    (2)vector 作为函数参数 / 返回值

    // 1. 传引用(避免拷贝,推荐)
    void printVector(const vector<int>& v) { // const 引用:不修改原 vector
    for (int x : v) cout << x << " ";
    }

    // 2. 传值(深拷贝,不推荐,除非需修改副本)
    void modifyVector(vector<int> v) {
    v.push_back(100);
    }

    // 3. 返回 vector(NRVO 优化,无拷贝)
    vector<int> createVector(int n) {
    vector<int> v(n);
    for (int i=0; i<n; ++i) v[i] = i*i;
    return v;
    }

    (3)vector 存储智能指针(避免内存泄漏)

    存储指针时,优先用智能指针(unique_ptr/shared_ptr)替代裸指针,自动管理内存:

    #include <memory>

    vector<unique_ptr<int>> v;
    v.emplace_back(new int(10)); // 或 v.push_back(make_unique<int>(10));(C++14+)
    v.emplace_back(make_unique<int>(20));

    // 无需手动 delete,智能指针自动释放
    for (const auto& ptr : v) {
    cout << *ptr << " "; // 10 20
    }

    六、vector 常见坑与避坑指南

    1. 迭代器失效(最常见坑)

    迭代器失效场景及解决方案:

    失效场景示例代码(错误)解决方案(正确)
    扩容后旧迭代器失效 vector<int> v{1,2}; auto it = v.begin(); v.push_back(3); *it = 10; 扩容后不使用旧迭代器;或提前 reserve 避免扩容
    erase 后删除位置后的迭代器失效 for (auto it = v.begin(); it != v.end(); ++it) { if (*it == 2) v.erase(it); } 用 erase 的返回值更新迭代器:it = v.erase(it);(不 ++it)
    clear() 后迭代器失效 v.clear(); auto it = v.begin(); *it = 5; clear() 后不使用原迭代器;需遍历重新获取 begin()

    正确的遍历删除示例:

    vector<int> v{1,2,3,2,4};
    // 错误:erase 后 it 失效,++it 触发未定义行为
    // for (auto it = v.begin(); it != v.end(); ++it) {
    // if (*it == 2) v.erase(it);
    // }

    // 正确:用 erase 返回值更新迭代器
    for (auto it = v.begin(); it != v.end(); ) {
    if (*it == 2) {
    it = v.erase(it); // 删除后 it 指向下一个元素,无需 ++it
    } else {
    ++it;
    }
    }
    // 结果:v = [1,3,4]

    2. 空 vector 访问元素

    空 vector(size=0)调用 v[0]、v.at(0)、v.front()、v.back() 会触发未定义行为(at() 抛异常,其他直接崩溃):

    vector<int> v;
    // cout << v[0]; // 崩溃
    // cout << v.front(); // 崩溃
    if (!v.empty()) {
    cout << v[0]; // 安全
    }

    3. 混淆 size() 和 capacity()

    • 以为 capacity() 是元素个数(实际是容量);
    • 以为 clear() 会释放内存(实际仅 size=0,capacity 不变)。

    4. 多维 vector 内存不连续

    二维 vector 是 “vector 的 vector”,外层每个元素是独立的 vector,内存不连续,不能直接当作 C 风格二维数组传递(如 int**):

    vector<vector<int>> mat(3, vector<int>(4));
    // int** p = mat.data(); // 错误:mat.data() 返回 vector<int>*,而非 int**

    5. 频繁在中间插入 / 删除元素

    vector 不适合频繁在中间 / 头部插入 / 删除元素(O (n) 时间复杂度),若需此操作,应改用 list(双向链表,插入 / 删除 O (1))或 deque(双端队列,头部 / 尾部插入 O (1))。

    七、vector 与其他容器对比(选型指南)

    容器随机访问尾部插入 / 删除中间插入 / 删除内存连续性适用场景
    vector O(1) O (1) 均摊 O(n) 随机访问、尾部增删为主
    list O(n) O(1) O(1) 频繁中间插入 / 删除
    deque O(1) O(1) O(n) 否(分段连续) 头部 / 尾部增删频繁、需随机访问
    array O(1) 不支持 不支持 元素个数固定、无需动态扩容

    总结

    vector 是 C++ 中最实用的容器,核心优势是随机访问高效 + 动态扩容 + 接口丰富,适合大多数 “尾部增删、随机访问” 的场景。使用时需重点关注:

  • 内存管理:用 reserve() 提前预留容量,避免频繁扩容;
  • 迭代器失效:erase 后用返回值更新迭代器,扩容后不使用旧迭代器;
  • 效率优化:优先用 emplace_back 替代 push_back,传递时用引用避免拷贝;
  • 避坑:不访问空 vector 元素,不混淆 size() 和 capacity()。
  • 一、vector 底层实现的深度细节(源码级视角)

    vector 的核心是三个迭代器(指针),以 GCC 的 std::vector 实现为例,其内部成员简化后如下:

    template <typename T, typename Allocator = std::allocator<T>>
    class vector {
    private:
    T* _M_start; // 指向首元素(begin())
    T* _M_finish; // 指向尾元素的下一个位置(end())
    T* _M_end_of_storage; // 指向内存块末尾(capacity() 对应位置)
    // 分配器(负责内存申请/释放、对象构造/析构)
    Allocator _M_get_allocator();
    };

    这三个指针直接决定了 vector 的核心属性:

    • size() = _M_finish – _M_start(当前元素个数);
    • capacity() = _M_end_of_storage – _M_start(总容量);
    • empty() = _M_start == _M_finish(是否为空)。

    1. 分配器(Allocator)的作用

    vector 不直接调用 new/delete,而是通过分配器管理内存,这是 STL 容器的通用设计。分配器的核心功能:

    • allocate(n):申请能容纳 n 个 T 类型对象的原始内存(未构造对象);
    • deallocate(p, n):释放 p 指向的、能容纳 n 个 T 的内存;
    • construct(p, args…):在 p 指向的内存上构造 T 对象(调用 T 的构造函数);
    • destroy(p):销毁 p 指向的对象(调用 T 的析构函数)。

    例如,push_back(val) 的底层逻辑(简化):

    void push_back(const T& val) {
    if (_M_finish == _M_end_of_storage) { // 容量不足,扩容
    reallocate(); // 分配新内存、移动旧元素、释放旧内存
    }
    allocator.construct(_M_finish, val); // 在当前尾部构造元素(拷贝构造)
    ++_M_finish; // 移动尾指针
    }

    2. 扩容的底层步骤(以 GCC 为例)

    当需要扩容时,vector 会调用 reallocate 函数,步骤如下:

  • 计算新容量:若原容量为 0,则新容量为 1;否则为原容量的 2 倍(GCC 策略);
  • 申请新内存:通过分配器 allocate(new_cap) 申请更大的内存块;
  • 移动旧元素:用 std::move 将旧元素移动到新内存(C++11 后),避免拷贝开销;
    • 若元素不可移动(无移动构造函数),则退化为拷贝;
  • 销毁旧元素:调用 allocator.destroy 销毁旧内存中的元素;
  • 释放旧内存:调用 allocator.deallocate 释放旧内存块;
  • 更新指针:_M_start 指向新内存首地址,_M_finish 指向新内存中元素的尾后位置,_M_end_of_storage 指向新内存块末尾。
  • 注意:扩容后,所有指向旧内存的迭代器、指针、引用全部失效(因为旧内存已被释放)。

    3. 不同编译器的扩容策略差异

    编译器扩容倍数(默认)说明
    GCC(g++) 2 倍 初始容量 0,第一次 push_back 扩容到 1,之后每次翻倍
    MSVC(VS) 1.5 倍 初始容量 0,第一次扩容到 1,第二次到 2,第三次到 3(1.52=3),第四次到 4(1.53=4.5→取整 4)
    Clang 2 倍 与 GCC 类似,但部分版本对小容量(<1024)采用 2 倍,大容量采用 1.5 倍优化

    示例(MSVC 环境):

    vector<int> v;
    v.push_back(1); // capacity=1
    v.push_back(2); // capacity=2(1.5*1=1.5→取整2)
    v.push_back(3); // capacity=3(1.5*2=3)
    v.push_back(4); // capacity=4(1.5*3=4.5→取整4)
    v.push_back(5); // capacity=6(1.5*4=6)

    二、迭代器失效的全场景解析(含插入 / 删除细节)

    迭代器失效是 vector 最容易踩坑的点,需明确所有可能导致失效的操作及原因:

    1. 插入元素导致的失效

    插入操作失效范围原因分析
    push_back/emplace_back 若未扩容:仅尾后迭代器(end())失效;若扩容:所有迭代器、指针、引用失效 未扩容时,_M_finish 移动,end() 指向的位置变化;扩容时,旧内存释放,所有指针失效
    insert(pos, …) (未扩容) pos 及之后的迭代器失效(_M_finish 移动,后续元素地址变化) 插入元素后,pos 之后的元素被后移,地址改变,对应迭代器失效
    insert(pos, …) (扩容) 所有迭代器、指针、引用失效 同 push_back 扩容场景,旧内存释放

    示例(未扩容的 insert 失效):

    vector<int> v{1,2,3,4};
    auto it = v.begin() + 2; // 指向 3(地址为 p)
    v.insert(it, 5); // 插入 5,v 变为 [1,2,5,3,4](未扩容)
    // 此时 it 原本指向 p,但插入后 3 的地址变为 p+1,it 仍指向 p(现在是 5),已失效
    cout << *it; // 输出 5(但逻辑上 it 应指向原位置的 3,实际已失效)

    2. 删除元素导致的失效

    删除操作失效范围原因分析
    pop_back() 仅尾后迭代器(end())失效 _M_finish 前移,end() 指向的位置变化
    erase(pos) pos 及之后的迭代器失效(元素前移,地址变化) 删除后,pos 之后的元素前移,原 pos 指向的元素被覆盖,后续迭代器地址改变
    erase(first, last) first 及之后的迭代器失效 同 erase(pos),区间后的元素前移,地址变化
    clear() 所有迭代器失效(size=0,_M_finish = _M_start) 元素被销毁,原迭代器指向的位置已无有效元素

    正确处理 erase 失效的示例(遍历删除特定元素):

    vector<int> v{1,2,3,2,4};
    auto it = v.begin();
    while (it != v.end()) {
    if (*it == 2) {
    it = v.erase(it); // 用返回值更新 it(指向删除后的下一个元素)
    } else {
    ++it; // 不删除时正常移动
    }
    }
    // 结果:v = [1,3,4](正确)

    3. 其他导致失效的操作

    • resize(n):若 n > 原容量(触发扩容),所有迭代器失效;若 n <= 原容量,[n, 原size) 范围的迭代器失效;
    • reserve(n):若 n > 原容量(触发扩容),所有迭代器失效;若 n <= 原容量,无影响;
    • assign(…):清空原元素并重新赋值,若新容量变化(可能扩容),所有迭代器失效;
    • swap(v2):迭代器会指向被交换后的容器元素(如 v1 的迭代器会指向 v2 的元素),但逻辑上视为 “有效但指向对象改变”。

    三、vector 与自定义类型的交互(构造 / 析构细节)

    当 vector 存储自定义类型时,需关注其构造函数、析构函数、拷贝 / 移动语义的影响,否则可能导致性能问题或错误。

    1. 自定义类型的默认构造函数要求

    • vector<T> v(n) 或 resize(n) 会默认构造 n 个 T 对象,因此 T 必须有可访问的默认构造函数(无参构造函数);
    • 若 T 没有默认构造函数,需用 vector<T> v(n, val) 或列表初始化,避免默认构造。

    示例(错误与正确用法):

    struct A {
    int x;
    A(int x) : x(x) {} // 仅自定义构造函数,无默认构造
    };

    // 错误:v(5) 会尝试默认构造 5 个 A,但 A 无默认构造
    // vector<A> v(5);

    // 正确:用带初始值的构造
    vector<A> v(5, A(10)); // 每个元素都是 A(10) 的拷贝
    // 或用 emplace 系列函数(C++11+)
    vector<A> v;
    v.reserve(5);
    for (int i=0; i<5; ++i) {
    v.emplace_back(i); // 直接构造 A(i),无需默认构造
    }

    2. 拷贝与移动语义对性能的影响

    • 若自定义类型无移动构造函数,vector 扩容时会对元素进行深拷贝(调用拷贝构造),开销大;
    • 若定义了移动构造函数(T(T&&)),扩容时会优先移动元素(浅拷贝资源,如指针),性能大幅提升。

    示例(移动语义优化):

    struct BigData {
    int* data;
    size_t size;

    // 构造函数:分配大内存
    BigData(size_t n) : size(n), data(new int[n]) {}

    // 拷贝构造:深拷贝(开销大)
    BigData(const BigData& other) : size(other.size), data(new int[other.size]) {
    memcpy(data, other.data, size * sizeof(int));
    }

    // 移动构造:浅拷贝(开销小)
    BigData(BigData&& other) noexcept : size(other.size), data(other.data) {
    other.data = nullptr; // 避免被析构释放
    }

    // 析构函数:释放内存
    ~BigData() { delete[] data; }
    };

    // 测试扩容时的性能差异
    vector<BigData> v;
    v.reserve(1000); // 提前 reserve 避免扩容(若不 reserve)
    // 若 BigData 有移动构造,扩容时移动元素(快);否则拷贝(慢)
    for (int i=0; i<1000; ++i) {
    v.emplace_back(10000); // 构造 10000 个 int 的 BigData
    }

    3. 析构函数的自动调用

    vector 会在以下场景自动调用元素的析构函数:

    • 元素被删除时(erase、pop_back、clear);
    • vector 自身被销毁时(生命周期结束);
    • 扩容时,旧内存中的元素被销毁(destroy)。

    这意味着:若自定义类型管理资源(如堆内存),只要正确实现析构函数,vector 会自动释放资源,无需手动操作(避免内存泄漏)。

    四、异常安全性(Exception Safety)

    vector 的操作需保证 “异常安全”—— 当操作中抛出异常时,容器状态应保持一致(要么完全成功,要么完全回滚),避免资源泄漏或数据损坏。

    1. 插入操作的异常安全

    • push_back/emplace_back:若在构造新元素时抛出异常(如 T 的构造函数抛异常),vector 会保持原有状态(不会添加元素,内存不变);
    • 若在扩容的 “移动 / 拷贝元素” 阶段抛异常(如 T 的拷贝构造抛异常):
      • 对于可移动且移动构造不抛异常(noexcept)的元素,vector 会回滚(销毁已移动的元素,保留旧内存);
      • 对于只能拷贝且拷贝构造抛异常,C++ 标准要求 vector 必须释放新内存,恢复旧状态(保证无内存泄漏)。

    2. 删除操作的异常安全

    erase/pop_back/clear 销毁元素时,若 T 的析构函数抛异常,C++ 标准未强制规定处理方式(通常视为程序错误,因为析构函数不应抛异常)。因此,自定义类型的析构函数必须 noexcept(可显式声明 ~T() noexcept)。

    五、C++ 标准对 vector 的更新(C++11 及以后)

    vector 在后续标准中新增了多个实用特性,提升易用性和性能:

    1. C++11 新增特性

    • 移动语义:支持 vector<T> v2 = move(v1)(转移资源,避免拷贝);
    • emplace 系列函数:emplace_back、emplace 直接在容器中构造元素(比 push_back/insert 少一次拷贝);
    • 初始化列表:vector<int> v{1,2,3} 直接初始化;
    • 数据指针:data() 方法返回底层数组指针(替代 &v[0],更安全);
    • 范围 for 循环:简化遍历。

    2. C++17 新增特性

    • emplace_back 返回引用:auto& ref = v.emplace_back(10);(可直接获取新元素的引用);
    • 非成员 swap 优化:std::swap(v1, v2) 等价于 v1.swap(v2),但更通用;
    • 并行算法支持:std::sort(std::execution::par, v.begin(), v.end()) 可并行排序(需包含 <execution>)。

    3. C++20 新增特性

    • erase_if:直接删除满足条件的元素(简化遍历删除代码):

      vector<int> v{1,2,3,4,5};
      std::erase_if(v, [](int x) { return x % 2 == 0; }); // 删除偶数,v = [1,3,5]

    • ** constexpr vector**:支持在编译期使用 vector(需元素类型为字面类型):

      constexpr vector<int> make_vec() {
      vector<int> v;
      v.push_back(1);
      v.push_back(2);
      return v;
      }
      constexpr auto v = make_vec(); // 编译期构造 vector

    六、性能优化的极致技巧

    除了 reserve 和 emplace_back,还有一些进阶技巧可进一步优化 vector 性能:

    1. 批量操作替代单次操作

    • 用 insert(begin, first, last) 批量插入,替代多次 push_back(减少迭代器检查和可能的扩容);
    • 用 assign(first, last) 批量赋值,替代 clear() + 多次插入。

    2. 避免不必要的 shrink_to_fit

    shrink_to_fit 会导致重新分配内存 + 拷贝元素(O (n) 开销),若后续可能再次添加元素,会再次触发扩容,得不偿失。仅在确定不再添加元素且需释放内存时使用。

    3. 利用内存连续性优化缓存

    vector 的连续内存布局友好于 CPU 缓存(局部性原理),遍历速度远快于 list 等非连续容器。例如,对大规模数据排序时,vector 比 list 快数倍(缓存命中率更高)。

    4. 自定义分配器优化特定场景

    • 若频繁创建 / 销毁小 vector,可使用内存池分配器(如 boost::pool_allocator),减少 malloc/free 开销;
    • 若 vector 内存需共享或特殊对齐(如 SIMD 指令要求 16/32 字节对齐),可自定义分配器控制内存分配。

    七、边缘场景与特殊用法

    1. 存储 bool 类型的特殊性

    vector<bool> 是特化版本,为节省空间,每个 bool 元素仅占 1 位(而非 1 字节),因此:

    • 不支持 data() 方法(无法返回 bool* 指针);
    • 迭代器返回的是代理对象(std::vector<bool>::reference),而非直接引用;
    • 性能可能不如 vector<char>(位操作有额外开销)。

    若需高效访问,可改用 vector<char> 替代 vector<bool>。

    2. 零容量 vector 的 data() 行为

    C++11 后规定:空 vector(size=0)的 data() 可返回 nullptr 或任意非空指针(但解引用是未定义行为)。因此,对空 vector 调用 data() 后,不可直接操作指针:

    vector<int> v;
    int* p = v.data();
    // *p = 10; // 未定义行为(无论 p 是否为 nullptr)

    3. 作为函数返回值的优化(NRVO)

    编译器的具名返回值优化(NRVO) 可避免 vector 返回时的拷贝:

    vector<int> create(int n) {
    vector<int> v(n); // 具名对象
    // … 填充数据
    return v; // NRVO 生效:直接构造到调用者的内存中,无拷贝/移动
    }

    // 调用时
    vector<int> v = create(1000); // 无拷贝,效率极高

    注意:若返回前对 v 有分支操作(如 if (cond) return v; else return other;),NRVO 可能失效,此时会触发移动构造(仍比拷贝高效)。

    总结

    vector 的深度理解需结合底层指针实现、内存管理机制、标准规范细节和编译器差异。核心要点:

    • 三个指针(_M_start/_M_finish/_M_end_of_storage)决定了 size、capacity 和迭代器行为;
    • 扩容机制因编译器而异,需通过 reserve 提前规避性能损耗;
    • 迭代器失效场景需严格规避,尤其是插入 / 删除后的迭代器更新;
    • 自定义类型需正确实现移动语义和析构函数,确保性能和安全;
    • 善用 C++11 及以后的新特性(如 emplace_back、erase_if)提升代码质量。

    示例代码:

    下面再给大家一些vector中,常用的成员函数的使用样例:

    #include<iostream>
    #include<vector>
    using namespace std;

    //vector的使用,同时vector也是STL里面一个嘎嘎好使的一个东西
    //vector本质上其实是一个类模版
    //所以我们就要注意,在创建vector变量时,要进行显式实例化
    //即是vector<要存放的类型> v

    void testvectorconstructor()
    {
    //其实和string的使用是特别像的
    //但是需要显式实例化
    //类名后面加类型名字
    //直接告诉编译器我们是要往这里面放什么类型的数据
    //而不是让编译器去自己推测
    std::vector<int> v1;
    //但是要注意C++中并没有对vector进行<<和>>的运算符重载,
    //所以想要输入或者输出vectr变量中的数据,就得我们自己去借助循环输入输出
    //那么其实是和顺序表的输入输出差不多的

    vector<int> v2(5, 1);
    //前面一个参数5表示要创建长度为5的数组
    //后面一个参数1表示是要数组的每个数据都是存储为1=
    //注意:是不能在括号里面去什么1 2 3 4 5 的将每一个数据初始化
    //vector不支持,想要输入就用cin>>

    //但是new可以
    int* arr = new int[5](1, 2, 3, 4, 5);
    delete[] arr;

    for (int i = 0; i < v2.size(); ++i)
    {
    cout << v2[i] << " ";
    }
    cout << endl;

    vector<int> v3(5);
    //前面一个参数5表示要创建长度为5的数组
    //如果我们没有传后面一个参数的话,那么编译器是默认为初始值为0的
    for (int i = 0; i < v3.size(); ++i)
    {
    cout << v3[i] << " ";
    }
    cout << endl;

    //不传参的时候是可以使用插入成员函数进行插入数据
    //但是不能通过我们自己输入去插入数据,即使用cin>>
    //因为我们没有指定数组的长度,那么自然无法使用cin>>去插入数据
    //编译器还没那么聪明
    vector<int> v4;

    //vector<int> v5(5);
    //for (int i = 0; i < v5.size(); ++i)
    //{
    //cin >> v5[i];
    //}
    //for (int i = 0; i < v5.size(); ++i)
    //{
    //cout << v5[i] << " ";
    //}
    //cout << endl;

    //vector<int> v6(5,2);//即使是我们一开始就指定是某个统一值,也是可以进行输入的
    //for (int i = 0; i < v6.size(); ++i)
    //{
    //cin >> v6[i];
    //}
    //for (int i = 0; i < v6.size(); ++i)
    //{
    //cout << v6[i] << " ";
    //}
    //cout << endl;

    //我们可以不传参,也可以只传要创建的数组的长度
    //析构函数编译器会自己调用,不需要我们操心

    //上面创建的都只是存储整型的一维数组
    //vector中是什么都可以放的,因为vector本质上就是相当于数组
    //而数组中其实就是什么类型的数据都能存放
    //所以我们也可以存放double、float、甚至是string类变量
    //只需要修改<>里面的类型名字就行了
    vector<float> v6(5, 1.00);
    for (int i = 0; i < v6.size(); ++i)
    {
    cout << v6[i] << " ";
    //在 C++ 中,vector<float> v6(5, 1.00); 初始化的向量元素值是 1.00,
    //但输出时默认不显示小数部分,
    //是因为 cout 对浮点数的默认输出格式会省略无意义的小数位
    //(即当小数部分为 0 时,只显示整数部分)。
    //希望注意
    }
    cout << endl;

    vector<float> v7(5, 1.10);
    for (int i = 0; i < v7.size(); ++i)
    {
    cout << v7[i] << " ";
    //在 C++ 中,vector<float> v6(5, 1.00); 初始化的向量元素值是 1.00,
    //但输出时默认不显示小数部分,
    //是因为 cout 对浮点数的默认输出格式会省略无意义的小数位
    //(即当小数部分为 0 时,只显示整数部分)。
    //希望注意
    }
    cout << endl;

    //存储string变量
    vector<string> v8(5,"hello");
    for (int i = 0; i < v8.size(); ++i)
    {
    cout << v8[i] << " ";
    //在 C++ 中,vector<float> v6(5, 1.00); 初始化的向量元素值是 1.00,
    //但输出时默认不显示小数部分,
    //是因为 cout 对浮点数的默认输出格式会省略无意义的小数位
    //(即当小数部分为 0 时,只显示整数部分)。
    //希望注意
    }
    cout << endl;

    //那么如果是想实现二维数组呢?
    //其实就是在vector里面再放一个vector
    //这样子就相当于是第一个vector里面存储的数据是vector
    //而第二个vector里面是存储着整型或者什么什么乱七八糟的
    vector<vector<int>> vv1(5);
    //如上,就是创建整型类型的二维数组,然后后面的参数5是指一共有5行
    //此时就没有第二个参数了,要是得是vector<int>类型的数据,但是一般没有
    //那么要怎么创建n列呢?
    //其实就要用到循环了
    for (int i = 0; i < vv1.size(); ++i)
    {
    //vv1.size()是指第一个vector有几个数据,
    //那么就是对应二维数组有几行
    //那么如果我们要创建列数
    //就需要去指定到第几行,然后对着一行去指定这一行要有几列
    //注意,是可以不同行有不同列的
    //比如第一行有1列,第二行有2列等等等等

    vv1[i].resize(4);//注意:vv1[i]也是vector类型的哦
    //我们要借助resize函数来进行
    //resize的作用就是对所使用它的vector进行指定空间的扩容或者缩容
    //要是resize里面放的第一个参数小于所使用它的vector的数据个数的话
    //那么编译器就会将所使用它的vector的数据只保留前第一个参数个
    //而要是大于的话,那么就是将所使用它的vector的数据个数扩大到第一个参数个
    //要是不传入第二个参数的话,那么默认为0
    //否则就是第二个参数的值

    //可以说是嘎嘎嘎嘎好使
    }

    vector<vector<int>> vv2(5);
    //如上,就是创建整型类型的二维数组,然后后面的参数5是指一共有5行
    //此时就没有第二个参数了,要是得是vector<int>类型的数据,但是一般没有
    //那么要怎么创建n列呢?
    //其实就要用到循环了
    for (int i = 0; i < vv2.size(); ++i)
    {
    //vv1.size()是指第一个vector有几个数据,
    //那么就是对应二维数组有几行
    //那么如果我们要创建列数
    //就需要去指定到第几行,然后对着一行去指定这一行要有几列

    //注意,是可以不同行有不同列的
    //比如第一行有1列,第二行有2列等等
    vv2[i].resize(i+1,1);//注意:vv1[i]也是vector类型的哦
    //我们要借助resize函数来进行
    //resize的作用就是对所使用它的vector进行指定空间的扩容或者缩容
    //要是resize里面放的第一个参数小于所使用它的vector的数据个数的话
    //那么编译器就会将所使用它的vector的数据只保留前第一个参数个
    //而要是大于的话,那么就是将所使用它的vector的数据个数扩大到第一个参数个
    //要是不传入第二个参数的话,那么默认为0
    //否则就是第二个参数的值

    //可以说是嘎嘎嘎嘎好使
    }

    //而要是我们相想对二维数组插入数据的话,
    //其实就是使用类似之前遍历二维数组所使用的双层循环
    for (int i = 0; i < vv1.size(); ++i)
    {
    for (int j = 0; j < vv1[i].size(); ++j)//vv1[i].size()即第i行的列数
    {
    cout << vv1[i][j]<<" ";//即类似C语言中的二维数组遍历
    }
    cout << endl;
    }

    //但是呢,在vector中,一般是比较建议使用迭代器的
    vector<int>::iterator it = v2.begin();
    //那么要是觉得前面的 vector<int>:: 写起来很麻烦
    //可以直接使用auto去让编译器自己推测
    auto it = v2.begin();

    while (it != v2.end())
    {
    cout << *it << " ";//一定记得解引用
    it++;
    }
    cout << endl;
    //同样的,也可以使用auto
    for (auto n : v2)
    {
    cout << n << " ";
    }

    //还有一些迭代器其实就是和string类的迭代器差不多的道理。

    }

    //还有类似size、capacityinsert、push_back、append、reserve什么什么的,
    //都是和string类似的用法和功能,但是其实还是要知道,
    //对于vector,我们是不传整型什么0,1,2了
    //而是全部必须传迭代器
    //唯一比较需要深知的就是resize这个函数的功能

    //我们同样也来些例子,迭代器其实本质上,还是和我们平时的使用是差不多的
    void testvectorpush()
    {
    vector<int> v1;
    v1.push_back(1);
    v1.push_back(2);
    v1.push_back(3);
    v1.push_back(4);
    v1.push_back(5);
    v1.push_back(6);

    //那么在正式使用之前,我们要先知道,迭代器,到底表示什么
    //那么其实用我们平时的整型下标来理解就行,因为两个表示的意思差不多
    //首先呢,迭代器其实就是类似指针的东西,指向某个位置,不是整型,不是整型,不是整型
    //那么呢,其实bengin()就相当于是下标0,
    //end()就相当于是size,即最后一个有效数据的后一个位置的下标
    //是可以这么理解的,这个是没问题的
    //但是我们是不能直接对迭代器类型进行表达的,因为毕竟是指针非指针的东西
    //无法正常像整型一样表达
    //那么其实也是可以对迭代器进行加加减减的,这个是可以的
    //比如begin()+1,其实就是相当于0+1==1,是的,就是这样
    //我们在begin()后面加几,其实就是和我们指定下标为几是一样的
    //end()也是一样的道理
    //但是想要直接得到所对应的值,还是需要对迭代器类型进行解引用的
    //反正就是可以把迭代器类型的变量当作指针来对待,解引用,加加减减什么什么的
    //那么是要两个迭代器相减,结果才会是整型,还是就相当于是指针相减,编译器会自动结果为整型
    //比如begin()+3-begin(),其实结果就是3,简简单单

    //迭代器是 “指向元素的标识”,行为类似指针,
    //begin() 对应下标 0,end() 对应下标 size(),支持移动、解引用和相减(结果为整数)。

    v1.insert(v1.begin(), 0);
    //在下标为0的位置去插入0

    v1.insert(v1.end(), 7);
    //在下标为6的位置去插入7,最后一个有效数据的后一个位置的下标

    v1.insert(v1.begin()+2, 9);
    //在下标为2的位置去插入9

    for (auto n : v1)
    {
    cout << n << " ";
    }

    cout << endl;

    for (vector<int>::iterator it = v1.begin(); it < v1.end(); it++)
    {
    cout << *it << " ";
    }

    cout << endl;

    vector<int> v2(10, 2);
    cout << "v2 capacoty:" << v2.capacity() << endl;
    v2.reserve(100);
    cout << "v2 capacoty:" << v2.capacity() << endl;

    }

    //但是要注意的是,在vector里面,是没有提供find、rfind什么什么的参数的
    //所以,要么我们自己实现,要么其实就是用std里面提供find函数
    //但是对于那个函数,是要传迭代器的,而不是普通的整型
    void teststdfind()
    {
    vector<int> v1;
    v1.push_back(1);
    v1.push_back(2);
    v1.push_back(3);
    v1.push_back(4);
    v1.push_back(5);
    v1.push_back(6);

    //不能直接就用iterator的类型去接收,还是要加上类域,好让编译器知道是哪一个类的迭代器
    vector<int>::iterator pos=std::find(v1.begin(), v1.end(), 2);
    //std的find函数要求前面两个参数分别传入查找开始的地方和查找结束的地方
    //最后一个参数,也就是第三个参数,才是要查找的值

    cout << pos-v1.begin() << endl;//我们是不能直接输出迭代器类型的变量的
    //因为它其实勉勉强强算是个指针,所以我要用它减去最开始的begin的迭代器
    //结果才算是整型,其实就是两个指针相减

    vector<int>::iterator pos1 = std::find(v1.begin(), v1.end()+2, 2);
    cout << pos – v1.begin() << endl;

    }

    int main()
    {
    testvectorconstructor();
    testvectorpush();
    teststdfind();
    return 0;
    }

    大家结合着上面的样例,就能掌握大部分vector的使用方法了。

    结语:以 vector 为阶,奔赴编程的更深山海

    小伙伴们,当你看到这里,相信已经对 C++ 的 vector 容器有了全面且扎实的认知。从开篇对 vector “动态数组” 本质的初探,到各种初始化方式的灵活运用,再到成员函数的分类实践、内存管理的深度拆解,乃至迭代器失效的避坑指南和高级场景的拓展,我们一步步揭开了这个 STL 核心容器的神秘面纱。这一路的学习,或许你曾为 “size 与 capacity 的区别” 困惑过,曾因 “迭代器失效” 踩过坑,也曾为 “emplace_back 比 push_back 更高效” 的底层逻辑恍然大悟 —— 而这些,正是编程学习中最宝贵的成长印记。

    回顾我们的学习路径,从 string 类的模拟实现到 vector 的全面掌握,其实一直在践行一个核心原则:“知其然,更要知其所以然”。开篇时我就说过,光会使用一个工具,永远无法达到更高的高度。vector 的强大,不仅在于它能替代静态数组解决 “长度固定” 的痛点,更在于其底层设计的精妙 —— 三个核心指针对内存的精准把控、不同编译器对扩容策略的优化、分配器对 “内存申请 – 对象构造” 的分离管理,这些细节背后,是 C++ 语言对 “效率与易用性平衡” 的极致追求。而我们花时间去理解这些底层逻辑,不是 “无用功”,而是在构建自己的编程认知体系:当你明白扩容的 “分配 – 拷贝 – 释放” 流程,就会自然而然地想到用 reserve () 优化性能;当你清楚迭代器本质是指针,就不会在插入删除后误用失效的迭代器;当你理解 emplace 系列函数的 “原地构造” 原理,就会在处理自定义大对象时做出更高效的选择。

    在学习 vector 的过程中,我们也接触到了很多编程的通用思想。比如 “空间换时间”—— 提前 reserve 容量以避免频繁扩容,用连续内存布局提升缓存命中率;比如 “场景化选型”——vector 适合尾部增删和随机访问,而频繁中间插入则该选 list,这告诉我们没有 “万能的容器”,只有 “最适合的场景”;再比如 “异常安全”—— 析构函数不抛异常、移动构造用 noexcept 修饰,这些细节看似琐碎,却能让我们的代码在复杂场景下更健壮。这些思想,不仅适用于 vector,更适用于后续的所有容器学习,甚至是整个编程生涯。

    我知道,vector 的知识点并不简单。成员函数的分类记忆、内存管理的细节辨析、迭代器失效的全场景规避,每一个点都需要反复理解和实践。或许你在看示例代码时,会觉得 “看起来懂了,但自己写还是会错”;或许你在处理多维 vector 时,会困惑于 “内存不连续” 的特性;或许你在优化性能时,会纠结 “什么时候用 reserve,什么时候用 shrink_to_fit”—— 这些都是正常的。编程学习本就是一个 “理解 – 实践 – 犯错 – 修正” 的循环过程,没有谁能一蹴而就。就像我们在示例代码中展示的,从基础的构造初始化,到迭代器遍历,再到 STL 算法的结合使用,每一个功能的掌握都需要亲手敲代码验证。当你真正把 “遍历删除元素” 的错误写法改成 “用 erase 返回值更新迭代器”,当你用 emplace_back 替代 push_back 感受到性能提升,当你用 vector 存储智能指针避免内存泄漏时,这些知识才真正内化为你的能力。

    还要特别提醒大家,vector 的学习只是 STL 容器的一个起点。接下来,我们会进入 vector 的模拟实现环节 —— 这将是对我们底层理解的终极检验。当我们亲手用三个指针实现 size ()、capacity (),亲手编写 reserve ()、resize (),亲手处理 insert () 和 erase () 的迭代器失效问题时,你会对 vector 的认知上升到一个全新的层次。而掌握了 vector 的模拟实现后,后续学习 list、deque、map 等其他容器时,你会发现很多相通的设计思想,学习难度也会大大降低。因为 STL 的容器虽然功能不同,但底层都围绕着 “内存管理”“元素操作”“迭代器设计” 这三个核心,而 vector 已经为我们打下了坚实的基础。

    最后,我想对每一位坚持学习的小伙伴说:编程这条路,没有捷径可走,但每一步都算数。或许你现在觉得 vector 的细节太多、太复杂,但请相信,这些看似零散的知识点,终将串联成你的核心竞争力。在算法竞赛中,vector 的高效随机访问能帮你快速实现复杂逻辑;在日常工作中,vector 的易用性和兼容性能让你高效完成开发任务;而你在学习中培养的 “底层思维”“场景化思维”“优化思维”,会让你在面对更复杂的问题时,依然能保持清晰的思路。

    请不要畏惧困难,也不要满足于 “会用就行” 的浅层次理解。当你遇到不懂的知识点,不妨回头再看看底层逻辑;当你写出 bug 时,不妨从内存布局、迭代器状态等角度排查;当你优化代码时,不妨思考 “是否还有更高效的实现方式”。就像 vector 的扩容机制一样,每一次 “突破舒适区” 的学习,都是在为自己的 “能力容量” 扩容,而这种扩容带来的成长,是指数级的。

    下一篇,我们将正式进入 vector 的模拟实现,亲手构建这个强大的容器。我相信,经过这一篇的全面学习,你已经具备了足够的基础。让我们带着对底层逻辑的好奇,带着对编程的热爱,继续前行。记住,编程的世界里,真正的高手,从来都是既能 “熟练使用工具”,又能 “看透工具本质” 的人。而你,正在成为这样的人。

    加油吧,小伙伴们!每一次对细节的较真,每一次对原理的探索,每一次对代码的打磨,都是在为自己的编程梦想添砖加瓦。愿你以 vector 为阶,不断攀登,奔赴编程的更深山海,遇见更优秀的自己!

     

    赞(0)
    未经允许不得转载:网硕互联帮助中心 » 对于C++中vector的详细介绍
    分享到: 更多 (0)

    评论 抢沙发

    评论前必须登录!