zoukankan      html  css  js  c++  java
  • vector详解

    引言

    emmm…这篇博客写作的缘由其实就是我在日常使用vector的时候发现对vector并不怎么了解所以决定写这篇博客的。
    写这篇博客,我参考了vector - C++ Reference中的内容,及侯捷先生的《STL源码剖析》一书,所实验的环境是CentOS7,g++4.8.5,所查看源码是SGI STL v3.3。这个版本的STL发布于2000年,而且是最新版。所以不包含C++11后的内容,所以遇到C++11后的内容我参考的是g++4.8.5中的STL。
    另外就是这篇博客内容似乎比较多,阅读参考之类的最好通过目录来阅读….

    vector简介

    vector是表现为可变长数组的序列容器。
    vector使用连续的存储位置来存储元素,也就是说,vector可以使用一个指针上的偏移量来访问其元素,就像数组一样。但与数组不同的是,它们的大小可以动态变化,其存储由容器自动处理。
    在vector内部,使用一个动态分配内存的数组来保存其元素。当插入新元素的时候,这个数组可能需要重新分配,以增加大小,这意味着分配一个新的数组,并且将原有的元素从旧的位置移到新的数组中。这个操作在时间方面是个开销极大的操作,因此,vector不会每次新添加一个元素就重新分配内存。
    相反,vector可能会分配一些额外的存储,以适应可能的增长,因此,vector可能具有一个比严格需求的存储size更大的capcity。不同的库可能采取不同的策略去平衡内存使用情况和内存分配,但是在任何情况下,重新分配内存应该只发生在大小的对数增长区间,以便在vector末尾插入单个元素的操作可以被提供均摊为常数时间的复杂度。
    因此,vector相比于数组,vector消耗更多的内存,以换取管理存储和动态增长的能力。
    与其它的动态序列容器(deque, lists, forward_lists)相比,vector可以非常高效的访问其元素,就像数组一样,并且可以相对高效的增加和删除元素从其末尾。对于在其它位置插入或删除元素的操作,它的性能要比其它的要差,而且其迭代器和引用的一致性相比lists和forward_lists要差。

    以上摘自vector - C++ Reference

    vector的定义

    template<class T, class Alloc = alloc>
    class vector {
    public:
      typedef T           value_type;
      typedef value_type* pointer;
      typedef value_type* iterator;
      typedef value_type& reference;
      typedef size_t      size_type;
      typedef ptrdiff_t   difference_type;
    protected:
      typedef simple_alloc<value_type, Alloc> data_allocator;
      iterator start;           // 表示目前使用空间的头
      iterator finish;          // 表示目前使用空间的尾
      iterator end_of_storage;  // 表示目前可用空间的尾
    
      void insert_aux(iterator position, const T& x);
      void deallocate() {
        if (start) 
          data_allocator::deallocate(start, end_of_storage - start);
      }
      void fill_initialize(size_type n, const T& value) {
        start = allocate_and_fill(n, value);
        finish = start + n;
        end_of_storage = finish;
      }
    public:
      iterator begin() { return start; }
      iterator end() { return finish; }
      size_type size() const { return size_type(end() - begin()); }
      size_type capcity() const {
        return size_type(end_of_storage - begin());
      }
      bool empty() const { return begin() == end(); }
      reference operator[](size_type n) { return *(begin() + n); }
      vector() : start(0), finish(0), end_of_storage(0) {}
      vector(size_type n, const T& value) { fill_initialize(n, value); }
      vector(int n, const T& value) { fill_initialize(n, value); }
      vector(long n, const T& value) { fill_initialize(n, value); }
      explicit vector(size_type n) { fill_initialize(n, T()); }
      ~vector() {
        destroy(start, finish);
        deallocate();
      }
      reference front() { return *begin(); } // 第一个元素
      reference back() { return *end(); } // 最后一个元素
      void push_back(const T& x) { // 将元素插入至最尾端
        if (finish != end_of_storage) {
          construct(finish, x);
          ++finish;
        } else insert_aux(end(), x);
      }
      void pop_back() { // 将最尾端元素取出
        --finish;
        destroy(finish);
      }
      iterator erase(iterator position) { // 清除某位置上的元素
        if (position + 1 != end()) 
          copy(position + 1, finish, position); // 后续元素往前移动
        --finish;
        destroy(finish);
        return position;
      }
      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()); }
      void clear() { erase(begin(), end()); }
    protected:
      // 配置空间并填满内容
      iterator allocate_and_fill(size_type n, const T& x) {
        iterator result = data_allocator::allocate(n);
        uninitialized_fill_n(result, n, x);
        return result;
      }
    };
    

    以上摘自侯捷先生的《STL源码剖析》,书上代码与SGI STL官网所下载代码相比,缺少很多C++11之后的内容,代码相对来说也进行了一定的简化。但是主体内容大致相同,比较好理解。
    如果想了解最新的包含C++11之后内容的,可以看机械工业出版社出版的闫常友所写的《大道至简》,不过还是推荐侯捷先生这本《STL源码剖析》去了解STL。

    vector使用及部分分析

    构造函数

    • explicit vector(const allocator_type& alloc = allocator_type());
    • explicit vector(size_type n);
    • explicit vector(size_type n, const value_type& val, const allocator_type& alloc = allocator_type())
    • template <class InputIterator> vector (InputIterator first, InputIterator last, const allocator_type& alloc = allocator_type());
    • vector (const vector& x);
    • vector (const vector& x, const allocator_type& alloc);
    • vector (vector&& x);
    • vector (vector&& x, const allocator_type& alloc);
    • vector (initializer_list<value_type> il, const allocator_type& alloc = allocator_type());

    前面几个都是普通的构造函数,值得一提的是最后一个,利用initializer_list来进行初始化。
    test.cpp
    result

    析构函数

    • ~vector()

    就是析构函数…没啥好说的

    重载函数=

    • vector& operator= (const vector& x);
    • vector& operator= (vector&& x);
    • vector& operator= (initializer_list<value_type> il);

    重载函数=依旧没什么好说的,就是正常的搞就行。值得说的是第二个重载函数,vector&& x 这个参数很迷啊,其实就是引用的引用而已,实现的就是移动构造函数,其实在上面构造函数就应该说的。这个方式可以提高效率,因为不需要创建一个新的vector对象,仅将一个vector的控制权交到了这个vector手上。我们可以从一个例子中证明:
    test.cpp
    其运行结果为:
    result
    从结果可见没有发生新的构造。

    Iterator函数

    vector的迭代器

    vector的迭代器可以由定义可知,其本身是一个T*类型的指针。作为普通的指针,其本身具备的操作有:
    - operator*
    - operator->
    - operator++
    - operator--
    - operator+
    - operator-
    - operator+=
    - operator-=
    具体使用方法,看下面的例子:
    test.cpp
    result

    begin

    • iterator begin() noexcept;
    • const_iterator begin() const noexcept;

    emmm…就是一个很普通的获取start位置的函数,可以参考前面源码。

    end

    • iterator end() noexcept;
    • const_iterator end() const noexcept;

    emmm…这个函数需要注意一下。它返回的是vector之后的一个被称为past_the_end的元素,这个元素是个虚拟的元素,它假装处于finish这个位置的后面一个位置。得到这个结果的主要原因就是C++在设置区间的时候一般都是左闭右开的区间,包含第一个位置而不包含最后一个位置。这个函数返回的iterator同样能够进行前面所说的*-+等运算。

    rbegin

    • reverse_iterator rbegin() noexcept;
    • const_reverse_iterator rbegin() const noexcept;

    这个函数与前面所不同的地方在于其返回的结果是reverse_iterator而不是iterator,它返回vector的指向end()返回元素的前一个元素,其实也就是最后一个元素了。
    另外就是这个reverse_iterator这个东西,本质上是利用了iterator的++和–所编写的一个新的类,具体可以参考SGI STL源码中的stl_iterator.h这个文件中的内容

    一个例子:
    test.cpp
    result

    rend

    • reverse_iterator rend() noexcept;
    • const_reverse_iterator rend() const noexcept;

    这个函数是与end()相对应的一个函数,它返回一个理论上的位置,是个虚拟的元素,在vector的第一个元素之前的元素。我们可以从下面的例子中得出这个结论:
    test.cpp
    其运行结果如下:
    result
    从上面我们可以看出来这个函数所返回的是vector第一个元素之前的一个虚拟元素,然而在具体表现上来看,指向了不在有效区间内的一个元素。
    所以要牢记左闭右开这个C++的常用区间。

    cbegin

    • const_iterator cbegin() const noexcept;

    顾名思义了…就是返回一个const类型的iterator。同样可以从一个简单的示例里验证这个结果:
    test.cpp
    尝试编译这个程序。
    result
    从编译器所报的错误可以得出cbegin()所返回的const_iterator是个常量迭代器(大概是我自己发明的名词吧….(逃)

    cend

    • const_iterator cend() const noexcept;

    与end()类似,返回一个const_iterator

    crbegin

    • const_reverse_iterator crbegin() const noexcept;

    与rbegin()类似,返回一个const_reverse_iterator

    crend

    • const_reverse_iterator crend() const noexcept;

    与rend()类似,返回一个const_reverse_iterator

    Capcity函数

    size

    • size_type size() const noexcept;

    这个函数返回vector的使用的大小,之所以说使用的大小,是因为还有一个实际大小。还记得最开始的vector定义的源码嘛。里面有三个iterator——start, finish, end_of_storage。这个size返回的就是finish - start的大小。
    当然这个函数就正常使用就行,不需要特别的记忆,很自然而然的理解就行了。

    max_size

    • size_type max_size() const noexcept;

    这个函数返回在size_type下所能使用的最大内存,这个函数的概念并不是与size()类似,也不与capacity()类型。直接看源码吧:

    size_type max_size() const {
      return size_type(-1) / sizeof(_Tp);
    }

    size_type在之前的源码中可以看到是typedef size_t size_type,而size_t(-1)的结果是什么呢,我在CentOS7中跑了一下,得出的结果是18446744073709551615。而size_t不同平台的定义又不同。这里又要说size_t的内容了。想了想还是不说了。
    所以这个函数的结果可以简单粗暴的看出来就是直接返回在当前这个平台下所能给_Tp类型的vector分配的最大容量。

    resize

    • void resize (size_type n);
    • void resize (size_type n, const value_type& val);

    先贴下源码:

    void resize(size_type __new_size, const _Tp& __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, _Tp()); }

    可以看出,列出的第一个函数在实现上调用第二个函数,而实现方式是调用下面会说的erase和insert,这两个函数,我们放到后面说。
    总而言之,这个函数的功能就是修改vector元素所占据的size大小,如果有需要才会修改capacity的大小。

    capacity

    • size_type capacity() const noexcept;

    这个函数返回vector当前的容量是多大, 这个函数与size()相对应,其源码为:

    size_type capacity() const {
      return size_type(_M_end_of_storage - begin());
    }

    其返回的结果就是end_of_storage和start的差值。也就是当前vector的真实大小。

    empty

    • bool empty() const noexcept;

    这个函数比较简单,就是判断finish和start位置是否相同。可以用来判断vector是否为空。

    reserve

    • void reserve (size_type n);

    嘛。这里注意一下是reserve不是reverse,前者是存储的意思,后者是逆置的意思。所以这个函数其实就是重新搞个capcity。当n比当前的capacity()要大的时候,就重新分配一段长度为n的内存空间,然后将原本的元素移至新的内存空间,如果要小,就忽略不管。
    我们可以看下源码。

    void reserve(size_type __n) {
      if (capacity() < __n) {
        const size_type __old_size = size();
        iterator __tmp = _M_allocate_and_copy(__n, _M_start, _M_finish); // 分配内存并拷贝
        // 释放旧内存
        destroy(_M_start, _M_finish); 
        _M_deallocate(_M_start, _M_end_of_storage - _M_start); 
        // 更新迭代器
        _M_start = __tmp;
        _M_finish = __tmp + __old_size;
        _M_end_of_storage = _M_start + __n;
      }
    }

    下面给出一个小示例:
    test.cpp
    其运行结果为:
    result

    shrink_to_fit

    • void shrink_to_fit();

    这个函数减少capacity至size的大小。在官方的国际标准里面说:

    The request is non-binding to allow latitude for implementation-specific optimizations.

    也就是说这个函数也就是个不一定有用的函数。当然在我的CentOS7中使用的g++4.8.2版本里面的STL中,任然包含这个函数,而且其可能是一个有用的函数,其源码为:

    template<typename _Tp, typename _Alloc = std::allocator<_Tp> >
    class vector : protected _Vector_base<_Tp, _Alloc> {
    ...
    public:
      _M_shrink_to_fit() {
        if (capacity() == size()) return false;
        return std::__shrink_to_fit_aux<vector>::_S_do_it(*this);
      }
      ...
      void shrink_to_fit() {
        _M_shrink_to_fit();
      }
    ...
    };

    好的发这个源码其实没有任何含义就是看看。

    元素访问函数

    operator[]

    • reference operator[] (size_type n);
    • const_reference operator[] (size_type n) const;

    这是一个重载函数,重载了[]操作符,使得vector能够像数组一样随机访问元素。而因为vector内存分配是连续的, 所以这个函数可以实现O(1)访问。源码如下:

    reference operator[](size_type __n) { 
      return *(begin() + __n); 
    }
    const_reference operator[](size_type __n) const { 
      return *(begin() + __n);
    }

    at

    • reference at (size_type n);
    • const_reference at (size_type n) const;

    这个函数实际上与前面的operator[]函数是一致的。看源码:

    void _M_range_check(size_type __n) const {
      if (__n >= this->size())
        __stl_throw_range_error("vector");
    }
    reference at(size_type __n) { 
      _M_range_check(__n);
      return (*this)[__n];
    }
    const_reference at(size_type __n) const {
      _M_range_check(__n);
      return (*this)[__n];
    }

    可以看到这个函数会比operator[]多一个边界检查,一旦超过边界会抛出一个异常。可以看个例子:
    test.cpp
    其运行结果为:
    result
    其中1>result表示把输出到stdout的内容重定向到result这个文件中,2>err.log表示把stderr重定向到err.log中。可以看到这个结果是具有边界检测的,而且一旦出错就会抛出异常。而operator[]不会,所以推荐使用at()这个函数来进行元素访问。

    front

    • reference front();
    • const_reference front() const;

    返回一个指向vector第一个元素的引用。

    back

    • reference back();
    • const_reference back() const;

    返回一个指向vector最后一个元素的引用。注意,这个函数与end()不同,其指向的就是确确实实的最后一个元素而不是最后一个元素后面一个虚拟的元素。可以从源码中验证这个:

    reference back() { return *(end() - 1); }
    const_reference back() const { return *(end() - 1); }

    data

    • value_type* data() noexcept;
    • const value_type* data() const noexcept;

    这是一个C++11新出来的函数(虽然现在C++17也已经出来了…
    不过很蛋疼…在CentOS7中使用的g++ 4.8.2中的STL给出的源码如下:

    #if _cplusplus >= 201103L
    _Tp* 
    #else
    pointer 
    #endif
    data() _GLIBCXX_NOEXCEPT { return std::__addressof(front()); }
    
    #if _cplusplus >= 201103L
    const _Tp*
    #else
    const_pointer
    #endif
    data() _GLIBCXX_NOEXCEPT { return std::__addressof(front()); }

    所以其实就是返回一个指针,指向vector内部使用存储数据的地址。
    例子:
    test.cpp
    其结果为:
    result
    所以就是这样咯。

    元素修改函数

    assign

    • template <class InputIterator> void assign (InputIterator first, InputIterator last);
    • void assign (size_type n, const value_type& val);
    • void assign (initializer_list<value_type> il);

    这个函数分配新的内存大小给vector,并通过给的参数对vector重新初始化。下面给出一个例子说明一下使用方法。
    test.cpp
    其执行结果为:
    result
    从上面就可以看出来了,如果要重新分配的内存大小比当前要大,那么必然是要重新进行分配的;如果要重新分配的内存大小比当前要小,那么就不会分配,而会erase掉这部分元素。具体可以看下源码(只给出列表第2个的源码):

    template <class _Tp, class _Alloc>
    void vector<_Tp, _Alloc>::_M_fill_assign(size_t __n, const value_type& __val) 
    {
      if (__n > capacity()) {
        vector<_Tp, _Alloc> __tmp(__n, __val, get_allocator());
        __tmp.swap(*this);
      }
      else if (__n > size()) {
        fill(begin(), end(), __val);
        _M_finish = uninitialized_fill_n(_M_finish, __n - size(), __val);
      }
      else
        erase(fill_n(begin(), __n, __val), end());
    }
    void assign(size_type __n, const _Tp& __val) {
      _M_fill_assign(__n, __val);
    }

    从上可以看出来,如果当前n比capacity()大的时候会重新分配,如果当前n比size()大而不比capacity()小的时候就直接fill了,如果当前n比size()还小,那就erase这部分比n大的部分,并fill这n个元素。

    push_back

    • void push_back (const value_type& val);
    • void push_back (value_type&& val);

    这个函数在vector的finish之后添加一个新的元素,并将finish指向这个新的元素。当然,这个函数本身是非常好理解的,也是非常基本的一个函数,不过,重点就在于,在vector还没有push_back的时候finish和end_of_storage的位置关系,以及这个关系所导致的不同操作。看源码:

    template <class _Tp, class _Alloc>
    void 
    vector<_Tp, _Alloc>::_M_insert_aux(iterator __position, const _Tp& __x)
    {
      if (_M_finish != _M_end_of_storage) {
        construct(_M_finish, *(_M_finish - 1));
        ++_M_finish;
        _Tp __x_copy = __x;
        copy_backward(__position, _M_finish - 2, _M_finish - 1);
        *__position = __x_copy;
      }
      else {
        const size_type __old_size = size();
        const size_type __len = __old_size != 0 ? 2 * __old_size : 1;
        iterator __new_start = _M_allocate(__len);
        iterator __new_finish = __new_start;
        __STL_TRY {
          __new_finish = uninitialized_copy(_M_start, __position, __new_start);
          construct(__new_finish, __x);
          ++__new_finish;
          __new_finish = uninitialized_copy(__position, _M_finish, __new_finish);
        }
        __STL_UNWIND((destroy(__new_start,__new_finish), 
                      _M_deallocate(__new_start,__len)));
        destroy(begin(), end());
        _M_deallocate(_M_start, _M_end_of_storage - _M_start);
        _M_start = __new_start;
        _M_finish = __new_finish;
        _M_end_of_storage = __new_start + __len;
      }
    }
    void push_back(const _Tp& __x) {
      if (_M_finish != _M_end_of_storage) {
        construct(_M_finish, __x);
        ++_M_finish;
      }
      else
        _M_insert_aux(end(), __x);
    }

    可以看出,在finish和end_of_storage不等时(因为vector中finish <= end_of_storage,所以此时其实就是finish比end_of_storage小的时候),vector就在finish后面添加了元素,并把finish后移。
    而在finish和end_of_storage相等的时候,首先构建新的内存,然后把原来的数据移入,再把新来的数据插入,把start,finish,end_of_storage更新。其中有一句const size_type __len = __old_size != 0 ? 2 * __old_size : 1;这句告诉我们,在SGI STL中,每一次push_back,一旦原本的空间不够用了,就直接申请一个原本空间2倍的空间。这一点,在不同的STL版本中不同,比如有的开始会申请原本空间的1.5倍,如果不够再申请为2倍之类的。

    pop_back

    • void pop_back();

    这个函数比较简单,就是把最后一个元素给destroy掉,这里说明一下destroy()。
    这个函数的源码如下:

    template<class T>
    inline void destroy(T* pointer) {
      pointer->~T();
    }

    好的看完就知道了,这玩意就是每次一destroy()就调用当前这个元素的析构函数。
    所以本质上来说,pop_back()只会释放掉finish位置的元素,而不会释放掉finish位置的内存。

    insert

    • iterator insert (const_iterator position, const value_type& val);
    • iterator insert (const_iterator position, size_type n, const value_type& val);
    • template <class InputIterator> iterator insert (const_iterator position, InputIterator first, InputIterator last);
    • iterator insert (const_iterator position, value_type&& val);
    • iterator insert (const_iterator position, initializer_list<value_type> il);

    首先就是使用insert()这个函数,无论如何,你需要指定一个要插入vector的位置,而且是iterator类型的,不过依旧可以利用指针来插入。下面给出一个使用示例:
    test.cpp
    结果:
    result

    下面分析下源码,给出第二个函数的源码(第一个函数源码使用_M_insert_aux()实现,这个函数可以参考前面push_back()):

    template <class _Tp, class _Alloc>
    void vector<_Tp, _Alloc>::_M_fill_insert(iterator __position, size_type __n, const _Tp& __x)
    {
      if (__n != 0) {
        if (size_type(_M_end_of_storage - _M_finish) >= __n) {
          _Tp __x_copy = __x;
          const size_type __elems_after = _M_finish - __position;
          iterator __old_finish = _M_finish;
          if (__elems_after > __n) {
            uninitialized_copy(_M_finish - __n, _M_finish, _M_finish);
            _M_finish += __n;
            copy_backward(__position, __old_finish - __n, __old_finish);
            fill(__position, __position + __n, __x_copy);
          }
          else {
            uninitialized_fill_n(_M_finish, __n - __elems_after, __x_copy);
            _M_finish += __n - __elems_after;
            uninitialized_copy(__position, __old_finish, _M_finish);
            _M_finish += __elems_after;
            fill(__position, __old_finish, __x_copy);
          }
        }
        else {
          const size_type __old_size = size();        
          const size_type __len = __old_size + max(__old_size, __n);
          iterator __new_start = _M_allocate(__len);
          iterator __new_finish = __new_start;
          __STL_TRY {
            __new_finish = uninitialized_copy(_M_start, __position, __new_start);
            __new_finish = uninitialized_fill_n(__new_finish, __n, __x);
            __new_finish
              = uninitialized_copy(__position, _M_finish, __new_finish);
          }
          __STL_UNWIND((destroy(__new_start,__new_finish), 
                        _M_deallocate(__new_start,__len)));
          destroy(_M_start, _M_finish);
          _M_deallocate(_M_start, _M_end_of_storage - _M_start);
          _M_start = __new_start;
          _M_finish = __new_finish;
          _M_end_of_storage = __new_start + __len;
        }
      }
    }
    void insert (iterator __pos, size_type __n, const _Tp& __x) {
      _M_fill_insert(__pos, __n, __x);
    }

    首先就是如果n不等于0才会进行操作,等于0就直接过了,因为没有意义。
    然后如果当前的capacity()-size()也就是备用的部分空间够用的情况下,再如果position + n > finish的话,就把finish - nfinish这段元素,移到以finish之后一个元素开始的区间内,再把positionfinish - n内的区间给移到以原来的finish为结尾的区间内,再填充positionposition + n这段区间;而如果position + n < finish的话,就首先把finishn - elems_after这段给填充为x,之后再把positionfinish这段元素移到新的finish之后,再把这段填充。
    而如果capacity()-size()也就是备份区间不够用的情况下,很自然而然的就是要重新分配空间了。这个时候新申请的空间大小就是size() * 2或者size() + n,注意是size()不是capacity()。然后再新申请的空间内,首先把旧的vector元素复制到新的空间,当然首先只复制startposition大小的元素,然后再position后插入n个元素,最后再把原空间内positionfinish的元素复制到position + nnew_finish的空间内。
    最后更新start, finish, end_of_storage就行了。

    erase

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

    这个函数就是删除一个position的元素,或者删除一段从firstlast区间内的元素。下面给出源码:

    iterator erase(iterator __position) {
      if (__position + 1 != end())
        copy(__position + 1, _M_finish, __position);
      --_M_finish;
      destroy(_M_finish);
      return __position;
    }
    iterator erase(iterator __first, iterator __last) {
      iterator __i = copy(__last, _M_finish, __first);
      destroy(__i, _M_finish);
      _M_finish = _M_finish - (__last - __first);
      return __first;
    }

    比较好懂,就不说了,总之就是这个函数使用的时候需要稍微注意下。

    swap

    • void swap (vector& x);

    这个函数实现了当前vector与x进行交换。可以从一个例子验证其用法:
    test.cpp
    其结果如下:
    result
    不过这个函数值得关注的地方并非此处,我们可以查看一下SGI STL给出的源码,源码如下:

    template <class _Tp, class _Alloc = __STL_DEFAULT_ALLOCATOR(_Tp) >
    class vector : protected _Vector_base<_Tp, _Alloc> 
    {
    ...
    public:
        void swap(vector<_Tp, _Alloc>& __x) {
            __STD::swap(_M_start, __x._M_start);
            __STD::swap(_M_finish, __x._M_finish);
            __STD::swap(_M_end_of_storage, __x._M_end_of_storage);
        }
    ...
    }

    源码中的__STD就是std,调用std::swap交换头和尾,而不是交换每个元素。这点在SGI STL和其它版本的STL中差别比较大。比如我在CentOS7中使用的g++ 4.8.5版本中的swap:

    void _M_swap_data(_Vector_impl& __x)
    {
      std::swap(_M_start, __x._M_start);
      std::swap(_M_finish, __x._M_finish);
      std::swap(_M_end_of_storage, __x._M_end_of_storage);
    }
    void
    swap(vector& __x)
    #if __cplusplus >= 201103L
                    noexcept(_Alloc_traits::_S_nothrow)
    #endif
    {
      this->_M_impl._M_swap_data(__x._M_impl);
      _Alloc_traits::_S_on_swap(_M_get_Tp_allocator(), __x._M_get_Tp_allocator());
    }

    而这段代码的官方注释为

    This exchanges the elements between two vectors in constant time.(Three pointers, so it should be quite fast)

    从这个注释中可以得知,虽然那啥_S_on_swap不知道是干什么用的,但是可以确定的是,在stl中,swap仅交换了start, finish, end_of_storage这三个指针便交换了整个vector。当然为了验证这个,我也做了个大概也许能说明的实验:
    test1.cpp
    其结果为:
    result1
    如果是空间交换了的话,无论如何不会消耗时间不到1ms。

    clear

    • void clear() noexcept;

    这个函数比较简单粗暴,就是直接erase掉从begin()end()的所有元素。

    emplace

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

    这个函数是C++11新增的,与insert比较类似,向指定的一个位置插入一些元素。不过很迷的是,这个函数参数是可变长参数但是我尝试过emplace(vec.begin(), 1, 2, 3, 4, 5)结果并不能使用。
    而这个函数特点就是据说相比于insert()对较复杂的类来说效率比较高,因为&&的原因。
    看下源码:

    template<typename _Tp, typename _Alloc>
    template<typename... _Args>
    typename vector<_Tp, _Alloc>::iterator
    vector<_Tp, _Alloc>::emplace(iterator __position, _Args&&... __args) {
      const size_type __n = __position - begin();
      if (this->_M_impl._M_finish != this->_M_impl._M_end_of_storage && __position == end()) {
        _Alloc_traits::construct(this->_M_impl, this->_M_impl._M_finish, std::forward<_Args>(__args)...);
        ++this->_M_impl._M_finish;
      } else _M_insert_aux(__position, std::forward<_Args>(__args)...);
      return iterator(this->_M_impl._M_start + __n);
    }

    从源码中,里面的_M_insert_aux()我们并不会陌生,事实上这个函数在push_back()的源码中我们见过,只不过在这里,我们把插入的位置从end()变成了position。而在这里,我们看到一个新的东西std::forward

    emplace_back

    • template <class... Args> void emplace_back (Args&&... args);

    这个函数就跟上面那个函数一样,不过是与push_back函数类似。
    这个函数反正就正经的像push_back一样用就行,似乎官方也是比较推荐用这种方式了,理由跟上面一样,都是因为&&,对于比较复杂的类来说可以有很大的优化。唔源码就不看了。与前面都比较类似。

    内存分配函数

    get_allocator

    • allocator_type get_allocator() const noexcept;

    emmm..这个函数看名字就知道了….get嘛…就是get的意思…使用方式…就是vector - C++ Reference这样的方式….

    重载的关系运算符函数及swap

    • template <class T, class Alloc> bool operator== (const vector<T,Alloc>& lhs, const vector<T,Alloc>& rhs);

    这个函数就是判断两个vector是否相等。

    • template <class T, class Alloc> bool operator!= (const vector<T,Alloc>& lhs, const vector<T,Alloc>& rhs);

    这个函数就是判断两个vector是否不等。

    • template <class T, class Alloc> bool operator< (const vector<T,Alloc>& lhs, const vector<T,Alloc>& rhs);

    这个函数就是判断两个vector的大小。比较是按照字典序进行比较的。

    • template <class T, class Alloc> bool operator<= (const vector<T,Alloc>& lhs, const vector<T,Alloc>& rhs);

    这个函数就是判断两个vector的大小。比较是按照字典序进行比较的。

    • template <class T, class Alloc> bool operator> (const vector<T,Alloc>& lhs, const vector<T,Alloc>& rhs);

    这个函数就是判断两个vector的大小。比较是按照字典序进行比较的。

    • template <class T, class Alloc> bool operator>= (const vector<T,Alloc>& lhs, const vector<T,Alloc>& rhs);

    这个函数就是判断两个vector的大小。比较是按照字典序进行比较的。

    • template <class T, class Alloc> void swap (vector<T,Alloc>& x, vector<T,Alloc>& y);

    具体实现就是调用了上面的类中的成员函数swap。

    总结

    emmm….总算是各种各样油完了这篇博客….下面简单总结下。
    vector是个很好用的序列容器,其使用方式与数组类似,可以动态的管理内存空间,主要的函数有push_back, pop_back, insert, erase, resize()等,还有很多的重载函数,使得其可以像数组一样使用。
    vector在遇到内存不够用的时候,会重新申请一段内存空间,所以初始的时候可以把容量设置较大,以减少在时间上的损失。
    在其它方面,暂时没有看出来vector对于多线程是否是安全的。
    总而言之啊,还是要多学习一个。

    其它

    版权说明

    • 本文参考了大量的SGI STL源码,vector - C++ Reference,以及STL源码剖析。如果有侵犯任何人的版权的可以私信我或者email我。
    • 本文允许任何形式的转载或摘抄,但请先私信通知我下,并注明转载源地址或引用
    • 如果存在未先通知我而进行了转载或摘抄,一切后果,都没有。(逃

    更新日志

    1. emmm…写了一个小时…才更新了这么多…明天继续写…先提前发出来吧…
    2. emmm…今天下班回到家大概九点半开始写的,写到了11点…居然还没写完….很神奇…我一开始以为这篇博客可以一两个小时结束的….
    3. emmm…今天下班比较早…写了两个半小时…大概要写完了…应该明天就能结束了吧(希望不是个flag…
    4. emmmm…果然立了个flag…今天还是写了两个半小时…不过可能稍微效率比较低…明天肯定会完结了….这玩意写了一个星期我也是很迷啊…
    5. emmmm….今天把这篇博客写完了…貌似写了一周了….我好菜啊…以后发现什么问题再更新吧。就酱。
  • 相关阅读:
    Git工具提交github.com
    小程序-setData方法使用--改变默认数据data初始值方法
    小程序-全局变量
    小程序--获取openid
    小程序--错误{"errcode":40125,"errmsg":"invalid appsecret, view more at http://t.cn/RAEkdVq, hints: [ req_id: lE8J30972ns32 ]"}
    小程序-获取openid
    微信小程序--简易table表格
    微信小程序--简单页面跳转
    小程序禁止下拉更新
    小程序--登录获取code
  • 原文地址:https://www.cnblogs.com/wiklvrain/p/8179318.html
Copyright © 2011-2022 走看看