STL:vector

Posted 小键233

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了STL:vector相关的知识,希望对你有一定的参考价值。

一和二中吧STL 的基础都说明白了,那我们尝试着实现一下第一个容器:vector
(好敷衍呀~)

vector 算是比较简单的一种容器了,但饶是如此,我都写了好久(主要是其他的各种函数费时间)

准备工作

在直接开始说vector 的时候的时候,我会假设你懂得以下函数的运用(最好自己去实现一下吧)

  • uninitialized_copy
  • uninitialized_copy_n
  • uninitialized_fill
  • uninitialized_fill_n
  • copy
  • copy_n
  • fill
  • fill_n
  • copy_backward
  • lexicographical_compare
  • equal
  • distance
  • advance

STL 的设计非常出色,容器负责管理数据,各种算法反而通过迭代器进行。迭代器充当了容器和算法的粘合剂。这个概念一直贯穿中STL。

vector

vector 的内容很多,不会全部都讲,这里只说一些我觉得可以说的内容。
我很建议直接打开C++ 官网,看vector 的公有函数接口。C++11 的标准已经加入了移动语义,但是这里不讨论移动语义的内容,所以会忽略掉它。

迭代器

vector 的迭代器就是原生的指针。
vector 维护的是一个连续线性的空间。
它里面的typedef 如下:

template<typename T, typename Alloc = alloc>
class vector
{
public:
    typedef T value_type;
    typedef value_type* pointer;
    typedef const value_type* const_pointer;
    typedef value_type* iterator;
    typedef const value_type* const_iterator;
    typedef value_type& reference;
    typedef const value_type& const_reference;
    typedef size_t size_type;
    typedef ptrdiff_t difference_type;


protected:
    typedef simple_alloc<T, Alloc> data_alloc;
}

vector 的迭代器支持随机存储,是 Random Access Iterator。

数据结构与内存分配策略

曾经我很好奇vector 里面的数据是怎么维护的。一般而言,受直观思维的影响,我会认为有一个指针存储数据,一个变量维护长度信息,然后加上一些其他的变量。

然而真相是,vector 中只有三个原生指针!分别是:

iterator start; //数据的开始
iterator finish; //数据的结束
iterator end_of_storage; //拥有内存的结束

所以sizeof(vector) == 12 。(32位的机器),我面试的时候被问到,但是那时并不清楚。

这个设计在我看来非常简洁,而且出色。
之前我也做过线性表的数据结构实现,里面用到了pos 位置的概念。
但是如果这样子,当你在多线程中使用的时候,要额外考虑到pos 位置有没有被其他线程改动,从而要求每一步都要重新定位pos。

多么坑爹!

所以,当我看到vector 的设计时,实在是被惊艳到了。(这么说感觉我很low 的样子,好吧,实际就很low)

可能你也不知道上一段我在说什么,没关系,我也不在乎。
下面说说它的内存分配策略。

vector 的空间是连续的,每次如果申请的内存不够了,那么它会重新申请内存,并且把数据挪到新的空间中。
每次重新申请内存的大小是原来的两倍。如果原来的空间为0,那么申请1。

大概类似于这样的计算:

        const size_type old_size = size();
        size_type alloc_length = old_size > 0 ? (old_size<<1) : 1;

你在下面就会看到了:)

insert

insert 函数可以说是vector 的一个核心函数吧。vector 代码很多,但是逻辑复杂一点的就那几个。
先看一下C++ 官网上,insert 的声明:

//single element (1)    
iterator insert (const_iterator position, const value_type& val);
//fill (2)  
iterator insert (const_iterator position, size_type n, const value_type& val);
//range (3) 
template <class InputIterator>
iterator insert (const_iterator position, InputIterator first, InputIterator last);
//move (4)  
iterator insert (const_iterator position, value_type&& val);
//initializer list (5)  
iterator insert (const_iterator position, initializer_list<value_type> il);

一共有5个,去掉4 和5 就只有三个了(4不再讨论范围内,5我也不懂 =_= )

