C++ vector:从使用到底层核心剖析

在 C++ 标准库中,vector 是最常用的容器之一,它本质上是一个动态顺序表,兼具数组的随机访问特性和动态扩容的灵活性。本文将从基础使用、核心接口、迭代器失效、底层实现等维度,全面拆解 vector 的核心知识点,帮你彻底吃透这个容器。

一、vector 基础使用

1. 头文件与初始化

使用 vector 首先需要包含头文件<vector>,它的初始化方式灵活多样,适配不同场景:

cpp 复制代码
#include <iostream>
#include <vector>
using namespace std;

void test_vector_init() {
    // 1. 空vector
    vector<int> v1;  
    // 2. 初始化10个元素,每个元素值为1
    vector<int> v2(10, 1);  
    // 3. 用v2的迭代器区间初始化v3(拷贝v2所有元素)
    vector<int> v3(v2.begin(), v2.end());  
    // 4. 列表初始化(C++11及以上)
    vector<int> v4{1, 2, 0};  
}

2. 三种遍历方式

vector 的遍历和 string 高度相似,支持下标、迭代器、范围 for 三种方式:

cpp 复制代码
void test_vector_traverse() {
    vector<int> v{1, 2, 3, 4, 5};
    
    // 方式1:下标遍历(随机访问特性)
    for (size_t i = 0; i < v.size(); i++) {
        cout << v[i] << " ";
    }
    cout << endl;

    // 方式2:迭代器遍历
    vector<int>::iterator it = v.begin();
    while (it != v.end()) {
        cout << *it << " ";
        ++it;
    }
    cout << endl;

    // 方式3:范围for(C++11及以上)
    for (auto e : v) {
        cout << e << " ";
    }
    cout << endl;
}

3. 插入与删除

vector 的插入(insert)和删除(erase)是高频操作,需注意迭代器的使用规则:

cpp 复制代码
void test_vector_insert_erase() {
    vector<int> v(10, 1);  
    // 尾插元素2
    v.push_back(2);
    // 头插元素3(insert仅支持迭代器传参)
    v.insert(v.begin(), 3);
    // 在第5个位置前插入4(迭代器偏移)
    v.insert(v.begin() + 5, 4);
	
    for (auto e : v) {
        cout << e << " ";	// 输出:3 1 1 1 1 4 1 1 1 1 1 1 2
    }
    cout << endl;

    // 尾删元素
    v.pop_back();
    // 删除第5个位置的元素
    v.erase(v.begin() + 5);
    for (auto e : v) {
        cout << e << " ";	// 输出:3 1 1 1 1 1 1 1 1 1 1
    }
    cout << endl;
}

4. 二维 vector(二维数组)

vector 支持嵌套定义实现二维数组,相比原生二维数组更灵活(每行长度可不同):

cpp 复制代码
void test_vector_2d() {
    // 初始化10行5列的二维数组,每个元素初始值为1
    vector<int> v(5, 1);
    vector<vector<int>> vv(10, v);
    // 修改第3行第2列的元素(下标从0开始)
    vv[2][1] = 2;  

    // 遍历二维vector
    for (size_t i = 0; i < vv.size(); i++) {
        for (size_t j = 0; j < vv[i].size(); j++) {
            cout << vv[i][j] << " ";
        }
        cout << endl;
    }
}

5. 实用案例:杨辉三角

结合 vector 的初始化、resize、元素访问等特性,实现 LeetCode 经典题 "杨辉三角":

cpp 复制代码
class Solution {
public:
    vector<vector<int>> generate(int numRows) {
        // 初始化numRows行的二维vector
        vector<vector<int>> vv(numRows);
        for(size_t i = 0; i < numRows; i++) {
            // 第i行开辟i+1个空间,初始值为0
            vv[i].resize(i+1, 0);  
            // 每行首尾元素设为1
            vv[i].front() = vv[i].back() = 1;  
        }

        // 填充中间元素
        for(int i = 1; i < vv.size(); i++) {
            for(int j = 1; j < vv[i].size()-1; j++) {
                vv[i][j] = vv[i-1][j-1] + vv[i-1][j];
            }
        }
        return vv;
    }
};

