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的专栏

时间: 2024-10-10 16:35:40

STL(三):vector的相关文章

【C++】STL,vector容器操作

C++内置的数组支持容器的机制,但是它不支持容器抽象的语义.要解决此问题我们自己实现这样的类.在标准C++中,用容器向量(vector)实现.容器向量也是一个类模板.标准库vector类型使用需要的头文件:#include <vector>.vector 是一个类模板.不是一种数据类型,vector<int>是一种数据类型.Vector的存储空间是连续的,list不是连续存储的. 一. 定义和初始化vector< typeName > v1;       //默认v1为

带你深入理解STL之Vector容器

C++内置了数组的类型,在使用数组的时候,必须指定数组的长度,一旦配置了就不能改变了,通常我们的做法是:尽量配置一个大的空间,以免不够用,这样做的缺点是比较浪费空间,预估空间不当会引起很多不便. STL实现了一个Vector容器,该容器就是来改善数组的缺点.vector是一个动态空间,随着元素的加入,它的内部机制会自行扩充以容纳新元素.因此,vector的运用对于内存的合理利用与运用的灵活性有很大的帮助,再也不必因为害怕空间不足而一开始就配置一个大容量数组了,vector是用多少就分配多少. 要

STL容器 vector,list,deque 性能比较

C++的STL模板库中提供了3种容器类:vector,list,deque对于这三种容器,在觉得好用的同时,经常会让我们困惑应该选择哪一种来实现我们的逻辑.在少量数据操作的程序中随便哪一种用起来感觉差别并不是很大,但是当数据达到一定数量后,会明显感觉性能上有很大差异. 本文就试图从介绍,以及性能比较两个方面来讨论这个问题. vector - 会自动增长的数组 list - 擅长插入删除的链表 deque - 拥有vector和list两者优点的双端队列 性能竞技场 性能总结与使用建议 测试程序清

STL学习——Vector篇

STL学习--Vector篇 vector简介 vector的数据安排及操作方式与array非常相似,两者的区别在于空间运用的灵活性.array是静态空间,一旦配置了,就不能改变:要换个大(或小)一点的可以,但琐碎的事由客户端完成:首先配置一块新空间,然后将元素从旧址一一搬往新址,再把原来的空间释还给系统.而vector是动态空间,随着元素的加入,它的内部机制会自动扩充空间以容纳新元素.它对内存的合理利用和灵活运用有很大的帮助. vector实现关键技术:对大小的控制以及重新配置时的数据移动效率

C++——STL之vector, list, deque容器对比与常用函数

STL 三种顺序容器的特性对比: vector 可变数组,内存空间是连续的,容量不会进行缩减.支持高效随机存取,即支持[]和at()操作.尾部插入删除效率高,其他位置插删效率较低: list 双向链表,内存空间可不连续,不支持随机存取.插入和删除的效率很高: deque  双端队列,内存空间是多个连续的内存块,在一个映射结构中保存对这些块以及顺序的跟踪,可利用的内存更大,且内存大小是可以自动缩减的.支持随机存取,但是随机存取性能没有vector 好.首尾插入效率高,其他位置插删效率低: 使用注意

STL之vector

今天学习了STL 以前用的c,可是比赛回来发现c有点弱,c++的stl是比较实用的,适合比赛.所以学习了一下. vector. 这是一个容器,其实就是线性表. 使用之前在头部加上#include <vector> 然后就可以使用 vector<type> vec; //type is a kind of basic type (eg. int double ..) 然后访问这个表的时候需要声明一个变量 vector<type>::iterator ite; 然后就可以用

C++ STL:vector

  不定长数组:vetor 它就像一个二维数组,只是第一维的大小是固定的,但是第二维的大小不固定. 下面是一些尝试代码: 1. <pre name="code" class="cpp"> #include<cstdio> #include<cstring> #include<iostream> #include<algorithm> #include<cmath> #include<vec

【STL】- vector的使用

初始化: 1. 默认构造: vector<int> vint; 2. 用包含10个元素的数组初始化: vector<int> vint(ia, ia+10); 算法: 1. vint.push_back(i); 2. vint.size(); 3. vint[i]; 代码: 1 #include <vector> 2 #include <iostream> 3 using namespace std; 4 5 int ia[] = {123,1,32,53,

【STL】vector的insert方法详解

#include<vector> #include<iostream> using namespace std; int main() { vector<int> v(3); v[0]=2; v[1]=7; v[2]=9; v.insert(v.begin(),8);//在最前面插入新元素. v.insert(v.begin()+2,1);//在迭代器中第二个元素前插入新元素 v.insert(v.end(),3);//在向量末尾追加新元素. vector<int

C++的STL中vector内存分配方法的简单探索

STL中vector什么时候会自动分配内存,又是怎么分配的呢? 环境:Linux  CentOS 5.2 1.代码 #include <vector> #include <stdio.h> using namespace std; int main() { vector<int> x_vec; printf("data size : [%3d], mem size : [%3d]\n", x_vec.size(), x_vec.capacity())