您的位置:首页 > 编程语言 > C语言/C++

C++ primer第二次阅读学习笔记(第9章: 顺序容器)

2012-05-20 14:20 435 查看

第九章 顺序容器

顺序容器内的元素按位置进行存储和访问。元素的排列次序与元素值无关,而是由元素添加到容器的次序决定的。
标准库定义了三种顺序容器类型:vector,list,deque。它们的差别在于访问元素的方式,以及添加或删除元素相关操作的运行代价。容器仅定义少量操作,大多数额外操作由算法库提供。
为了定义容器对象首先要包含相关头文件,如#include<vector>,#include<list>,#include<deque>,所有的容器都是类模板,必须为容器指定容器存放的元素类型。
所有的容器都提供了默认构造函数,除此之外还提供其他构造函数。如1:C<T> c;默认构造函数。 2:C c(c2);复制构造。3:C c(b,e);用b,e标示的范围创建c,b、e为迭代器。4:C c(n,t)用n个t的元素创建c。5:C c(n)。创建有n个元素的c。调用默认构造函数。
将一个容器复制给另一个容器时,容器类型和元素类型都必须匹配。vector<int> ivec;vector<int>ivec2(ivec);
系统允许通过传递一对迭代器,将一个容器的一部分元素赋值给另一个容器。迭代器指向要复制的第一个元素和最后一个元素的下一位置。
list<string> slist(svec.begin(),svec.end());
char *word[]={"hello","world","buck","mulling","plump"};
list<string> word2(words,words+2)
分配和初始化指定数目的元素时,可以显式指定容器大小和一个元素初始化式,该初始化式必须是可以用于初始化其元素类型的对象的值。如list<string> str(20,"Hello,world"); 不提供初始化式时,元素类型必须是内置类型或是复合类型,或者是提供了默认构造函数。如果元素没有默认构造函数,则必须显式指定其元素初始化式。
接受容器大小作参数的构造函数,仅仅适用于顺序容器,关联容器不支持这种初始化。
容器元素必须满足一下两个条件:
1:元素类型必须支持赋值运算。
2:元素类型对象必须可以复制。
大多数类型满足上述要求,但引用类型除外。因为它不支持一般意义上的赋值运算(从一而终),因此没有元素是引用类型的容器。这就很好理解在学习设计模式--观察着模式时遇到的问题,不能使用引用,最后采用指针实现。另外IO库类型和auto_ptr类型均不能作为容器元素类型。原因与引用类似。
支持赋值和复制是容器类型的最低要求。一些容器操作对元素类型还有特殊要求。如当容器内存储的是类类型的元素时,初始化容器时仅指定容器大小,只有当类提供默认的构造函数,才能采取此种操作。
定义容器类型的容器时,>>之间必须指定空格。vector<vector<string> >line;
不同容器的迭代器支持的操作是不同的。只有vector和deque的容器提供迭代器算术运算和除==和!=关系以外的关系操作符来比较两个迭代器。(==和!=适用于所有迭代器)。只是因为这两种容器为其元素提供快速随机的访问。
如:list<int> ilist(vec.begin(),vec.end());
ilist.begin()+ilist.size()/2;//错误。因为list容器不支持(除==和!=之外)算数运算和关系运算。但是它支持++、--,及相等、不等运算。list容器迭代器使用++、--是迭代器指向下一个元素,但是不能使用iter+=n.;这也是为什么在前面介绍中iter与vec.end()比较时没有使用<=而使用了!=运算符。我们更倾向于使用更一般的支持所有容器的操作,这样便于代码维护和灵活性。
迭代器范围这个概念是标准库的基础。它使用一对迭代器实现。第一个迭代器指向第一个元素,另外一个指向超出末端下一位置。当first等于last,均指向超出末端下一元素,迭代器范围为空。当first与last不等时,迭代器范围内至少有一个元素。
一些容器操作会修改迭代器的内在状态或移动迭代器内的元素。这样的操作使所有指向被移动的元素的迭代器失效,同时也会使其他迭代器失效。使用无效的迭代器很容易产生严重的运行时错误。如每种容器都定义了一个或多个erase函数,这些函数提供删除容器元素的功能,任何对已删除元素的迭代器元素的操作都可能会导致严重的运行时错误。
每种顺序容器都提供了一组有用的类型定义以及一些操作:增加元素,删除元素,设置容器大小,获取容器的第一个和最后一个元素。
前面章节中我们介绍过size_type,iterator和const_iterator。这些都是在容器内定义的类型别名.
size_type 无符号整形,存储容器的大小。
iterator 迭代器类型。
const_iterator 只读迭代器类型
reverse_iterator 逆序寻址的迭代器。
different_type 有符号整形,存储两个迭代器只差。
value_type 容器中元素的类型。
reference 元素的左值引用类型。也是value_type&的同义词。
const_reference 元素的常量左值类型,等效于const value_type&;
以上这些类型对于程序员编写泛型程序非常有用。
如list<string>::iterator iter;//此举表明::右边的类型是在::左边指定的容器作用域内定义的。
c.begin(),c.end(),c.rbegin(),c.rend(),这些操作有两个不同的版本:const成员函数,非const成员函数。具体调用那些成员函数取决于容器时const类型还是非const类型。因为只有const类型的对象才可以调用const成员函数。。当调用const成员函数时它返回的是const_iterator类型的指针。
所有的顺序容器都支持push_back(),向容器尾部添加元素。添加的元素是push_back()参数的副本。也就是传值。
除了push_back运算,list和deque还支持push_front。这个操作实现在容器首部插入新元素的功能。但是vector不支持push_front,这与vector物理特性有关。
在容器中添加元素时,系统将元素值复制到容器里。类似的,使用一段元素初始化新容器时,新容器存放的原始元素的副本。被复制的元素与新容器中的元素各不相关。
使用insert可以向容器的任意指定的位置插入新元素。它有三个版本:
1:insert(iter,value);//iter指向新插入元素的位置。
虽然vector不支持push_front,但是可以使用insert实现push_front的功能。如
vector<string> svec;
svec.insert(svec.begin(),"value");
迭代器指向元素插入的位置,但是新元素是插入在迭代器指向的位置之前。因此迭代器可以指向任何位置,包括超出末端的下一位置。insert函数返回新插入元素的迭代器。
2:insert( iter ,num,value);//在iter插入指定数量的相同元素。
3:insert(iter , ter1,iter2);//iter为容器中要插入的位置,iter1和iter2标记了迭代器范围。
在vector容器中添加元素可能导致整个容器的重新加载,这样的话,该容器的所有迭代器都会失效。所谓的失效是指:所有通过begin(),end()或是通过insert等其他函数返回的迭代器指向错误的地方。此时如需要再次使用迭代器时,可以再次调用begin,或end函数获得。尤其是end函数返回的迭代器,何添加删除操作都会使其失效,因此尽量不要存储有end返回的迭代器,应该通过end调用每次重新计算新的end迭代器。
所有的容器都支持使用关系操作符,来实现两个容器的比较。容器的比较使用了元素类型定义的统一关系操作符,如果容器的元素不支持某种操作符,那么该容器也就不能做这种操作。也就是说只允许两个容器做其元素类型定义的关系运算。要将容器的比较与迭代器支持的关系操作符相区分。
size返回容器内元素的个数。empty用于判断容器是否为空。为空时,返回true。
resize用来改变容器所包含的元素个数。当当前的容器长度大于新的长度时,则将会进行截断操作,即该容器后部的元素会被删除。如果当前长度小于新的长度时,会在容器后部添加新元素。resize参数带有一个可选的元素值形参,如果在调用时提供这个参数,则所有新添加的元素都会被初始化为这个值。如果没有提供,则新添加的元素会采取值初始化的方式。注意:resize可能会使所有迭代器失效。
如果容器非空,则front和back成员函数将返回容器第一个和最后一个元素的引用。如
list<int> ::reference val=*vec.begin();
list<int>::value_type &val1=vec.front();
vector和deque支持下标操作。即c
.和c.at(n)。at函数提供对下标的检查,如果下表无效at函数将会抛出out_of_range异常。而c
将会导致未定义的错误。
容器类型提供了通用的erase和pop_front和pop_back操作来删除容器的元素。但push_front仅支持list和deque容器。
pop_front,pop_back用于删除容器内第一个和最后一个元素。注意它们返回的是void,并不是返回的被删除的元素值。为了获得被删除的元素值,必须在删除以前调用front或是back()函数。
erase用于删除一个或一段元素。它有两个版本:删除一个迭代器指向的单个元素,删除一对迭代器标记的一段元素。它返回一个迭代器,该迭代器指向被删除元素或元素段后面的元素。删除之前必须搜索到该元素,这可以使用标准库的find算法。使用标准库的算法前必须将algorithm头文件包含进来。在删除之前应确保find的返回值不是end迭代器。
调用clear将删除容器内所有元素,或者调用erase(c.begin(),c.end());
赋值操作符首先删除做操作数容器的所有元素,然后将有操作数容器的所有元素插入到左边容器。swap用于交换两容器。assign重新设置容器内容。原来的内容将会被清空。如c.assign(b,e),用b,e标记的范围复制到c中,从而实现为c重新赋值的目的。这一操作很有用。另一个版本为:c.assign(num,value);将c重新设置为存储n个值为t的元素。
swap操作不会删除或插入任何元素,也没有移动任何元素,因此不会导致迭代器失效。如在做swap交换前,iter指向svec1[3],swap之后iter指向svec2[3]。还是指向同一个元素。只是在不同容器罢了。
vector容器的元素以连续的方式存放,如果在添加元素时空间不够,那么vector必须重新分配更大的空间,然后将旧空间的数据复制到新空间内,接着插入元素,最后撤销旧空间。而对于list来说却不存在这个问题。为了是vector实现快速内存分配,vector容器预留了额外的存储区,用于存放新添加的元素,不必每添加一个新元素就必须重新分配一次容器。所分配的额外内存容量,因库的实现不同而不同。capacity()成员函数,返回在分配更多的存储空间之前能够存储的元素总数,也就是,已分配的空间/元素大小。reserve告诉vector容器应该预留多少个元素的存储空间。reserve设定的值就是capacity的大小。
实际测试表明:vector容器不得不分配新的空间时,以加倍当前容量的分配策略实现重新分配。
vector和deque提供了对元素的快速随即访问。但付出的代价是,在容器任意位置插入或删除元素,比在容器尾部插入和删除代价大。list能在任何位置快速插入和删除,但随即访问开销较大。从deque队列的两端插入和删除元素非常快,但在中间插入和删除付出的代价很高。
当只需在读取时在容器的中间插入元素,然后需要随即访问元素。可以再输入时将元素读入list容器,然后两list内的元素复制到vector中。如果无法确定采用哪种容器,在变写代码时只是用vector和list均支持的操作:使用迭代器而不是下标,这样在有必要时可以很方便的将vector容器改为使用list容器。

