c++ 手写STL篇(一)用数组实现vector核心功能

在 C++ 标准模板库(STL)的众多组件中,vector无疑是最常用且功能强大的数据结构之一。它就像一个智能的动态数组,既能像普通数组一样高效地随机访问元素,又能根据数据量自动调整大小,解决了传统数组固定大小的局限性。那么,vector是如何实现这些强大功能的呢?答案就藏在其底层基于数组的实现方式中。今天,我们就一同深入探索,如何用数组来构建vector的核心功能。

一、数组与 vector:一脉相承的 “进化关系”​

在开始构建vector之前,我们先来回顾一下数组。数组是一种线性数据结构,它在内存中占据一段连续的空间,就像一排整齐排列的储物格,每个储物格都有自己固定的编号(即数组的下标)。通过下标,我们可以快速地访问数组中的任意元素,这种随机访问的特性使得数组在数据读取和处理上具有很高的效率。​

然而,传统数组有一个明显的缺陷 —— 它的大小在创建时就固定了。一旦创建完成,我们无法轻易改变它的容量。这就好比一个固定大小的储物间,如果物品数量超出了储物间的容量,我们就会陷入困境。而vector正是为了解决这个问题应运而生,它基于数组进行扩展和优化,在保留数组随机访问优势的同时,实现了动态调整大小的功能,堪称数组的 “进化版”。

二、动态数组:vector 的核心存储结构​

vector的核心是一个动态数组。所谓动态数组,就是它能够根据数据的插入和删除,自动调整自身的大小,以适应数据量的变化。为了实现这一功能,vector需要在底层维护几个关键信息:​

(一)数据存储区​

这是vector存放实际数据的地方,本质上就是一个普通的数组。它就像vector的 “仓库”,所有的元素都被有序地存放在这个 “仓库” 的各个 “格子” 里。当我们向vector中插入元素时,数据就会被放入这个存储区;当我们访问元素时,也是从这里获取数据。​

(二)元素个数​

vector需要记录当前已经存储了多少个元素,这个信息就像是 “仓库管理员” 手中的账本,记录着每个时刻 “仓库” 里物品的数量。通过这个记录,我们可以知道vector当前的实际使用规模,也能在进行插入、删除等操作时,判断是否需要调整数组的大小。​

(三)容量大小​

容量表示vector当前能够容纳的最大元素数量,它是 “仓库” 当前的最大容量上限。当我们不断向vector中插入元素,一旦元素个数达到了容量大小,vector就需要进行扩容操作,重新分配一块更大的内存空间,将原有的数据复制到新空间中,以满足更多数据存储的需求。

动态内存分配策略:当容量不足时,vector会分配一块新的内存空间,将原有元素复制到新的内存中,然后释放原内存,这种策略确保了元素在内存中的连续性,但也带来了插入操作的额外开销。

为了确保平均时间下的插入操作具有常量时间复杂度,而不是线性复杂度。Vector使用指数翻倍策略扩容,每次扩容容量变成原始容量两倍

具体vector使用方法可以参考:

【C++】vector 基本使用(详解)

三、手写MyVector实现std::vector核心功能

#include <iostream>

template<typename T>
class MyVector
{
private:
    T* elements_;
    size_t size_;
    size_t capacity_;
public:
    //基本构造函数
    MyVector(): elements_(nullptr), size_ (0), capacity_(0) {}
    //析构函数,删除elements_释放空间
    ~MyVector()
    {
        delete[] elements_;
    }
    //拷贝构造(避免浅拷贝)
    MyVector(const MyVector &other): capacity_(other.capacity_), size_(other.size_)
    {
        elements_ = new T[capacity_];
        std::copy(other.elements_, other.elements_ + size_, elements_);
    }
    //迭代器构造方法
    MyVector(const T* begin, const T* end): capacity_(end - begin), size_(end - begin)
    {
        elements_ = new T[capacity_];
        for(size_t i = 0; i < end - begin; i++)
        {
            elements_[i] = *(begin+i);
        }
    }
    //(元素个数,元素值)构造方法
    MyVector(int n, const T& value = T()):capacity_(n), size_(n)
    {
        elements_ = new T[capacity_];
        for (size_t i = 0; i < n; i++)
        {
            elements_[i] = value;
        }
    }
    //扩容方法,为保证元素内存连续,将原始数组的元素全部复制到新数组,最后释放原始数组空间
    //这种策略保证容器元素的内存连续特性,从而支持O(1)的下标访问,但带来了插入操作效率相对较低的问题
    void reserve(size_t newcapacity)
    {
        if(newcapacity > capacity_)
        {
            T* newelements = new T[newcapacity];
            std::copy(elements_,elements_+size_,newelements);
            delete[] elements_;
            elements_ = newelements;
            capacity_ = newcapacity;
        }
    }
    //移除未使用的容量,减少内存使用。(c++11引入,但在std::vector中这只是一个请求,并不保证容量会减少)
    void shrink_to_fit()
    {
        T* newelements = new T[size_];
        std::copy(elements_,elements_+size_,newelements);
        delete[] elements_;
        elements_ = newelements;
        capacity_ = size_;
    }

