顺序容器性能比较

本博客内容来源与C++primer以及STL源码解析。学习顺序容器最重要的是了解每种容器的结构原理进而在实际应用中选择最合适的容器。

顺序容器类型介绍

vector/string:可变大小,可添加和删除元素,在尾部以外的位置添加和删除元素的很慢,随机访问很快(下标),but string专门存放字符。

vector因为存储在堆上,所以支持erase(), resieze()(重新划分容器容量)等操作;vector不用担心越界当空间不够用的时候,系统会自动按照一定的比例(对capacity()大小)进行扩充。在vector序列末尾添加(push_back())或者删除(pop_back())对象效率高,在中间进行插入或删除效率很低,主要是要进行元素的移动和内存的拷贝,原因就在于当内存不够用的时候要执行重新分配内存,拷贝对象到新存储区,销毁old对象,释放内存等操作,如果对象很多的话,这种操作代价是相当高的。为了减少这种代价,使用vector最理想的情况就是事先知道所要装入的对象数目,用成员函式 reserve( ) 预定下来;vector最大的优点莫过于是检索(用operator[])速度在这三个容器中是最快的。

array:元素保存在连续的内存中,固定大小,快速随机访问,but不能添加和删除元素 && 不能更改容器大小。

list/forward_list:双向链表/ 单向链表,forward_list没有size操作。内存空间不连续,通过指针进行操作。解决vector和string不能再任意位置添加和删除元素的弊端,but不支持随即快速访问(遍历整个容器),额外内存开销也很大(相比vector/string)。

deque:双端队列,快速随机访问,在两端添加或删除元素很快,but 在其他位置的insert和delete操作将会很慢。queue是由多个连续内存块构成,deque是list和vector的兼容,分为多个块,每一个块大小是512字节,块通过map块管理,map块里保存每个块得首地址。因此该容器也有索引操作operator[ ],效率没vector高。另外,deque比vector多了push_front( ) & pop_front( )操作。在两端进行此操作时与list的效率 差不多。

根据上面简单的介绍,我们知道了这几种典型的容器的优缺点,在使用过程中可根据这个优缺点选择容器,达到快速处理数据的目的。

说实话,一般都会选择使用vector,但是如果你真的真的不知道使用哪种容器时,你可以在使用过程中选择是否使用下标访问。比如,在vector和list之间选择时,可以在使用过程中采用他们的公共操作:迭代器(iterator),不使用下标访问,避免随机访问。
下面是选择顺序容器类型的一些准则
1. 如果我们需要随机访问一个容器则vector要比list好得多
2. 如果我们已知要存储元素的个数则vector 又是一个比list好的选择。
3. 如果我们需要的不只是在容器两端插入和删除元素则list显然要比vector好
4. 除非我们需要在容器首部插入和删除元素否则vector要比deque好
5. 如果只在容易的首部和尾部插入数据元素,则选择deque
6. 如果只需要在读取输入时在容器的中间位置插入元素,然后需要随机访问元素,则可考虑输入时将元素读入到一个List容器,接着对此容器重新拍学,使其适合顺序访问,然后将排序后的list容器复制到一个vector容器中

所有容器的操作(顺序、关联、无序)

1.类型别名

iterator  此容器类型的迭代器类型
const_iterator 可以读取元素,但不能修改元素的迭代器类型,类似于const的用法
size_type 无符号整数类型,保存此容器的最大可能大小
difference_type 带符号整数类型,保存两个iterator之间的距离
value_type  元素类型
reference 元素的左值类型,与value_type&含义相同
const_reference const value_type&

2.构造函数

C c;            默认构造函数,构造空容器
C c1(c2);       构造c2的拷贝c1
C c(b,e);       构造c,将迭代器b和e指定的范围内的元素拷贝到c(array不支持)
C c{a,b,c...};  列表初始化c

再仔细看一下,这些构造函数的实现代码,其实看懂并不难。这里只给出容器vector的构造函数。这里值得注意的是,顺序容器的构造函数是支持大小参数的,关联容器并不支持。

// alloc是SGI STL的空间配置器  
template <class T,class Alloc = alloc>
class vector
{
protectedvoid fill_initialize(size_type n, const T& value)
{
    start = alloct_and_fill(n,value);
    finish = start + n;
    end_of_storage = finish;
}
public:
// 本实作中默认构造出的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);}
 // 需要对象提供默认构造函数
explict vector(size_type n){fill_initialize(n,T());}
}
vector(const vector& x)  
{  
    start = allocate_and_copy(x.end() - x.begin(), x.begin(), x.end());  
    finish = start + (x.end() - x.begin());  
    end_of_storage = finish;  
} 

3.赋值与swap

c1=c2  将c1中的元素替换为c2中元素
c1={a,b,c...}  将c1中的元素替换为列表中元素(不适用于array)
a.swap(b)  交换a和b的元素 
swap(a,b)  与a.swap(b)等价
assign操作不适用关联容器和array
seq.assign(b,e) 将seq中的元素替换为迭代器b和e所表示的范围内的元素,迭代器b和e不能只想seq中的元素
seq.assign(i1) 将seq中的元素替换为初始化列表i1的元素
seq.assign(n,t) 将seq中的元素替换为n个值为t的元素

