自学内容网 自学内容网

C++_vector类

欢迎来到本期节目- - -

vector类

本期直接先上代码,然后以代码为例介绍需要注意的问题.


模拟实现:

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

namespace my_room
{

    template<class T>
    class vector
    {
    public:

        // Vector的迭代器是一个原生指针

        typedef T* iterator;
        typedef const T* const_iterator;

        iterator begin()
        {
            return _start;
        }
        iterator end()
        {
            return _finish;
        }

        const_iterator cbegin()const
        {
            return _start;
        }
        const_iterator cend() const
        {
            return _finish;
        }
//---------------------------------------------------------------------------------------------
//构造和析构


//无参默认构造
        vector()
        {
        //由于给了缺省值,所以可以给空
        }


//带参构造
        vector(size_t n, const T& value = T())
        {
            reserve(n);
            while (n--)
            {
                push_back(value);
            }
        }


//迭代器区间构造
        template<class InputIterator>
        vector(InputIterator first, InputIterator last)
        {
            size_t size = last - first;
            reserve(size);//提前开好空间,减少扩容次数
            while (first != last)
            {
                push_back(*first);
                first++;
            }
        }


//拷贝构造
        vector(const vector<T>& v)
        {
            vector<T> tmp(v.cbegin(), v.cend());//直接复用
            swap(tmp);
        }

//赋值重载拷贝
        vector<T>& operator=(vector<T> v)
        {
            swap(v);
            return *this;
        }

         ~vector()
         {
             delete[] _start;//为空也不影响
             _start = _finish = _endOfStorage = nullptr;
         }

//----------------------------------------------------------------------------------------------
//核心函数

            void reserve(size_t n)
            {
                if (n > capacity())
                {
                    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;
                    _endOfStorage = _start + n;
                }
            }

            void resize(size_t n, const T& value = T())
            {
                reserve(n);
                if (n <= size())
                {
                    _finish = _start + n;
                }
                else
                {
                    while (_finish != _start + n)
                    {
                        *(_finish++) = value;
                    }
                }
            }


            iterator insert(iterator pos, const T& x)
            {
                assert(pos <= end());

                size_t old_posi = pos - begin();
                if (_finish == _endOfStorage)
                {
                    size_t n = capacity() == 0 ? 4 : capacity() * 2;
                    reserve(n);
                }
                iterator tail = end();
                pos = begin() + old_posi;
                while (tail > pos)
                {
                    *tail = *(tail - 1);
                    tail--;
                }

                *pos = x;
                _finish++;
                return pos;
            }

            iterator erase(iterator pos)
            {
                assert(pos < end());
                iterator begin = pos+1;
                while (begin != _finish)
                {
                    *(begin-1) = *begin;
                    begin++;
                }
                _finish--;
                return pos;
            }


//----------------------------------------------------------------------------------------------
//简单函数
size_t size() const
            {
                return _finish - _start;
            }

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

            bool empty()const
            {
                return _start == _finish;
            }

            T& operator[](size_t pos)
            {
                assert(pos < size());
                return _start[pos];
            }
            const T& operator[](size_t pos)const
            {
                assert(pos < size());
                return _start[pos];
            }
            void push_back(const T& x)
            {
                insert(end(), x);
            }

            void pop_back()
            {
                erase(end() - 1);
            }

            void swap(vector<T>& v)
            {
                std::swap(_start, v._start);
                std::swap(_finish, v._finish);
                std::swap(_endOfStorage, v._endOfStorage);
            }


    private:

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

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

        iterator _endOfStorage = nullptr; // 指向存储容量的尾

    };
}

核心函数

reserve

首先我们看看reserve函数
当我们在实现vector时,我们都知道,实现异地扩容都要经历这个流程:

异地扩容
申请新空间
拷贝数据
释放旧空间
指向新空间
更新属性

注意

在这其中,拷贝数据就需要注意,在以往的顺序表中,我们可能会用memcpy来拷贝数据,当然在内置类型的情况下,memcpy不仅不会出错,而且比赋值拷贝高效;
但是这里是类模板,数据类型T可以是自定义类型,就有可能需要深拷贝,而memcpy本质上是浅拷贝,不满足需求,所以使用赋值重载拷贝完成自定义类型的拷贝.

除此之外,还需要注意的是更新属性问题,

_finish = _start+size();//size()返回的是 (_finish - _start)的值
此时_finish指向旧空间,_start指向新空间,返回的值根本不是有效数据的个数;
所以需要在扩容之前记录一个old_size记录数据个数,使_finish指向有效数据的末尾.
 void reserve(size_t n)
 {
     if (n > capacity())
     {
         size_t old_size = size();//记录old_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;
         _endOfStorage = _start + n;
     }
 }

resize

接下来瞧瞧resize
resize只需要注意,该函数会改变有效数据的个数以及有可能会扩容.

void resize(size_t n, const T& value = T())
{
    reserve(n);//管他容量够不够,直接让reserve去判断.
    if (n <= size())
    {
        _finish = _start + n;//直接删除数据
    }
    else
    {
        while (_finish != _start + n)
        {
            *(_finish++) = value;//添加数据
        }
    }
}

insert

接下来瞅瞅insert
在实现insert函数时,核心逻辑就是移动数据.

iterator insert(iterator pos, const T& x)
{
    assert(pos <= end());//注意可以尾插,所以取等

    size_t old_posi = pos - begin();//由于可能需要扩容,所以会导致迭代器指向旧空间,提前记录相对位置
    if (_finish == _endOfStorage)
    {
        size_t n = capacity() == 0 ? 4 : capacity() * 2;
        reserve(n);
    }
    
    iterator tail = end();
    pos = begin() + old_posi;//更新迭代器
    while (tail > pos)
    {
        *tail = *(tail - 1);//从后往前挨个 向后移动
        tail--;
    }

    *pos = x;//插入数据
    _finish++;
    return pos;
}

erase

最后瞄一下erase

iterator erase(iterator pos)
{
    assert(pos < end());//_finish没有数据,不能取等
    iterator begin = pos+1;
    while (begin != _finish)
    {
        *(begin-1) = *begin;//从前往后挨个  向前移动
        begin++;
    }
    _finish--;
    return pos;
}

迭代器

迭代器:

迭代器屏蔽了底层的实现细节,使得设计人员无需关心容器对象的内存分配就可以直接使用相应接口,达到类似指针的效果.

也就是说迭代器可能是指针,也可能是指针的封装.

迭代器类型
输入输出迭代器
随机迭代器
Random-access Iterator
双向迭代器
Bidirectional Iterator
前向迭代器
Forward Iterator
Input Iterator
Output Iterator

上图中迭代器支持的行为是向下兼容的,
假设p1,p2是迭代器,i是常量

Iterator描述行为
随机迭代器支持随机访问的迭代器p1++,p1- -,p1+i,p1-i,p1==p2,p1>p2,p1<p2
双向迭代器可以向前或者向后移动的迭代器p1++,p1- -,p1==p2
前向迭代器只能向前移动的迭代器p1++,p1==p2
输入迭代器只能向前移动并读取的迭代器p1++,p1==p2
输出迭代器只能向前移动并写入的迭代器p1++ ,p1==p2

很明显咱们的vector的迭代器是随机迭代器.

迭代器失效

什么是迭代器失效?
迭代器所指向的节点无效了,既该节点被删除了.

vector迭代器失效场景
  • 场景1:扩容
void test()
{
my_room::vector<int> arr;
for(int i = 0; i < 10; i++)//存入1到10
{
arr.push_back(i+1);
}

my_room::vector<int>::iterator old_back = arr.end()-1;//指向最后一个元素10
arr.reserve(1000);
cout<< *old_back <<endl;//程序崩溃----error代码,请勿模仿
}
扩容之后,old_back指向的空间已经被释放了,此时相当于使用野指针.
事实上,不一定只能通过直接显示调用reserve导致迭代器失效,也可以间接调用reserve发生扩容行为导致迭代器失效;
例如: 使用resize函数可能会扩容,使用insert,push_back函数也可能扩容
总的来说,就是底层涉及扩容的行为,就需要注意迭代器失效的问题.
  • 场景2:删除
void test()
{
my_room::vector<int> arr;
for(int i = 0; i < 10; i++)//存入1到10
{
arr.push_back(i+1);
}

my_room::vector<int>::iterator  start = arr.begin();//指向第一个元素1
while(start != arr.end())//预期删除所有元素
{
arr.erase(start);//------error代码,请勿模仿
}
cout << arr.size();//看看有没有删完,但实际上在vs中直接报错.
}
本来预期的是start一开始指向第一个元素,删除之后,剩下的元素往前移,然后继续删,直到删完.
但是实际上删除行为同样也有迭代器失效的风险.

在这里插入图片描述

迭代器失效解决方案

我们知道在使用vector时,当接口涉及扩容问题或者删除问题时,会有迭代器失效的问题.
解决办法就是使用之前对迭代器重新赋值.

void test()
{
my_room::vector<int> arr;
for(int i = 0; i < 10; i++)
{
arr.push_back(i+1);
}

my_room::vector<int>::iterator old_back = arr.end()-1;
arr.reserve(1000);

old_back = arr.end()-1;
cout<< *old_back <<endl;//程序正常运行
}
void test()
{
my_room::vector<int> arr;
for(int i = 0; i < 10; i++)
{
arr.push_back(i+1);
}

my_room::vector<int>::iterator  start = arr.begin();
while(start != arr.end())
{
start = arr.erase(start);
}
cout << arr.size();//程序正常运行
}

希望本片文章对您有帮助,请点赞支持一下吧😘💕


原文地址:https://blog.csdn.net/m0_73399947/article/details/142498108

免责声明:本站文章内容转载自网络资源,如本站内容侵犯了原著者的合法权益,可联系本站删除。更多内容请关注自学内容网(zxcms.com)!