二、vector 核心接口对比与细节

1. reserve:仅扩容,不初始化

vector 的reserve和 string 的reserve存在关键区别:

  • n < size()时,vector 的 reserve无任何操作
  • string 的 reserve 若缩容(多数编译器忽略),至少保证容量不低于 size ()。

2. resize:改变元素个数,按需初始化

resize会直接修改 vector 的元素个数,行为分三种情况:

  • n < size():删除末尾元素,仅调整有效元素个数;
  • size() < n < capacity():在末尾插入元素,用默认值初始化;
  • n > capacity():先扩容,再插入元素初始化。

注意 :vector 默认按元素类型的默认值初始化(如 int 为 0),而 string 固定初始化为'\0'

cpp 复制代码
// resize接口原型(可指定初始化值)
void resize (size_type n, value_type val = value_type());

3. 不支持重载 <<和>>

vector 没有默认的输入输出重载,需手动实现输入输出逻辑:

cpp 复制代码
// 单个元素输入
vector<int> v;
int x;
cin >> x;
v.push_back(x);

// 多个元素输入(初始化5个元素)
vector<int> v1(5, 0);
for (size_t i = 0; i < 5; i++) {
    cin >> v1[i];
}

三、迭代器失效

迭代器失效是 vector 使用中最容易出错的地方,本质是迭代器指向的空间失效指向位置的语义改变,主要分为两类场景:

1. 扩容导致的迭代器失效(野指针)

当 vector 插入元素触发扩容时,原空间会被释放,之前的迭代器变为野指针:

cpp 复制代码
// 错误示例:扩容后迭代器失效
void test_vector_invalid1() {
    vector<int> v{1,2,3,4};
    // 插入位置迭代器
    auto pos = v.begin() + 1;
    // 插入元素触发扩容,原pos指向的空间被释放
    v.insert(pos, 5);
    // 访问失效迭代器,结果未定义
    cout << *pos << endl; 
}

解决方法:插入时记录迭代器相对位置,扩容后重新修正迭代器:

cpp 复制代码
iterator insert(iterator pos, const T& x) {
    assert(pos >= _start && pos <= _finish);
    if (_finish == _end_of_storage) {
        // 记录相对位置
        size_t len = pos - _start;
        // 扩容
        reserve(capacity() == 0 ? 4 : 2 * capacity());
        // 修正迭代器指向新空间
        pos = _start + len;
    }
    // 元素后移 + 插入新元素
    iterator end = _finish - 1;
    while (end >= pos) {
        *(end + 1) = *end;
        --end;
    }
    *pos = x;
    ++_finish;
    // 返回新插入元素的迭代器
    return pos;
}

2. 插入 / 删除导致的迭代器语义失效

(1)insert 后迭代器语义失效

insert 会挪动元素,原迭代器指向的位置语义改变(不再是原来的元素):

cpp 复制代码
void test_vector_invalid2() {
    vector<int> v{1,5,2,3,4};
    auto p = find(v.begin(), v.end(), 2);
    if (p != v.end()) {
        // 插入后p不再指向2,而是指向新插入的40
        v.insert(p, 40);
        // 错误:修改的是40,而非原来的2
        (*p) *= 10; 
    }
}

解决方法:接收 insert 返回的新迭代器,重新定位:

cpp 复制代码
auto p = find(v.begin(), v.end(), 2);
if (p != v.end()) {
    // 接收新迭代器,指向插入的40
    p = v.insert(p, 40);
    // 修改原来的2(p+1位置)
    (*(p + 1)) *= 10; 
}

(2)erase 后迭代器语义失效

erase 删除元素后,原迭代器指向的位置变为下一个元素,若直接 ++ 会跳过元素或死循环:

cpp 复制代码
// 错误示例:删除所有偶数,迭代器失效导致漏删/死循环
void test_vector_invalid3() {
    vector<int> v{1,2,3,4};
    auto it = v.begin();
    while (it != v.end()) {
        if (*it % 2 == 0) {
            v.erase(it); // 删除后it失效
        }
        ++it; // 失效迭代器++,行为未定义
    }
}