除了三种顺序容器,标准库还提供了三种顺序容器适配器:queue,priority__queue,stack。适配器是标准库的通用概念,包括容器适配器、迭代器适配器和函数适配器。容器适配器让一种已存在的容器类型采用另一种不同的抽象类型的工作方式实现。例如:stack适配器可使任何一种顺序容器以栈的方式工作。
适配器通用的操作和类型
size_type unsigned 类型
value_type 同容器类型
container_type 基础容器的类型,适配器在此容器上实现。
所有的适配器都支持所有的关系操作符。与容器一样,使用适配器也要包含头文件,#include<stack>#include<queue>
所有的适配器都定义了两个构造函数,默认的构造函数用于创建空对象,而带有一个容器参数的构造函数将参数容器的副本作为其基础值。如
deque<int> deq;
stack<int> stk(deq);
默认的 stack和queue都是基于deque来实现,而priority_queue则是基于vector来实现,在创建适配器时,通过将一个顺序容器指定为适配器的第二个类型的实参,可覆盖其关联的基础容器的类型。如
stack<string,vector<string> >str_stk;
stack <string,vecotr<string> >str_stk2(str_stk);
stack适配器所关联的基础容器可以是任意类型的顺序容器类型。因此stack可以建立在vector、list、deque容器之上。而queue适配器要求其关联的基础容器必须提供push_front运算,因此只能建立在list、deque容器上,而不能建立在vector上。priority_queue要求提供随即访问能力,因此只能建立在deque和vector上,不能建立在list上。
两个相同类型的适配器可以做关系比较。这是通过其元素依次比较来实现。
stack栈适配器
s.empty() //如栈为空范围true。
s.size(); //返回栈中元素个数。size_type类型。
s.pop(); //弹出栈顶。
s.top(); //返回栈顶元素。
s.push(value); //入栈。
标准库队列使用先进先出的存储和检索策略。进入队列的对象被放在尾部,从首部取出对象。标准库提供两种风格的队列:先进先出队列,优先级队列。
优先级队列允许用户为队列中元素设置优先级,这种队列不是将新元素放在队列尾部,而是放在比它优先级低的元素前面,默认使用元素类型的<操作符来确定它们之间的优先级关系。例如:机场行李检查队列,30分钟后即将离港的航班乘客通常被移到队前面,以便他们能在飞机起飞前完成检查过程。还有就是操作系统的调度表,它决定在大量等待的进程中选择下一个要执行的进程。优先级队列C++primer一带而过,有必要自己找些资料。

所有的容器适配器都是根据其基础容器类型所支持的操作来定义自己的操作。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: