STL的关键组件包括容器(container)、迭代器(iterator)及算法(algorithm),本文就这3部分内容进行总结。
容器按照类型分为序列式容器和关联式容器,其中序列式容器包括vector、deque、list等可序群集,关联式容器包括set、multiset、map、multimap等。
vector实现采用动态数组方式,可以直接存取任意元素,支持在尾部插入和删除元素且实现速度快,在数组前端和内部插入和删除元素效率较低,因为涉及到修改元素后面的所有元素位置发生改变,具体使用实例如下所示:
#include <iostream>
#include <vector>
using namespace std;
int main(){
vector<char> a;
for(int i=0;i<5;i++){
a.push_back('a'+i);
}
for(int i=0;i<5;i++){
cout<<a[i]<<" ";
}
cout<<endl;
vector<int> b(5,19);
b.insert(b.begin(),17);
for(int i=0;i<6;i++){
cout<<b[i]<<" ";
}
cout<<endl;
return 0;
}
deque是double-ended queue的缩写,表示双端队列,也是采用动态数组实现,支持随机存取任何元素,可以在前端和尾部插入元素且效率高,在内部插入元素需要移动其他元素效率较低,具体使用实例如下所示:
#include <iostream>
#include <deque>
using namespace std;
int main(){
deque<char> a;
for(int i=0;i<5;i++){
a.push_back('a'+i);
a.push_front('H'+i);
}
for(int i=0;i<10;i++){
cout<<a[i]<<" ";
}
cout<<endl;
deque<int> b(5,19);
b.insert(b.begin()+2,17);
b.insert(b.end()-3,16);
for(int i=0;i<7;i++){
cout<<b[i]<<" ";
}
cout<<endl;
return 0;
}
list为双向链表,每个元素内存保存有数据及前后元素指针,不支持随机存取,只能通过遍历的方式访问元素,其优点在于在list内部插入或删除元素效率较高,只需改变前节点的next和后节点的prev,其他元素不受影响,因此效率较高,具体使用实例如下:
#include <iostream>
#include <list>
using namespace std;
int main(){
list<char> a;
for(int i=0;i<5;i++){
a.push_back('a'+i);
a.push_front('H'+i);
}
for(list<char>::iterator it=a.begin();it!=a.end();it++){
cout<<*it<<" ";
}
a.erase(a.begin(),a.end());
cout<<endl<<a.size();
cout<<endl;
list<int> b(5,19);
b.insert(++b.begin(),17);
b.insert(--b.end(),16);
for(list<int>::iterator it=b.begin();it!=b.end();it++){
cout<<*b.begin()<<" ";
b.pop_front();
b.pop_back();
}
cout<<endl;
return 0;
}
此外,string和array虽然不是容器类,但是也可以视为STL容器来使用STL算法。
关联式容器依据特定的排序准则,自动为其元素排序,排序准则以函数形式实现,比较值或者键,缺省情况下采用operator<来比较,通常采用二叉树结构存储,节点的左子树小于该元素,右子树大于该元素。关联式容器不支持push_back、push_front、pop_front、pop_back等操作,因为其是已序序列,程序员不能自己给其设定位置,插入只需使用insert,删除使用erase。set中每个元素存储一个变量,而map存储一个pair对组(2个变量),multi-前缀主要用来说明元素是可以重复的。具体的使用实例如下所示:
#include <iostream>
#include <set>
//#include <multiset>
#include <map>
//#include <multimap>
using namespace std;
int main(){
set<char> a;
for(int i=0;i<5;i++){
a.insert('a'-i);
a.insert('a'-i);
}
for(set<char>::iterator it=a.begin();it!=a.end();it++){
cout<<*it<<" ";
}
cout<<endl;
multiset<char> b;
b.insert('s');
b.insert('a');
b.insert('s');
b.insert('h');
b.insert('e');
for(set<char>::iterator it=b.begin();it!=b.end();it++){
cout<<*it<<" ";
}
cout<<endl;
map<int,string> c;
c.insert(make_pair(5,"hello"));
c.insert(make_pair(4,"nihao"));
c[3]="good afternoon";
c[5]="hello!";
c[3]="good afternoon";
c[3]="good";
c[6]="nihao";
for(map<int,string>::iterator it=c.begin();it!=c.end();it++){
cout<<it->first<<" "<<it->second<<endl;
}
map<string,int> e;
e["haha"]=78;
multimap<string,int> d;
d.insert(make_pair("alex",111));
d.insert(make_pair("alex",111));
d.insert(make_pair("alex",101));
d.insert(make_pair("ace",111));
//d["James"]=23;
//d["alex"]=111;
//d["alex"]=123;
for(multimap<string,int>::const_iterator it=d.begin();it!=d.end();it++){
cout<<it->first<<" "<<it->second<<endl;
}
cout<<endl;
d.erase("alex");
for(multimap<string,int>::const_iterator it=d.begin();it!=d.end();it++){
cout<<it->first<<" "<<it->second<<endl;
}
return 0;
}
由以上可以测试得知,不存在multimap与multiset头文件,使用它们时只需包含map与set头文件即可,map描述了一对一的关系,键不能重复,multimap描述了多对多的关系,键可以重复。map支持operator[]操作符,而multimap因为其键不唯一,由于不确定性所以不支持operator[]操作,可以看出关联式容器给自动排序,erase函数可以根据键和迭代器。
迭代器分为两类:双向迭代器和随机存取迭代器,list、map、multimap、set、multiset等提供的是双向迭代器,可以双向前进即operator++、operator--,但不能随机访问即operator+、operator-。vector、deque、string等提供的是随机存取迭代器,支持随机访问。
为了处理容器内的元素,STL提供了一些标准算法,包括搜寻、排序、拷贝、重新排序、修改、数值运算等,算法并非容器的成员函数,而是一种搭配迭代器使用的全局函数,因此只需提供一份算法,可应用于所有容器类,因此降低了代码的数量,这也是泛型编程的思想即数据结构与函数分离。
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
int main(){
vector<int> x;
x.push_back(2);
x.push_back(5);
x.push_back(3);
x.push_back(6);
x.push_back(1);
x.push_back(4);
vector<int>::iterator it;
it=min_element(x.begin(),x.end());
cout<<*it<<endl;
it=max_element(x.begin(),x.end());
cout<<*it<<endl;
for(it=x.begin();it!=x.end();it++){
cout<<*it<<endl;
}
sort(x.begin(),x.end());
for(it=x.begin();it!=x.end();it++){
cout<<*it<<endl;
}
it=find(x.begin(),x.end(),3);
reverse(it,x.end());
for(it=x.begin();it!=x.end();it++){
cout<<*it<<endl;
}
return 0;
}
从以上程序可以看出,min_element和max_element通过传入两个迭代器指针,获得迭代器之间的最小和最大元素的迭代器指针,使用sort对选定的两个迭代器之间的元素进行排序,find函数在指定的区间查找某个元素并返回其所在迭代器指针,reverse函数翻转指定迭代器范围内的所有元素。STL函数规定采用相同的接口表示范围,这种范围使用前闭后开区间表示,且前后顺序不能颠倒。当处理两个区间时,只需一个区间前后两端迭代器,另一个区间只需前端迭代器。
if(equal(x.begin(),x.end(),y.begin())){
cout<<"true"<<endl;
}
else
cout<<"false"<<endl;
copy(x.begin(),x.end(),y.begin());
for(it=y.begin();it!=y.end();it++){
cout<<*it<<endl;
}
x.resize(3);
for(it=x.begin();it!=x.end();it++){
cout<<*it<<endl;
}
以上程序为equal、copy、resize等函数的测试实例。迭代器配接器包括安插性迭代器(Insert iterator)、流迭代器(Stream iterator)、逆向迭代器(Reverse iterator)。
首先介绍3种安插性迭代器back_inserter、front_inserter、inserter,如下图所示:
#include <iostream>
#include <deque>
#include <algorithm>
using namespace std;
int main(){<span style="white-space:pre"> </span>
<span style="white-space:pre"> </span>deque<int> x;
<span style="white-space:pre"> </span>x.push_back(2);
<span style="white-space:pre"> </span>x.push_back(5);
<span style="white-space:pre"> </span>x.push_back(3);
<span style="white-space:pre"> </span>x.push_back(6);
<span style="white-space:pre"> </span>x.push_back(1);
<span style="white-space:pre"> </span>x.push_back(4);
<span style="white-space:pre"> </span>deque<int> y;
<span style="white-space:pre"> </span>deque<int>::iterator it;
<span style="white-space:pre"> </span>cout<<"x: ";
<span style="white-space:pre"> </span>for(it=x.begin();it!=x.end();it++){
<span style="white-space:pre"> </span>cout<<*it<<" ";
<span style="white-space:pre"> </span>}
<span style="white-space:pre"> </span>cout<<endl;
<span style="white-space:pre"> </span>y.resize(x.size());
<span style="white-space:pre"> </span>copy(x.begin(),x.end(),y.begin());
<span style="white-space:pre"> </span>cout<<"y: ";
<span style="white-space:pre"> </span>for(it=y.begin();it!=y.end();it++){
<span style="white-space:pre"> </span>cout<<*it<<" ";
<span style="white-space:pre"> </span>}
<span style="white-space:pre"> </span>cout<<endl;
<span style="white-space:pre"> </span>y.push_back(0);
<span style="white-space:pre"> </span>cout<<"push(0) y: ";
<span style="white-space:pre"> </span>for(it=y.begin();it!=y.end();it++){
<span style="white-space:pre"> </span>cout<<*it<<" ";
<span style="white-space:pre"> </span>}
<span style="white-space:pre"> </span>cout<<endl;
<span style="white-space:pre"> </span>copy(x.begin(),x.end(),inserter(y,y.begin()+1));
<span style="white-space:pre"> </span>cout<<"inserter: ";
<span style="white-space:pre"> </span>for(it=y.begin();it!=y.end();it++){
<span style="white-space:pre"> </span>cout<<*it<<" ";
<span style="white-space:pre"> </span>}
<span style="white-space:pre"> </span>cout<<endl;
<span style="white-space:pre"> </span>copy(x.begin(),x.end(),back_inserter(y));
<span style="white-space:pre"> </span>cout<<"back_inserter: ";
<span style="white-space:pre"> </span>for(it=y.begin();it!=y.end();it++){
<span style="white-space:pre"> </span>cout<<*it<<" ";
<span style="white-space:pre"> </span>}
<span style="white-space:pre"> </span>cout<<endl;
<span style="white-space:pre"> </span>copy(x.begin(),x.end(),front_inserter(y));
<span style="white-space:pre"> </span>cout<<"front_inserter: ";
<span style="white-space:pre"> </span>for(it=y.begin();it!=y.end();it++){
<span style="white-space:pre"> </span>cout<<*it<<" ";
<span style="white-space:pre"> </span>}
<span style="white-space:pre"> </span>cout<<endl;<span style="white-space:pre"> </span>
<span style="white-space:pre"> </span>return 0;<span style="white-space:pre"> </span>
}
以上的测试实例可以看出,copy容器时,需resize容器的大小,否则拷贝失败,调用copy函数时,若第三个参数不直接选择目标容器的迭代器指针,也可使用安插性迭代器,使用inserter时,即调用insert函数,所以还需给出插入位置迭代器指针,使用back_inserter时,即调用push_back,同理使用front_inserter时,即调用push_front。不过要注意,map、set等关联性容器不支持push_back和push_front操作,所以只能使用inserter,vector不能使用push_front,所以。。
流迭代器包括istream_iterator和ostream_iterator两种,具体使用实例如下所示:
#include <iostream>
#include <string>
#include <vector>
#include <algorithm>
#include <iterator>
using namespace std;
int main(){
vector<string> coll;
copy(istream_iterator<string> (cin),istream_iterator<string> (),back_inserter(coll));
sort(coll.begin(),coll.end());
unique_copy(coll.begin(),coll.end(),ostream_iterator<string> (cout,"\n"));
return 0;
}
注意,使用流迭代器时需要包含头文件<iterator>,这点与安插性迭代器不同,此外,unique_copy函数去除了相邻的相同元素,因此一般搭配sort函数一起使用。
除了安插性迭代器和流迭代器,还有一个逆向迭代器,主要是容器内预定义的rbegin和rend函数,注意rbegin和end-1相等,rend和begin-1相等。
下面介绍更易型算法,remove函数的程序实例如下:
#include <iostream>
#include <string>
#include <vector>
#include <algorithm>
#include <iterator>
using namespace std;
int main(){
vector<string> coll;
coll.push_back("nihao");
coll.push_back("hello");
coll.push_back("how are you");
vector<string>::iterator end=remove(coll.begin(),coll.end(),"hello");
cout<<distance(end,coll.end())<<endl;
copy(coll.begin(),coll.end(),ostream_iterator<string>(cout,"\n"));
cout<<coll.size()<<endl;
copy(coll.begin(),end,ostream_iterator<string>(cout,"\n"));
coll.erase(end,coll.end());
for(vector<string>::iterator it=coll.begin();it!=coll.end();it++){
cout<<*it<<endl;
}
return 0;
}
remove函数将移除的元素放到了容器的尾部,并不改变容器的大小和begin及end迭代器指针,返回一个新的end迭代器,可以利用distance函数计算随机访问迭代器的距离,若想彻底删除元素可使用容器的erase成员函数。关联式容器是已序的,所以不能使用remove,删除只能用erase函数。基本容器类都定义了自己的更易型成员函数如erase,所以尽量使用容器类定义的成员函数,防止出现意外情况。
一天的时间,STL正式入门阶段,加油。。。