C++提供了使用抽象进行高效编程的方式,标准库中定义了许多容器类以及一系列泛型函数,使程序员可以更加简洁、抽象和有效地编写程序,其中包括:顺序容器,关联容器和泛型算法。本文将简介顺序容器(vectorlistdeque)的相关内容。

1.顺序容器的概念

标准库vector类型,就是一种最常见的顺序容器,它将单一类型元素聚集起来成为容器,然后根据位置来存储和访问这些元素,这就是顺序容器。顺序容器的元素排列顺序与其值无关,而仅仅由元素添加到容器里的次序决定。

标准库定义了三种顺序容器:vectorlistdeque。它们的区别在于访问元素的方式,以及添加或删除元素相关操作的运行代价。如下表:

顺序容器 功能
vector 支持快速随机访问
list 支持快速插入/删除
deque 双端队列

(1) 头文件

为了定义一个容器类型的对象,必须先包含相关的头文件:

#include <vector>  // vector
#include <list> // list
#include <deque> // deque

(2) 定义

所有容器都是类模版,要定义某种特殊的容器,必须在容器名后加一对尖括号,里面提供存放元素的类型:

vector<string> sVec; // empty vector that can hold strings
list<int> iList; // empty list that can hold ints
deque<float> fDeque; // empty deque that can holds floats

(3)初始化

容器的构造函数:

构造函数 含义
C<T> c 创建一个名为c的空容器,C是容器类型名,如vectorT是元素类型,如intstring。适用于所有容器
C c(c2) 创建容器c2的副本cc2c必须具有相同的容器类型,并存放相同类型的元素。适用于所有容器
C c(n) 创建有n个初始化元素的容器c。只适用顺序容器
C c(n, t) 使用n个为t的元素创建容器c,其中值t必须是容器类型C的元素类型的值,或者是可以转换为该类型的值。只适用顺序容器
C c(b, e) 创建容器c,其中元素是迭代器be标示的范围内元素的副本。适用于所有容器

注意: 所有的容器类型都定了默认构造函数,用于创建制定类型的空容器对象。默认构造函数不带参数。为了使程序更加清晰、简短,容器类型最常用的构造函数时默认构造函数。在大多数的程序中,使用默认构造函数能达到最佳运行性能,并且使容器更容易使用。

  • 将一个容器初始化为另一个容器的副本
vector<int> iVec;
vector<int> iVec2(iVec); // ok
vector<double> dVec(iVec); // error, iVec holds int not double
list<int> iList(iVec); // error, iVec is not list<int>

注意:讲一个容器复制给另一个容器时,必须类型匹配(容器的类型和元素的类型都必须相同)。

  • 初始化为一段元素的副本

    通过使用迭代器,间接实现将一种容器内的元素复制给另一种容器。使用迭代器时,不要求容器类型相同,容器内的元素类型也可以不相同,只要它们相互兼容,能够将要复制的元素转换为所构建的新容器的元素类型,即可实现复制。
vector<string> sVec;
// initialize sList with copy of each element of sVec
list<string> sList(sVec.begin(), sVec.end());
// calculate the midpoint in the vector
vector<string>::iterator mid = sVec.begin() + sVec.size() / 2;
// initialize front with first half of sVec: the elements up to but not including *mid
vector<string> front(sVec.begin(), mid); // also can initialize with a pointer
char* words[] = {"first", "second", "third", "forth"};
int sizeOfWords = sizeof(words) / (sizeof(char*));
vector<string> word2(words, words + sizeOfWords); // cout
for ( int idx=0; idx<sizeOfWords; idx ++ )
cout << word2[idx] << endl;
  • 分配和初始化指定数目的元素

    创建顺序容器时,可显式地指定容器大小和一个(可选的)元素初始化式。容器的大小可以是常量或者非常量表达式,元素初始化式必须是可用于初始化其元素类型对象的值:
const list<int>::size_type listSize = 64; // also can be: int listSize = 64
list<std::string> lstr(listSize, "str"); // 64 strings, each is str vector<int> iVec(listSize); // 64 ints, each initialized to 0

(4)容器内元素的类型约束

C++语言中,大多数类型都可用作容器的元素类型。容器元素类型必须满足最基本的两个约束:

  • 元素类型必须支持赋值运算;
  • 元素类型的对象必须可以复制。

