C++ vector 详解:从入门到模拟实现,一文搞懂动态数组

本文系统讲解 C++ STL 中 std::vector 的全部核心知识:从基础API到底层扩容机制,再到手写模拟实现,适合所有C++学习者阅读。


一、前言

在 C++ 的 STL(Standard Template Library)中,vector 无疑是用得最频繁的容器之一。它提供了动态数组的能力,在保持连续内存布局的同时,自动管理内存的分配和释放。

无论你是刚学完 C 语言数组、准备过渡到 C++ 的初学者,还是已经在刷 LeetCode、写项目的进阶开发者——理解 vector 的方方面面,都是 C++ 学习中无法绕过的必修课。

本文特点:

  • 分层讲解:从基础用法到底层原理,你可以按需跳读
  • 大量可运行代码:每一个接口都配有完整的示例代码
  • 模拟实现:手写一个 MyVector,帮助你真正理解底层发生了什么
  • 常见面试题覆盖:扩容机制、迭代器失效、emplace_back vs push_back 等高频考点

在正式开始之前,建议你先收藏本文,方便随时查阅。


二、vector 是什么?

2.1 官方定义

std::vector 是 C++ 标准库中的一个顺序容器(Sequence Container),它在内部维护了一个动态分配的连续数组

template<
    class T,
    class Allocator = std::allocator<T>
> class vector;

2.2 核心特性

特性 说明
连续存储 元素在内存中连续存放,支持 O(1) 随机访问
动态扩容 容量不足时自动重新分配更大的内存空间
尾部高效 尾部插入/删除平均 O(1)(均摊复杂度)
中间低效 中间插入/删除 O(n),需要移动后续所有元素

2.3 vector vs 普通数组

// 普通数组 —— 大小在编译期确定,不可改变
int arr[10];           // 固定10个元素,无法扩容

// vector —— 大小可在运行时动态变化
vector<int> vec;       // 初始为空
vec.push_back(1);      // 往里面加,自动扩容
vec.push_back(2);
// 现在 vec 有2个元素,容量可能大于2

一句话总结:vector 就是一个会自己管理内存、"会长大"的数组。

2.4 物理结构图解

vector 底层通常由三个指针来管理:

    _start          _finish       _end_of_storage
      |                |                   |
      v                v                   v
   +---+---+---+---+---+---+---+---+---+---+---+
   | 1 | 2 | 3 | 4 |   |   |   |   |   |   |   |
   +---+---+---+---+---+---+---+---+---+---+---+
    <---  size=4   ---> <-  capacity-size ->
    <------------- capacity() = 10 ------------------>
  • _start:指向数组起始位置
  • _finish:指向最后一个元素的下一个位置
  • _end_of_storage:指向分配内存的末尾

size() = _finish - _startcapacity() = _end_of_storage - _start

理解这个三指针模型,对你之后看源码和模拟实现都会有很大帮助。


三、vector 的构造方式

vector 提供了多种构造函数,覆盖了各种使用场景。

3.1 默认构造

vector<int> v1;                // 空 vector,size=0, capacity=0
vector<string> v2;             // 存储 string 的空 vector

3.2 指定大小的构造

vector<int> v3(5);             // 5个元素,值均为 int() = 0
vector<int> v4(5, 42);         // 5个元素,值均为 42
vector<string> v5(3, "hello"); // 3个元素,值均为 "hello"

注意vector<int> v(5) 调用了 5 次 int()(值为 0),而不是只分配空间不初始化。

3.3 拷贝构造

vector<int> v6(5, 10);         // {10, 10, 10, 10, 10}
vector<int> v7(v6);            // 深拷贝,v7 = {10, 10, 10, 10, 10}
vector<int> v8 = v6;           // 等价于拷贝构造

拷贝构造会进行深拷贝——v7 和 v6 各自拥有独立的内存空间,修改其中一个不会影响另一个。

3.4 迭代器范围构造

int arr[] = {1, 2, 3, 4, 5};
vector<int> v9(arr, arr + 5);           // {1, 2, 3, 4, 5}

vector<int> v10(v9.begin(), v9.end());  // 也可用另一个vector的迭代器
vector<int> v11(v9.begin() + 1, v9.begin() + 4); // {2, 3, 4},左闭右开

string s = "hello";
vector<char> v12(s.begin(), s.end());   // {'h', 'e', 'l', 'l', 'o'}

这个构造方式非常灵活——任何满足 InputIterator 的类型都可以传进去。