注意到,从c++98 到 c++11 ,insert 的返回值有所改变了。
代码如下:

    /**  insert 函数,返回指向最近插入的那个元素的迭代器
    */
    iterator insert(const_iterator position, const value_type& val)
    {
        size_type n = position - begin();
        if( finish != end_of_storage && position == end() )
        {
            construct(finish, val);
            ++finish;
        }
        else
            insert_aux( const_cast<iterator>(position), val);
        return begin() + n;
    }
    iterator insert(const_iterator position, size_type n, const value_type& val);
    //下面针对 n 的参数重载了 int 和long,下面有解释
    iterator insert(const_iterator position, int n, const value_type& val)
    {
        return insert(position, static_cast<size_type>(n), val);
    }
    iterator insert(const_iterator position, long n, const value_type& val)
    {
        return insert(position, static_cast<size_type>(n), val);
    }

    /** 要考虑到迭代器的不同类型,这里进行了一次萃取
    */
    template<typename Input_iter>
    iterator insert(const_iterator position, Input_iter first, Input_iter last)
    {
        size_type n = position - begin();
        range_insert(position, first, last, _iterator_category(first) );
        return begin() + n;
    }

仔细一看,你就会发现,fill 的版本中,对第二个参数分别进行了int 和long 的重载。他们都是接受整型数据,这样的重载会不会画蛇添足呢?
不会
仔细比对一下range 和 fill 的函数签名,你会发现,它们的区别在于后面两个参数,其中range 后面接受两个参数类型由模板参数决定。

这会导致什么情况,也许你想到了,就是当我们期望调用fill 版本的insert ,并且后面两个参数类型一样,如都是int 或者都是long 的时候,它会推导为调用 range 版本。类似这样:

vector<int> v;
int n = 100;
int val = 0;
v.insert(v.begin(), n, val); 

我们期望fill 版本中间的那个参数能明确地接受表示数量的整型参数 ,所以才对int 和long 进行重载。但是这个还是不够的,因为还有char。但是,基于考虑到很少有人会将char 视为表示数量的类型,就不加以考虑了。

类似的问题,也出现在vector 的构造函数中。

一开始我的解决方法是用萃取技术,将所有表示数量的类型萃取出来。但是这样导致的问题是代码不够清晰明了,所以,就采用了重载的方法。这也是SGI 中的解决方法。

代码中还涉及到insert_aux, range_insert 和 insert 的fill版本的实现。其实它们都差不多,下面给出range_insert 的实现:

template<typename T, typename Alloc>
template<typename Input_iter>
void vector<T, Alloc>::range_insert(const_iterator position, Input_iter first,
                Input_iter last, input_iterator_tag)
{
    iterator pos = const_cast<iterator>(position);
    for(; first != last; ++first)
    {
        pos = insert(pos, *first);
        ++pos;
    }
}

template<typename T, typename Alloc>
template<typename Forward_iter>
void vector<T, Alloc>::range_insert(const_iterator pos, Forward_iter first,
                Forward_iter last, forward_iterator_tag)
{
    iterator position = const_cast<iterator>(pos);
    if(first != last)
    {
        //后面的编写和insert_aux 差不多
        size_type n(0);
        distance(first, last, n);
        if( size_type( end_of_storage - finish ) > n)
        {
            size_type elems_after = finish - position;
            if( elems_after > n)  //往后挪,腾出位置
            {
                iterator insert_end = finish - n;
                uninitialized_copy(insert_end, finish, finish);
                copy(position, insert_end, insert_end);
                copy(first, last, position);
                finish += n;
            }
            else 
            {
                iterator mid = first;
                advance(mid, elems_after);
                iterator now = uninitialized_copy(mid, last, finish);        //TO DO advance
                finish = copy(position, finish, now);
                copy(first, mid, position);
            }
        }
        else
        {
            //重新分配内存
            size_type old_size = size();
            size_type new_size = old_size + (old_size > n? old_size: n);
            iterator new_start = data_alloc::allocate(new_size);
            iterator new_finish = new_start;
            XJ_TRY
            {
                new_finish = uninitialized_copy(start, position, new_start);
                new_finish = uninitialized_copy(first, last, new_finish);
                new_finish = uninitialized_copy(position, finish, new_finish);
            }
            XJ_CATCH
            {
                destroy(new_start, new_finish);
                data_alloc::deallocate(new_start, new_size);
                throw;
            }
            destroy(start, finish);
            deallocate();
            start = new_start;
            finish = new_finish;
            end_of_storage = start + new_size;
        }
    }
}

range_insert 对两种迭代器进行了重载:input_iterator and forward_iterator

我并不是很明白为什么要这么做。先记下来,也许以后就明白了。
range_insert 的行为可以表示如下:

  • 如果容量足够的话:

    • 如果插入点之后的元素数量(记为after) > 要插入的数量(记为n),那么先将后面那部分uniitialized_copy 到相应的位置,然后将前面那部分copy 到相应的位置,最后将插入的数据copy 到腾出的空间中。
    • 如果after <= n ,那么先将插入数据的后面那部分uninitialized_copy 到相应的位置,然后的after 的元素uninitalized_copy 到相应的位置,最后把剩下的数据copy 到腾出的空间中
  • 如果容量不够的话:

    • 那么开辟新的空间,依次copy 进去就好

