STL 源码剖析序列式容器之vector(四)

来源:互联网 发布:域名中国商标 编辑:程序博客网 时间:2024/05/18 02:50

申明:本文主要参考书籍<STL 源码剖析>

1 vector概述

stl vector是序列容器模板类,其支持指定类型的数据存储和随机访问;

对于vector而言,如果在其尾部插入或删除项,其时间复杂度为常量;如果在中间或者头部插入或者删除项,则其时间复杂度为线性的,因为为了保持原本的相对次序,在插入或者删除项之后的所有项都必须进行移动;简单的说:vector类似于动态数组的概念,该动态数据能容纳任何类型的对象,并且可以按需动态伸缩空间;

总结:由于vector维护的是一个连续线性空间,所以vector支持随机存取
      注意:vector动态增加大小时,并不是在原空间之后持续新空间(因为无法保证原空间之后尚有可供配置的空间),而是以原大小的两倍另外配置一块较大的空间,然后将原内容拷贝过来,然后才开始在原内容之后构造新元素,并释放原空间。因此,对vector的任何操作,一旦引起空间重新配置,指向原vector的所有迭代器就都失效了

常用的vector构造函数

  • 构造一个空元素的vector对象

vector<Element>v;

  • 构造一个容纳10个元素的vector对象(并不表示其最多只能容纳10个元素)

vector<Element>v(10);

  • 构造一个容纳10个元素的vector对象并初始化各个元素项

vector<Element>v(10,Element());

  • 构造一个原有的vector对象的拷贝
    vector<Element>v(v1);

添加元素使用push_back函数将元素添加至vector的末尾等其他方式

获得vector的元素个数使用size函数可以获得vector的元素个数,如果判断vector 的元素个数是否为0,建议使用empty函数


2 vector 注意点:

给定一个

vector<int> v;

表达式v[0]生产一个指向vector中第一个元素的引用,所以,&v[0]是指向那个首元素的指针。vector中的元素被C++标准限定为存储在连续内存中,就像是一个数组,所以,如果我们想要传递v给这样的C风格的API:

void doSomething(const int* pInts, size_t numInts);

我们可以这么做:

doSomething(&v[0], v.size());

也许吧。可能吧。唯一的问题就是,如果v是空的。如果这样的话,v.size()是0,而&v[0]试图产生一个指向根本就不存在的东西的指针。这不是件好事。其结果未定义。一个较安全的方法是这样:

if (!v.empty()) {doSomething(&v[0], v.size());}

如果你在一个不好的环境中,你可能会碰到一些半吊子的人物,他们会告诉你说可以用v.begin()代替&v[0],因为(这些讨厌的家伙将会告诉你)begin返回指向vector内部的迭代器,而对于vector,其迭代器实际上是指针。

那经常是正确的,并不总是如此,你不该依赖于此。

begin的返回类型是iterator,而不是一个指针,当你需要一个指向vector内部数据的指针时绝不该使用begin。如果你基于某些原因决定键入v.begin(),就应该键入&*v.begin(),因为这将会产生和&v[0]相同的指针,这样可以让你有更多的打字机会,而且让其他要弄懂你代码得人感觉到更晦涩。坦白地说,如果你正在和告诉你使用v.begin()代替&v[0]的人打交道的话,你该重新考虑一下你的社交圈了。


