STL浅谈(一)——vector

在C++中,STL (Standard Template Library)是必学,也是非常重要的一块内容。STL为特定场景的数据保存提供了极大的方便。同时STL也是面试官必问的问题之一。STL有着高可重用性,高性能,高移植平台和跨平台的优点。这篇博客叫STL浅谈,因为STL涉及到的内容太多了,这里只能随便介绍介绍。

  1. STL的构成

STL主要由六大块构成:

  • 容器:用于存储数据的工具,例如:vector,list,deque, set,map等
  • 算法:实现了按照用户的要求对容器中数据进行特定的操作,例如sort,find,copy以及遍历等
  • 迭代器:通过迭代器可以访问工具中的特定数据
  • 仿函数:协助算法完成不同的策略,例如排序算法就需要加入指定的仿函数(从小到大还是从大到小排序)。
  • 适配器:用来修饰容器或者仿函数或迭代接口的东西
  • 空间适配器:负责空间的配置与管理
  1. STL中的vector
    这篇博客主要就是用于介绍一下vector的底层实现原理以及其中的各种坑点。

vector的底层实现其实是一个数组。vector实现了变长数组的功能,它就像数组一样,可以通过索引随机访问,但是由于数据安全,向vector中随机插入元素的效率便显得比较低下。除此之外,vector还向外提供了一个size的概念,即返回当前的vector中有多少个元素。同时还有一个capacity的概念,即返回当前的vector的容量。这一变量一般由系统自动分配,调用reverse后可以认为的改变其capacity的大小。

  1. 通过代码写一个MyVector类自主实现vector这一容器
    由于vector能够存储不同类型的数据,因此vector需要通过模板类来实现:
class MyVector{
        T* elements;  //指向动态数组的指针
        int count;  //记录添加进数组的元素个数
        int capacity; //记录vector的容量
};

以上的几个数据便实现了一个vector,但是vector的强大之处并不是这几个简单的数据,而是其拥有强大的接口,下面的代码实现了一些常用的接口:

#include<stdio.h>

//定义一个自己的名空间,避免和std中的ector的关键字冲突
namespace mySTL{
    template<typename T>
    class MyVector{
        T* elements;  //指向动态数组的指针
        int vecSize;  //记录添加进数组的元素个数
        int vecCapacity; //记录vector的容量

    public:
        //默认构造函数
        MyVector();
        //拷贝构造
        MyVector(MyVector<T>& arr);
        //传数组,同时指定长度的构造
        MyVector(MyVector<T>& arr, int startIndex, int endIndex);

        //析构函数
        ~MyVector();

        //将元素添加到vector中
        void push_back(T element);

        //弹出vector中的最后一个元素
        void pop_back();
        
        //获取当前的数据元素个数
        int size()const{
            return this->vecSize;
        }

        //获取当前的vector的容量
        int capacity()const{
            return this->vecCapacity;
        }

        //改变当前的vector的容量
        void reserve(int newCapacity);

        //改变当前元素的个数
        void resize(int newSize);

        //重载改变当前元素个数的方法,涉及到默认值
        void resize(int newSize, const T& element);

        //改写[]符号,支持随机访问
        T& operator[](int index);

        //判断当前的vector是为空
        bool empty(){
            return 0 == this->vecSize;
        }

    private:
        //设置一个内部方法,当现有空间不足时申请一块新的空间,内部方法用__开头
        void __realloc(int newCapacity);
    };

    //默认构造函数
    template <typename T>
    MyVector<T>::MyVector(){
        //默认给一个初始容量
        this->vecCapacity = 5;
        this->vecSize = 0;
        //申请一块预留空间,大小为capacity
        this->elements = new T[this->vecCapacity];

    }

    //传数组的方式构造
    template <typename T>
    MyVector<T>::MyVector(MyVector<T>& arr){
        //采用拷贝构造函数构造新的vector
        this->vecSize = arr.size();
        this->vecCapacity = arr.capacity();
        //元素值需要逐个拷贝
        this->elements = new T[this->vecCapacity];
        for(int i = 0; i < this->vecSize; ++i){
            *(this->elements + i) = arr[i];
        }
    }

    //传数组,同时指定长度的构造
    template <typename T>
    MyVector<T>::MyVector(MyVector<T>& arr, int startIndex, int endIndex){
        this->vecSize = endIndex - startIndex;
        this->vecCapacity = this->vecSize;
        this->elements = new T[this->vecCapacity];
        int index = 0;
        for(int i = startIndex; i < endIndex; ++i){
            *(this->elements + (index++)) = arr[i];
        }
    }

    //析构函数
    template <typename T>
    MyVector<T>::~MyVector(){
        //将申请的空间释放掉,否则会产生内存泄漏
        delete[] this->elements;
    }

    //将元素添加到vector中
    template <typename T>
    void MyVector<T>::push_back(T element){
        //先判断空间是否已经满了,如果满了则需要重新开辟空间
        if(this->vecSize >= this->vecCapacity){
            this->__realloc(this->vecCapacity * 2);
        }
        //size加1
        ++this->vecSize;
        *(this->elements + this->vecSize) = element;
    }

    //弹出vector中的最后一个元素
    template <typename T>
    void MyVector<T>::pop_back(){
        //先判断是否为空,为空则报错
        if(this->empty()){
            perror("vector is empty!");
            exit(-1);
        }
        //弹出最后一个元素只需把size减掉即可,当有新的元素压入时就会覆盖掉现有元素
        --this->vecSize;
    }