erase

erase 是删除元素的操作,标准的函数签名如下:

iterator erase (const_iterator position);
iterator erase (const_iterator first, const_iterator last);

98 和 11 的标准在于参数由iterator 变为 const_iterator

这个函数行为倒是清晰明了,如下:

    /** erase 函数
    */
    iterator erase (const_iterator pos)
    {
        iterator position = const_cast<iterator>(pos);
        if( position + 1 != finish)
            copy(position+1, finish, position);
        --finish;
        destroy(finish);
        return position;
    }
    iterator erase (const_iterator first, const_iterator last)
    {
        iterator pos = const_cast<iterator>(first);
        iterator i = copy(iterator(last), finish, pos);
        destroy(i, finish);
        finish = i;
        return pos;
    }

at 与 opeator []

at 和 operator [] 都是随机获取元素的操作。以前没仔细看文档,并不明白它们的区别。
据C++ 的标准,at 是有进行下标安全检查,operator[] 就没有。

实现如下:

    void _check_range(size_type n)
    {
        if(n >= this->size() )
        {
            printf("vector out of range ");
            abort();
        }
    }
    /** at funciton ,这个会抛出异常 
    */
    reference at(size_type index)
    {
        _check_range(index);
        return (*this)[index];
    }

其实check range 这么直接粗暴并不好,正确的行为应该是抛出异常。但是为了练习使用,简单一点就好。

operator [] 的实现如下:

    /** operator []
    */
    reference operator [] (size_type _n)
    {
        return start[_n];
    }
    const_reference operator [] (size_type _n) const
    {
        return start[_n];
    }

emplce 与 emplce_back

这两个函数都是C++11 才加进来的。
emplace 在制定的位置初始化一个元素,并且插入它。
emplace_back 就是在后面初始化一个元素,并且插入它。

我想说这个是因为它用到了可变参数。
先看签名:

template <class... Args>
iterator emplace (const_iterator position, Args&&... args);
template <class... Args>
void emplace_back (Args&&... args);

可变参数的语法展开来说又是一堆了,详细的可以看[1]。
实现倒是意外的简单:

    /** emplace and emplace_back
    */
    template< typename ... Args>
    iterator emplace(const_iterator pos, Args&& ... args)
    {
        return insert(pos, T(args...) );
    }
    template<typename ... Args>
    void emplace_back( Args&& ... args)
    {
        push_back(T(args ... ) );
    }

reverse_iterator

reverse_iterator 是反向迭代器,是一个颠倒黑白的小能手。
先看需求,一般来说遍历元素如下:

for(typename vector<int>::iterator iter = v.begin();
    iter!=v.end(); ++iter)
{
    ...
}

*有些编译器不需要typename ,但是G++ 要

现在我希望能够以同样直观的方式,逆向遍历元素,类似这样:

for(typename vector<int>::reverse_iterator riter = v.rbegin();
    riter != v.rend(); ++riter);
{
    ...
}

那这个时候就需要reverse_iterator 上场了。

它的实现如下:

/** _reverse_iterator
*/
template<typename Iterator>
class _reverse_iterator
{
protected:
    Iterator current;
public:
    typedef typename iterator_traits<Iterator>::iterator_category iterator_category;
    typedef typename iterator_traits<Iterator>::value_type value_type;
    typedef typename iterator_traits<Iterator>::difference_type difference_type;
    typedef typename iterator_traits<Iterator>::pointer pointer;
    typedef typename iterator_traits<Iterator>::reference reference;
    typedef Iterator    iterator_type;
    typedef _reverse_iterator<Iterator> self;

    /** constructor function
    */
    _reverse_iterator() {}
    explicit _reverse_iterator(iterator_type x) : current(x) {}
    _reverse_iterator(const self& x):current(x.current) {}