3.5 初始化列表构造(C++11)

vector<int> v13 = {1, 2, 3, 4, 5};
vector<int> v14{10, 20, 30};
vector<string> v15{"apple", "banana", "cherry"};

这是 C++11 引入的语法,也是日常开发中最直观的写法。

3.6 移动构造(C++11)

vector<int> createVector() {
    vector<int> temp = {1, 2, 3, 4, 5};
    return temp;  // 现代编译器使用移动语义,不会额外拷贝
}

vector<int> v16 = createVector();  // 高效,直接"窃取"temp的资源

3.7 容易踩的坑:() vs {} 初始化

vector<int> a(3, 5);   // 调用填充构造:{5, 5, 5}
vector<int> b{3, 5};   // 调用初始化列表构造:{3, 5}

// 这就是为什么以下写法会让人困惑:
vector<int> c(10);     // 10个元素,每个都是0
vector<int> d{10};     // 1个元素,值为10
vector<int> e(10, 1);  // 10个元素,每个都是1
vector<int> f{10, 1};  // 2个元素:10和1

四、vector 的迭代器

4.1 迭代器类型一览

迭代器 获取方法 说明
正向迭代器 begin() / end() 从前往后遍历
反向迭代器 rbegin() / rend() 从后往前遍历
const 正向 cbegin() / cend() 只读正向遍历
const 反向 crbegin() / crend() 只读反向遍历

vector 中,迭代器的底层实现就是原生指针T*),这也是 vector 迭代器是随机访问迭代器(Random Access Iterator)的原因——指针天然支持 +n-n[] 等操作。

4.2 遍历 vector 的四种方式

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

int main() {
    vector<int> v = {10, 20, 30, 40, 50};

    // 方式1:下标访问
    cout << "下标遍历:" << endl;
    for (size_t i = 0; i < v.size(); ++i) {
        cout << v[i] << " ";
    }
    cout << endl;

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

    // 方式3:范围 for(C++11,最推荐日常使用)
    cout << "范围for遍历:" << endl;
    for (int x : v) {
        cout << x << " ";
    }
    cout << endl;

    // 方式4:for_each + lambda(C++11)
    cout << "for_each遍历:" << endl;
    for_each(v.begin(), v.end(), [](int x) {
        cout << x << " ";
    });
    cout << endl;

    return 0;
}

输出:

下标遍历:
10 20 30 40 50
迭代器遍历:
10 20 30 40 50
范围for遍历:
10 20 30 40 50
for_each遍历:
10 20 30 40 50

4.3 反向遍历

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

// 反向迭代器
for (auto it = v.rbegin(); it != v.rend(); ++it) {
    cout << *it << " ";  // 5 4 3 2 1
}
cout << endl;

4.4 迭代器支持的操作(随机访问)

vector<int> v = {10, 20, 30, 40, 50};
auto it = v.begin();

it + 2;        // 指向 30
*(it + 2);     // 30
it[3];         // 40(等价于 *(it + 3))
it += 1;       // 指向 20

auto it2 = v.end();
it2 - it;      // 得到两个迭代器间的距离(ptrdiff_t)
it < it2;      // true,随机访问迭代器支持比较

五、vector 的容量管理

容量管理是理解 vector 底层行为的关键。这一部分认真看,面试常考。

5.1 核心接口

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

int main() {
    vector<int> v;

    cout << "初始 size: " << v.size()         // 0
         << ", capacity: " << v.capacity() << endl;  // 0

    v.push_back(1);
    cout << "插入1个后 size: " << v.size()    // 1
         << ", capacity: " << v.capacity() << endl;  // 可能是1(取决于编译器)

    v.reserve(100);
    cout << "reserve(100)后 size: " << v.size()    // 仍然是1!
         << ", capacity: " << v.capacity() << endl;  // 至少100

    v.shrink_to_fit();
    cout << "shrink_to_fit后 size: " << v.size()    // 1
         << ", capacity: " << v.capacity() << endl;  // 接近1

    return 0;
}

5.2 重点辨析:resize() vs reserve()

这是初学者最容易混淆的两个函数:

resize(n) reserve(n)
改变 size 是,size 变为 n 否,size 不变
改变 capacity 可能(n > capacity 时会扩容) 可能(n > capacity 时会扩容)
新增元素的值 有默认值(或指定值) 不添加元素
主要用途 需要确保有 n 个元素 预分配空间,避免频繁扩容

