STL中vector的实现及面试问题 - Go语言中文社区

STL中vector的实现及面试问题


一、前言:
在学习c++的时候我们会接触两个库,一个是boost库另外一个就是STL库。关于STL库候捷先生的《STL源码剖析》中已经写的很详细了,今天我就关于STL中的vector实现及面试中的一些与之相关的问题做一个讲解。
在面试C++的时候关于vector是作为基础知识经常被问到的,如果面试官问你vector的实现原理,你会怎么回答呢?

二、vector的实现原理及实现机制
关于vector简单的讲就是一个动态增长的数组,里面有一个指针指向一片连续的内存空间,当空间装不下的时候会自动申请一片更大的空间(空间配置器)将原来的数据拷贝到新的空间,然后就会释放旧的空间。当删除的时候空间并不会被释放只是清空了里面的数据。
vector的数据安排以及操作方式与array非常相似,两者的唯一区别在于空间运用的灵活性,array是静态空间一旦配置了就不能改变大小,如果要扩大或缩小容量的话,就要把数据搬到新大小的数组里面,然后再把原来的空间释放还给系统。vector是动态空间是随着元素的加入,它的内部机制会自动的扩充空间来容纳新的元素。因此,vector的运用对于内存的合理利用与运用的灵活性有很大的帮助,我们不必因为害怕空间不足而一开始就开辟一块很大的内存。

vector的实现技术,关键在于其对大小的控制以及重新配置时的数据移动效率。一旦vector的旧有空间满载,如果客户端每新增一个元素,vector的内部只是扩充一个元素的空间,实为不智。因为所谓的扩充空间(无论多大),过程是配置新空间–数据移动–释还旧空间的成本很高。vector维护的是一个连续线性空间,所以vector支持随机访问。
在vector的动态增加大小的时候,并不是在原有的空间上持续新的空间(无法保证原空间的后面还有可供配置的空间),而是以原大小的两倍另外配置一块较大的空间,然后将原内容拷贝过来,并释放原空间。因此,对vector的任何操作一旦引起了空间的重新配置,指向原vector的所有迭代器就会都失效了这是程序员易犯的一个错误。
下面我们来看一下vector里的函数:
这里写图片描述
三、模拟STL中vector的实现
上面是vector的函数,下面我们来看模拟STL中vector的代码,由于在STL中vector的实现用到了类型萃取,所以我们要先实现一个类型萃取。
类型萃取的代码:

struct _TrueType
{
    bool Get()
    {
        return true;
    }
};
struct _FalseType
{
    bool Get()
    {
        return false;
    }
};
template<class _Tp>
struct TypeTraits
{
    typedef _TrueType _IsPODType;
};

template<>
struct TypeTraits<bool>
{
    typedef _TrueType _IsPODType;
};

template<>
struct TypeTraits<char>
{
    typedef _TrueType _IsPODType;
};

template<>
struct TypeTraits<unsigned char>
{
    typedef _TrueType _IsPODType;
};

template<>
struct TypeTraits<short>
{
    typedef _TrueType _IsPODType;
};

template<>
struct TypeTraits<unsigned short>
{
    typedef _TrueType _IsPODType;
};

template<>
struct TypeTraits<int>
{
    typedef _TrueType _IsPODType;
};

template<>
struct TypeTraits<unsigned int>
{
    typedef _TrueType _IsPODType;
};

template<>
struct TypeTraits<long>
{
    typedef _TrueType _IsPODType;
};

template<>
struct TypeTraits<unsigned long>
{
    typedef _TrueType _IsPODType;
};

template<>
struct TypeTraits<long long>
{
    typedef _TrueType _IsPODType;
};

template<>
struct TypeTraits<unsigned long long>
{
    typedef _TrueType _IsPODType;
};

template<>
struct TypeTraits<float>
{
    typedef _TrueType _IsPODType;
};

template<>
struct TypeTraits<double>
{
    typedef _TrueType _IsPODType;
};

template<>
struct TypeTraits<long double>
{
    typedef _TrueType _IsPODType;
};

template<class _Tp>
struct TypeTraits<_Tp*>
{
    typedef _TrueType _IsPODType;
};

模拟实现vector:

#include<iostream>
#include"TypeTraits.h"
#include<string>
#include<assert.h>
using namespace std;

template<class T>
class Vector
{
public:
    typedef T* Iterator;
    typedef const T* ConstIterator;
    Vector(size_t n = 3)
        :_start(new T[n])
        , _finish(_start)
        , _endofStorage(_start + n)
    {}
    Vector(const Vector<T>& v)
        :_start(new T[v.Size()])
        , _finish(0)
        , _endofStorage(0)
    {
        if (TypeTraits<T>::_IsPODType().Get())
        {
            memcpy(_start, v._start, sizeof(T)*v.Size());
        }
        else
        {
            for (size_t i = 0; i<v.Size(); i++)
            {
                _start[i] = v._start[i];
            }
        }
        _finish = _start + v.Size();
        _endofStorage = _start + v.Size();
    }
    Vector<T>& operator=(const Vector<T>& v)
    {
        swap(_start, v._start);
        _finish = v._finish;
        _endofStorage = v._finish;
        return *this;
    }
    Vector<T>& operator=(Vector<T>& v)
    {
        swap(_start, v._start);
        _finish = v._finish;
        _endofStorage = v._endofStorage;
        return *this;
    }

