vector<T> 1.以类似于普通数组的方式存储类型为 T 的对象,只是它的容量可以根据需要存储的对象个数进行调整。 2.使用下标运算符[]可以像数组那样访问vector容器中的元素。 3.vector容器的大小是其中元素的个数--调用size()来获得容器的大小; vector容器的容量是当前它可以包含的最大元素个数--调用capacity()来获得容器的大小。 (在创建特定容量的vector对象时,其大小和容量是相同的) 如:std::vector<int> a(10) (如果给vector添加一个或多个元素,其大小就会以某个比例增长,以容纳新元素,所以其容量就会大于其大小,一定不会小于其大小) 4.只添加或删除序列尾的元素时,vector是最高效的。如果增大容量,vector会自动增长。 5.可以在vector的开头和中间插入和删除元素,但其效率相当低。 (因为需要移动插入或删除点后面的所有元素,插入时还得在自由存储区中分配一个新内存空间) deque<T> 1.deque是double-ended queue的缩写,这是其主要特性。 2.可以使用下标运算符[]像数组那样访问deque容器中的元素。 3.deque容器的大小总是等于其容量--调用size()来获取的;deque容器没有capacity()这个成员函数。 4.deque优于vector容器的方面是可以在序列开头和末尾高效地添加或删除对象。 5.deque容器使用起来比vector容器略慢。在deque序列的中间可以添加或删除元素,但过程比较慢,因为总要复制已有元素。 list<T> 1.list容器在链表中存储类型为 T 的元素。在序列中存储元素,并能在序列的中间插入或删除元素时,就应使用list容器。 2.可以在序列的任意位置高效地插入和删除元素,无需移动已有元素。 3.list容器的主要限制是随机访问元素比较慢,因为这需要从list的第一个元素或最后一个元素开始遍历整个list,查找每个元素。 4.一般的STL sort()算法不能应用于list容器,因为它需要一个提供了随机访问元素功能的迭代器。 但是,仍可以使用内置的sort()函数对list容器的内容排序。 1 vectors容器的能力 size和capacity的区别:vector容器除了提供通用的三个跟元素数目有关的函数size(), empty()和max_size(),另外还提供了一个独特的capacity()函数,那么这个capacity()和size()的区别是什么呢? * size(): 返回容器中实际的元素数目。 * capacity(): 返回容器在重新扩充内存之前可能容纳的最多元素数目。 * max_size(): 返回容器在当前系统框架下可能容纳的最多元素数目。 我们可以理解为vector容器会预留一段内存空间,这个内存空间可能会容纳10个元素,但目前只插入了5个元素,那么size()返回的就是5,而 capacity()返回的就是10。当vector目前的内存空间不足以容纳新的元素时,就会自动扩充内存空间,而这个过程会产生两点影响: * 重新分配内存会使所有针对容器中元素的引用、指针和iterator失效。 * 重新分配内存会浪费时间,降低性能。 为了不至于使扩充内存的现象频繁发生,我们可以根据需要提前为vecotor容器预留足够大的内存空间,可以使用reserve()函数来预留元素的内存空间: std::vector<int> col1; //create a empty vector col1.reserve(80); //reserve memory for 80 elements 另外也可以通过为vector的构造函数传递参数来预留内存空间: std::vector<T> col1(10); //create a vector and initialize it with 10 elements //calls 10 times the default constructor of type T 当然,使用这种方法的前提是容器元素类型必须提供缺省的构造函数。但是对于复杂的数据类型,这种初始化要占用一定的时间。如果你的目的仅仅是为了预留一定的元素空间,优先使用reserve()函数,它只是预留内存空间,并不进行元素的初始化。 通过下面简单的例子我们就可以看的更清楚: #include <iostream> #include <vector> using namespace std; int main() { vector<int> col1; col1.reserve(10); col1.push_back(2); col1.push_back(5); cout << "col1 size:" << col1.size() << endl; cout << "col1 capacity:" << col1.capacity() <<endl; cout << endl; vector<int> col2(10); cout <<"col2 size:" << col2.size() << endl; cout << "col2 capacity:" <<col2.capacity() <<endl; } 运行结果如下: col1 size:2 col2 capacity:10 col2 size:10 col2 capacity:10 值得注意的是,vector容器可以扩充内存空间,却不可以缩小内存空间。给reserve()函数传递一个小于当前容器capacity值的参数是no-op(无效果操作)。正是因为vector容器不能缩小内存空间,所以当删除和修改元素时,可以保证被操作元素之前的针对该容器元素的所有引用、指针、iterator仍然有效;但是插入操作就不能保证这种有效性了。 虽然不能直接缩小vector容器的capacity,但通过辅助手段还是可以一定程度缩小这个capacity值。当使用swap()交换两个 vector容器的内容时,其对应的capacity也相应交换。基于这个理论,我们可以设计下面的模板函数,用来在保留容器元素的情况下缩小容器的 capacity值: template <typename T> void shrinkCapacity(std::vector<T>& v) { std::vector<T> tmp(v); //copy elements into a new vector v.swap(tmp); //swap internal vector data } 你也可以把上面函数浓缩成一个语句: std::vector<T>(v).swap(v); //shrink capacity of vector for type T 2 vector容器的操作 Element access vector容器为元素的直接访问提供了四个操作符: * c.at(idx): 返回idx索引处的元素,进行索引值的有效性检查,如果超出了有效范围,抛出out_of_range异常。 * c[idx]: 返回idx索引处的元素,不进行索引值的有效性检查。 * c.front(): 返回第一个元素,不进行存在性检查。 * c.back(): 返回最后一个元素,不进行存在性检查。 跟数组类似,vector容器的元素索引值也从0开始计数,所以最后一个元素的索引值是size()-1。对于nonconstant(非常量)vector容器,上面四个操作符返回对应元素的引用(reference),可以通过这些操作符直接修改元素的值。值得注意的是,只有at()操作符进行索引值有效性检查,如果无效抛出out_of_range异常,其他三个操作符都不进行有效性检查,如果idx无效或者容器为空,那么[], front(), back()的行为没有定义(不确定): std::vector<int> col1; //create a new empty vector col1[5] = 3; //runtime error? undefined behavior cout << col1.front(); //runtime error? undefined behavior col1.at(5) = 3; //throws out_of_range exception 所以,为了程序的健壮性,你必须自己进行有效性检查: std::vector<int> col1; ...... if (col1.size() > 5) { col1[5] = 3; } if (!col1.empty()) { cout << col1.front(); } 3 vector容器作为普通数组 虽然c++标准中没有明确地指出vector容器的元素是否排列在连续的内存空间中,但事实上这一点是得到保证的。所以下面语句对于有效的索引值i结果是为true: &v[i] == &v[0] + i 这个保证意味着我们可以把vector容器作为普通的动态数组来使用,比如: std::vector<char> v; v.resize(41); //make room for 41 characters (include '/0') strcpy(&v[0], "hello, world!"); printf("%s/n", &v[0]); 当然在使用之前,我们要使用resize()或者reserve()来保证动态数组有足够的内存空间来容纳复制的数据。而且,当作为动态字符数组来使用时,要特别注意字符串后面要以'/0'字符结束。 需要注意的是,不可以把第一个元素的Iterator作为第一个元素的地址来使用,因为iterator跟实现相关,它的含义可能跟普通指针大相径庭。 printf("%s/n", v.begin()); //error! printf("%s/n", &v[0]); //ok! 4 vector容器的异常处理 针对vector容器的异常处理,c++标准库保证下面事项: * 使用push_back()插入元素发生异常,保证对容器内容没有影响。 * 使用insert()插入元素发生异常,如果插入元素的复制操作(拷贝构造函数和赋值操作)不会抛出异常,保证对容器内容没有影响。 * pop_back()不会抛出异常。 * erase()和clear()不会抛出异常。 * swap()不会抛出异常。 * 如果容器元素是简单数据类型,不使用复杂的c++特性,那么针对容器的操作要么成功,要么不会产生影响。 deque 容器在逻辑结构上跟vector容器非常相似,唯一的不同就是deque容器双向增长,在开头和结果插入删除元素都同样高效。但是在内部的实现结构上跟 vector容器大相径庭,deque容器的元素并不是分布在连续的内存空间中,而是分为多个不同的内存块,第一内存块和最后一个内存块在增长方向上刚好相反。 1 deque容器的能力 和vector容器相比,deque容器有下面一些能力特征: * 在容器开头和结尾插入和删除元素都同样高效。 * deque容器的内部结构跟vector容器不同,不是连续的平面结构,所以一般来说对deque容器元素的访问和iterator的移动操作跟vector相比有些缓慢。 * 因为deque容器的内部结构是由多个不同的内存块组成,所以deque的迭代器(iterator)不再可能是普通的指针,而是一种进行了封装的智能指针。 * 因为deque容器的内部结构是由多个不同的内存块组成,所以对于一些对内存块大小有限制的系统,deque容器容纳的最大元素数目可能要比vector多。也就是说max_size()的返回值可能比较大。 * deque 容器没有提供对自身的capacity进行的控制的外部接口,对内存的管理完全由自身负责。除了在容器开头和结尾插入和删除元素外,其他的插入和删除操作都会使对应的元素引用、指针和迭代器失效。因为deque容器的内部结构是由多个不同的内存块组成,所以在插入和删除操作上通常要比vector高效,因为不同移动所有的元素。 * deque容器的内存块当不再使用时,可能会被释放掉,所以跟vector容器的单项增长不同,deque容器的内存大小会缩减。但什么时机释放不再使用的内存块,决定于具体的实现。 * 跟vector容器相同,在中间插入和删除元素时效率较低。 * 跟vector容器相同,deque的iterator也是随机访问迭代器。 综上所述,当下列需求吻合时,你应该选择deque容器: * 你需要在开头和结尾两个方向操作(插入、删除)元素。 * 你不需要引用(reference)容器中的元素。 * 你需要容器所占用的内存空间自动调整,特别是自动释放内存。 2 deque容器的操作 deque容器的操作接口跟vector容器极为相似,区别之处在以下两点: * deque容器不提供对capacity进行控制的接口(capacity()和reserve()函数)。 * deque容器提供了在容器开始位置操纵元素的接口push_front()和pop_front()。 对于deque容器,仍然需要注意的两点是: * 对于随机元素访问操作符at(), [], front(), back(), 除了at()进行有效性检查外,其它操作符都不进行范围有效性检查,所以在使用时要特别小心。 * deque容器的插入和删除元素操作都有可能导致内存的重新分配,因此除了在容器开头和结尾插入删除元素外,其它时机都会使对应的元素引用、指针和迭代器失效,要特别注意。 3 deque容器的异常处理 deque容器对异常处理的支持跟vector容器基本相同,额外的push_front()和pop_front()接口行为分别对照push_back()和pop_back()接口。c++标准库提供了下面的说明: * 如果使用push_front()或者push_back()插入元素时发生了异常,保证对原有元素没有影响,容器保持原状。 * pop_front()和pop_back()不抛出任何异常。