    //改变当前的vector的容量
    template <typename T>
    void MyVector<T>::reserve(int newCapacity){
        //当传入的newCapacity比原来的大时才能重新分配空间,否则不理会
        if(this->vecCapacity < newCapacity){
            this->__realloc(newCapacity);
        }
    }

    //改变当前元素的个数
    template <typename T>
    void MyVector<T>::resize(int newSize){
        //resize则需要先判断newSize是否大于capacity, 以及是否大于原size
        //申请空间,直到容量大于newSize为止
        while(this->vecCapacity < newSize){
            this->__realloc(this->vecCapacity * 2);
        }

        //如果newSize大于原size,则需要将多出的部分进行赋值
        if(newSize > this->vecSize){
            for(int i = this->vecSize; i < newSize; ++i){
                ++this->vecSize;
                *(this->elements + i) = 0;
            }
        }

    }

    //重载改变当前元素个数的方法,涉及到默认值
    template <typename T>
    void MyVector<T>::resize(int newSize, const T &element){
        while(this->vecCapacity < newSize){
            this->__realloc(this->vecCapacity * 2);
        }

        //如果newSize大于原size,则需要将多出的部分进行赋值
        if(newSize > this->vecSize){
            for(int i = this->vecSize; i < newSize; ++i){
                ++this->vecSize;
                *(this->elements + i) = element;
            }
        }
    }

    //改写[]符号,支持随机访问
    template <typename T>
    T& MyVector<T>::operator[](int index){
        return *(this->elements + index);
    }

    //设置一个内部方法,当现有空间不足时申请一块新的空间,内部方法用__开头
    template<typename T>
    void MyVector<T>::__realloc(int newCapacity){
        //当现有的预留空间不够时,分配新的空间
        this->vecCapacity = newCapacity;
        //申请一块新的空间
        T *temp = new T[this->vecCapacity];

        //将原空间中的数据搬迁过来
        for (int i = 0; i < this->vecSize; ++i)
        {
            *(temp + i) = *(this->elements + i);
        }

        //将原申请的空间全部释放,避免内存泄漏
        delete[] this->elements;
        //让原来的指针指向新开辟的空间
        this->elements = temp;
    }
}

下面做个用例测试:

#include<iostream>
#include "MyVector.hpp"

int main(){
    mySTL::MyVector<int> vec;
    std::cout << "验证push_back()函数" << std::endl;
    vec.push_back(1);
    vec.push_back(2);
    vec.push_back(3);
    std::cout << "该vector当前的容量为:" << vec.capacity() << "该vector当前的大小为:" << vec.size() << std::endl;
    
    std::cout << "验证重载的[]是否有用" << std::endl;
    for(int i = 0; i < vec.size(); ++i){
        std::cout << vec[i] << " ";
    }
    std::cout << std::endl;
    
    std::cout << "验证pop_back()函数是否弹出最后一个元素" << std::endl;
    vec.pop_back();
    vec.pop_back();
    vec.pop_back();
    if(vec.empty()){
        std::cout << "所有元素已经弹出" << std::endl;
    }

    //重新压入5个元素
    for(int i = 1; i < 6; ++i){
        vec.push_back(i);
    }

    std::cout << "验证是否自动重新分配了内存空间" << std::endl;
    vec.push_back(6);
    std::cout << vec.capacity() << std::endl;

    std::cout << "验证是否重新分配了内存空间,以及是否重新更改了resize" << std::endl;
    vec.reserve(37);
    
    std::cout << "该vector的容量为:" << vec.capacity() << "该vector的大小为:" << vec.size() << std::endl;

    vec.resize(22);
    std::cout << "该vector的容量为:" << vec.capacity() << "该vector的大小为:" << vec.size() << std::endl;

    std::cout << "验证拷贝构造函数" << std::endl;
    mySTL::MyVector<int> vec2(vec);
    std::cout << "新vector的容量为:" << vec2.capacity() << "新vector的大小为:" << vec2.size() << std::endl;

    std::cout << "验证带值的resize" << std::endl;
    int s = vec2.size();
    vec2.resize(100, 100);
    for(int i = s; i < vec2.size(); ++i){
        std::cout << vec2[i] << " ";
    }
    std::cout << std::endl;

    std::cout << "验证部分值的构造函数" << std::endl;
    mySTL::MyVector<int> vec3(vec2, 2, 8);
    std::cout << "新vector的容量为:" << vec3.capacity() << "新vector的大小为:" << vec3.size() << std::endl;
}

输出结果:
在这里插入图片描述
以上的代码简单的实现了一个vector的容器,当然,相比于真正的vector还相差很多。这里只做简单的介绍,毕竟vector已经可以直接调用了,不需要重复造轮子。下面稍微说一下vector的坑点。

  • 坑点
  • 在vector中resize会分配内存空间并对新分配的内存做个初始化,而reserve只会申请新的空间,并不会做初始化。因此使用reserve申请了新空间时,在没有push_back元素时不能用[]访问。而resize则可以。

-使用[]访问元素时要及其注意不能越界,否则不会报错,但是返回的结果绝对是未知的。在vector中有个at方法,可以控制保证不会越界访问。

  • 在真正的vector中有个迭代器的概念,由于vector有自动重新分配空间的功能,因此最好不要通过迭代器往里面写数据,否则会造成迭代器失效,产生不可预知的错误。因为迭代器本质为指针,指向开辟的空间,如果reserve发生了,则原来的内存已经被释放了,迭代器也就跟着失效了。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值