除此外,一些容器操作对元素类型还有特殊要求。如果元素类型不支持这些要求,则相关的容器操作就不能执行:我们可以定义该类型的容器,但不能使用某些特定的操作。

另外,旧版C++标准中,指定容器作为容器类型时,必须使用如下空格:

vector<vector<int> > myVec;  // the space required between close >

而在新版标准中,并无要求:

vector<vector<int> > myVec; // ok
vector<vector<int>> myVec; // ok

2.顺序容器的操作

每种顺序容器都提供了一组有用的类型定义以及以下操作:

  • 在容器中添加元素;
  • 在容器中删除元素;
  • 设置容器大小;
  • (如果有的话)获取容器内的第一个和最后一个元素

(1)容器定义的类型别名

类型别名 含义
size_type 无符号整型,足以存储此容器类型的最大可能容器长度
iterator 此容器类型的迭代器类型
const_iterator 元素只读迭代器类型
reverse_iterator 按逆序寻址元素的迭代器类型
const_reverse_iterator 元素只读逆序迭代器类型
difference_type 足够存储两个迭代器差值的有符号整型,可为负数
value_type 元素类型
reference 元素的左值类型,是value_type&的同义词
const_value_type 元素的常量左值类型,等效于const value_type&

例如:

// iter is the iterator type defined by vector<string>
vector<string>::iterator iter; // cnt is the difference_type type defined by vector<int>
vector<int>::difference_type cnt;

(2)容器内元素操作

  • beginend成员
操作 功能
c.begin() 返回一个迭代器,指向容器c的第一个元素
c.end() 返回一个迭代去,指向容器c的最后一个元素的下一个位置
c.rbegin() 返回一个逆序迭代器,指向容器c的最后一个元素**
c.rend() 返回一个逆序迭代器,指向容器c的第一个元素前面的位置

注意:

(a) 迭代器范围是左闭右开区间,标准表达方式为:

// includes the first and each element up to but not including last
[first, lase)

(b) 容器元素都是副本。在容器中添加元素时,系统是将元素值复制到容器里,被复制的原始值与新容器中的元素互不相关,此后,容器内元素值发生变化时,被复制的原值不会收到影响,反之亦然。

(c) 不要存储end操作返回的迭代器。添加或者删除vectordeque容器内的元素都会导致迭代器失效。

vector<int> v(42);
// cache begin and end iterator
vector<int>::iterator first = v.begin(), last = v.end(); while( first!= last ) // disaster: this loop is undefined
{
// insert new value and reassign first, which otherwise would be invalid
first = v.insert(++first, 2);
++ first; // advance first just past the element we added
}
  • 添加元素
操作 功能
c.push_back(t) 在容器c的尾部添加值为t的元素,返回void类型
c.push_front(t) 在容器c的前端添加值为t的元素,返回void类型(只适用于listdeque容器类型)
c.insert(p, t) 在迭代器p所指向的元素前面插入值为t的新元素,返回指向新添加元素的迭代器
c.insert(p, n, t) 在迭代器p所指向的元素前面添加插入n个值为t的新元素,返回void类型
c.insert(p, b, e) 在迭代器p所指向元素前面插入由迭代器bc标记范围的元素,返回void类型
// add elements at the end of vector
vector<int> iVec;
for ( int idx=0; idx<4; ++ idx )
{
iVec.push_back( idx );
} // insert an element
vector<string> sVec;
string str("Insert");
// warning: inserting anywhere but at the end of a vector is an expensive operation
sVec.insert(sVec.begin(), str); // insert some elements
sVec.insert(sVec.begin(), 10, "Anna");
string array[4] = {"first", "second", "third", "forth"};
sVec.insert(sVec.end(), array, array+4);
  • 容器大小的操作
操作 功能
c.size() 返回容器c中元素个数,返回类型为c::size_type
c.max_size() 返回容器c可容纳的最多元素个数,返回类型为c::size_type
c.empty() 返回标记容器大小是否为0的布尔值
c.resize(n) 调整容器c的长度大小,使其能容纳n个元素。如果n<c.size(),则删除多余的元素,否则,添加采用值初始化的新元素
c.resize(n, t) 调整容器c的大小,使其能包纳n个元素,所有元素的值都为t
vector<int> iVec(10, 1); // 10 ints, each has value 1
iVec.resize(15); // adds 5 elements of value 0 to back of iVec
iVec.resize(25, -1); // adds 10 elements of value -1 to back of iVec
iVec.resize(5); // erases 20 elements from the back of iVec

