1

Vector底层实现 - Lonely丶MoXuan

 2 years ago
source link: https://www.cnblogs.com/LonelyMoNan/p/16647533.html
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

Vector底层实现

vector的三个私有成员

:_start   记录初始位置
, _finish 记录有效字符
, _endofstoage  记录容量大小

vector会存储的类型不同,所以要用模版来定类型 

typedef T* iterator;

iterator _start;
iterator _finish;
iterator _endofstoage;

也就是T*

构造函数的方法很多 可以用迭代器的范围来构造

//用迭代器构造的构造函数

传过来的是它的迭代器的类型 我们也用它的类型来接收 不比加* &

 三个属性先初始化

只要根据传过来的范围来push_back()即可

push_back函数后面会实现

        //用迭代器构造的构造函数
        template <class InputIterator>
        vector(InputIterator first, InputIterator last)
            : _start(nullptr)
            , _finish(nullptr)
            , _endofstoage(nullptr)
        {
            while (first != last)
            {
                push_back(*first);
                ++first;
            }
        }

构造是 也可以根据n分val来构造 所以这个功能也需要提供

传过来的是n个 用size_t接收 因为n必须是>=0的  而val是根据类型 所以用模版类型接受  

有些情况下 val会不传参  那么我们就会提供他的默认构造  (注意 在C++中,内置类型也是有默认构造的)

三个属性初始话

先用reserve函数创建n个空间

在分别push_back()添加val

        //构造n个val的构造函数
        vector(size_t n, const T& val = T())
            : _start(nullptr)
            , _finish(nullptr)
            , _endofstoage(nullptr)
        {
            reserve(n);
            for (size_t i = 0; i < n; ++i)
            {
                push_back(val);
            }
        }

因为某些情况 第一个参数是int  第二个参数也是int 会调用到迭代器的函数 因为这两个类型更加适配,所以会出问题,所以需要再提供一个第一个参数为int的相同函数,来避免这种情况

    //构造n个val的构造函数
        //因为用int会调用到其他函数 所以为了区分 单独写出一个第一个为int
        vector(int n, const T& val = T())
            : _start(nullptr)
            , _finish(nullptr)
            , _endofstoage(nullptr)
        {
            reserve(n);
            for (int i = 0; i < n; ++i)
            {
                push_back(val);
            }
        }

swap函数

这个函数是用来给拷贝构造使用

交换类的三个属性成员

        //交换
        void swap(vector<T>& v)
        {
            std::swap(_start, v._start);
            std::swap(_finish, v._finish);
            std::swap(_endofstoage, v._endofstoage);
        }

拷贝的本质是把一个有数据的拷贝给一个无数据的,只需要用这个无数据的迭代器去调用迭代器构造,给一个临时tmp,最后再用这个无数据的与临时tmp交换  即可

因为传过来的是另一个vector 所以必须用vector<T> 接收  C++传参是特别需要注意的,真的很容易乱

        //拷贝构造
        vector(const vector<T>& v)
            : _start(nullptr)
            , _finish(nullptr)
            , _endofstoage(nullptr)
        {
            vector<T> tmp(v.begin(), v.end());//复用用迭代器构造的构造函数
            swap(tmp);
        }

v1=v2  是两个vector的数据赋值 所以它的返回值必须是vector<t>  而传参数时 也必须是vector<t> 

函数本质也是交换,所以直接调用swap  这里之所以能直接调用 而不影响到v2 是因为函数是用传值传参,它是不会影响到v2本体的,(现代写法)

返回时是返回本体 (*this)

    vector<T>& operator=(vector<T> v)//赋值重载   不用引用  现代写法
        {
            swap(v);//现代写法 
            return *this;
        }

判断是否为空 当不为空时才需要析构  如果为空 去析构 会崩溃

把数据释放,并且它三个属性置空

    // 资源管理
        ~vector()
        {
            if (_start)
            {
                delete[] _start;
                _start = _finish = _endofstoage = nullptr;
            }
        }

迭代器部分

vector的迭代器本质就是指针  根据传进来的类型  iterator就是这个类型的指针

并且迭代器分为const版本和非const版本 

所以需要提供两个版本

        //迭代器
        typedef T* iterator;
        typedef const T* const_iterator;

注意 end返回的是你的实际有效字符 而不是你的的空间多大

        iterator begin()
        {
            return _start;
        }

        iterator end()
        {
            return _finish;
        }

        const_iterator begin() const
        {
            return _start;
        }

        const_iterator end() const
        {
            return _finish;
        }

你的有效字符 _finish  而_finish实际上是有效字符的下一个位置