    //重载=操作符,支持拷贝赋值操作
    MyVector &operator=(const MyVector& other)
    {
        if(this != &other)
        {
            delete[] elements_;
            capacity_ = other.capacity_;
            size_ = other.size_;
            elements_ = new T[capacity_];
            std::copy(other.elements_, other.elements_ + size_, elements_);
        }
        return *this;
    }
    //下标访问支持
    T &operator[](const size_t idx)
    {
        if(idx >= size_)
        {
            throw std::out_of_range("Index out of range!\n");
        }
        return elements_[idx];
    }
    //末尾添加元素
    void push_back(const T &value)
    {
        //动态扩容,每次size和capacity相等后容器容量翻倍,这种策略确保了平均情况下的插入操作具有常数时间复杂度,而不是线性时间复杂度。
        //但是这种策略带来了空间冗余(capacity > size)
        if(size_ == capacity_)
        {
            reserve(capacity_ == 0 ? 1: 2 * capacity_);
        }
        elements_[size_++] = value;
    }
    //删除末尾元素
    void pop_back()
    {
        if(size_ > 0)
        {
            size_--;
        }
    }
    //向idx位置插入新元素(根据索引删除操作也要注意保证内存连续,删掉之后索引后面的元素前移,这里不实现了)
    void insert(size_t idx, const T& value)
    {
        if(idx >= size_)
        {
            throw std::out_of_range("Index out of range!\n");
        }
        if(size_ == capacity_)
        {
            reserve(capacity_ == 0 ? 1: 2 * capacity_);
        }
        for(size_t i = size_; i > idx; i--)
        {
            elements_[i] = elements_[i - 1];
        }
        elements_[idx] = value;
        size_++;
    }
    //清空容器(不是释放空间)
    void clear()
    {
        size_ = 0;
    }
    //迭代器
    T* begin()
    {
        return elements_;
    }

    T* end()
    {
        return elements_ + size_;
    }

    size_t Size() const
    {
        return size_;
    }

    size_t Capacity() const
    {
        return capacity_;
    }

    bool empty()
    {
        return size_ == 0 ? true : false;
    }
};

以上代码实现了std::vector的核心功能,除了Size,Capacity两个接口意外都与std::vector保持一致。

下面来测试这个类

int main()
{
    //基本构造测试
    MyVector<int> vec_1;
    std::cout << "capacity: " << vec_1.Capacity() << std::endl; 
    //push_back测试和动态扩容测试
    vec_1.push_back(10);
    std::cout << "capacity: " << vec_1.Capacity() << std::endl; 
    vec_1.push_back(20);
    std::cout << "capacity: " << vec_1.Capacity() << std::endl; 
    vec_1.push_back(30);
    std::cout << "capacity: " << vec_1.Capacity() << std::endl; 
    //删除冗余空间测试
    vec_1.shrink_to_fit();
    std::cout << "capacity: " << vec_1.Capacity() << std::endl;
    //插入测试
    vec_1.insert(1,15);
    std::cout << "capacity: " << vec_1.Capacity() << std::endl; 
    //不同遍历方法测试
    std::cout << "============================" << std::endl;
    for(int i = 0; i < vec_1.Size(); i++)
    {
        std::cout << vec_1[i] << std::endl;
    }
    std::cout << "============================" << std::endl;
    for(auto i : vec_1)
    {
        std::cout << i << std::endl;
    }
    std::cout << "============================" << std::endl;
    for(auto i = vec_1.begin() ; i < vec_1.end(); i++)
    {
        std::cout << *i << std::endl;
    }
    std::cout << "============pop_back================" << std::endl;
    //pop_back测试
    vec_1.pop_back();
    for(int i = 0; i < vec_1.Size(); i++)
    {
        std::cout << vec_1[i] << std::endl;
    }
    std::cout << "==============vec_2==============" << std::endl;
    //不同构造方法测试
    MyVector<int> vec_2(3,6);
    for(int i = 0; i < vec_2.Size(); i++)
    {
        std::cout << vec_2[i] << std::endl;
    }
    std::cout << "==============vec_3==============" << std::endl;
    //拷贝构造
    MyVector<int> vec_3(vec_2);
    for(int i = 0; i < vec_3.Size(); i++)
    {
        std::cout << vec_3[i] << std::endl;
    }
    std::cout << "==============vec_4==============" << std::endl;
    //迭代器构造
    MyVector<int> vec_4(vec_2.begin(),vec_2.end());
    for(int i = 0; i < vec_4.Size(); i++)
    {
        std::cout << vec_4[i] << std::endl;
    }
    return 0;
}

输出结果:

注意看插入过程中容量的变化,在构造完成后capacity=0,插入第一个元素后capacity=1,接着插入capacity=2,下一次再扩容的时候capacity变成了4,而不是3,扩容过程是翻倍的,不是线性的。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值