注意:resize操作可能会使迭代器失效。在vectordeque容器上做resize操作可能使其所有迭代器都失效。对于所有容器类型,如果resize操作压缩了容器,则指向已删除的元素的迭代器失效。

  • 访问元素
操作 功能
c.back() 返回容器c的最后一个元素的引用,如果c为空,则该操作未定义
c.front() 返回容器c的第一个元素的引用,如果c为空,则该操作未定义
c[n] 返回下标为n的元素的引用,如果n<0n>c.size(),则该操作未定义(只适用于vectordeque容器)
c.at(n) 返回下标为n的元素的引用。如果下标越界,则该操作未定义(只适用于vectordeque容器)

注意:使用越界的下标,或调用空容器的frontback函数,都会导致程序出现 严重的错误。

  • 删除元素:与插入元素对应容器类型提供了删除容器内元素的操作。
操作 功能
c.erase(p) 删除迭代器p所指向的元素,返回一个迭代器,它指向被删除元素后面的元素。如果p指向容器内的最后一个元素,则返回的迭代器指向容器的超出末端的下一位置,如果p本身就是指向超出末端的下一位置的迭代器,则该函数未定义
c.erase(b, e) 删除迭代器be标记的范围内的所有元素。返回一个迭代器,它指向被删除元素段后面的元素。如果e本身就是指向超出末端的下一位置的迭代器,则返回的迭代器也指向容器末端的下一位置
c.clear() 删除容器c内的所有元素,返回void
c.pop_back() 删除容器c的最后一个元素,返回void。如果c为空容器,则该操作未定义
c.pop_font() 删除容器c的第一个元素,返回void。如果c为空容器,则该操作未定义

注意:

(a) pop_front操作通常与front操作配套使用,实现栈(先进先出)的方式处理:

while ( !vec.empty() )
{
// do something with the current top of vec
process(vec.front());
// remove first element
vec.pop_front();
}

(b)删除一个或一段元素更通用的方法是erase操作。erase操作不会检查它的参数,使用时必须确保用作参数的迭代器或迭代器范围是有效的。

(c) 寻找一个指定元素的最简单的方法是使用标准库的find算法(编程时需要添加头文件#include <algorithm>)。find函数需要一对标记查找范围的迭代器以及一个在该范围内查找的值作为参数。查找完成后,返回一个迭代器,它指向具有指定值的第一个元素或超出末端的下一位置。

string searchValue("find");
vector<std::string> vec(1, "find");
vector<string>::iterator iter = std::find(vec.begin(), vec.end(), searchValue);
if ( iter!= vec.end() )
cout << *iter << endl;

(d) 删除所有元素,可以用clear或将beginend迭代器传递给erase函数。

vec.clear(); // delete all the elements within the container
vec.erase(vec.begin(), vec.end()); // equivalent
  • 赋值与swap

赋值操作中,首先删除其左操作数容器内的所有元素,然后将右操作数容器中的所有容器插入到左边容器中:

vec1 = vec2; // replace contents of vec1 with a copy of elements in vec2
// equivalent operation using erase and insert
vec1.erase(vec1.begin(), vec1.end()); // delete all elements in vec1
vec1.insert(vec2.begin(), vec2.end()); // insert vec2
操作 功能
c1=c2 删除容器c1中所有的元素,然后将c2的元素复制给c1c1c2的类型(包括容器类型和元素类型)必须相同
c1.swap(c2) 交换内容:调用完该函数后,c1中存放的是c2原来的元素,c2中存放的是原来c1的元素。c1c2的类型必须相同。该函数的执行速度通常要比将c2复制到c1的操作快
c.assign(b, e) 重新设置c的元素,将迭代器bc标记范围内的所有元素复制到c中。be必须不是指向c中元素的迭代器
c.assign(n, t) c重新设置为存储n个值为t的元素

注意:

(a) swap操作不会删除或插入任何元素,而且保证在常量的时间内刷新交换。由于容器内没有移动任何元素,因此迭代器不会失效。

(b) 在这里补充一点,vector容器大小有两个描述参数sizecapacitysize前面已经讲述过,指容器中当前已存储元素的数目,而capacity存储的是容器所分配的存储空间可以存储的元素总数。一般来说capacity >= size。在clear, 赋值(c1 = c2),assign(不超过原容器大小)等操作中,并未改变容器的capacity,也就是说,只是把已经分配好的内存上写入的元素数据清掉或者重新赋值,但并未对存储空间进行变动;但是swap操作时,sizecapacity都会改变。

vector<int> vec(100); // size: 100, capacity: 100;
vec.clear(); // size: 0, capacity: 100 vector<int> vec2(50);
vec = vec2; // size: 50, capacity: 100 vector<int> vec3(30);
vec.assign(vec3.begin(), vec.end()); // size: 30, capacity: 100 vec.swap(vec3); // error! vector<int> v1(30), v2(50);
v1.swap(v2); // v1: size 50, capacity 50; v2: size 30, capacity 30

基于此原因,有些时候,当我们想删除一个容器的所有元素的同时,又想把容器占用的内存释放掉时,clear并不能完全实现这一目的,但是可以通过swap:

vector<int> vec(100);      // size 100, capacity 100
vector<int>().swap( vec ); // size 0, capacity 0

(c) vector容器中有reserve操作,可以设定存储空间大小:

vector<int> vec(24);  // size 24, capacity 24
vec.reserve(50); // size 24, capacity 50
cout<< "size" << vec.size() << endl <<
"capacity" << vec.capacity() << endl;

3.结束语

我们很喜欢使用容器,因为确实很便捷,相比于数组,它可以很随意的实现元素的添加、删除等。我们也无需担心内存分配的问题,因为标准库会帮我们都搞定。但是我们最好还是了解一下。

vector为例,为了支持快速的随机访问,vector容器的元素以连续的方式存放,即每一个元素都挨着前一个元素存储。当我们向容器中添加元素时,想想会发生什么:如果容器中已经没有空间容纳新元素,由于容器必须连续存储以便索引访问,所以不能在内存中随便找个地方来存储新元素,而是必须重新分配存储空间,存放在旧存储空间的元素被复制到新存储空间里,接着插入新元素,最后撤销旧的存储空间。如果vector容器在每次添加新元素时,都要这么分配和撤销内存空间,那么其性能将会非常慢!所以,标准库不会这么做,为了使vector容器实现快速的内存分配,其实际分配的容量要比当前所需的空间大一些,例如分配旧存储空间n倍(例如2倍)大小的新存储空间,这样的策略显著提高了其效率。

vector容器的内存分配策略是以最小的代价连续存储元素,通过访问上的便利弥补其存储代价,虽然list容器优于vector容器,但是大部分情况下人们还是觉得vector更好用。实际中vector的增长效率比起listdeque通常会更高。

参考文献:

  • 《C++ Primer中文版(第四版)》,Stanley B.Lippman et al. 著, 人民邮电出版社,2013。

C++ 容器(一):顺序容器简介的更多相关文章

  1. C++ 顺序容器基础知识总结

    0.前言 本文简单地总结了STL的顺序容器的知识点.文中并不涉及具体的实现技巧,对于细节的东西也没有提及.一来不同的标准库有着不同的实现,二来关于具体实现<STL源码剖析>已经展示得全面细 ...

  2. C++学习基础四——顺序容器和关联容器

    —顺序容器:vector,list,queue1.顺序容器的常见用法: #include <vector> #include <list> #include <queue ...

  3. C++ 顺序容器

    <C++ Primer 4th>读书笔记 顺序容器内的元素按其位置存储和访问.容器类共享公共的接口,每种容器类型提供一组不同的时间和功能折衷方案.通常不需要修改代码,只需改变类型声明,用一 ...

  4. C++ Primer : 第九章 : 顺序容器的定义、迭代器以及赋值与swap

    顺序容器属于C++ STL的一部分,也是非常重要的一部分. 顺序容器包括: std::vector,包含在头文件<vector>中 std::string, 包含在头文件<strin ...

  5. 顺序容器:vector,deque,list

    1.顺序容器:vector,deque,list 容器类共享公共接口,只要学会其中一种类型就能运用另一种类型.每种容器提供一组不同的时间和功能这种方案,通常不需要修改代码,秩序改变类型声明,每一种容器 ...

  6. C++ Primer 5th 第9章 顺序容器

    练习9.1:对于下面的程序任务,vector.deque和list哪种容器最为适合?解释你的选择的理由.如果没有哪一种容器优于其他容器,也请解释理由.(a) 读取固定数量的单词,将它们按字典序插入到容 ...

  7. STL顺序容器【vector】【deque】【list】

    我们都知道,stl在集装箱船分为两类,订购集装箱和相关的容器. 顺序容器有三种即动态数组vector,双端队列deque,以及链表list (对csdn的文字排版严重吐槽.写好的版发表了就变了) 一: ...

  8. C++顺序容器知识总结

    容器是一种容纳特定类型对象的集合.C++的容器可以分为两类:顺序容器和关联容器.顺序容器的元素排列和元素值大小无关,而是由元素添加到容器中的次序决定的.标准库定义了三种顺序容器的类型:vector.l ...

  9. c++11の顺序容器

      容器是一种容纳特定类型对象的集合.C++的容器可以分为两类:顺序容器和关联容器.顺序容器的元素排列和元素值大小无关,而是由元素添加到容器中的次序决定的.标准库定义了三种顺序容器的类型:vector ...

随机推荐

  1. Servlet学习(七)——cookie

    一.会话技术简介 1.存储客户端的状态 例如网站的购物系统,用户将购买的商品信息存储到哪里?因为Http协议是无状态的,也就是说每个客户访问服务器端资源时,服务器并不知道该客户端是谁,所以需要会话技术 ...

  2. 【转载】HTTP Session 内存到内存复制的拓扑结构

    http://www.oschina.net/question/129540_23215 HTTP 协议本身是“连接 - 请求 - 应答 - 关闭连接”的模式,是一种无状态协议:然而随着 web 动态 ...

  3. STM8S汇编代码分析

    转载:http://blog.csdn.net/u010093140/article/details/50021897使用STVD建立完汇编工程项目之后(具本建立方法可以看我的另一篇博文http:// ...

  4. POJ 2976 Dropping tests【二分 最大化平均值】

    题意:定义最大平均分为 (a1+a2+a3+---+an)/(b1+b2+---+bn),求任意去除k场考试的最大平均成绩 和挑战程序设计上面的最大化平均值的例子一样 判断是否存在x满足条件 (a1+ ...

  5. ActiveMQ学习笔记(2)----JMS的基本概念和模型

    1. JMS 的基本概念 1. JMS是什么? JMS Java Message Service,Java消息服务,是Java EE中的一种技术. 2. JMS规范 JMS定义了Java中访问消息中间 ...

  6. nginx禁止特定UA访问

    一.UA是什么? User Agent 简称UA,就是用户代理.通常我们用浏览器访问网站,在网站的日志中,我们的浏览器就是一种UA. 二.禁止特定UA访问 最近有个网站(www.C.com)抄袭公司主 ...

  7. 【Tool】Linux下的Spark安装及使用

    1. 确保自己的电脑安装了JAVA Development Kit JDK, 用来编译Java应用, 如 Apache Ant, Apache Maven, Eclipse. 这里是我们安装Spark ...

  8. worldcount

    码云项目地址 PSP2.1 PSP阶段 预估耗时 (分钟) 实际耗时 (分钟) Planning 计划 20 20 Estimate 估计这个任务需要多少时间 60 70 Development 开发 ...

  9. linux指令--用户和工作组管理

    >>前言    Linux是一个多用户.多任务的操作系统,Linux系统的初衷之一就是满足多用户同时工作的需求,因此,linux需要具备很好的安全性,需要对用户进行管理,用户又分几种,管理 ...

  10. 紫书 例题11-7 UVa 753 (网络流最大流)

    设一个源点, 到所有设备连一条弧, 容量为1, 然后设一个汇点, 所有插座到汇点连弧, 容量为1, 然后 转换器也连一条弧, 容量为1. 最后最大流就是答案.其中注意节点数要开大一些. #includ ...