所以需要减去初始的位置 得到它真正的有效字符

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

capacity

计算的是vector的空间大小 也就是你的记录空间大小减去初始位置

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

vector是支持随意访问的,所以【】的重载必不可少

返回的是vector里存储的类型  实际上就是模版类型  因为传出去也代表它需要改变 所以需要传引用

        T& operator[](size_t n)
        {
            assert(n < size());
            //return *(_start + n);
            return _start[n]; //这个也可以
        }

【】重载 有些地方是需要提供const版本 不允许更改的版本

        const T& operator[](size_t n) const
        {
            assert(n < size());
            //return *(_start + n);
            return _start[n]; //这个也可以
        }

reserve

扩容空间  但不初始化

这里需要注意扩容后的三个属性更新出现的问题  正常运行会崩溃。  问题的关键:开辟一个新空间时,他们三个的类型是指针!而不是下标,一个地址更新,去用以前的指针,去更改这个新的地址,是会崩溃的,而下标是固定位置,地址在怎么更新,下标的位置就是固定的。

我们需要记录原先的有效字符个数

正常比较和扩容,只是这里用memcpy函数时,出现嵌套情况时,会出现浅拷贝情况

需要用赋值 深拷贝

另外两个要更新时,要用预先存储好的数据来更新 详情看注释!

        void reserve(size_t n)
        {
            size_t sz = size();
            if (n > capacity())
            {
                T* tmp = new T[n];
                if (_start)  //如果_start为空时 就不需要拷贝数据了
                {
                    //memcpy(tmp, _start, size() * sizeof(T));//把_start的数据拷贝到tmp  //这样做 在嵌套时,会发生浅拷贝
                    for (size_t i = 0; i < size(); i++)//正确做法是直接赋值  在vector<vector> 这种嵌套时 是深拷贝
                    {
                        tmp[i] = _start[i];
                    }
                    delete[] _start;
                }
                _start = tmp;//更新过后 _start就不再是nullptr
            }
            //_finish = _start + size();//×  结果为空 解引用无地址 赋值会崩溃 ==_start+(_finish-_start)  这里的_finsih最后是等于空  
            //而跳出函数后 _finish解引用再赋值会崩溃 因为空地址无法赋值

            _finish = _start + sz;//√ 结果为_start有地址。解引用能赋值   ==_start+0 ==  这里先让原先的_finish-_start==0  再+上0  因为_start已经更新过了 所以需要在开头记录_size的大小 
            //而更新过的_start不是空 这时候在调用size _finish-_start就不是0了 而是其他值了
            _endofstoage = _start + n;
        }

resize

扩容,但会初始化数据

要扩容的大小 大于实际 空间大小(不是实际字符大小!) 时,我们先开需要大小空间即可

如果n大于实际字符大小时

我们需要在实际字符的位置后开始不断添加val(要初始化的值)

如果n小于实际字符大小

那么就把实际字符大小改为  n个  即 初始值+n

        void resize(size_t n, T val = T())
        {
            if (n > capacity())
            {
                reserve(n);
            }

            if (n > size())
            {
                while (_finish < _start + n)
                {
                    *_finish = val;
                    ++_finish;
                }
            }
            else
            {
                _finish = _start + n;
            }
        }

push_back()

只要需要添加字符的函数,基本是需要检查空间是否足够的

一种情况为 实际空间为0 还未扩容时 默认给它开4个空间,如果有空间,但满时,扩二倍即可(为什么扩容二倍?没什么!因为合适 或者1.5倍,扩容其他倍数要么太小,要么太大,1.5和2的倍数是适应性最好的

扩容好后,或者空间足够时

直接在有效字符的位置添加字符 (为什么不先++?因为前面说过,实际字符的指针实际上是指向实际字符的后一位,所以你要添加,直接在实际字符指针添加即可)

最后添加好后,实际字符++ (这也是返回真实字符时,不直接返回_finish,而是返回_finish-_start

因为嵌套的作用,此函数在insert函数实现后,可以直接复用insert就可以了

        void push_back(const T& x)
        {
            /*if (_finish == _endofstoage)
            {
                size_t newCapacity = capacity() == 0 ? 4 : capacity() * 2;
                reserve(newCapacity);
            }

            *_finish = x;
            ++_finish;*/

            insert(end(), x);
        }

pop_back()

此函数只需要把实际字符--即可

先判断合法性,实际字符必须大于初始值

因为嵌套的作用,此函数在erase函数实现后,可以直接复用erase就可以了

        void pop_back()
        {
            /*        if (_finish > _start)
                    {
                        --_finish;
                    }*/
            erase(end() - 1);
        }

insert

注意此函数会有迭代器失效问题!

通常此函数是不需要返回的,但因为迭代器失效问题,所以必须有返回值,因为返回的就是此指向此函数的指针 也就是T* 模版类型指针  所以用重命名的iterator即可  

pos是指向要插入的位置,这个函数都是用指针来指针,所以没办法使用下标,这也是它的失效的问题之一!

首先判断是否需要扩容

上面的reserve提到过,扩容后,_start的地址是会变的,而pos也是迭代器,它也是指向这个地址的指针,它不会跟着更新地址而变化。

所以这个pos它指向的位置,是原来_start的位置,而这个位置已经被释放了,所以再去使用这个pos时,是会崩溃的! 

所以为了防止这种情况,我们需要先记录这个pos的位置,等待_start的地址更新好后,我们要根据原先这个存储好pos的位置,在去用_start的地址,去更新新的pos位置,并且pos的位置不变。

然后开始移动pos后的数据 给pos位置开出空间,能让val插入

最后在pos的位置插入val

再++实际空间

最后需要放回插入后的位置

那么我们在使用是,需要用迭代器去接收,才能防止迭代器的失效,因为原先的迭代器已经失效,需要根据这个返回值,来更新迭代器。

    while (it != v1.end())
    {
        if (*it % 2 == 0)
        {
            it = v1.insert(it, 100);//因为迭代器更新数据会失效 所以要用it接收 防止失效
            ++ it;//返回的是插入的位置 再次++会再次遇到原来的位置 所以插入后 要自增++一次
        }
        ++it;
    }

erase

此函数存在迭代器失效的问题

正常删除即可

只是返回必须返回删除后的下一个值

使用这个函数时,也是需要迭代器用函数返回值接收,来更新迭代器

    while (it != v.end())
    {
        if ((*it) % 2 == 0)
        {
            it = v.erase(it);
        }
        else
        {
            ++it;
        }

    }

clear

置空功能,只需要把有效字符置为初始值即可。

        //置空
        void clear()
        {
            _finish = _start;//把有效字符改为初始
        }

接下来是源码

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

namespace moxuan
{
    template<class T>
    class vector
    {
    public:
        //迭代器
        typedef T* iterator;
        typedef const T* const_iterator;

        //默认无参构造
        vector()
            :_start(nullptr)
            , _finish(nullptr)
            , _endofstoage(nullptr)
        {}

        //用迭代器构造的构造函数
        template <class InputIterator>
        vector(InputIterator first, InputIterator last)
            : _start(nullptr)
            , _finish(nullptr)
            , _endofstoage(nullptr)
        {
            while (first != last)
            {
                push_back(*first);
                ++first;
            }
        }

        //构造n个val的构造函数
        vector(size_t n, const T& val = T())
            : _start(nullptr)
            , _finish(nullptr)
            , _endofstoage(nullptr)
        {
            reserve(n);
            for (size_t i = 0; i < n; ++i)
            {
                push_back(val);
            }
        }

        //构造n个val的构造函数
        //因为用int会调用到其他函数 所以为了区分 单独写出一个第一个为int
        vector(int n, const T& val = T())
            : _start(nullptr)
            , _finish(nullptr)
            , _endofstoage(nullptr)
        {
            reserve(n);
            for (int i = 0; i < n; ++i)
            {
                push_back(val);
            }
        }

        //交换
        void swap(vector<T>& v)
        {
            std::swap(_start, v._start);
            std::swap(_finish, v._finish);
            std::swap(_endofstoage, v._endofstoage);
        }

        //拷贝构造
        vector(const vector<T>& v)
            : _start(nullptr)
            , _finish(nullptr)
            , _endofstoage(nullptr)
        {
            vector<T> tmp(v.begin(), v.end());//复用用迭代器构造的构造函数
            swap(tmp);
        }

        vector<T>& operator=(vector<T> v)//赋值重载   不用引用  现代写法
        {
            swap(v);//现代写法 
            return *this;
        }

        // 资源管理
        ~vector()
        {
            if (_start)
            {
                delete[] _start;
                _start = _finish = _endofstoage = nullptr;
            }
        }

        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 _endofstoage - _start;
        }

        void reserve(size_t n)
        {
            size_t sz = size();
            if (n > capacity())
            {
                T* tmp = new T[n];
                if (_start)  //如果_start为空时 就不需要拷贝数据了
                {
                    //memcpy(tmp, _start, size() * sizeof(T));//把_start的数据拷贝到tmp  //这样做 在嵌套时,会发生浅拷贝
                    for (size_t i = 0; i < size(); i++)//正确做法是直接赋值  在vector<vector> 这种嵌套时 是深拷贝
                    {
                        tmp[i] = _start[i];
                    }
                    delete[] _start;
                }
                _start = tmp;//更新过后 _start就不再是nullptr
            }
            //_finish = _start + size();//×  结果为空 解引用无地址 赋值会崩溃 ==_start+(_finish-_start)  这里的_finsih最后是等于空  
            //而跳出函数后 _finish解引用再赋值会崩溃 因为空地址无法赋值

            _finish = _start + sz;//√ 结果为_start有地址。解引用能赋值   ==_start+0 ==  这里先让原先的_finish-_start==0  再+上0  因为_start已经更新过了 所以需要在开头记录_size的大小 
            //而更新过的_start不是空 这时候在调用size _finish-_start就不是0了 而是其他值了
            _endofstoage = _start + n;
        }

        void resize(size_t n, T val = T())
        {
            if (n > capacity())
            {
                reserve(n);
            }

            if (n > size())
            {
                while (_finish < _start + n)
                {
                    *_finish = val;
                    ++_finish;
                }
            }
            else
            {
                _finish = _start + n;
            }
        }

        void push_back(const T& x)
        {
            /*if (_finish == _endofstoage)
            {
                size_t newCapacity = capacity() == 0 ? 4 : capacity() * 2;
                reserve(newCapacity);
            }

            *_finish = x;
            ++_finish;*/

            insert(end(), x);
        }

        void pop_back()
        {
            /*        if (_finish > _start)
                    {
                        --_finish;
                    }*/
            erase(end() - 1);
        }

        T& operator[](size_t n)
        {
            assert(n < size());
            //return *(_start + n);
            return _start[n]; //这个也可以
        }

        const T& operator[](size_t n) const
        {
            assert(n < size());
            //return *(_start + n);
            return _start[n]; //这个也可以
        }

        //插入 注意会有迭代器失效问题!
        iterator insert(iterator pos, const T& val)
        {
            assert(pos >= _start && pos <= _finish);
            if (_finish == _endofstoage)
            {
                size_t n = pos - _start;//因为更新start后 pos无法跟着更新 所以要记录pos的位置
                size_t newsize = capacity() == 0 ? 4 : capacity() * 2;
                reserve(newsize);
                pos = _start + n;
            }
            iterator cur = _finish - 1;
            while (cur >= pos)
            {
                *(cur + 1) = *cur;
                --cur;
            }
            *pos = val;
            ++_finish;
            return pos;
        }

        //删除
        iterator erase(iterator pos)//返回删除后的下一个位置
        {
            assert(pos >= _start && pos < _finish);
            iterator it = pos+1;
            while (it != _finish)
            {
                *(it-1) = *it;
                ++it;
            }
            --_finish;
            return pos++;//返回删除后的下一个位置
        }

        //置空
        void clear()
        {
            _finish = _start;//把有效字符改为初始
        }

    private:
        iterator _start;
        iterator _finish;
        iterator _endofstoage;
    };

接下来是用来测试这个vector的可行性  杨辉三角

大家可以源码拿去编译器上,然后调用这个test4函数即可

    class Solution {
    public:
        vector<vector<int>> generate(int numRows) {
            vector<vector<int>> vv;
            vv.resize(numRows);
            for (size_t i = 0; i < vv.size(); ++i)
            {
                // 杨辉三角,每行个数依次递增
                vv[i].resize(i + 1, 0);

                // 第一个和最后一个初始化成1
                vv[i][0] = 1;
                vv[i][vv[i].size() - 1] = 1;
            }

            for (size_t i = 0; i < vv.size(); ++i)
            {
                for (size_t j = 0; j < vv[i].size(); ++j)
                {
                    if (vv[i][j] == 0)
                    {
                        // 中间位置等于上一行j-1 和 j个相加
                        vv[i][j] = vv[i - 1][j - 1] + vv[i - 1][j];
                    }
                }
            }

            for (size_t i = 0; i < vv.size(); ++i)
            {
                for (size_t j = 0; j < vv[i].size(); ++j)
                {
                    cout << vv[i][j] << " ";
                }
                cout << endl;
            }
            cout << endl;

            return vv;
        }
    };

    void test4()
    {
        vector<vector<int>> ret = Solution().generate(5);

        for (size_t i = 0; i < ret.size(); ++i)
        {
            for (size_t j = 0; j < ret[i].size(); ++j)
            {
                cout << ret[i][j] << " ";
            }
            cout << endl;
        }
        cout << endl;
    }
}

这就是本文章的全部内容,感谢您能观看到这里,如有问题请评论或私信。感谢观看!


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK