vector的综合学习与OJ

vector是我们日常中经常使用的一种数据结构,在c语言中一般被称为顺序表,其在空间中的分布顺序是连续的。为了更好的理解vector的功能与使用,我将通过模拟实现vector来逐步讲述其功能与需要注意的点。

框架定义

首先我们需要知道,vector需要存储不同类型的数据,可以是存一堆整形也可以是存一串串的字符串,甚至可以存vector,因此,我们需要用模版来实现vector。并且,由于模版类声明和定义不可分离,因此我们大部分代码都要写在.h文件中

在STL原装库中,vector是通过3个迭代器来进行定义的,分别是_start,_finish与_endof_Storage,名字可能略有不同,但是具体功能都在下文表示出来了。

  template<class T>

  class vector
  {
    public:
       typedef T* iterator;

       typedef const T* const_iterator;
    
    private:

        iterator _start; // 指向数据块的开始

        iterator _finish; // 指向有效数据的尾

        iterator _endof_Storage; // 指向存储容量的尾

    };

构造/析构函数

vector的构造方式有很多种,如下文代码所示,第一种为显式默认构造,直接令vector()=default,这是在告诉编译器我不是忘记写了,而是我就只需要默认构造,那么当你不传任何参数的时候,编译器就默认帮你生成一个实例。

第二个是开辟n个空间,并在每个空间里面放一个T类型的value数据,如果第二个参数没有写的话,就会调用T的默认构造。在该构造中,首先初始化所有变量,接着先开辟n个数据的空间。reserve函数的功能就是开辟空间,后续会提到。接着把空间所有数据都初始化为value。

第三个是根据其他容器来构造vector实例,直接定义一个it为传进来的first迭代器,接着通过循环把数据都push_back到实例内,push_back的功能就是尾插,在后续会提到。

第四个是通过其他vector<T>实例来初始化该实例,此时需要先把空间开好,接着将数据一个一个往内挪,再把_finish给改了,这样就能实现相关功能了。

第五个是赋值重载函数,因为这是传值传参,因此当构造函数结束,参数就会被销毁,先通过swap函数将变量给进行交换,此时v中的数据就是原实例的数据,函数结束既销毁,因为c++原先是可以连续赋值的,于是可以返回一个*this用于连续赋值。

第六个是析构函数,只要delete[]空间,把三个变量全部改成nullptr即可。

    vector() = default;//默认构造

    vector(int n, const T& value = T())//大量连续构造
    {
        _start = _finish = _endof_Storage = nullptr;
        reserve(n);
        for (int i = 0; i < n; i++)
        {
            _start[i] = value;
        }
    }

    template<class InputIterator>//利用其他容器来构造

    vector(InputIterator first, InputIterator last)
    {
        _start = _finish = _endof_Storage = nullptr;
        auto it = first;
        size_t i = 0;
        while (it != last)
        {
            push_back(*it);
            it++;
        }
    
    }

        vector(const vector<T>& v)
        {
            reserve(v.size());
            for (size_t i = 0; i < v.size(); i++)
            {
                _start[i] = v._start[i];
            }
            _finish = _start + v.size();
        }

vector<T>& operator= (vector<T> v)//赋值构造
{
    swap(v);
    return *this;
}

    ~vector()//析构
    {
        delete[] _start;
        _start = _finish = _endof_Storage= nullptr;
    }

空间类函数

空间类函数中包括2个数值返回型函数和2两个空间变动型函数。size即返回空间中已有数据个数,capacity即返回空间中已经开辟的空间个数。再往后就是reseve函数,若申请的空间小于已有空间,不进行缩容,若申请的空间大于已有的空间,则进行扩容,首先定义一个old_size用于记录当前数据个数,接着new一个n个大小的T*数据用于存储数据,如果_start已经初始化,将数据挪过去,并删除原先的_start,并让_finish和_end_of_storage进行改变。resize分为三种情况,n大于当前容积,小于当前容积但大于size,小于size,具体看下面代码。

           size_t size() const
           {
               return _finish - _start;
           }

           size_t capacity() const
           {
               return _endof_Storage - _start;
           }

           void reserve(size_t n)
           {
               if (n > capacity())
               {
                   size_t old_size = size();
                   T* tmp = new T[n];
                   if (_start) // 检查 _start 是否已经初始化
                   {
                       for (size_t i = 0; i < old_size; i++)
                       {
                           tmp[i] = _start[i];
                       }
                       delete[] _start;
                   }
                   _start = tmp;
                   _finish = _start + old_size;
                   _endof_Storage = _start + n;
               }
           }
           void resize(size_t n, const T& value = T())
           {
               if (n > capacity())
               {
                   reserve(n);
                   auto it = _finish;
                   while (it != _endof_Storage)
                   {
                       *it = value;
                       it++;
                   }
               }
               else if (n < capacity() && n > size())
               {
                   auto it = _finish;
                   while (it != _endof_Storage)
                   {
                       *it = value;
                       it++;
                   }
               }
               else
               {
                   _finish = _start + n;
               }
           }

功能函数

尾插,尾删,交换函数与前文的string函数的尾插,尾删,交换函数相似,不予赘述。而insert和erase就有趣的多。

insert函数首先定义一个size_t类型的数据len来记录pos到_start的距离,接着判断是否需要扩容,如果需要扩容,则调用reserve函数,但请注意,此时便会出现迭代器失效。为什么呢?我们的pos指向的是原来的空间中的某个位置,但当使用reserve进行扩容以后,_start的空间会发生改变,因为扩容的本质是另外开辟一片空间来使用,扩容结束以后,pos位置指向的位置却没有发生改变,这就会导致我们的pos不再指向目标点,因此,我们需要重新定义迭代器,len的功能这时候就出现了。在定义好迭代器后就是常规的插入。但是在最后要返回一个pos,因为若是外面也用了pos进行记录,当insert结束外面的pos也会失效,返回一个pos让外面的pos接收就可以避免这样的情况。

erase也是如此,当删除数据以后,会导致pos的位置指向不该指向的位置,如果pos<size(),那就会导致pos的位置指向pos的下一个数据,如果pos==size(),还会导致越界。该迭代器失效需要在外部进行通过判断调整。

           void push_back(const T& x)
           {
               if (_finish == _endof_Storage)
               {
                   reserve(capacity() == 0 ? 4 : 2 * capacity());
               }
               *_finish++ = x;
               
           }

           void pop_back()
           {
               if (_finish != _start)
               {
                   _finish--;
               }

           }

           void swap(vector<T>& v)
           {
               if (v != *this)
               {
                   std::swap(_start, v._start);
                   std::swap(_finish, v._finish);
                   std::swap(_endof_Storage, v._endof_Storage);
               }
           }

           iterator insert(iterator pos, const T& x)//
           {
               size_t len = pos - _start;
               assert(pos >= _start && pos <= _finish); // 确保 pos 是有效的
               if (_finish == _endof_Storage)
               {
                   size_t new_capacity = (_start == nullptr) ? 4 : 2 * capacity();
                   reserve(new_capacity);//pos 迭代器失效
                   pos = _start + len;
               }
               auto it = _finish;
               while (it > pos)
               {
                   *it = *(it - 1);
                   it--;
               }
               *pos = x;
               _finish++;
               return pos;
           }

           void erase(iterator pos)
           {
               assert(pos >= _start || pos <= _finish);
               auto it = pos;
               while (it < _finish)
               {
                   *it = *(it+1);
                   it++;
               }
               _finish--;
           }

以上,就是vector<T>的部分学习经历,

相关推荐
花王江不语12 分钟前
设计模式学习笔记
笔记·学习·设计模式
和光同尘@17 分钟前
74. 搜索二维矩阵(LeetCode 热题 100)
数据结构·c++·线性代数·算法·leetcode·职场和发展·矩阵
一去不复返的通信er19 分钟前
SVD预编码
算法·信息与通信·预编码算法·通信原理
前端熊猫29 分钟前
CSS Grid 布局学习笔记
css·笔记·学习·grid
柠石榴1 小时前
【练习】【二分】力扣热题100 34. 在排序数组中查找元素的第一个和最后一个位置
c++·算法·leetcode·二分
Tisfy1 小时前
LeetCode 2209.用地毯覆盖后的最少白色砖块:记忆化搜索之——深度优先搜索(DFS)
算法·leetcode·深度优先·dfs·题解·记忆化搜索·深度优先搜索
梦里是谁N1 小时前
【deepseek之我问】如何把AI技术与教育相结合,适龄教育,九年义务教育,以及大学教育,更着重英语学习。如何结合,给出观点。结合最新智能体Deepseek
人工智能·学习
Trouvaille ~1 小时前
【C++篇】树影摇曳,旋转无声:探寻AVL树的平衡之道
数据结构·c++·算法·蓝桥杯·计算机科学·平衡二叉树·avl
虾球xz2 小时前
游戏引擎学习第116天
java·学习·游戏引擎
linwq82 小时前
OkHttp使用和源码分析学习(二)
学习·okhttp