    iterator_type base() const { return current; }
    reference operator* () 
    {
        iterator_type tmp = current;
        return *--tmp;
    }
    pointer operator -> ()
    {
        return &(this->operator*());  //获得最原始的那个地址
    }
    self& operator ++ ()
    {
        --current;
        return *this;
    }
    self operator ++ (int)
    {
        self tmp = *this;
        --current;
        return tmp;
    }
    self& operator -- ()
    {
        ++current;
        return *this;
    }
    self operator -- (int)
    {
        self tmp(*this);
        ++current;
        return tmp;
    }
    self operator + (difference_type n) const
    {
        return self(current-n);
    }
    self& operator += (difference_type n)
    {
        current -= n;
        return *this;
    }
    self operator - (difference_type n) const
    {
        return self(current + n);
    }
    self& operator -= (difference_type n)
    {
        current += n;
        return *this;
    }
    reference operator [] (difference_type n) const
    {
        return *(*this+n);
    }
};

/** _reverse_iterator relational operator
*/
template<typename Iterator>
inline bool operator == (const _reverse_iterator<Iterator>&x, const _reverse_iterator<Iterator>& y)
{
    return (x.base() == y.base() );
}
template<typename Iterator>
inline bool operator != (const _reverse_iterator<Iterator>& x,const _reverse_iterator<Iterator>& y)
{
    return !(x==y);
}
template<typename Iterator>
inline bool operator < (const _reverse_iterator<Iterator>& x,const _reverse_iterator<Iterator>& y)
{
    return y.base() < x.base();   //这是诡异的逻辑
}
template<typename Iterator>
inline bool operator > (const _reverse_iterator<Iterator>& x,const _reverse_iterator<Iterator>& y)
{
    return x.base() < y.base();
}

template<typename Iterator>
inline bool operator <= (const _reverse_iterator<Iterator>& x, const _reverse_iterator<Iterator>& y)
{
    return !(x>y);
}
template<typename Iterator>
inline bool operator >= (const _reverse_iterator<Iterator>& x, const _reverse_iterator<Iterator>& y)
{
    return !(x<y);
}

/** _reverse_iterator binary operator function
*/
template<typename Iterator>
inline typename _reverse_iterator<Iterator>::difference_type
    operator - (const _reverse_iterator<Iterator>& x, const _reverse_iterator<Iterator>& y)
{
    return y.base()-x.base();
}
template<typename Iterator>
inline _reverse_iterator<Iterator> operator + (const typename _reverse_iterator<Iterator>::difference_type n, const _reverse_iterator<Iterator>& x)
{
    return _reverse_iterator<Iterator>(x.base()-n);
}

代码略长,并且完整地定义了6 种 relational operator 。
至少可以给我们一些提示,设计一个类的时候,要像设计一个type 一样。

它的命名并不是 reverse_iterator ,而是 _reverse_iterator
这么做的原因是,typedef 的时候,名字不能重复,很快你就会看到解释了。
但是后文依然会说reverse_iterator,这样比较方便。

设计好reverse_iterator 之后,我们就可以在vector 中加东西了:

//  typedef reverse_iterator<iterator> reverse_iterator;  
//这样会报错,但是标准的代码就是那么写的. i don't know how to deal with it;
    typedef _reverse_iterator<const_iterator> const_reverse_iterator;
    typedef _reverse_iterator<iterator> reverse_iterator;

注释的那行解释了命名的原因。

有了这些,我们就可以设计 rbegin and rend

    /** rbegin and rend
    */
    reverse_iterator rbegin() { return reverse_iterator( end() ); }
    const_reverse_iterator rbegin() const { return const_reverse_iterator( end() ); }
    reverse_iterator rend() { return reverse_iterator( begin() ); }
    const_reverse_iterator rend() const { return const_reverse_iterator( begin() ); }

shrink_to_fit

shrink_to_fit 是C++11 才有的。它的行为是将容量压缩到size 大小。
它的加入就是为了解决vector 的空间一旦申请,除了生命周期结束,期间不会归还内存的问题。
实现如下:

    /** shrink_to_fit 
    */
    void shrink_to_fit()
    {
        const size_type n = size();
        if(capacity() != n)
        {
            iterator new_start = 0;
            if(n>0)
            {
                new_start = allocate_copy( n, begin(), end() );
                destroy(begin(), end() );
            }
            deallocate();
            start = new_start;
            end_of_storage = finish = start + n;
        }
    }

在了解vector 的空间分配策略后,才能更好地使用它。
代码在github 上可以得到。

[参考资料]
[1] 使用C++11变长参数模板 处理任意长度、类型之参数实例 - yanxiangtianji的专栏

以上是关于STL:vector的主要内容,如果未能解决你的问题,请参考以下文章

STL vector理解

STL之vector的erase函数和遍历

vector源码1(参考STL源码--侯捷)

c++ stl函数模板类型推导

STL:在海量数据中排序和搜索

(C++基础_STL) —— vector 类的基本应用