C++之vector剖析及模拟实现方式

 更新时间:2025年09月18日 09:31:55   作者:一枝小雨  
文章详解C++ vector实现,涵盖三个指针管理动态数组、容量扩容机制、元素增删操作及深拷贝原理,强调迭代器安全性和memcpy对自定义类型的风险,提供使用示例与注意事项

1.0 std 库 vector 源码

官方文档:vector

成员变量

template<class T>
class vector
{
public:
        typedef T* iterator;

private:
        iterator _start;
        iterator _finish;
        iterator _endofstorage;
};

1.1 vector 类的基本结构与迭代器

类定义与成员变量

template<class T>
class vector
{
public:
    typedef T* iterator;              // 普通迭代器类型
    typedef const T* const_iterator;  // const迭代器类型
    
private:
    iterator _start;         // 指向数组首元素
    iterator _finish;        // 指向最后一个元素的下一个位置
    iterator _endofstorage;  // 指向存储空间末尾的下一个位置
};

vector 使用三个指针来管理动态数组:

  • _start:指向数组的第一个元素
  • _finish:指向最后一个元素之后的位置(即当前元素数量的末尾)
  • _endofstorage:指向已分配内存的末尾之后的位置(即当前容量的末尾)

构造函数与析构函数

// 默认构造函数
vector()
    :_start(nullptr)
    ,_finish(nullptr)
    ,_endofstorage(nullptr)
{}

// 析构函数
~vector()
{
    delete[] _start;  // 释放动态分配的内存
    _start = _finish = _endofstorage = nullptr;  // 指针置空
}
  • 默认构造函数初始化所有指针为 nullptr
  • 析构函数释放动态分配的内存并重置所有指针

迭代器访问方法

// 普通正向迭代器
iterator begin() { return _start; }
iterator end() { return _finish; }

// 只读正向迭代器
const_iterator begin() const { return _start; }
const_iterator end() const { return _finish; }
  • 提供迭代器访问方法,使 vector 支持范围 for 循环和标准库算法
  • const 版本确保 const 对象只能进行只读访问

1.2 容量管理

容量查询方法

// 获取元素数量
size_t size() const { return _finish - _start; }

// 获取当前容量
size_t capacity() const { return _endofstorage - _start; }
  • size() 返回当前元素数量
  • capacity() 返回当前分配的内存容量

扩容机制

// 扩容方法
void reserve(size_t n)
{
    size_t sz = size();  // 提前计算当前大小
    if (n > capacity())
    {
        T* tmp = new T[n];  // 分配新内存
        if (_start)  // 防止第一次分配内存时 memcpy 出错
        {
            // 这里使用 memcpy 是有问题的,只能对内置类型
            // 的vector进行扩容,具体问题和解决方案在
            // 后续“memcpy:更深一层次的深浅拷贝问题”
            memcpy(tmp, _start, sizeof(T) * sz);  // 拷贝数据
            delete[] _start;  // 释放旧内存
        }
        _start = tmp;
        _finish = tmp + sz;
        _endofstorage = tmp + n;
    }
}
  • reserve 方法用于预分配内存,避免多次重新分配
  • 需要提前计算 size(),因为重新分配后 _start 会改变
  • 注意:使用 memcpy 只能对内置类型进行拷贝,对于自定义类型会有问题

元素访问方法

// 下标运算符重载
T& operator[](size_t i)
{
    assert(i < size());  // 越界检查
    return _start[i];
}

// const 版本下标运算符
const T& operator[](size_t i) const
{
    assert(i < size());  // 越界检查
    return _start[i];
}
  • 提供类似数组的随机访问功能
  • 包含越界检查,提高代码安全性

resize

/* resize */
// val不能给0,因为不知道T的类型,所以给一个T的缺省值
void resize(size_t n, const T& val = T())
{
        // 缩小size
        if (n < size())
                _finish = _start + n;
        else // 增大size
        {
                // 假如需要扩容
                if (n > capacity())
                {
                        reserve(n);
                }

                while (_finish < _start + n)
                {
                        *_finish = val;
                        ++_finish;
                }
        }
}
  • 可以增大或减小 vector 的大小
  • 增大时用指定值填充新元素(默认为 T 类型的默认值)
  • 缩小时只是调整 _finish 指针,不释放内存

1.3 添加与删除元素

push_back

// 尾插元素
void push_back(const T& x)
{
    // 空间不足时扩容
    if (_finish == _endofstorage)
    {
        size_t newcapacity = capacity() == 0 ? 2 : capacity() * 2;
        reserve(newcapacity);
    }
    
    *_finish = x;  // 在末尾位置添加元素
    ++_finish;     // 更新末尾指针
}

// 也可以直接借助insert完成尾插
void push_back(const T& x) { insert(_finish, x); }
  • 当容量不足时自动扩容(通常翻倍)
  • 在尾部添加元素并更新指针

insert

// pos位置插入
void insert(iterator pos, const T& x)
{
    assert(pos <= _finish);  // 检查位置有效性
    
    // 空间不够就增容
    if (_finish == _endofstorage)
    {
        // 记下pos相对于_start的位置
        size_t n = pos - _start;
        size_t newcapacity = capacity() == 0 ? 2 : capacity() * 2;
        reserve(newcapacity);
        // 空间增容导致原pos迭代器失效,更新迭代器位置
        pos = _start + n;
    }
    
    // 后移元素
    iterator end = _finish - 1;
    while (end >= pos)  // 依次把pos及pos后面的数据往后挪1位
    {
        *(end + 1) = *end;
        --end;
    }
    
    *pos = x;    // 插入新元素
    ++_finish;   // 更新末尾指针
}
  • 插入操作需要移动后续元素,时间复杂度为 O(n)
  • 扩容会导致迭代器失效,需要重新计算位置

erase

// 删除指定位置元素
iterator erase(iterator pos)
{
    assert(pos < _finish);  // 检查位置有效性
    
    iterator it = pos;
    while (it < _finish)  // 将后续元素前移
    {
        *it = *(it + 1);
        ++it;
    }
    
    --_finish;  // 更新末尾指针
    return pos;  // 返回删除后该位置的迭代器
}

// 尾删
void pop_back() { erase(_finish - 1); }
  • 删除操作需要移动后续元素,时间复杂度为 O(n)
  • 返回删除后位置的迭代器,便于连续删除操作

1.4 拷贝构造与赋值重载

拷贝构造函数

/* 拷贝构造函数 */
vector(const vector<T>& v)
{
        _start = new T[v.capacity()];
        _finish = _start;
        _endofstorage = _start + v.capacity();
        // 拷贝数据
        for (size_t i = 0; i < v.size(); ++i)
        {
                *_finish = v[i];
                ++_finish;
        }
}

更简洁的写法:

/* 拷贝构造函数(更简洁的写法) */
vector(const vector<T>& v)
        :_start(nullptr)
        ,_finish(nullptr)
        ,_endofstorage(nullptr)
{
        reserve(v.capacity());        // 直接把空间开好,避免增容
        for (const auto& e : v)        // 把 v 的数据直接一个个push_back进去
                push_back(e);
}
  • 实现深拷贝,避免多个 vector 共享同一内存
  • 先预分配足够空间,然后逐个拷贝元素

赋值重载

/* 赋值重载 */
vector<T>& operator=(const vector<T>& v)
{
        if (this != &v)        // 防止自己赋值给自己
        {
                delete[] _start;
                _start = new T[v.capacity()];
                memcpy(_start, v._start, sizeof(T) * v.size());
                _finish = _start + v.size();
                _endofstorage = _start + v.capacity();
        }
        return *this;
}

赋值重载更简洁的写法:

/* 赋值重载更简洁的写法(现代写法) */
vector<T>& operator=(vector<T> v)
{
        swap(v);
        return *this;
}
  • 使用"拷贝-交换"技术实现赋值运算符
  • 参数通过值传递自动调用拷贝构造函数
  • 交换内容后,临时对象 v 在函数结束时自动析构

深浅拷贝问题

为什么我们需要深拷贝?

/* 深浅拷贝问题 */
void test_vector4()
{
        vector<int> v1;
        v1.push_back(1);
        v1.push_back(2);
        v1.push_back(3);
        v1.push_back(4);

        // 如果我们自己没有实现深拷贝的拷贝构造,就会发生和string类一样的浅拷贝问题
        // 两个vector对象的指针指向同一块空间,最后析构时同一块空间被重复释放,发生了错误
        // 所以我们需要自己实现深拷贝
        vector<int> v2(v1);
        for (size_t i = 0; i < v1.size(); ++i)
        {
                cout << v2[i] << " ";
        }
        cout << endl;

        // 赋值同理
        vector<int> v3;
        v3.push_back(10);
        v3.push_back(20);
        v3.push_back(30);
        v3.push_back(40);

        v1 = v3;
        print_vector(v1);
        for (auto e : v1)
        {
                cout << e << " ";
        }
        cout << endl;
}

1.5 memcpy导致的更深一层次的深浅拷贝问题

受篇幅限制,这里给出文章链接:C++ memcpy导致的深拷贝问题

1.6 使用示例

遍历与修改

void test_vector1()
{
    vector<int> v;
    v.push_back(1);
    v.push_back(2);
    v.push_back(3);
    v.push_back(4);

    // 使用迭代器遍历和修改
    vector<int>::iterator it = v.begin();
    while (it != v.end())
    {
        *it += 1;
        cout << *it << " ";
        ++it;
    }
    cout << endl;

    // 范围for循环
    for (auto& e : v)
    {
        e -= 1;
        cout << e << " ";
    }
    cout << endl;

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

插入与删除

void test_vector2()
{
    vector<int> v;
    v.push_back(1);
    v.push_back(2);
    v.push_back(3);
    v.push_back(4);
    v.push_back(5);
    v.push_back(6);

    v.insert(v.begin(), 0);  // 在开头插入0

    // 删除所有偶数
    vector<int>::iterator it = v.begin();
    while (it != v.end())
    {
        if (*it % 2 == 0)
        {
            it = v.erase(it);  // 删除元素并更新迭代器
        }
        else
        {
            ++it;
        }
    }
}

总结

以上为个人经验,希望能给大家一个参考,也希望大家多多支持脚本之家。

相关文章

  • C++使用数组来实现哈夫曼树

    C++使用数组来实现哈夫曼树

    给定N个权值作为N个叶子结点,构造一棵二叉树,若该树的带权路径长度达到最小,称这样的二叉树为最优二叉树,也称为哈夫曼树(Huffman Tree)。哈夫曼树是带权路径长度最短的树,权值较大的结点离根较近
    2022-05-05
  • C语言使用libZPlay录制声音并写到文件的方法

    C语言使用libZPlay录制声音并写到文件的方法

    这篇文章主要介绍了C语言使用libZPlay录制声音并写到文件的方法,实例分析了C语言操作音频文件的相关技巧,需要的朋友可以参考下
    2015-06-06
  • 深入理解C++函数栈帧

    深入理解C++函数栈帧

    本文主要介绍了C++函数栈帧,详细的介绍了C++函数栈帧的概念以及使用,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2021-07-07
  • C语言详细讲解指针数组的用法

    C语言详细讲解指针数组的用法

    在C语言和C++等语言中,数组元素全为指针变量的数组称为指针数组,指针数组中的元素都必须具有相同的存储类型、指向相同数据类型的指针变量。指针数组比较适合用来指向若干个字符串,使字符串处理更加方便、灵活
    2022-05-05
  • C语言由浅入深讲解文件的操作下篇

    C语言由浅入深讲解文件的操作下篇

    C语言具有操作文件的能力,比如打开文件、读取和追加数据、插入和删除数据、关闭文件、删除文件等。与其他编程语言相比,C语言文件操作的接口相当简单和易学
    2022-04-04
  • 详细总结C++的排序算法

    详细总结C++的排序算法

    趁空闲时间,小编决定把C++的排序算法分析并总结下,以便温故知新。也方便需要的朋友可以参考学习。
    2016-07-07
  • C++使用OpenCV进行物体识别与检测的三种方法

    C++使用OpenCV进行物体识别与检测的三种方法

    物体识别与检测是计算机视觉中的核心任务之一,它被广泛应用于自动驾驶、安防监控、图像分析等领域,通过物体检测技术,计算机能够从图像中识别出特定的物体或目标,本文将介绍如何使用 C++ 和 OpenCV 库进行物体识别与检测,需要的朋友可以参考下
    2025-04-04
  • C++线程安全的队列你了解嘛

    C++线程安全的队列你了解嘛

    这篇文章主要为大家详细介绍了C++线程安全的队列,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下,希望能够给你带来帮助
    2022-03-03
  • C语言 动态内存管理全面解析

    C语言 动态内存管理全面解析

    动态内存是相对静态内存而言的。所谓动态和静态就是指内存的分配方式。动态内存是指在堆上分配的内存,而静态内存是指在栈上分配的内存,本文带你深入探究C语言中动态内存的管理
    2022-02-02
  • C语言数据结构算法之实现快速傅立叶变换

    C语言数据结构算法之实现快速傅立叶变换

    这篇文章主要介绍了C语言数据结构算法之实现快速傅立叶变换的相关资料,需要的朋友可以参考下
    2017-06-06

最新评论