C++STL-vector详解(模拟实现)

C++中的vector详解

vector是C++标准模板库(STL)中的一个非常重要的容器,它提供了动态数组的功能。下面我将详细介绍vector的各个方面。

1.基本概念

vector是一个能够存储任意类型数据的动态数组,它具有以下特点:

  • 可以动态改变大小(不像普通数组大小固定)
  • 在内存中是连续存储的
  • 支持随机访问(可以通过下标直接访问元素)
  • 在尾部插入和删除元素效率高,在中间或头部插入删除效率较低

2.基本用法

头文件和声明

#include <vector>  // 必须包含的头文件

// 声明vector
std::vector<int> v1;             // 空vector,存储int类型
std::vector<double> v2(10);      // 有10个元素的vector,初始值为0.0
std::vector<std::string> v3(5, "hello"); // 5个元素,每个都是"hello"

初始化方式

std::vector<int> v1;                // 空vector
std::vector<int> v2(5);             // 5个元素,默认值为0
std::vector<int> v3(5, 10);         // 5个元素,每个都是10
std::vector<int> v4{1, 2, 3, 4};    // 初始化列表
std::vector<int> v5(v4);            // 拷贝构造
std::vector<int> v6(v5.begin(), v5.end()); // 通过迭代器范围构造

常用操作

访问元素
std::vector<int> v = {1, 2, 3, 4, 5};

// 使用下标访问(不检查边界)
int a = v[2];  // a = 3

// 使用at()访问(会检查边界,越界抛出异常)
int b = v.at(2); // b = 3

// 访问第一个和最后一个元素
int first = v.front(); // first = 1
int last = v.back();   // last = 5

// 使用迭代器访问
for(auto it = v.begin(); it != v.end(); ++it) {
    std::cout << *it << " ";
}
修改元素
v[2] = 10;      // 修改第3个元素为10
v.at(3) = 20;   // 修改第4个元素为20
添加元素
v.push_back(6);     // 在末尾添加元素6
v.emplace_back(7);  // C++11新方法,效率更高
v.insert(v.begin() + 2, 8); // 在第3个位置插入8
删除元素
v.pop_back();       // 删除最后一个元素
v.erase(v.begin() + 1); // 删除第2个元素
v.erase(v.begin(), v.begin() + 2); // 删除前2个元素
v.clear();          // 清空vector
大小和容量
int size = v.size();       // 当前元素个数
bool empty = v.empty();    // 是否为空
int cap = v.capacity();    // 当前分配的存储空间大小
v.resize(10);              // 改变大小
v.reserve(100);            // 预留空间

3.重要特性

1.空间增长问题

  • capacity的代码在vs和g++下分别运行会发现,vs下capacity是按1.5倍增长的,g++是按2倍增长的。这个问题经常会考察,不要固化的认为,vector增容都是2倍,具体增长多少是根据具体的需求定义的。VS是PJ版本STL,g++是SGI版本STL。
  • reserve只负责开辟空间,如果确定知道需要用多少空间,reserve可以缓解vector增容的代价缺陷问题。
  • resize在开空间的同时还会进行初始化,影响size。
// 测试vector的默认扩容机制
void TestVectorExpand()
{
	size_t sz;
	vector<int> v;
	sz = v.capacity();
	cout << "making v grow:\n";
	for (int i = 0; i < 100; ++i) 
	{
	v.push_back(i);
		if (sz != v.capacity()) 
		{
			sz = v.capacity();
			cout << "capacity changed: " << sz << '\n';
		}
	}
}
vs:运行结果:vs下使用的STL基本是按照1.5倍方式扩容
making foo grow:
capacity changed: 1
capacity changed: 2
capacity changed: 3
capacity changed: 4
capacity changed: 6
capacity changed: 9
capacity changed: 13
capacity changed: 19
capacity changed: 28
capacity changed: 42
capacity changed: 63
capacity changed: 94
capacity changed: 141
g++运行结果:linux下使用的STL基本是按照2倍方式扩容
making foo grow:
capacity changed: 1
capacity changed: 2
capacity changed: 4
capacity changed: 8
capacity changed: 16
capacity changed: 32
capacity changed: 64
capacity changed: 128
vector<int> v;
v.reserve(100); // 预分配空间,避免多次扩容
for(int i=0; i<100; i++) {
    v.push_back(i);
}

2. 迭代器失效问题

导致迭代器失效的主要操作

空间重新配置操作

  • push_back:当vector容量不足时会导致扩容
  • insert:插入元素可能导致扩容
  • reserve:显式改变容量
  • resize:增加size可能导致扩容
  • assign:重新分配内容可能导致空间变化

元素删除操作

  • erase:删除元素会使被删除位置及其后的迭代器失效
  • pop_back:删除尾部元素会使指向尾部的迭代器失效
  • clear:清空vector使所有迭代器失效

迭代器失效的具体表现

扩容导致的失效

扩容底层其实是开辟新内存,然后把原内存中的数据拷贝到新内存中再释放原内存,如果扩容后没有更新迭代器则会访问到已经释放的内存,从而失效

vector<int> v = {1,2,3};
auto it = v.begin();
v.push_back(4); // 可能导致扩容
// it已失效,不能再使用
插入删除导致的失效

erase删除pos位置元素后,pos位置之后的元素会往前搬移,没有导致底层空间的改变,理论上讲迭代器不应该会失效,但是:如果pos刚好是最后一个元素,删完之后pos刚好是end的位置,而end位置是没有元素的,那么pos就失效了。因此删除vector中任意位置上元素时,vs就认为该位置选代器失效了。

 int a[] = { 1, 2, 3, 4 };
 vector<int> v(a, a + sizeof(a) / sizeof(int));
 // 使用find查找3所在位置的iterator
 vector<int>::iterator pos = find(v.begin(), v.end(), 3);
 // 删除pos位置的数据,导致pos迭代器失效。
 v.erase(pos);
 cout << *pos << endl; // 此处会导致非法访问

不同编译器的处理差异

VS (Visual Studio)
  • 对迭代器失效检查严格
  • 一旦失效立即崩溃(调试模式下)
  • 包括所有可能导致失效的操作
g++ (Linux)
  • 检查相对宽松
  • 部分失效情况可能不会立即崩溃
  • 但使用失效迭代器仍会导致未定义行为

正确的处理方式

插入操作后

auto it = v.begin() + 2;
v.insert(it, 10); // insert返回新的有效迭代器
it = v.insert(it, 10); // 正确做法:接收返回值

删除操作后

  • 删除vector中所有偶数
auto it = v.begin();
while (it != v.end()) {
    if (*it % 2 == 0) {
        it = v.erase(it); // erase返回下一个有效迭代器
    } else {
        ++it;
    }
}

扩容操作后

auto it = v.begin();
size_t distance = it - v.begin(); // 保存距离
v.reserve(100);                  // 可能导致扩容
it = v.begin() + distance;       // 重新计算迭代器
迭代器失效的根本原因
  1. 内存重新分配:扩容操作会导致vector使用新的内存空间,原有迭代器仍指向旧内存
  2. 元素位置移动:插入/删除操作会改变元素位置,使原有迭代器指向错误位置
  3. 尾后迭代器失效:任何改变vector大小的操作都会使end()迭代器失效

模拟实现

假设模拟实现的vector中的reserve接口中,使用memcpy进行的拷贝,以下代码会发生什么问题?

int main()
{
	vector<string> v;
	v.push_back("1111");
	v.push_back("2222");
	v.push_back("3333");
	return 0;
}

memcpy 的基本特性

memcpy 是 C 标准库函数,其原型为:

void* memcpy(void* dest, const void* src, size_t count);

特点:

  • 二进制拷贝:逐字节复制内存内容

  • 高效:通常由编译器优化为最有效的拷贝方式

  • 浅拷贝只复制指针或值本身,不处理资源所有权
    在这里插入图片描述

  • 结论:如果对象中涉及到资源管理时,千万不能使用memcpy进行对象之间的拷贝,因为memcpy是浅拷贝,否则可能会引起内存泄漏甚至程序崩溃。

7. 总结

memcpy 在 vector 实现中的主要问题是:

  • 无法正确处理管理资源的类
  • 破坏了C++对象的生命周期规则
  • 导致资源管理混乱(内存泄漏或重复释放)

正确的做法是:

  1. 使用 placement new 和拷贝构造/移动构造
  2. 对每个元素单独处理
  3. 保证异常安全
  4. 遵循C++对象模型规则

理解这一点对于实现符合标准的容器类至关重要,也是C++资源管理核心思想的体现。

#pragma once
#include <iostream>
#include <string>
#include <assert.h>
using namespace std;

namespace sty
{
    /**
     * @brief 模拟实现的vector类模板
     * @tparam T 存储的元素类型
     */
    template<class T>
    class vector
    {
    public:
        // 迭代器类型定义
        typedef T* iterator;               // 普通迭代器
        typedef const T* const_iterator;   // const迭代器

        /******************* 构造函数和析构函数 *******************/
        
        /**
         * @brief 默认构造函数(C++11风格)
         */
        vector() = default;
        
        /**
         * @brief 使用迭代器范围构造vector
         * @tparam InputIterator 输入迭代器类型
         * @param first 范围起始迭代器
         * @param last 范围结束迭代器
         */
        template <class InputIterator>
        vector(InputIterator first, InputIterator last)
        {
            while (first != last)
            {
                push_back(*first);  // 逐个插入元素
                ++first;
            }
        }