3 vector 的定义(源代码

#include<iostream>using namespace std;#include<memory.h>  // alloc是SGI STL的空间配置器template <class T, class Alloc = alloc>class vector{public:// vector的嵌套类型定义,typedefs用于提供iterator_traits<I>支持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:// 这个提供STL标准的allocator接口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(){// 由于使用的是data_allocator进行内存空间的分配,// 所以需要同样使用data_allocator::deallocate()进行释放// 如果直接释放, 对于data_allocator内部使用内存池的版本// 就会发生错误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 max_size() const { return size_type(-1) / sizeof(T); }// 返回重新分配内存前最多能存储的对象个数size_type capacity() const { return size_type(end_of_storage - begin()); }bool empty() const { return begin() == end(); }reference operator[](size_type n) { return *(begin() + n); }// 本实作中默认构造出的vector不分配内存空间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(const vector<T, Alloc>& x){start = allocate_and_copy(x.end() - x.begin(), x.begin(), x.end());finish = start + (x.end() - x.begin());end_of_storage = finish;}~vector(){// 析构对象destroy(start, finish);// 释放内存deallocate();}vector<T, Alloc>& operator=(const vector<T, Alloc>& x);// 提供访问函数reference front() { return *begin(); }reference back() { return *(end() - 1); }////////////////////////////////////////////////////////////////////////////////// 向容器尾追加一个元素, 可能导致内存重新分配//////////////////////////////////////////////////////////////////////////////////                          push_back(const T& x)//                                   |//                                   |---------------- 容量已满?//                                   |//               ----------------------------//           No  |                          |  Yes//               |                          |//               ↓                          ↓//      construct(finish, x);       insert_aux(end(), x);//      ++finish;                           |//                                          |------ 内存不足, 重新分配//                                          |       大小为原来的2倍//      new_finish = data_allocator::allocate(len);       <stl_alloc.h>//      uninitialized_copy(start, position, new_start);   <stl_uninitialized.h>//      construct(new_finish, x);                         <stl_construct.h>//      ++new_finish;//      uninitialized_copy(position, finish, new_finish); <stl_uninitialized.h>////////////////////////////////////////////////////////////////////////////////void push_back(const T& x){// 内存满足条件则直接追加元素, 否则需要重新分配内存空间if (finish != end_of_storage){construct(finish, x);++finish;}elseinsert_aux(end(), x);}////////////////////////////////////////////////////////////////////////////////// 在指定位置插入元素//////////////////////////////////////////////////////////////////////////////////                   insert(iterator position, const T& x)//                                   |//                                   |------------ 容量是否足够 && 是否是end()?//                                   |//               -------------------------------------------//            No |                                         | Yes//               |                                         |//               ↓                                         ↓//    insert_aux(position, x);                  construct(finish, x);//               |                              ++finish;//               |-------- 容量是否够用?//               |//        --------------------------------------------------//    Yes |                                                | No//        |                                                |//        ↓                                                |// construct(finish, *(finish - 1));                       |// ++finish;                                               |// T x_copy = x;                                           |// copy_backward(position, finish - 2, finish - 1);        |// *position = x_copy;                                     |//                                                         ↓// data_allocator::allocate(len);                       <stl_alloc.h>// uninitialized_copy(start, position, new_start);      <stl_uninitialized.h>// construct(new_finish, x);                            <stl_construct.h>// ++new_finish;// uninitialized_copy(position, finish, new_finish);    <stl_uninitialized.h>// destroy(begin(), end());                             <stl_construct.h>// deallocate();////////////////////////////////////////////////////////////////////////////////iterator insert(iterator position, const T& x){size_type n = position - begin();if (finish != end_of_storage && position == end()){construct(finish, x);++finish;}elseinsert_aux(position, x);return begin() + n;}iterator insert(iterator position) { return insert(position, T()); }void pop_back(){--finish;destroy(finish);}iterator erase(iterator position){if (position + 1 != end())copy(position + 1, finish, position);--finish;destroy(finish);return position;}iterator erase(iterator first, iterator last){iterator i = copy(last, finish, first);// 析构掉需要析构的元素destroy(i, finish);finish = finish - (last - first);return first;}// 调整size, 但是并不会重新分配内存空间void resize(size_type new_size, const T& x){if (new_size < size())erase(begin() + new_size, end());elseinsert(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;}// 提供插入操作//////////////////////////////////////////////////////////////////////////////////                 insert_aux(iterator position, const T& x)//                                   |//                                   |---------------- 容量是否足够?//                                   ↓//              -----------------------------------------//        Yes   |                                       | No//              |                                       |//              ↓                                       |// 从opsition开始, 整体向后移动一个位置                     |// construct(finish, *(finish - 1));                    |// ++finish;                                            |// T x_copy = x;                                        |// copy_backward(position, finish - 2, finish - 1);     |// *position = x_copy;                                  |//                                                      ↓//                            data_allocator::allocate(len);//                            uninitialized_copy(start, position, new_start);//                            construct(new_finish, x);//                            ++new_finish;//                            uninitialized_copy(position, finish, new_finish);//                            destroy(begin(), end());//                            deallocate();////////////////////////////////////////////////////////////////////////////////template <class T, class Alloc>void insert_aux(iterator position, const T& x){if (finish != end_of_storage)    // 还有备用空间{// 在备用空间起始处构造一个元素,并以vector最后一个元素值为其初值construct(finish, *(finish - 1));++finish;T x_copy = x;copy_backward(position, finish - 2, finish - 1);*position = x_copy;}else   // 已无备用空间{const size_type old_size = size();const size_type len = old_size != 0 ? 2 * old_size : 1;// 以上配置元素:如果大小为0,则配置1(个元素大小)// 如果大小不为0,则配置原来大小的两倍// 前半段用来放置原数据,后半段准备用来放置新数据iterator new_start = data_allocator::allocate(len);  // 实际配置iterator new_finish = new_start;// 将内存重新配置try{// 将原vector的安插点以前的内容拷贝到新vectornew_finish = uninitialized_copy(start, position, new_start);// 为新元素设定初值 xconstruct(new_finish, x);// 调整水位++new_finish;// 将安插点以后的原内容也拷贝过来new_finish = uninitialized_copy(position, finish, new_finish);}catch(...){// 回滚操作destroy(new_start, new_finish);data_allocator::deallocate(new_start, len);throw;}// 析构并释放原vectordestroy(begin(), end());deallocate();// 调整迭代器,指向新vectorstart = new_start;finish = new_finish;end_of_storage = new_start + len;}}////////////////////////////////////////////////////////////////////////////////// 在指定位置插入n个元素//////////////////////////////////////////////////////////////////////////////////             insert(iterator position, size_type n, const T& x)//                                   |//                                   |---------------- 插入元素个数是否为0?//                                   ↓//              -----------------------------------------//        No    |                                       | Yes//              |                                       |//              |                                       ↓//              |                                    return;//              |----------- 内存是否足够?//              |//      -------------------------------------------------//  Yes |                                               | No//      |                                               |//      |------ (finish - position) > n?                |//      |       分别调整指针                              |//      ↓                                               |//    ----------------------------                      |// No |                          | Yes                  |//    |                          |                      |//    ↓                          ↓                      |// 插入操作, 调整指针           插入操作, 调整指针           |//                                                      ↓//            data_allocator::allocate(len);//            new_finish = uninitialized_copy(start, position, new_start);//            new_finish = uninitialized_fill_n(new_finish, n, x);//            new_finish = uninitialized_copy(position, finish, new_finish);//            destroy(start, finish);//            deallocate();////////////////////////////////////////////////////////////////////////////////template <class T, class Alloc>void insert(iterator position, size_type n, const T& x){// 如果n为0则不进行任何操作if (n != 0){if (size_type(end_of_storage - finish) >= n){      // 剩下的备用空间大于等于“新增元素的个数”T x_copy = x;// 以下计算插入点之后的现有元素个数const size_type elems_after = finish - position;iterator old_finish = finish;if (elems_after > n){// 插入点之后的现有元素个数 大于 新增元素个数uninitialized_copy(finish - n, finish, finish);finish += n;    // 将vector 尾端标记后移copy_backward(position, old_finish - n, old_finish);fill(position, position + n, x_copy); // 从插入点开始填入新值}else{// 插入点之后的现有元素个数 小于等于 新增元素个数uninitialized_fill_n(finish, n - elems_after, x_copy);finish += n - elems_after;uninitialized_copy(position, old_finish, finish);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);// 以下配置新的vector空间iterator new_start = data_allocator::allocate(len);iterator new_finish = new_start;__STL_TRY{// 以下首先将旧的vector的插入点之前的元素复制到新空间new_finish = uninitialized_copy(start, position, new_start);// 以下再将新增元素(初值皆为n)填入新空间new_finish = uninitialized_fill_n(new_finish, n, x);// 以下再将旧vector的插入点之后的元素复制到新空间new_finish = uninitialized_copy(position, finish, new_finish);}#         ifdef  __STL_USE_EXCEPTIONScatch(...){destroy(new_start, new_finish);data_allocator::deallocate(new_start, len);throw;}#         endif /* __STL_USE_EXCEPTIONS */destroy(start, finish);deallocate();start = new_start;finish = new_finish;end_of_storage = new_start + len;}}}};


4 前面也说了很多访问容器vector的方法(那么也不得不说说迭代器)

标准库还提供了另一种检测元素的方法:使用迭代器(iterator)。迭代器是一种允许程序员检查容器内元素,并实现元素遍历的数据类型

标准库为每一种标准容器(包括vector)定义了一种迭代器类型。迭代器类型提供了比下标操作更一般化的方法:所有的标准库容器都定义了相应的迭代器类型,而只有少数的容器支持下标操作。因为迭代器对所有的容器都适用,现代C++程序更倾向于使用迭代器而不是下标操作访问容器元素,即使对支持下标操作的vector类型也这样。

4.1 vector 的迭代器定义如下:

vector<int>::iterator iter;

 这条语句定义了一个名为iter的变量,它的数据类型是由vector<int>定义的iterator类型。每个标准库容器类型都定义了一个名为iterator的成员,这里的iterator与迭代器实际类型的含义相同。

vector的迭代器具体的使用可以参考<STL 源码剖析>

值得特殊说明:迭代器和迭代器类型

首次遇到有关迭代器的术语时可能会困惑不解,产生困惑的原因之一是由于本书中同一个术语iterator表示两个不同的事物。一般性提及的是迭代器的概念;而特别提及的则是由容器定义的具体的iterator类型,如vector<int>。

  重点要理解的是,定义了许多用作迭代器的类型,这些类型在概念上是相关的。若一种类型支持一组确定的行为(这些行为允许程序员遍历容器内的元素,并允许程序员访问这些元素值),我们就称这种类型为迭代器。

  不同的容器类定义了自己的iterator类型,用于访问容器内的元素。换句话说,每个容器定义了一种名为iterator的类型,而这种类型支持(概念上的)迭代器的各种行为


总结:vector 的迭代器当然还是很复杂,自己可以去深究,我也只是简单的抛出概念。

4.2 vector 的数据结构

     vector数据结构,采用的是连续的线性空间,属于线性存储。他采用3个迭代器_First、_Last、_End来指向分配来的线性空间的不同范围,下面是声明3个迭代器变量的源代码

template<class _Ty, class _A= allocator< _Ty> > class vector{     ...     protected:     iterator _First, _Last, _End; };

_First指向使用空间的头部,_Last指向使用空间大小(size)的尾部,_End指向使用空间容量(capacity)的尾部。

借助一个小示例:

int data[6]={3,5,7,9,2,4}; vector<int> vdata(data, data+6); vdata.push_back(6); ...

vector初始化时,申请的空间大小为6,存放下了data中的6个元素。当向vdata中插入第7个元素“6”时,vector利用自己的扩充机制重新申请空间,数据存放

结构如下图所示:

当插入第7个元素“6”时,vector发现自己的空间不够了,于是申请新的大小为12的内存空间(自增一倍),并将前面已有数据复制到新空间的前部,然后插入第7个元素。此时_Last迭代器指向最后一个有效元素,而_End迭代器指向vector的最后有效空间位置。我们利用vector的成员函数size可以获得当前vector的大小,此时为7;利用capacity成员函数获取当前vector的容量,此时为12。



5 vector对象的操作

操作调用方式

操作说明

v.empty()

判断v是否为空

v.size()

返回v中元素的个数

v.push_back(t)

v的末尾添加一个元素

V[n]

返回v中位置为n的元素

V1 = v2

v1中元素替换为v2中元素副本

V1==v2

判断是否相等

!=, <, <=, >, >=

直接用于vector对象的相互比较


1.push_back   在数组的最后添加一个数据
2.pop_back    去掉数组的最后一个数据 
3.at                得到编号位置的数据
4.begin           得到数组头的指针
5.end             得到数组的最后一个单元+1的指针
6.front        得到数组头的引用
7.back            得到数组的最后一个单元的引用
8.max_size     得到vector最大可以是多大
9.capacity       当前vector分配的大小
10.size           当前使用数据的大小
11.resize         改变当前使用数据的大小,如果它比当前使用的大,者填充默认值
12.reserve      改变当前vecotr所分配空间的大小
13.erase         删除指针指向的数据项
14.clear          清空当前的vector
15.rbegin        将vector反转后的开始指针返回(其实就是原来的end-1)
16.rend          将vector反转构的结束指针返回(其实就是原来的begin-1)
17.empty        判断vector是否为空
18.swap         与另一个vector交换数据


注,以下是一些需要注意的地方

Ø vectorstring一样,长度、下标等类型是size_type,但是vector获取size_type时,需要指定类型,如vector<int>::size_type这样的方式

Ø vector的下标操作,例如v[i],只能用于操作已经存在的元素,可以进行覆盖、获取等,但是不能通过v[i++]这种方式来给一个vector容器添加元素,该功能需要用push_back操作完成,下标不具备该功能

Ø C++程序员习惯优先使用!=而不是<来编写循环判断条件





11 0
原创粉丝点击