值得一提的是:复制相关运算会导致指向左边容器中内部的迭代器、引用和指针失效。而swap操作将容器内容替换不会导致指向容器的迭代器、引用和指针失效(array和string类型的容器除外)

4.大小

c.size() c中元素数目(不支持forward_list)
c.max_size()  c中可保存的最大元素数目
c.empty()  如c中存储了元素,返回false,否则返回true
c.resize(n) 改变容器大小为n(array不能使用哦)
//我们说说管理容器大小的操作
//shrink_to_fit只适用于vector、string和deque
//capacity、reserve只适用于vector和string
c.shrink_to_fit()   请将capacity()减少为与size()相同大小,
c.capacity()        不重新分配内存空间的话,c可以保存多少元素
c.reserve(n)        分配至少能容纳n个元素的内存空间,并不改变容器中元素的数量,只影响vector预先分配多大的内存空间

除了forward_list 外,其他容器均支持上述三个大小相关的操作,forward_list 只支持max_size和empty。
如果resize缩小容器,则指向容器删除元素的指针、引用和iterator将会失效;对vector、string或deque使用resize,可能导致指针、引用和iterator失效。
请注意区分capacity和size哦!!!

5.添加/删除(不适用array)
在做插入删除时,不同容器的操作接口都不同,这些操作会改变容器的大小。

c.insert(args)  将args中的元素拷贝进c
c.push_back(e)/c.empleace_back(args) 在c的尾部创建一个值为e或者由args创建的元素。返回void
c.emplace(inits) 使用inits构造c中的一个元素
c.erase(args) 删除args指定的元素。forward_list 有特殊版本的erase。
c.pop_back() 删除c中尾元素,若c为空,函数行为未定义。forward_list不支持。
c.pop_front() 删除c中首元素,若c为空,函数行为未定义,vector和string不支持。
c.clear()  删除c中的所有元素,返回void

关系运算符
==,!=   所有容器都支持这两个操作
<,<=,>,>= 无序关联容器不支持,只适用于 vector 和 deque容器

在执行操作删除时,有可能会重新分配内存,这里提供一个连接,讲解vector源码,有注释,http://blog.csdn.net/hackbuteer1/article/details/7724547
关系运算符左右两边的运算对象必须是相同类型的容器,并且必须保存相同类型的元素。比较两个容器的大小,实际上是对元素的逐对比较,例如

vector<int> v1 = {1,2,3,4,5,6};
vector<int> v2 = {1,2,9};
v1//因为在元素[2]处v2[2]>v1[2]

这里我们说说插入删除的特殊性:
向vector、string、deque中插入元素会使所有指向容器的迭代器、引用和指针失效。
删除deque中首尾位置之外的任何元素都会使所有iterator、引用和指针失效。指向vector或string中删除点之后位置的iterator、引用和指针都会失效。
还有一点哦,成员函数执行删除操作时病没有检查参数,在删除之前我们必须确保他们存在哟。

6.获取迭代器

c.begin(),c.end()  返回指向c的首元素和尾元素的迭代器
c.cbegin(),c.cend() 返回const_iterator

获取迭代器之后,那么问题来了,若要访问容器中的元素该怎么进行呢?下面提供基础的访问元素的操作。

c.back()    返回c中尾元素的引用,想想如果c为空,返回的是什么?
c.front()   返回c中首元素的引用,同理思考c为空的情况。
c[n]        返回c中下标为n的元素的引用,考虑n的类型及范围,若n>=c.size()?
c.at[n]     返回c中下标为n的元素的引用,考虑n的类型及范围,若n越界,则抛出异常out_of_range。

说了这么多,发现几乎每一个操作都有它的特殊性,这里at和下标操作只适用于string、vector、array和deque。接下来想想c为空时,back和front操作的返回值,这个会出错的哦!对空c使用back和front就像下标越界一样,出现严重的错误,在使用中一定一定要留意。

vector<int> vec;
cout<0];   //运行错误
cout<0];//异常out_of_range

7.反向容器的额外成员(不适用forward_list)

reverse_iterator  按逆序寻址元素的迭代器
const_reverse_iterator  不能修改元素的逆序迭代器
c.rbegin(),c.rend() 返回指向c的尾元素和首元素的迭代器
c.crbegin(),c.crend() 返回const_reverse_iterator  

总之,一切改变容器大小的操作都不适用array,使用失效的iterator、引用、指针会产生严重的运行时错误。
避免失效的安全方法就是使用时,不要缓存end返回的迭代器。

end = c.end();
while(c.bengin()!=end)将end用c.end()代替哦,这样比较安全。

今天是里约奥运会开幕式,听着奥运会的开幕式,把今天自己看到的东西记录下来。但是呢,感觉自己还是不是写东西的料,写的很是乱七八糟,就权当是给自己做了个在线笔记吧,以后翻找的话也好查找,类似于技术博客的东西是不是不需要很多文字功底呢,就碎碎念吧,看到那记到那,奖励给以后的自己,现在发现很多东西,要有笔记,过后说不定就忘了呢。

你可能感兴趣的:(STL)