解决方法:接收 erase 返回的迭代器(指向删除元素的下一个位置):

cpp 复制代码
// 正确版本:删除所有偶数
void test_vector_erase_fix() {
    vector<int> v{1,2,3,4};
    auto it = v.begin();
    while (it != v.end()) {
        if (*it % 2 == 0) {
            // 接收返回值,更新迭代器
            it = v.erase(it);
        } else {
            ++it; // 仅非删除场景++
        }
    }
    // 输出:1 3
    for (auto e : v) cout << e << " ";
}

四、vector 底层实现关键细节

1. 迭代器区间构造(模板复用)

vector 的迭代器区间构造函数设计为模板函数,支持任意容器的迭代器初始化(只要类型匹配):

cpp 复制代码
// 类模板的成员函数可嵌套函数模板
template <class InputIterator>
vector(InputIterator first, InputIterator last) {
    while (first != last) {
        push_back(*first);
        ++first;
    }
}

// 用法:仅拷贝v1的前3个元素
vector<int> v1{1,2,3,4,5};
vector<int> v2(v1.begin(), v1.begin()+3);

2. 浅拷贝问题修复

vector 扩容时若用 memcpy 拷贝元素,会导致自定义类型(如 string)的浅拷贝问题,需改用赋值运算符:

cpp 复制代码
void reserve(size_t n) {
    if (n > capacity()) {
        size_t old_size = size();
        T* tmp = new T[n];
        // 错误:memcpy是浅拷贝,自定义类型会析构重复释放
        // memcpy(tmp, _start, size() * sizeof(T));
        // 正确:调用T的赋值运算符,深拷贝自定义类型
        for (size_t i = 0; i < old_size; i++) {
            tmp[i] = _start[i];
        }
        delete[] _start;
        _start = tmp;
        _finish = _start + old_size;
        _end_of_storage = _start + n;
    }
}

3. 内置类型的 "伪构造函数"

vector 的 resize 接口默认参数为T(),为了适配内置类型(如 int),C++ 编译器会为内置类型模拟构造函数:

cpp 复制代码
void resize(size_t n, T val = T()) {
    if (n < size()) {
        _finish = _start + n;
    } else {
        reserve(n);
        while (_finish < _start + n) {
            *_finish = val;
            ++_finish;
        }
    }
}
// 调用示例:int()等价于0,double()等价于0.0
v.resize(10); // 内置类型用默认值初始化

4. const_iterator 的 typename 修饰

在模板中使用vector<T>::const_iterator时,需加typename说明这是类型(否则编译器视为静态成员):

cpp 复制代码
// 错误:编译器无法区分const_iterator是类型还是成员
// vector<T>::const_iterator it = v.begin();
// 正确1:加typename说明是类型
typename vector<T>::const_iterator it1 = v.begin();
// 正确2:用auto自动推导(推荐)
auto it2 = v.begin();
相关推荐
csbysj20202 小时前
Perl 数组
开发语言
Lucis__2 小时前
哈希实现&封装unordered系列容器
数据结构·c++·算法·哈希封装
唐装鼠2 小时前
C语言syslog()函数(deepseek)
c语言·开发语言·syslog
froginwe112 小时前
SQL MIN() 函数详解
开发语言
青岛少儿编程-王老师2 小时前
CCF编程能力等级认证GESP—C++7级—20251227
开发语言·c++
汉克老师2 小时前
GESP2025年12月认证C++四级真题与解析(编程题2 (优先购买))
c++·sort·结构体·优先级·gesp4级·gesp四级
brevity_souls2 小时前
Java 中 String、StringBuffer 和 StringBuilder
java·开发语言
我可以将你更新哟2 小时前
在Ubuntu 22.04上安装C++编译工具
linux·c++·ubuntu
ss2732 小时前
类的线程安全:多线程编程-银行转账系统:如果两个线程同时修改同一个账户余额,没有适当的保护机制,会发生什么?
java·开发语言·数据库