本部分主要是讨论“容器和算法”,具体内容包括:顺序容器、关联容器以及泛型算法。第9章深入探讨vector和其他顺序容器类型,第10章介绍关联容器,即不是顺序排列,而是按键排序的,第11章介绍泛型算法,这些算法通常作用于容器或序列中某一范围的元素。所谓泛型指的就是这些算法可以作用于不同的容器类型,而这些容器又可以容纳多种不同类型的元素。
将单一类型元素聚集起来成为容器,然后根据位置来存储和访问这些元素,这就是顺序容器,顺序容器的元素排列次序与元素值无关,而是由元素添加到容器里的次序决定,标准库定义了三种顺序容器类型:vector、list和deque(双端队列),他们的差别在于访问元素的方式,以及添加或删除元素相关操作的运行代价,标准库还提供了三种容器适配器:stack、queue、priority_queue,适配器是根据原始的容器类型所提供的操作,通过定义新的操作接口,来适应基础的容器类型。
1、顺序容器类型:容器只定义了少量的操作,大多数额外操作则由算法库提供。
vector:支持快速随机访问;list:支持快速插入/删除;deque:双端队列
2、顺序容器的定义:所有的容器类型都定义了默认构造函数,用于创建指定类型的空容器对象。默认构造函数不带参数。
#include<vector> #include<list> #include<deque> vector<string> svec; list<int> ilist; deque<Sales_item> items;
3、将一个容器复制给另一个容器时,类型必须匹配,容器类型和元素类型都必须相同。
vector<int> ivec; vector<int> ivec2(ivec); //ok,ivec is vector<int> list<int> ilist(ivec); //error,ivec is not list<int> vector<double> dvec(ivec); //error,ivec holds int not double
4、初始化为一段元素的副本:尽管不能直接将一种容器内的元素复制给另一个容器,但系统允许通过传递一对迭代器间接实现该功能。使用迭代器时,不要求容器类型相同,容器内的元素类型也可以不相同,只要它们相互兼容,能够将要复制的元素转化为所构建的新容器的元素类型,即可实现复制。
list<string> slist(svec.begin(),svec.end()); vector<string>::iterator mid=svec.begin()+svec.size()/2; deque<string> front(svec.begin(),mid); deque<string> back(mid,svec.end());
5、创建顺序容器时,可显式指定容器大小和一个元素初始化式,接受容器大小做形参的构造函数只适用于顺序容器,而关联容器不支持这种初始化。
const list<int>::size_type list_size=64; list<string> slist(list_size,"eh?");
6、容器的容器,注意在指定容器元素为容器类型时,必须使用空格。否则系统会认为>>是单个符号,为右移操作符。
vector< vector<string> > lines;//ok,space required between close> vector< vector<string>> lines;//error,>>treated as shift operator
7、常用迭代器运算
*iter;//返回迭代器iter所指向的元素的引用 iter->mem;//对iter进行解引用,获取指定元素中名为mem的成员。等效于(*iter).mem iter1==iter2;//比较两个迭代器是否相等,当两个迭代器指向同一个容器 iter1!=iter2;//中的同一个元素,或者都指向end(),两个迭代器相等
8、vector和deque容器的迭代器提供额外的运算:迭代器算术运算,以及使用除了==和!=之外的关系操作符来比较两个迭代器。
iter+n; iter-n; iter1+=iter2; iter1-=iter2;//这是迭代器加减法的复合赋值运算,将iter1加上或减去iter2的运算结果赋给iter1 iter1-iter2;//两个迭代器的减法,其运算结果加上右边的迭代器即得左边的迭代器
9、使用左闭合区间[first,last)的编程意义:当first与last相等时,迭代器范围为空;当first与last不相等时,迭代器范围内至少有一个元素,而且first指向该区间中的第一个元素。此外,通过若干次自增运算可以使first的值不断增大,直到first==last为止。
10、begin和end成员
c.begin():返回一个迭代器,它指向容器c的第一个元素;c.end():返回一个迭代器,它指向容器c的最后一个元素的下一位置
c.rbegin():返回一个逆序迭代器,它指向容器c的最后一个元素;c.rend():返回一个逆序迭代器,它指向容器c的第一个元素前面的位置
11、在顺序容器中添加元素:
c.push_back(t);//在容器c的尾部添加值为t的元素,返回void类型 c.push_front(t);//在容器c的前端添加值为t的元素,返回void类型,只适用list和deque容器类型 c.insert(p,t);//在迭代器p所指向的元素前面插入值为t的新元素,返回指向新添加元素的迭代器 c.insert(p,n,t);//在迭代器p所指向的元素前面插入n个值为t的新元素,返回void类型 c.insert(p,b,e);//在迭代器p所指向的元素前面插入由迭代器b和e标记的范围内的元素,返回void类型
12、避免存储end操作返回的迭代器,如下面代码,该段代码将导致死循环,问题在于这个程序将end操作返回的迭代器值存储在名为last的局部变量中,循环体中实现了元素的添加运算,添加元素会使得存储在last中的迭代器失效。
vector<int>::iterator first=v.begin(),last=v.end(); while(first!=last){ first=v.insert(first,32); ++first; }
为了避免存储end迭代器,可以在每次做完插入运算后重新计算end迭代器值:
while(first!=v.end()){ first=v.insert(first.42); ++first; }
13、所有的容器类型都支持用关系操作符来实现两个容器的比较,比较的容器必须具有相同的容器类型,而且其元素类型也必须相同。
/* ivec1:1 3 5 7 9 12 ivec2:0 2 4 6 8 10 12 ivec3:1 3 9 ivec4:1 3 5 7 ivec5:1 3 5 7 9 12 */ ivec1<ivec2;//false ivec2<ivec1;//true ivec1<ivec3;//true ivec1<ivec4;//false ivec1==ivec4;//false ivec1==ivec5;//true
14、容器大小的操作
c.size();//返回容器c中的元素个数。返回类型为c::size_type c.max_size();//返回容器c可容纳的最多元素个数,返回类型为c::size_type c.empty();//返回标记容器大小是否为0的布尔值 e.resize(n);//调整容器c的长度大小,使其容纳n个元素,如果n<c.size(),则删除多出来的元素,否则添加 //采用值初始化的新元素 e.resize(n,t);//调整c的大小,使其能容纳n个元素,所有添加的元素值都为t
15、访问元素:
c.back();//返回容器c的最后一个元素的引用。如果c为空,则该操作未定义 c.front();//返回容器c的第一元素的引用。 c[n];//返回下标为n的元素的引用,该操作只适用于vector和deque c.at(n);//返回下标为n的元素的引用,如果下标越界,则该操作未定义,该操作只适用于vector和deque
16、删除元素
c.erase(p);//删除迭代器p所指向的元素,返回一个迭代器,它指向被删除元素后面的元素 e.erase(b,e);//删除迭代器b和e所标记的范围内的元素 c.clear();//删除容器c内的所有元素,返回void c.pop_back();//删除容器c的最后一个元素,返回void c.pop_front();//删除容器c的第一个元素,返回void,只适用于list或deque容器
17、赋值与swap
c1=c2;//删除容器c1的所有元素,然后将c2的值复制给c1 c1.swap(c2);//交换内容 c.assign(b,e);//重新设置c的元素,b和e必须不是指向c中元素的迭代器 c.assign(n,t);//将容器c重新设置为存储n个值为t的元素
18、容器的capacity(容量)和size(长度)的区别:size指容器当前拥有的元素个数;而capacity则指容器在必须分配心存储空间之前可以存储的元素个数。
19、string类型的查找操作,这些操作都返回string::size_type类型的值
s.find(args);//在s中查找args的第一次出现 s.rfine(args);//在s中查找args的最后一次出现 s.find_first_of(args);//在s中查找args的任意字符的第一次出现 s.find_last_of(args);//在s中查找args的任意字符的最后一次出现 s.find_first_not_of(args);//在s中查找第一个不属于args的字符 s.find_last_not_of(args);//在s中查找最后一个不属于args的字符
20、容器适配器:包括容器适配器、迭代器适配器和函数适配器。使用适配器是,必须包含相关的头文件:
#include<stack> #include<queue>
关联容器和顺序容器的本质区别在于:关联容器通过键存储和读取元素,而顺序容器则通过元素在容器中的位置顺序存储和访问元素,虽然关联容器的大部分行为与顺序容器相同,但其独特之处在于支持键的使用。关联容器支持通过键来高效地查找和读取元素,两个基本的关联容器类型是map和set,map的元素以键-值(key-value)对的形式组织,键用作元素在map中的索引,而值则表示所存储和读取的数据。set仅包含一个键,并有效地支持关于某个键是否存在的查询。
1、关联容器类型包括:(1) map,关联数组,元素通过键来存储和读取;(2) set,大小可变的集合,支持通过键实现的快速读取;(3) multimap,支持同一个键多次出现的map类型;(4) multiset,支持同一个键多次出现的set类型
2、一般来说,如果希望有效地存储不同值的集合,那么使用set容器比较合适,而map容器则更适用于存储每个键所关联的值的情况。
3、pair类型,该类型在utility头文件中定义
#include<utility> pair<T1,T2> p1;//创建一个空的pair对象,它们的两个元素分别是T1和T2类型,采用值初始化 pair<T1,T2> p1(v1,v2);//创建一个pair对象,它们的两个元素分别是T1和T2类型,其中first成员初始化为v1, //而second初始化为v2 make_pair(v1,v2);//以v1和v2值创建一个新的pair对象,其元素类型分别是v1和v2的类型 p.first;//返回p中名为first的数据成员 p.second;//返回p中名为second的数据成员
举例说明如下:
pair<string,string> anno; pair<string,int>word_count; typedef pair<string,string> Author; Author proust("Xue","Jamy"); //pair对象的操作 pair<string,string> author("James","Joye"); string firstBook; if(author.first="Xue" && author.second=="Jamy") firstBook="The Love Story"; //生成新的pair对象 pair<string,string> next_auth; string first,last; while(cin>>first>>last){ next_auth=make_pair(first,second); }
4、关联容器共享大部分但并非全部的顺序容器操作,关联容器不提供front,push_front,pop_front,back,push_back以及pop_back操作。关联容器不能通过容器大小来定义,因为这样的话就无法知道键所对应的值是什么,关联容器还可以根据键来排列元素。
5、map类型的定义:必须具备map头文件
map<string,int> word_count; map<k,v> m;//创建一个名为m的空map对象,其键和值的类型分别为k和v map<k,v> m(m2);//创建m2的副本m,m和m2必须有相同的键类型和值类型 map<k,v> m(b,e);//创建map类型的对象m,存储迭代器b和c标记的范围内所有的元素的副本,元素的类型必须 //能够转换为pair<const k,v>
6、实际应用中,键类型必须定义<操作符,而且该操作符应能正确的工作。至于是否支持其他的关系或相当运算,则不做要求。
7、map定义的类型
map<K,V>::key_type //在map容器中,用作索引的键的类型 map<K,V>::mapped_type //在map容器中,键所关联的值的类型 map<K,V>::value_type //一个pair类型,它的first元素具有const map<K,V>::key_type类型, //而second元素则为map<K,V>::mapped_type类型 //map迭代器进行解引用将产生pair类型的对象 map<string,int>::iterator map_it=word_count.begin(); cout<<map_it->first; cout<<" "<<map_it->second; map_it->first="new key"; //Error:key is const ++map_it->second; //OK:we can change value through an iterator
8、给map添加元素:可以用insert成员实现,或者先用下标操作符获取元素,然后给获取的元素赋值。使用下标访问map与使用下标访问数组或vector的行为截然不同,用下标访问不存在的元素将导致在map容器中添加一个新的元素,它的键即为该下标值。
map<string,int> word_count; word_count["Anna"]=1;//如果word_count中没有Anna的元素,则插入 m.insert(e);//e是一个用在m上的value_type类型的值。 m.insert(beg,end);//beg和end是标记元素范围的迭代器 m.insert(iter,e);//e是一个用在m上的value_type类型的值。 word_count.insert(map<string,int>::value_type("Anna",1)); word_count.insert(make_pair("Anna",1));
9、查找并读取map中的元素
m.count(k);//返回m中k的出现次数 m.find(k);//如果m容器中存在按k索引的元素,则返回指向该元素的迭代器,如果不存在,则返回end()
10、从map对象中删除元素
m.erase(k);//删除m中键为k的元素。返回size_type类型的值,表示删除的元素个数 m.erase(p);//从m中删除迭代器p所指向的元素。p必须指向m中确实存在的元素,而且不能等于m.end(),返回void m.erase(b,e);//从m中删除一段范围内的元素,返回void
11、set类型:只是单纯的键的集合。
set容器支持大部分的map操作,两种例外包括:set不支持下标操作符,而且没有定义mapped_type类型,在set容器中,value_type不是pair类型,而是与key_type相同的类型,它们指的是set中存储的元素类型。
12、set容器的定义和使用:必须包含set头文件
与map容器一样,set容器的每个键都只能对应一个元素,以一段范围的元素初始化set对象,或在set对象中插入一组元素时,对于每个键,事实上都只添加一个元素。
vector<int> ivec; for(vector<int>::size_type i=0;i!=10;++i){ ivec.push_back(i); ivec.push_back(i); }//ivec有20个元素,即两个0~9的副本 set<int> iset(ivec.begin(),ivec.end()); cout<<ivec.size()<<endl; //20 cout<<iset.size()<<endkl//10:iset只能保存不同元素的值
13、multimap和multiset类型:允许一个键对应多个实例。multimap和multiset所支持的操作分别与map和set的操作相同,只有一个例外:multimap不支持下标运算。因为这类容器中,某个键可能对应多个值。
(1) 元素的添加和删除
multimap<string,string> authors; //Add Elements authors.insert(make_pair(string("Xue"),string("Love"))); authors.insert(make_pair(string("Xue"),string("Hello"))); //Del string search_item("Xue"); multimap<string,string>::size_type cnt=authors.erase(search_item);
(2) 与众不同的面向迭代器的解决方案
m.lower_bound(k);//返回一个迭代器,指向键小于k的第一个元素 m.upper_bound(k);//返回一个迭代器,指向键大于k的第一个元素 m.equal_bound(k);//返回一个迭代器的pair对象,它的first成员等价于m.lower_bound(k). //而second成员等价于m.upper_bound(k)
(3) equal_range函数:返回存储一对迭代器的pair对象,如果该值存在,则pair对象中的第一个迭代器指向该键关联的第一个实例,第二个迭代器指向该键关联的最后一个实例的下一个位置,如果找不到匹配的元素,则pair对象中的两个迭代器都将指向此键应该插入的位置。
标准库容器定义的操作非常少,标准库没有给容器添加大量的功能函数,而是选择提供一组算法,这些算法大都不依赖特定的容器类型,可作用在不同类型的容器和不同类型的元素中,本章详细介绍泛型算法以及对迭代器更详尽的描述。泛型算法本来从来不执行容器操作,只是单独依赖迭代器和迭代器操作实现,算法基于迭代器及其操作实现,而并非基于容器操作。
1、使用泛型算法必须包含algorithm头文件#include<algorithm>.标准库还定义了一组泛化的算术算法,其命名习惯与泛型算法相同,使用这些算法则必须包含numberic头文件#include<numeric>。
2、用于指定累加起始值的第三个实参是必要的,因为accumulate对将要累加的元素类型一无所知。
//将sum设置为vec的元素之和再加上42 int sum=accumulate(vec.begin(),vec.end(),42); //把string型的vector容器的元素连接起来 string sum=accumulate(v.begin(),v.end(),string(""))'
3、find_first_of的使用:在第一段范围内查找与第二段范围中任意元素匹配的元素,然后返回一个迭代器,指向第一个匹配的元素,如果找不到匹配元素,则返回第一个范围的end迭代器。以下例子统计有多少个名字同时出现在这两个列表中。
size_t cnt=0; list<string>::iterator it=roster1.begin(); while((it=find_first_of(it,roster1.end(),roster2.begin(),roster2.end())){ ++cnt; ++it; } cout<<"Found"<<cnt <<"name on both rosters"<<endl;
4、写容器元素的算法,有些算法直接将数据写入到输入序列,另外一些则带有一个额外的迭代器参数指定写入目标。
(1) 写入输入序列的元素:本质上是安全的,只会写入与制定输入范围数量相同的元素。
fill(vec.begin(),vec.end(),0);
(2) 不检查写入操作的算法:fill_n函数带有的参数包括一个迭代器、一个计数器以及一个值。注意不能对没有元素的空容器调用
fill_n函数(或者类似的算法)
vector<int> vec;//empty vector fill_n(vec.begin(),10,0);//Error:attempts to write 10 elements in vec
(3) 确保算法有足够的元素存储输入数据的一种方法是使用插入迭代器,插入迭代器可以给基础容器添加元素的迭代器,通常用迭代器给容器元素赋值时,被赋值的是迭代器所指向的元素,而是用插入迭代器赋值时,则会在容器中添加一个新元素,其值等于赋值运算的右操作数的值。为了说明如何安全使用写容器的算法,下面使用back_inserter,必须包含iterator头文件。
//引入back_inserter vector<int> vec; fill_n(back_inserter(vec),10,0); //写入到目标迭代器的算法:copy从输入范围读取元素,然后赋值给ivec vector<int> ivec; copy(ilist.begin(),ilist.end(),back_inserter(ivec)); //上述例子的效率比较差,用下面方法代替 vector<int> ivec(ilist.begin(),ilist.end()); //这个调用将所有值为0的实例替换成42 replace(ilist.begin(),ilist.end,0,42); //ilist没有改变,ivec存储ilist的一份副本,在ivec中所有0都变成了42 vector<int> ivec; replace_copy(ilist.begin(),ilist.end(),back_inserter(ivec),0,42);
5、对容器元素重新排序的算法,一个实例:统计段落中使用了多少个由6个或以上的字母组成的单词。
(1) 去除重复:
sort(words.begin(),words.end()); vector<string>::iterator end_unique=unique(words.begin(),words.end()); words.erase(end_unique,words.end());
(2) 定义需要的使用函数:
bool isShorter(const string &s1,const string &s2) { return s1.size()<s2.size(); } bool GT6(const string &s) { return s.size()>=6; }
(3) 排序算法,stable_sort算法保留相等元素的原始相对位置。
stable_sort(words.begin(),words.end(),isShorter);
(4) 统计长度不小于6的单词
vector<string>::size_type wc=count_if(words.begin(),words.end(),GT6);