    void PushBack(const T& x)
    {
        checkStorage();
        Insert(End(), x);
    }
    void PopBack()
    {
        assert(Size());
        --_finish;
    }
    void Insert(Iterator pos, const T& x)
    {
        checkStorage();
        for (Iterator tmp = End(); tmp != pos; tmp--)
        {
            *(tmp) = *(tmp - 1);
        }
        *pos = x;
        _finish++;
    }
    void Erase(Iterator pos)
    {
        for (Iterator tmp = pos; tmp != End(); tmp++)
        {
            *tmp = *(tmp + 1);
        }
        _finish--;
    }
    Iterator Begin()
    {
        return _start;
    }
    Iterator End()
    {
        return _finish;
    }
    const T& operator[](size_t pos) const
    {
        assert(pos<Size());
        return _start[pos];
    }
    size_t Size() const
    {
        return _finish - _start;
    }
    size_t Capacity()
    {
        return _endofStorage - _start;
    }
protected:
    Iterator _start;
    Iterator _finish;
    Iterator _endofStorage;
    void checkStorage()
    {
        if (_finish == _endofStorage)
        {
            size_t size = Size();
            size_t capacity = Capacity();
            capacity = size * 2;
            T *tmp = new T[capacity];
            if (_start)
            {
                for (size_t i = 0; i<Size(); i++)
                {
                    tmp[i] = _start[i];
                }
                delete[] _start;
            }
            _start = tmp;
            _finish = _start + size;
            _endofStorage = _start + capacity;
        }
    }

};

void test1()
{
    Vector<int> v;
    v.PushBack(1);
    v.PushBack(2);
    v.PushBack(3);
    v.PushBack(4);
    v.PopBack();
    Vector<int>::Iterator it;
    for (it = v.Begin(); it != v.End(); it++)
    {
        cout << *it << " ";
    }
    cout << endl;
    /*vector<int> v2(v);
    vector<int>::iterator it2;
    for(it2=v2.begin();it2!=v2.end();it2++)
    {
    cout << *it2 << " ";
    }
    cout<<endl;*/
}

void test2()
{
    Vector<int> v;
    v.PushBack(1);
    v.PushBack(2);
    v.PushBack(3);
    v.PushBack(4);
    v.Insert(v.Begin(), 7);
    v.Erase(v.End());
    Vector<int>::Iterator it;
    for (it = v.Begin(); it != v.End(); it++)
    {
        cout << *it << " ";
    }
    cout << endl;
}

由于STL中的vector用到了迭代器,所以上面的代码中也加入了迭代器,其实上面的代码如果加上自己写的空间配置器那就更好了。

四、vector中resize与reserve的区别(提高效率)
我们先看一下Cplusplus中对resize与reserve的表示吧:
这里写图片描述
reserve:(预留一定的空间)
reserve是直接扩充到已经确定的大小,可以减少多次开辟、释放空间的问题,就可以提高效率,其次还可以减少多次要拷贝数据的问题。
reserve只是当要开辟空间大于其原空间会开辟至需要的空间,而小于就不会更改其空间。

reserve只是保证vector中的空间大小(capacity)最少达到参数所指定的大小n
在区间[0, n ]的范围内,如果下标是index,vector[index]有可能是合法的,也有可能是非法的,具体视情况而定。
resize:(重新分配大小)
若要开辟的空间的size大于其原来的size,那么resize之后要存放的数据就放在原size后的位置上。
若要开辟的空间小于原size则就保留前n个数据(之后的会自动的删除)

为了实现resize的语义,resize的接口做了两个保证:
1、保证区间[0, newsize]范围内的数据是有效的,下标index在这个区间内的话,那么vector[index]就是有效的。
2、保证区间[0, newsize]范围外的数据是无效的,下标index在这个区间外的话,那么vector[index]就是无效的。


reserve与resize的相同点:
就是它们都保证了vector空间的大小,至少达到它们参数所指定的大小。
下面给出reserve与resize的源码:
reserve:

 void reserve(size_type n) {
    if (capacity() < n) {
      const size_type old_size = size();
      iterator tmp = allocate_and_copy(n, start, finish);
      destroy(start, finish);
      deallocate();
      start = tmp;
      finish = tmp + old_size;
      end_of_storage = start + n;
    }
  }

resize:

void resize(size_type new_size, const T& x) {
    if (new_size < size()) 
      erase(begin() + new_size, end());
    else
      insert(end(), new_size - size(), x);
  }

 void resize(size_type new_size) {    resize(new_size, T()); }
版权声明:本文来源CSDN,感谢博主原创文章,遵循 CC 4.0 by-sa 版权协议,转载请附上原文出处链接和本声明。
原文链接:https://blog.csdn.net/Payshent/article/details/73835795
站方申明:本站部分内容来自社区用户分享,若涉及侵权,请联系站方删除。
  • 发表于 2021-05-29 18:51:23
  • 阅读 ( 567 )
  • 分类:面试题

0 条评论

请先 登录 后评论

官方社群

GO教程

推荐文章

猜你喜欢