        /**
         * @brief 构造包含n个val的vector
         * @param n 元素数量
         * @param val 初始值
         */
        vector(size_t n, const T& val = T())
        {
            reserve(n);  // 预分配空间
            for (size_t i = 0; i < n; i++)
            {
                push_back(val);  // 插入n个val
            }
        }


        /**
         * @brief 拷贝构造函数
         * @param v 要拷贝的vector
         */
        vector(const vector<T>& v)
        {
            reserve(v.size());  // 预分配空间
            for (auto& e : v)
            {
                push_back(e);  // 逐个拷贝元素
            }
        }

        /**
         * @brief 析构函数
         */
        ~vector()
        {
            if (_start)  // 如果内存已分配
            {
                delete[] _start;  // 释放内存
                _start = _finish = _end_of_storage = nullptr;  // 重置指针
            }
        }

        /******************* 容量相关操作 *******************/
        
        /**
         * @brief 清空vector
         */
        void clear()
        {
            _finish = _start;  // 简单地将_finish指针移回_start
        }

        /**
         * @brief 交换两个vector的内容
         * @param v 要交换的vector
         */
        void swap(vector<T>& v)
        {
            std::swap(_start, v._start);
            std::swap(_finish, v._finish);
            std::swap(_end_of_storage, v._end_of_storage);
        }

        /**
         * @brief 赋值重载,现代写法
         * @param v 要赋值的vector
         * @return vector& 返回当前对象的引用
         */
        vector<T>& operator=(vector<T> v)
        {
            swap(v);  // 交换内容
            return *this;
        }

        /**
         * @brief 预留空间(不影响size)
         * @param n 要预留的空间大小
         */
        void reserve(size_t n)
        {
            if (n > capacity())  // 只有当n大于当前容量时才需要处理
            {
                size_t old_size = size();
                T* tmp = new T[n];  // 分配新空间
                
                // 拷贝元素到新空间
                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;
            }
        }

        /******************* 迭代器相关操作 *******************/
        
        iterator begin() { return _start; }
        iterator end() { return _finish; }
        const_iterator begin() const { return _start; }
        const_iterator end() const { return _finish; }

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

        /******************* 元素访问操作 *******************/
        
        T& operator[](size_t i)
        {
            assert(i < size() && i >= 0);  // 越界检查
            return _start[i];
        }
        
        const T& operator[](size_t i) const
        {
            assert(i < size() && i >= 0);
            return _start[i];
        }

        /******************* 修改操作 *******************/
        
        /**
         * @brief 在尾部添加元素
         * @param x 要添加的元素
         */
        void push_back(const T& x)
        {
            if (_finish == _end_of_storage)  // 检查是否需要扩容
            {
                reserve(capacity() == 0 ? 4 : 2 * capacity());  // 扩容策略
            }
            *_finish = x;  // 在尾部构造元素
            ++_finish;
        }

        /**
         * @brief 删除尾部元素
         */
        void pop_back()
        {
            assert(!empty());  // 不能对空vector执行pop_back
            --_finish;
        }

        /**
         * @brief 在指定位置插入元素
         * @param pos 插入位置迭代器
         * @param x 要插入的元素
         * @return iterator 返回指向新插入元素的迭代器
         */
        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;  // 重新计算pos位置
            }

            // 向后移动元素
            iterator end = _finish - 1;
            while (end >= pos)
            {
                *(end + 1) = *end;
                --end;
            }
            
            *pos = x;  // 插入新元素
            ++_finish;
            return pos;
        }

        /**
         * @brief 删除指定位置的元素
         * @param pos 要删除元素的位置
         * @return iterator 返回指向被删除元素后面元素的迭代器
         */
        iterator erase(iterator pos)
        {
            assert(pos >= _start && pos < _finish);  // 检查位置有效性
            
            // 向前移动元素覆盖要删除的元素
            iterator it = pos + 1;
            while (it != end())
            {
                *(it - 1) = *it;
                it++; 
            }
            --_finish;  // 调整_finish指针
            return pos;
        }

        /**
         * @brief 调整vector的大小
         * @param n 新的大小
         * @param val 新增元素的初始值(默认为T())
         */
        void resize(size_t n, T val = T())
        {
            if (n < size())  // 缩小size
            {
                _finish = _start + n;
            }
            else  // 增大size
            {
                reserve(n);  // 预分配空间
                while (_finish < _start + n)
                {
                    *_finish = val;  // 初始化新增元素
                    ++_finish;
                }
            }
        }

    private:
        iterator _start = nullptr;          // 指向数据块的起始位置
        iterator _finish = nullptr;         // 指向最后一个元素的下一个位置
        iterator _end_of_storage = nullptr; // 指向存储空间末尾的下一个位置
    };

    /******************* 辅助函数 *******************/
    
    template<class Container>
    void print_container(const Container& v)
    {
        auto it = v.begin();
        while (it != v.end())
        {
            cout << *it << " ";
            it++;
        }
        cout << endl;
    }
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值