代码演示:

vector<int> v;
v.reserve(10);
cout << "reserve(10)后:";  // size=0, capacity=10
// v[0] = 5;   // 错误!size还是0,不能访问v[0]

v.resize(5);
cout << "resize(5)后:";    // size=5, capacity=10(没缩容)
// v[0] = 5;   // 正确!现在size=5,可以访问v[0]~v[4]

v.resize(10, 99);
cout << "resize(10, 99)后:";  // size=10, capacity=10
// 前5个元素不变,后5个填99

5.3 扩容机制:VS(1.5倍)vs g++(2倍)

这是 vector 最经典的面试题之一。

不同编译器对 vector 的扩容策略不同:

编译器 扩容因子 示例(连续push_back)
Visual Studio (MSVC) 约 1.5 倍 1 → 2 → 3 → 4 → 6 → 9 → 13…
GCC (libstdc++) 2 倍 1 → 2 → 4 → 8 → 16 → 32…
Clang (libc++) 2 倍 同 GCC

为什么选择 1.5 倍或 2 倍?

  • 2 倍扩容:实现简单(左移一位),扩容次数少,但可能浪费较多内存。比如容量 1024,再 push 一次就变成 2048,之前的总容量(1+2+4+…)永远凑不出恰好回收完所有旧空间。

  • 1.5 倍扩容:理论上可以在多次扩容后复用之前释放的内存块(因为旧容量之和可能大于新容量)。内存利用率更优,但实现复杂一点。

验证代码:

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

int main() {
    vector<int> v;
    size_t oldCap = v.capacity();

    for (int i = 0; i < 100; ++i) {
        v.push_back(i);
        if (v.capacity() != oldCap) {
            cout << "size=" << v.size()
                 << ", capacity=" << v.capacity()
                 << "(扩容了!)" << endl;
            oldCap = v.capacity();
        }
    }
    return 0;
}

GCC 下输出示例:

size=1, capacity=1(扩容了!)
size=2, capacity=2(扩容了!)
size=3, capacity=4(扩容了!)
size=5, capacity=8(扩容了!)
size=9, capacity=16(扩容了!)
size=17, capacity=32(扩容了!)
size=33, capacity=64(扩容了!)
size=65, capacity=128(扩容了!)

可以看到明显的 2 倍扩容规律。

5.4 扩容的代价

扩容并不是免费的。当容量不足时,vector 需要做三件事:

  1. 分配一块新的、更大的内存
  2. 将旧空间的所有元素拷贝(或移动)到新空间
  3. 释放旧空间

如果 vector 中存储的是复杂的自定义类型,且拷贝构造函数代价较高,频繁扩容会严重影响性能。这就是为什么在实际开发中,如果能预估元素数量,应当提前 reserve()


六、vector 的元素访问

6.1 访问方式一览

vector<int> v = {10, 20, 30, 40, 50};

v[0];           // 10 —— 不检查越界
v.at(0);        // 10 —— 检查越界,越界抛 out_of_range 异常
v.front();      // 10 —— 首元素引用
v.back();       // 50 —— 尾元素引用
v.data();       // 返回 int*,指向底层数组首地址

6.2 operator[] vs at() 深入对比

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

int main() {
    vector<int> v = {1, 2, 3};

    // v[5] = 10;      // 越界!不报错,未定义行为(可能崩也可能不崩)
    
    try {
        v.at(5) = 10;  // 越界!抛出 std::out_of_range 异常
    } catch (const out_of_range& e) {
        cout << "捕获异常:" << e.what() << endl;
    }

    return 0;
}
operator[] at()
越界检查 有(抛异常)
性能 更快 略慢(多一次边界判断)
推荐场景 确定不越界时(如循环 i < v.size() 不确定是否越界时(如外部传参)

6.3 data() —— 与 C 接口交互

当你需要调用一个接受 C 风格数组的接口时,data() 非常有用:

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

// 传递给只接受 int* 的 C 函数
void c_style_func(const int* arr, size_t n) {
    for (size_t i = 0; i < n; ++i) {
        printf("%d ", arr[i]);
    }
}

c_style_func(v.data(), v.size());  // 输出: 1 2 3 4 5

注意:当 v.empty() 时,v.data() 返回的指针可能为 nullptr,使用前要检查。


七、vector 的增删改操作

7.1 尾部操作:push_back / emplace_back / pop_back

vector<int> v;

// push_back —— 先构造临时对象,再拷贝/移动到vector
v.push_back(10);
v.push_back(20);
// v = {10, 20}

// emplace_back —— 直接在vector内部构造对象(C++11)
v.emplace_back(30);
// v = {10, 20, 30}

// pop_back —— 删除尾部元素
v.pop_back();
// v = {10, 20}

7.2 push_back vs emplace_back —— 效率对比

这是另一个面试高频考点,用自定义类型来演示最清楚:

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

class Person {
public:
    string name;
    int age;

    Person(const string& n, int a) : name(n), age(a) {
        cout << "Person 构造:" << name << endl;
    }
    Person(const Person& other) : name(other.name), age(other.age) {
        cout << "Person 拷贝构造:" << name << endl;
    }
    Person(Person&& other) noexcept : name(move(other.name)), age(other.age) {
        cout << "Person 移动构造:" << name << endl;
    }
};

int main() {
    vector<Person> v;
    v.reserve(10);  // 提前预留空间,避免扩容干扰观察

    cout << "=== push_back ===" << endl;
    v.push_back(Person("Alice", 25));
    // 输出:构造 → 移动构造

    cout << "=== emplace_back ===" << endl;
    v.emplace_back("Bob", 30);
    // 输出:构造(只有一次!)

    return 0;
}

输出:

=== push_back ===
Person 构造:Alice
Person 移动构造:Alice
=== emplace_back ===
Person 构造:Bob

可以看到,push_back 先构造了临时对象 Person("Alice", 25),再将其移动到 vector 中(2 次调用),而 emplace_back 直接在 vector 内部构造对象(1 次调用)。

结论:在往 vector 中添加复杂对象时,优先使用 emplace_back

7.3 任意位置插入:insert

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

// 在指定位置插入单个元素
auto it = v.begin() + 2;     // 指向 3
v.insert(it, 99);
// v = {1, 2, 99, 3, 4, 5}

// 在指定位置插入 n 个相同元素
v.insert(v.begin(), 3, 0);
// v = {0, 0, 0, 1, 2, 99, 3, 4, 5}

// 在指定位置插入一段迭代器范围
vector<int> extra = {100, 200};
v.insert(v.end(), extra.begin(), extra.end());
// v = {0, 0, 0, 1, 2, 99, 3, 4, 5, 100, 200}

注意insert 在非尾部位置插入元素时,需要将插入位置之后的所有元素向后移动,时间复杂度为 O(n)。频繁在中间插入应该考虑 list 或其他容器。

7.4 任意位置删除:erase

vector<int> v = {10, 20, 30, 40, 50};

// 删除单个元素
v.erase(v.begin() + 2);  // 删除 30
// v = {10, 20, 40, 50}

// 删除一段范围 [first, last)
v.erase(v.begin() + 1, v.begin() + 3);  // 删除 20 和 40
// v = {10, 50}

inserterase返回一个新的有效迭代器——这点非常重要,与迭代器失效直接相关(见第八章)。

7.5 assign / clear / swap

vector<int> v;

v.assign(5, 42);           // v = {42, 42, 42, 42, 42}——整体赋值
v.assign({1, 2, 3});       // v = {1, 2, 3}——用初始化列表赋值

v.clear();                 // 清空所有元素,size=0(但capacity可能不变)

vector<int> v2 = {100, 200};
v.swap(v2);                // 交换v和v2的内容(只交换指针,O(1))
// v = {100, 200}, v2 = {1, 2, 3}

swap 的性能技巧:如果你想让 vector 真正释放内存(capacity 变为 0),可以用一个空 vector 与它交换:

vector<int> v(1000);
v.clear();                      // size=0,但 capacity 可能还是 1000

vector<int>().swap(v);          // 真正释放内存!v 与临时空 vector 交换
// 现在 v.capacity() == 0

八、迭代器失效问题(重点+难点)

8.1 什么是迭代器失效?

迭代器本质是指向容器内元素的"指针"。当容器内部发生了内存重新分配元素移动时,之前获取的迭代器可能指向已经释放的内存或错误的元素——这就是迭代器失效

8.2 导致迭代器失效的操作

操作 失效范围 原因
push_back(触发扩容) 所有迭代器 底层内存整体搬迁
insert(触发扩容) 所有迭代器 同上
insert(未触发扩容) 插入点及之后的迭代器 后面元素整体后移
erase 删除点及之后的迭代器 后面元素整体前移
resize(触发扩容) 所有迭代器 底层内存整体搬迁

8.3 失效场景演示

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

int main() {
    vector<int> v = {1, 2, 3, 4, 5};

    auto it = v.begin() + 2;  // it 指向 3

    cout << "删除前 *it = " << *it << endl;  // 3

    v.erase(v.begin() + 1);   // 删除 2 —— 触发元素移动

    // cout << *it << endl;   // 危险!it 已失效,未定义行为

    return 0;
}

8.4 解决方案:利用返回值更新迭代器

inserterase 都返回一个有效的迭代器:

  • insert(pos, val) 返回指向新插入元素的迭代器
  • erase(pos) 返回指向被删除元素下一个位置的迭代器
vector<int> v = {1, 2, 3, 4, 5};

// 安全删除偶数元素的正确写法
for (auto it = v.begin(); it != v.end(); /* 不要在for头里++ */) {
    if (*it % 2 == 0) {
        it = v.erase(it);  // erase 返回下一个有效位置
    } else {
        ++it;
    }
}
// v = {1, 3, 5}

常见错误写法,会导致迭代器跳跃或失效:

// 错误!删除后it失效,++it 是未定义行为
for (auto it = v.begin(); it != v.end(); ++it) {
    if (*it % 2 == 0) {
        v.erase(it);
    }
}

面试要点:记住 “erase 返回下一个有效迭代器,用 it = v.erase(it) 来接住它。”


九、vector 的扩容机制深入

9.1 三指针模型回顾

回忆第二节的图,vector 底层用三个指针管理内存。扩容的核心逻辑就是:

// 伪代码
void push_back(const T& val) {
    if (_finish == _end_of_storage) {
        // 空间不足,需要扩容
        reserve(capacity() == 0 ? 1 : capacity() * 2);  // 2倍扩容为例
    }
    *_finish = val;
    ++_finish;
}

9.2 扩容流程详解

reserve(new_cap) 被调用且 new_cap > capacity() 时:

  1. 分配新空间new new_cap * sizeof(T) 字节
  2. 迁移元素:将旧空间的元素拷贝/移动到新空间
  3. 释放旧空间delete[] 旧空间
  4. 更新指针_start_finish_end_of_storage 指向新空间
void reserve(size_t n) {
    if (n > capacity()) {
        T* tmp = new T[n];                     // 1. 开辟新空间
        size_t sz = size();
        
        // 2. 拷贝/移动旧元素到新空间
        for (size_t i = 0; i < sz; ++i) {
            tmp[i] = _start[i];                // 调用拷贝赋值
        }
        
        delete[] _start;                       // 3. 释放旧空间
        
        _start = tmp;                          // 4. 更新指针
        _finish = _start + sz;
        _end_of_storage = _start + n;
    }
}

9.3 memcpy 陷阱

扩容时拷贝数据,能用 memcpy 吗?

// 错误的做法
memcpy(tmp, _start, sz * sizeof(T));  // 浅拷贝!只拷贝字节

对于存储自定义类型(如 string、含指针成员的类型)的 vector,memcpy 是灾难性的。 原因:

  • memcpy 是浅拷贝,只复制字节,不会调用拷贝构造函数
  • 如果类型包含指向堆内存的指针(如 string 内部的 char*),浅拷贝会导致两个对象共享同一块内存
  • 释放旧空间时析构函数会释放那块共享内存,新空间中的指针就成了野指针

正确做法是逐个调用拷贝/移动操作,让每个元素正确完成深拷贝。


十、vector 的模拟实现

理解 vector 的最好方式就是自己写一个。下面我们逐步实现一个简化版的 MyVector

10.1 整体架构

template<typename T>
class MyVector {
private:
    T* _start;            // 指向数据起始位置
    T* _finish;           // 指向最后一个元素的下一个位置
    T* _end_of_storage;   // 指向分配空间的末尾

public:
    // 迭代器(就是原生指针)
    typedef T* iterator;
    typedef const T* const_iterator;

    // ... 成员函数见下文
};

10.2 构造函数

// 默认构造
MyVector() : _start(nullptr), _finish(nullptr), _end_of_storage(nullptr) {}

// 填充构造:n个val
MyVector(size_t n, const T& val = T())
    : _start(new T[n]), _finish(_start + n), _end_of_storage(_start + n) {
    for (size_t i = 0; i < n; ++i) {
        _start[i] = val;
    }
}

// 迭代器范围构造
template<typename InputIterator>
MyVector(InputIterator first, InputIterator last) {
    size_t n = last - first;//包含的元素个数
    
    _start = new T[n];//创建一个包含n个T类元素的空间
    
    //默认当前的size和capacity是同样的大小
    _finish = _start + n;
    _end_of_storage = _start + n;
		//一一进行赋值拷贝(不能用memcpy)
    for (size_t i = 0; i < n; ++i) {
        _start[i] = first[i];
    }
}

10.3 拷贝构造 & 赋值重载(深拷贝)

// 拷贝构造 —— 深拷贝
MyVector(const MyVector<T>& v) {
    size_t n = v.size();
    
    _start = new T[n];
    _finish = _start + n;
    _end_of_storage = _start + n;
    
    for (size_t i = 0; i < n; ++i) {
        _start[i] = v._start[i];
    }
}

// 现代写法:拷贝并交换(Copy and Swap)
void swap(MyVector<T>& v) {
		//用库里面的swap进行交换
    std::swap(_start, v._start);
    std::swap(_finish, v._finish);
    std::swap(_end_of_storage, v._end_of_storage);
}

MyVector<T>& operator=(MyVector<T> v) {  // 传值,自动调用拷贝构造
    swap(v);                              // 交换,v 的析构函数帮我们清理旧资源
    return *this;
}

10.4 容量相关

size_t size() const     { return _finish - _start; }
size_t capacity() const { return _end_of_storage - _start; }
bool empty() const      { return _start == _finish; }

void reserve(size_t n) {
    if (n > capacity()) //需要扩容 
    {
        T* tmp = new T[n];
        size_t sz = size();

        // 逐个拷贝(不能 memcpy!)
        for (size_t i = 0; i < sz; ++i) 
        {
            tmp[i] = _start[i];
    	}

        delete[] _start;//释放旧空间

        _start = tmp;
        _finish = _start + sz;
        _end_of_storage = _start + n;
    }
}

void resize(size_t n, const T& val = T()) {
    if (n < size()) 
    {
        // 缩小:只需移动 _finish
        _finish = _start + n;
    } 
    else 
    {
        // 扩大:可能需要扩容
        if (n > capacity()) {
            reserve(n);
        }
        // 填充新增的位置
        for (size_t i = size(); i < n; ++i) {
            _start[i] = val;
        }
        _finish = _start + n;
    }
}

10.5 元素访问

T& operator[](size_t i)             { return _start[i]; }
const T& operator[](size_t i) const { return _start[i]; }

T& front()             { return *_start; }
const T& front() const { return *_start; }

T& back()             { return *(_finish - 1); }
const T& back() const { return *(_finish - 1); }

iterator begin()             { return _start; }
const_iterator begin() const { return _start; }
iterator end()               { return _finish; }
const_iterator end()   const { return _finish; }

10.6 增删操作

void push_back(const T& val) {
    if (_finish == _end_of_storage) 
    {
        // 空间不足,扩容
        reserve(capacity() == 0 ? 1 : capacity() * 2);
    }
    *_finish = val;
    ++_finish;
}

void pop_back() 
{
    if (!empty()) 
    {
        --_finish;
    }
}

// insert —— 在pos位置前插入val,返回新插入元素的迭代器
iterator insert(iterator pos, const T& val) 
{
    size_t offset = pos - _start;  // 记录偏移量(防止扩容后pos失效)

    if (_finish == _end_of_storage) 
    {
        reserve(capacity() == 0 ? 1 : capacity() * 2);
    }

    pos = _start + offset;  // 用偏移量恢复 pos

    // 将pos及之后的元素整体后移一位
    for (iterator it = _finish; it > pos; --it) {
        *it = *(it - 1);
    }

    *pos = val;
    ++_finish;
    return pos;
}

// erase —— 删除pos位置的元素,返回下一个有效迭代器
iterator erase(iterator pos) 
{
    // 将pos之后的元素整体前移一位
    for (iterator it = pos; it < _finish - 1; ++it) 
    {
        *it = *(it + 1);
    }
    --_finish;
    return pos;  // pos现在指向原来pos+1位置的元素
}

10.7 析构函数

~MyVector() 
{
    delete[] _start;
    _start = _finish = _end_of_storage = nullptr;
}

10.8 完整源码

将以上各部分整合起来,再加上一些辅助函数:

#include <iostream>
#include <algorithm>  // for std::swap
#include <cassert>
using namespace std;

template<typename T>
class MyVector {
private:
    T* _start;
    T* _finish;
    T* _end_of_storage;

public:
    typedef T* iterator;
    typedef const T* const_iterator;

    // ===== 构造 / 析构 =====
    MyVector() : _start(nullptr), _finish(nullptr), _end_of_storage(nullptr) {}

    MyVector(size_t n, const T& val = T())
        : _start(new T[n]), _finish(_start + n), _end_of_storage(_start + n) {
        for (size_t i = 0; i < n; ++i) _start[i] = val;
    }

    template<typename InputIterator>
    MyVector(InputIterator first, InputIterator last) {
        size_t n = last - first;
        _start = new T[n];
        _finish = _start + n;
        _end_of_storage = _start + n;
        for (size_t i = 0; i < n; ++i) _start[i] = first[i];
    }

    MyVector(const MyVector<T>& v) {
        size_t n = v.size();
        _start = new T[n];
        _finish = _start + n;
        _end_of_storage = _start + n;
        for (size_t i = 0; i < n; ++i) _start[i] = v._start[i];
    }

    // 拷贝并交换
    void swap(MyVector<T>& v) {
        std::swap(_start, v._start);
        std::swap(_finish, v._finish);
        std::swap(_end_of_storage, v._end_of_storage);
    }

    MyVector<T>& operator=(MyVector<T> v) {
        swap(v);
        return *this;
    }

    ~MyVector() {
        delete[] _start;
        _start = _finish = _end_of_storage = nullptr;
    }

    // ===== 容量 =====
    size_t size()     const { return _finish - _start; }
    size_t capacity() const { return _end_of_storage - _start; }
    bool empty()      const { return _start == _finish; }

    void reserve(size_t n) {
        if (n > capacity()) {
            T* tmp = new T[n];
            size_t sz = size();
            for (size_t i = 0; i < sz; ++i) tmp[i] = _start[i];
            delete[] _start;
            _start = tmp;
            _finish = _start + sz;
            _end_of_storage = _start + n;
        }
    }

    void resize(size_t n, const T& val = T()) {
        if (n < size()) {
            _finish = _start + n;
        } else {
            if (n > capacity()) reserve(n);
            for (size_t i = size(); i < n; ++i) _start[i] = val;
            _finish = _start + n;
        }
    }

    // ===== 元素访问 =====
    T& operator[](size_t i)             { return _start[i]; }
    const T& operator[](size_t i) const { return _start[i]; }
    T& front()             { return *_start; }
    const T& front() const { return *_start; }
    T& back()              { return *(_finish - 1); }
    const T& back()  const { return *(_finish - 1); }

    // ===== 迭代器 =====
    iterator begin()             { return _start; }
    const_iterator begin() const { return _start; }
    iterator end()               { return _finish; }
    const_iterator end()   const { return _finish; }

    // ===== 增删改 =====
    void push_back(const T& val) {
        if (_finish == _end_of_storage) {
            reserve(capacity() == 0 ? 1 : capacity() * 2);
        }
        *_finish = val;
        ++_finish;
    }

    void pop_back() {
        if (!empty()) --_finish;
    }

    iterator insert(iterator pos, const T& val) {
        size_t offset = pos - _start;
        if (_finish == _end_of_storage) {
            reserve(capacity() == 0 ? 1 : capacity() * 2);
        }
        pos = _start + offset;
        for (iterator it = _finish; it > pos; --it) *it = *(it - 1);
        *pos = val;
        ++_finish;
        return pos;
    }

    iterator erase(iterator pos) {
        for (iterator it = pos; it < _finish - 1; ++it) *it = *(it + 1);
        --_finish;
        return pos;
    }

    void clear() { _finish = _start; }
};

// ===== 测试代码 =====
int main() {
    // 测试默认构造 & push_back
    MyVector<int> v;
    for (int i = 1; i <= 10; ++i) {
        v.push_back(i);
    }

    cout << "遍历 v:";
    for (size_t i = 0; i < v.size(); ++i) {
        cout << v[i] << " ";
    }
    cout << endl;

    // 测试 insert
    v.insert(v.begin() + 3, 99);
    cout << "insert 后:";
    for (auto x = v.begin(); x != v.end(); ++x) cout << *x << " ";
    cout << endl;

    // 测试 erase
    v.erase(v.begin() + 3);
    cout << "erase 后:";
    for (auto x = v.begin(); x != v.end(); ++x) cout << *x << " ";
    cout << endl;

    // 测试拷贝构造
    MyVector<int> v2(v);
    cout << "拷贝构造 v2:";
    for (size_t i = 0; i < v2.size(); ++i) cout << v2[i] << " ";
    cout << endl;

    // 拷贝独立验证
    v2[0] = 999;
    cout << "修改v2后,v[0]=" << v[0] << " (不受影响)" << endl;

    cout << "\n所有测试通过!" << endl;
    return 0;
}

测试输出:

遍历 v:1 2 3 4 5 6 7 8 9 10
insert 后:1 2 3 99 4 5 6 7 8 9 10
erase 后:1 2 3 4 5 6 7 8 9 10
拷贝构造 v2:1 2 3 4 5 6 7 8 9 10
修改v2后,v[0]=1 (不受影响)

所有测试通过!

十一、最佳实践 & 常见陷阱

11.1 提前预留空间

// 差 —— 频繁扩容
vector<int> v;
for (int i = 0; i < 100000; ++i) {
    v.push_back(i);  // 可能触发十几次扩容
}

// 好 —— 一次到位
vector<int> v;
v.reserve(100000);
for (int i = 0; i < 100000; ++i) {
    v.push_back(i);  // 不会触发扩容
}

11.2 尽量用 emplace_back

对于存储非基本类型的 vector,emplace_backpush_back 少一次拷贝/移动。如果类型构造代价大(如 string 的拷贝),收益更加明显。

11.3 遍历时删除:用返回值接 erase

前面第八章已经详细演示了,核心口诀:it = v.erase(it),不要在其他地方 ++it

11.4 删除元素时的"迭代器跳跃"

// 错误:删除元素后还 ++it,会跳过一个元素
vector<int> v = {1, 2, 3, 4, 5};
for (auto it = v.begin(); it != v.end(); ++it) 
{
    if (*it % 2 == 0) {
        v.erase(it);   // 危险!
    }
}
// 结果不可预测

// 正确:只在未删除时才 ++it
for (auto it = v.begin(); it != v.end(); ) 
{
    if (*it % 2 == 0) {
        it = v.erase(it);  // 不 ++it,因为 erase 已经"前进"了一步
    } else {
        ++it;
    }
}

11.5 vector<bool> 是特殊的

vector<bool> 不是真正的 vector!标准对它做了特化——每个 bool 只占 1 个 bit 而非 1 个 byte。这导致:

vector<bool> vb = {true, false, true};
// vb[0] 返回的不是 bool&,而是一个"代理对象"
// bool& ref = vb[0];  // 编译错误!

// 如果需要真正的 bool 数组,用 vector<char> 或 deque<bool>

11.6 二维 vector 初始化

// 5行3列,全部初始化为0
vector<vector<int>> matrix(5, vector<int>(3, 0));

// 等价于:
// [[0,0,0],
//  [0,0,0],
//  [0,0,0],
//  [0,0,0],
//  [0,0,0]]

11.7 存储指针时注意内存管理

vector<int*> v;
v.push_back(new int(10));  // 记住要手动 delete!

// 内存释放
for (int* p : v)
{
    delete p;
}
v.clear();

// 更推荐:用智能指针
vector<unique_ptr<int>> v;
v.push_back(make_unique<int>(10));  // 自动管理内存,更安全

十二、总结

12.1 核心知识速查表

分类 重点内容
构造 6种构造方式,注意 () vs {} 的区别
迭代器 随机访问迭代器,底层是原生指针
容量 resize 改 size,reserve 预留空间;扩容 1.5x(VS) / 2x(g++)
访问 operator[] 快但不检查,at() 安全但略慢
增删 尾部 O(1),中间 O(n);emplace_back 优于 push_back
迭代器失效 扩容全失效;insert/erase 使操作点及之后失效;用返回值更新
模拟实现 三指针模型,深拷贝不能 memcpy

12.2 与相关容器对比

特性 vector list deque
内存布局 连续 链式(不连续) 分段连续
随机访问 O(1) O(n) O(1)
头插/头删 O(n) O(1) O(1)
尾插/尾删 O(1) O(1) O(1)
中间插/删 O(n) O(1)(找到位置后) O(n)
迭代器失效 扩容全失效 永不失效(除非删元素本身) 扩容全失效

12.3 推荐进一步学习


如果这篇博客对你有帮助,欢迎点赞、收藏,也欢迎在评论区指出任何问题或讨论~


更多推荐