Chapter 3 泛型编程风格 Generic Programming
标准模板库(STL)由两个组件构成:
- 容器(container):包括vector,list,set,map等类
vector和list是顺序性容器(sequential container),即会依次维护第一个,第二个……直到最后一个元素。我们在顺序性容器上主要进行所谓的迭代(iterate)操作
map和set属于关联容器(associative container),关联容器可以让我们快速查找元素中的元素值
- 泛型算法(generic algorithm):包括find(), sort(), replace(), merge()等
泛型算法的两大特点:
1、和想要操作的元素类型无关,通过function template技术。
2、和容器彼此独立,借由一对iterator(first和last)指示迭代的元素范围。
3.1 指针的算术运算
int find(const int *array)
{
return array[2];
}
int main()
{
int array[] = {1,2,3,4,5};
cout<<find(array);
}
output:3
虽然array是以第一个元素的指针传入find()中,但仍可通过下标运算符访问array的每个元素,就如同此array是个对象(而非指针形式)一般。
这是因为所谓下标操作就是将array的起始地址加上索引值,产生出某个元素的地址,然后改地址再被提领(dereference)以返回元素值。
例如下例返回第三个元素(索引从0开始):
int main()
{
int array[] = {1,2,3,4,5};
cout<<array[2];
cout<<*(array+2);
}
output:33
另外,array+2指的是指针的算术运算,会把”指针所指的类型“的大小考虑进去。假设array的第一个元素的地址为1000,存储的数据类型为int,array+2便是array的地址加上两个整数元素的大小。有的机器的int长度为4 byte,那么这个指针运算的答案便是1008
取得元素地址后,还需要提领,对于array[2],指针的算数运算和提领操作会自动进行。
Example:
假设对于一个储存整数的vector,以及一个整数值。如果此值存在于vector内,我们必须返回一个指针指向该值;反之则返回0,表示此值并不在vector内。
int *find(vector<int> &vec,int value)
{
for(int ix=0;ix<vec.size();++ix)
{
if(vec[ix]==value)
return &vec[ix];
}
return 0;
}
为了让该函数能够处理其他理性类型(前提是该类型有相等运算符),修改为:
template<typename elemType>
elemType * find(vector<elemType> &vec,elemType value)
{
for(int ix=0;ix<vec.size();++ix)
{
if(vec[ix] == value)
return &vec[ix];
}
return 0;
}
int main()
{
int array[5] = {1,2,3,4,5};
string srray[3] = {"i","love","u"};
vector<int> vec(array,array+5);
vector<string> svec(srray,srray+3);
cout<<find(vec,4)<<" "<<*(find(vec,4))<<endl;
cout<<find(svec,{"u"})<<" "<<*(find(svec,{"u"}))<<endl;
}
output:
0x68177c 4
0x681b20 u
目前该函数还是只能处理vector,如何让其能够处理array数据类型,除了重载,还有什么方法吗?
在本例中,可将上述问题分解为:
(1)将array的元素传入find(),而非指明该array
(2)将vector的元素传入find()而不指明vector
首先解决问题(1),上面已经解释, 当数组传给函数,或是由函数中返回,仅有第一个元素的地址会被传递。
为了实现find()函数,须设法告诉函数在何处停止。有两种方法
方法一:增加一个参数表示array大小;方法二:传入另一个地址,指示array读取操作的终点。
方法一实现1:通过下标运算符访问元素
template<typename elemType>
elemType * find(elemType *array,int size,elemType value)
{
if(!array||size<1)
return 0;
for(int ix=0;ix<size;++ix)
{
if(array[ix] == value)
return &array[ix];
}
return 0;
}
int main()
{
int array[5] = {1,2,3,4,5};
string srray[3] = {"i","love","u"};
cout<<find(array,5,4)<<" "<<*(find(array,5,4))<<endl;
cout<<find(srray,3,{"u"})<<" "<<*(find(srray,3,{"u"}))<<endl;
}
方法一实现2:通过指针来进行每个元素的定位
template<typename elemType>
elemType * find(elemType *array,int size,elemType value)
{
if(!array||size<0)
return 0;
for(int ix=0;ix<size;++ix,++array)
{
if(*array == value)
return &array[ix];
}
return 0;
}
int main()
{
int array[5] = {1,2,3,4,5};
string srray[3] = {"i","love","u"};
cout<<find(array,5,4)<<" "<<*(find(array,5,4))<<endl;
cout<<find(srray,3,{"u"})<<" "<<*(find(srray,3,{"u"}))<<endl;
}
方法二实现2:用第二个指针取代参数size,该指针扮演标兵的角色。该版本可以将array的声明从参数列表中完全移除。
template<typename elemType>
elemType * find(elemType *first,elemType *last,elemType value)
{
if(!first||!last)
return 0;
for(;first!=last;++first)
{
if(*first == value)
return first;
}
return 0;
}
int main()
{
int array[5] = {1,2,3,4,5};
string srray[3] = {"i","love","u"};
cout<<find(array,array+5,4)<<" "<<*(find(array,array+5,4))<<endl;
cout<<find(srray,srray+3,{"u"})<<" "<<*(find(srray,srray+3,{"u"}))<<endl;
}
在调用方法二的时候,第二个地址标示出了数组最后一个元素的下一个地址,我们仅仅拿出了这个地址和其他元素的地址进行比较,这是合法的;但倘若对此地址进行读取或写入操作,那就不合法;
对于问题(2):vector和array都是以一块连续内存储存其所有元素,所以可采用和array一样的处理方式。但切记,vector可以是空的,array则不然,例如以下写法:
vector<string> svec;定义了一个空的储存string元素的vector
若调用如下函数
find(&svec[0],&svec[svec.size()],search_value);
会产生错误,因此需要先确认svec不为空,即
if(!svec.empty()) 繁琐
因此通常会将“取用第一个元素的地址”的操作包装为函数,如下:
template<typename elemType>
inline elemType *begin(const vector<elemType> &vec)
{
return vec.empty() ?0:&vec[0];
}
end()函数类似,返回0或最后一个元素的下一个地址。采用这种方式,便能使find()应用于任意vector之上。
find(begin(svec),end(svec),search_value);
至此,我们实现出的find()函数能够同时处理vector和array。
但该函数无法应用于list容器,之前写的函数都有一个假设:所有元素储存在连续的空间里。
而list的元素以一组指针相互链接:前向指针指向下一个元素,后向指针指向上一个元素。难道要再写一个find()函数吗?
试想:能否可以在底层指针的行为之上提供一层抽象,取代程序原本的“指针直接操作”方式。把底层指针的处理放在该抽象层中,让用户无需面对指针操作。
3.2 了解Iterator(泛型指针):
如何定义iterator呢?从上例来看,定义应该提供:
(1):迭代对象(容器)的类型,用来决定如何访问下一个元素;
(2):iterator所指的元素类型,决定iterator提领操作的返回值;因此,它可能的定义形式便是把上述两个类型作为参数,传给iterator class:
可能的形式,但并非STL的做法:
iterator<vector,string> iter;
实际语法见第四章~~~
使用例子:
vector<string>::iterator iter = vec.begin();
对于const vector:
vector<string>::const_iterator iter = cs_vec.begin();
// const_iterator 对象可以用于const vector 或非 const vector,它自身的值可以改(可以指向其他元素),
// 但不能改写其指向的元素值
// 如果你传一个const类型的容器,那么只能用const_iterator来遍历
重新实现find(),让它重新支持两种形式:即一对指针,或是一对指向某种容器的iterator
template<typename IteratorType,typename elemType>
IteratorType
find(IteratorType first,IteratorType last,elemType value)
{
for(;first!=last;++first)
if(value==*first)
return first;
return last;
}
int main()
{
string ia[3] = {"i","love","u"};
cout << find(ia,ia+3,"u")<<endl;
cout << *find(ia,ia+3,"u")<<endl;
//cout << find(ia,ia+3,{"u"})<<endl;
//cout << *find(ia,ia+3,{"u"})<<endl;
//会出错,因为
}
可以和之前的方法二实现2对比:
template<typename elemType>
elemType * find(elemType *first,elemType *last,elemType value)
{
if(!first||!last)
return 0;
for(;first!=last;++first)
{
if(value==*first)
return first;
}
return 0;
}
int main()
{
string ia[3] = {"i","love","u"};
//cout << find(ia,ia+3,"u")<<endl;
//cout << *find(ia,ia+3,"u")<<endl;
//出错
cout << find(ia,ia+3,{"u"})<<endl;
cout << *find(ia,ia+3,{"u"})<<endl;
}
to do:
int main()
{
string ia[3] = {"i","love","u"};
string x = {"u"};
cout << find(ia,ia+3,x)<<endl;
cout << *find(ia,ia+3,x)<<endl;
// 这两种方法都不会出错
cout << find(ia,ia+3,ia[3])<<endl;
cout << *find(ia,ia+3,ia[3])<<endl;
}
3.4 使用顺序性容器
定义顺序性容器的方法:
(1)产生空的容器
list<string> slist;
vector<int> ivec;
(2)产生特定大小的容器,每个元素都以其默认值作为初值(int和double这类内置的算术类型,其默认值为0)
list<int> ilist(1024);
vector<string> svec(32);
(3)产生特定大小的容器,并为每个元素指定初值
vector<int> ivec(10,-1);
list<string> slist(16,"unassigned");
(4)通过一对iterator产生容器。这对iterator用来标示一整组作为初值的元素范围
int ia[5] = {1,1,2,3,5};
vector<int> fib(ia,ia+5);
(5)根据某个容器产生出新容器。复制原容器内的元素,作为新容器的初值
list<string> slist;
list<string> slist2(slist);
两个特别的操作函数,用来在容器末尾进行插入和删除操作
即push_back()和pop_back();
另外。list和deque提供了push_front()和pop_front()在容器起始进行插入和删除
需要注意的是,两个pop函数并不会返回被删除的值
- 如果需要读取前端的值,可采用front() // 不附带删除操作
- 如果需要读取前端的值,可采用back() //不附带删除操作
上面提到 push_back(), push_front()都属于特殊化的插入操作,除了通用的插入函数 insert(),还支持四种变形:
- iterator insert(iterator position,elemType value) // 将value插入position之前,返回一个iterator指向被插入的元素
- void insert(iterator position,int count,elemType value) // 在position之前插入count个元素,值都为value
- void insert(iterator1 position, iterator2 first,iterator2 last) // 在position之前插入[first,last)所标示的各个元素
// eg:在5之前插入3,4
// int main()
// {
// int ia1[] = {1,2,5,6};
// int ia2[] = {3,4};
// list<int> elems(ia1,ia1+4);
// list<int>::iterator it = find(elems.begin(),elems.end(),5);
// elems.insert(it,ia2,ia2+2);
// display(elems.begin(),elems.end());
// }
- iterator insert(iterator position)可在position之前插入元素,元素的初值为其所属类型的默认值。
pop_front() 和 pop_back() 都属于特殊化的删除(erase)操作。每个容器除了拥有通用的删除函数erase(),还支持两种变形:
- iterator erase(iterator posit) // 可删除posit所指的元素。
- iterator erase(iterator first , iterator last) // 可删除[first,last)范围内的元素
但要注意的是,list并不支持iterator的偏移运算( 如 list.erase(it,it+num_tries))
3.5 使用泛型算法
- find()
用于搜索无序(unordered)集合中是否存在某值。搜索范围由iterator[first,last)标出。如果找到目标,返回一个iterator指向该值,否则返回一个iterator指向last
- binary_search()
用于有序(sorted)集合的搜索。如果搜索到目标,就返回true;否则返回false。比find()更有效率。因为find()属于linear search
- count()
返回数值相符的元素数目
- search()
比对某个容器内是否存在某个子序列。例如给定序列{1,3,5,7,2,9},如果搜索子序列{5,7,2},则search()会返回一个iterator指向子序列起始处。如果子序列不存在,就返回一个iterator指向容器末尾
3.6 设计一个泛型算法
任务1:给定一个整数vector,要求返回一个vector内含原vector之中小于10的整数值
vector<int> less_than_10(const vector<int> &vec)
{
vector<int> nvec;
for(int ix=0;ix<vec.size();++ix)
{
if(vec[ix]<10)
nvec.push_back(vec[ix]);
}
return nvec;
}
若改变需求,想找到所有小于11的元素,那就得将此函数通用化
vector<int> less_than(const vector<int> &vec, int less_than_val)
任务2:允许指定不同的比较操作,如大于,小于,等等
方法:可以通过函数调用来取代less-than运算符。加入第三个参数pred来指定一个函数指针,其参数列表有两个整数,返回值为bool
首先定义函数:
bool less_than(int v1,int v2)
{
return v1<v2? true:false;
}
bool greater_than(int v1,int v2)
{
return v1>v2? true:false;
}
调用方式:
vector<int> big_vec;
int value;
vector<int> lt_10 = filter(big_vec,value,less_than);
如何实现filter()函数(更新版本的less_than_10函数)?
vector<int> filter_ver1(const vector<int> &vec, int filter_value, bool (*pred)(int,int))
{
vector<int> nvec;
for(int ix=0;ix<vec.size();++ix)
if(pred(vec[ix],filter_value))
nvec.push_back(vec[ix]);
return nvec;
}
截至目前,我们通过传入filter_value和函数指针,能够指定比较的值,和比较本身的定义(eg:>,<,=,>=,...),扩展了原函数
接下来考虑使用泛型算法find_if()来取代for循环的使用,即将find_if()反复作用于数列上,找出每个符合条件的元素——所谓”条件“则由用户指定的函数指针定义;
关于反复作用的意思:在”不对任意元素进行两次以上的查看“前提下,反复地在容器身上进行 find()操作,用另一个例子说明:
// 找出每个等于10的元素
// 泛型算法 find()接受三个参数:两个 iterator标示出检测范围 [first,last),第三个参数是想要寻找的数值
// return:若找到目标,返回一个iterator指向该值,否则返回一个iterator指向last
int count_occurs(const vector<int> &vec,int val)
{
vector<int>::const_iterator iter = vec.begin();
int occurs_count = 0;
while((iter = find(iter,vec.end(),val)) != vec.end())
{
++iter;
++occurs_count;
}
return occurs_count;
}
可以看到上面的函数并没有使用for循环,而是不断地使用find()函数在容器上进行操作,同时该过程没有对任意元素进行过查看两次以上的操作。
那我们应该怎么设计find_if() 函数,让其在反复作用的基础下找到所有小于10的元素呢?
在具体设计之前,先考虑一个实际的问题——引入函数指针做法的效率不高
为了解决该问题,我们引入了**function object(函数对象)**的概念,使我们得以将某组行为传给函数,从而消除”通过函数指针来调用函数“时需付出的额外代价
Function Object
所谓Function Object,是某种class的实例对象,这类class对function call运算进行了重载操作,使得function object能被当成一般函数来使用。本质上是为了实现某种与函数等价的功能。
目的是为了效率,我们可以令call运算符成为inline,从而消除”通过函数指针来调用函数“时需付出的额外代价
标准库事先定义了一组function object。分为算术运算(arithmetic),关系运算(relational)和逻辑运算(logical)三大类,以下列表的type在实际使用中会被替换为内置类型或class类型:
六个算术运算:plus<type>, minus<type>, negate<type>, multiplies<type>, divides<type>, modules<type>
六个关系运算:less<type>, less_equal<type>, greater<type>, greater_equal<type>, equal_to<type>, not_equal_to<type>
三个逻辑运算,分别对应于&& || !运算符:logical_and<type>, logical_or<type>, logical_not<type>
当使用事先定义的function object,首先得包含相关头文件
#include <functional>
使用函数对象的一个例子,默认下,sort会使用底部元素的类型所提供的less_than运算符,将元素升序排列,如果传入greater_than function object,元素就会以降序排序:
sort( vec.begin(),vec.end(),greater<int>() );
其中的greater<int>()会产生一个未命名的class template object,传给sort()
可以看到,sort()就是之前提到的泛型算法的一种 (find()
) ,而 greater<int>()
就是函数对象。这两者不要混淆!
Function Object Adapter
上述的function object
并不那么恰好
符合find_if
的要求,比如function object less<type>
期望外界传入两个值,如果第一个值小于第二个值就返回true;而本例每个元素都必须和用户所指定的数值比较。因此,我们想把less<type>
转化为一个一元(unary)运算符,这可通过“将此第二个参数绑定(bind)至用户指定的数值”完成,这样的话less就会将每个元素拿出来一一与用户指定的数值比较。
标准库提供的adapter(适配器)应运而生。
function object adapter会对function object进行修改操作,所谓bind adapter(绑定适配器)作为函数对象适配器的一种,会将函数对象的参数绑定至某特定值,使binary function object转化为unary function object,满足我们的需求。
标准库提供了两个binder adapter:
bindlst会将指定值绑定至第一操作数
bind2nd将指定值绑定至第二操作数
以下是修改后的filter()
,使用了bind2nd adapter
vector<int> filter(const vector<int> &vec, int val, less<int> <)
{
vector<int>::const_iterator iter = vec.begin();
vector<int> nvec;
while((iter = find_if(iter,vec.end(),bind2nd(lt,val)))!=vec.end())
{
nvec.push_back(*iter);
cout<<*iter<<" ";
iter++;
}
cout<<endl;
return nvec;
}
int main()
{
const int elem_size = 8;
int ia[elem_size] = {12,8,43,0,6,21,3,7};
vector<int> ivec(ia,ia+elem_size);
less<int> func;
filter(ivec,8,func);
}
接下来要做的就是消除filter()和vector元素类型和vector容器类型的依赖关系,以使filter()更加泛型化?
- 消除元素依赖性:将元素类型加入template声明中
- 消除容器依赖性:传入一对iterator[first,last),并在参数列表中增加另外一个iterator,用以指定从何处开始复制元素
template<typename InputIterator, typename OutputIterator,
typename ElemType, typename Comp>
OutputIterator filter(InputIterator first,InputIterator last,
OutputIterator at, const ElemType &val, Comp pred)
{
while((first = find_if(first,last,bind2nd(pred,val)))!=last)
{
cout << "found value: "<< *first <<endl;
*at = *first;
++at;
++first;
// *at++ = *first++;
}
return at;
}
int main()
{
const int elem_size = 8;
int ia[elem_size] = {12,8,43,0,6,21,3,7};
vector<int> ivec(ia,ia+elem_size);
int ia2[elem_size];
vector<int> ivec2(elem_size);
cout<<"less than elem_size = "<<elem_size<<endl;
filter(ivec.begin(),ivec.end(),ivec2.begin(),elem_size,less<int>());
cout<<"greater than elem_size = "<<elem_size<<endl;
filter(ivec.begin(),ivec.end(),ivec2.begin(),elem_size,greater<int>());
}
还有一种adapter是所谓的negater
,其能够对function object
的真伪值取反,需要注意的是
not1
对unary function object
的真伪值取反
not2
对binary function object
的真伪值取反
例如要找出大于等于10的元素,可以使用not1和less<int>
while((iter = find_if(iter,vec.end(),not1(bind2nd(less<int>,10))))!=vec.end()) {}
3.7 使用Map
Map
被定义为一对(pair)数值,key
通常是个字符串,扮演索引的角色;value
则是对应的值(不一定是int)
例:编写一个对文章中每个字出现的次数加以分析的程序
// 显然可以建立一个map,带有string key 和 int value
#include<iostream>
#include<map>
#include<string>
using namespace std;
int main()
{
map<string,int> words;
string tword;
// 可以输入Ctrl+Z退出cin
while(cin>>tword)
{
words[tword]++;
}
map<string,int>::iterator it = words.begin();
for(;it!=words.end();++it)
cout<<"key="<<it->first<<" value="<<it->second<<endl;
}
map
对象有一个名为first的member,对应于key,在上例便是单字字符串;另有一个名为second的member,对应于value,对应出现次数。
查询map内是否存在某个key的三种方法:
// 方法一
int count = 0;
if(!(count = words["hang"]))
words中没有hang字符串
但注意,这种查询方法无意中把之前没有的key加入到了words中
// 方法二
// 利用map的find()函数
// 注意:不要和泛型算法的find()搞混了
words.find("hang");
// 如果key在words中,会返回一个iterator指向key/value形成的pair(pair class 是标准库的一员),反之则返回end()
int count = 0;
map<string,int>::iterator it;
it = words.find("hang");
if(it!=words.end())
count = it->second;
// 方法三
// 使用map的count()函数,count()会返回某特定项在map内的个数
int count = 0;
string search_word("hang");
if(words.count(search_word))
count = words[search_word];
任何一个key值在map内最多只有一份。如果需要储存多份相同的key值,就必须使用multimap
3.8 使用Set
Set
由一群key组合而成,若我们仅想知道某个值得是否在某个集合内,就可使用set
,例如图形遍历算法
以上例继续说明,若我不需统计一些中性词汇的出现次数,可构造一个set
#include<set>
#include<string>
int main()
{
map<string,int> words;
set<string> word_exclusion;
string tword;
while(cin>>tword)
{
if(word_exclusion.count(tword))
// 如果tword在“排除字集”内,就跳过此次迭代的剩余部分
continue;
// 一旦执行至此,表示tword并不属于“排除字集”
words[tword]++;
}
}
对于任何key值,set只能存储一份。如果要储存多份相同的key值,必须使用multiset
另外,set元素依据其所属类型默认的less_than运算符进行排列,如下:
int ia[10] = {1,3,5,8,5,3,1,5,8,1};
vector<int> vec(ia,ia+10);
set<int> iset(vec.begin(),vec.end());
iset的元素将是{1,3,5,8}
如果要为set加入单一元素,可使用单一参数的insert():
iset.insert(ival);
如果要为set加入某个范围的元素,可使用双参数的insert():
iset.insert(vec.begin(),vec.end());
泛型算法还有很多和set相关的算法:set_intersection(), set_union(), set_difference(), set_symmetric_difference()
3.9 如何使用 Iterator Inserter
考虑3.6节对filter()
的实现,将源端容器中每一个符合条件的元素一一赋值至目的端容器
while ((first = find_if(first, last, bind2nd(pred, val))) != last)
*at++ = *first++;
这种形式下,目的端容器必须足够大,以储存被赋值进来的每个元素,filter()无法知道每次对at递增后,at是否仍指向一个有效的容器位置。因此,在3.6节的测试程序中,我们设定了目的端容器的大小
const int elem_size = 8;
int ia[elem_size] = {12,8,43,0,6,21,3,7};
vector<int> ivec(ia,ia+elem_size);
vector<int> ivec2(elem_size);
filter(ivec.begin(),ivec.end(),ivec2.begin(),elem_size,less<int>());
实际上,所有“会对元素进行复制行为的泛型算法”,例如
copy(), copy_backwards(), remove_copy(), replace_copy(), unique_copy()
等等,都和filter的实现极其相似。每个算法都接受一个iterator,标示出复制的起始位置。每复制一个元素,都会被赋值,iterator则会递增至下个位置。我们必须保证在每一次赋值操作中,目的端容器都足够大,可以储存这些被赋值进来的元素。
但这并不意味我们必须总是传入某个固定大小的容器至上述算法。
标准库提供了三个所谓的insertion adapter,这些adapter可以让我们避免使用容器的assignment运算符:
欲使用这三种adapter,首先必须包含iterator
back_insertor()
会以容器的push_back()函数取代assignment运算符。对vector来说,这是比较适合的inserter。传入back_insertor() 的参数,应该就是容器本身:
vector<int> result_vec;
unique_copy(ivec.begin(),ivec.end(),back_inserter(result_vec));
这会把ivec的元素复制到result_vec中而不需要提前定义result_vec容器的大小
inserter()
会以容器的insert()函数取代assignment运算符。inserter()接受两个参数;一个是容器,一个是iterator,指向容器的插入操作起点,以vector为例:
vector<string> svec_res;
unique_copy(svec.begin(),svec.end(),inserter(svec_res,svec_res.end()));
int ia[10] = {1,3,5,8,5,3,1,5,8,1};
int ia2[2] = {111,222};
vector<int> result_vec(ia2,ia2+2);
vector<int> vec(ia,ia+10);
unique_copy(vec.begin(),vec.end(),inserter(result_vec,result_vec.begin()+1));
vector<int>::iterator it = result_vec.begin();
for(;it!=result_vec.end();++it)
{
cout<<*it<<" ";
}
// 111 1 3 5 8 5 3 1 5 8 1 222
// 可以看到这种方法留给coder的操作空间更大,而上面的方法只能插入到最后
front_inserter()
会以容器的push_front()函数代替assignment运算符,这个inserter适用于list和deque
list<int> ilist_clone;
copy(ilist.begin(),ilist.end(),front_inserter(ilist_clone));
int ia[10] = {1,3,5,8,5,3,1,5,8,1};
list<int> ilist(ia,ia+10);
list<int> ilist_clone;
copy(ilist.begin(),ilist.end(),front_inserter(ilist_clone));
list<int>::iterator it = ilist_clone.begin();
for(;it!=ilist_clone.end();++it)
{
cout<<*it<<" ";
}
// 1 8 5 1 3 5 8 5 3 1 ,这里的元素顺序和ilist是相反的,因为push_front是在容器起始处进行插入操作的,因此最后面的元素反而会到新容器的最前面
// 用filter()函数测试本节的内容:
template<typename InputIterator, typename OutputIterator,
typename ElemType, typename Comp>
OutputIterator filter(InputIterator first,InputIterator last,
OutputIterator at, const ElemType &val, Comp pred)
{
while((first = find_if(first,last,bind2nd(pred,val)))!=last)
{
cout << "found value: "<< *first <<endl;
*at++ = *first++;
// 使用插入迭代器会把赋值操作改变为其它操作
}
return at;
}
需要注意的是adapter并不能用在array上,array并不支持元素插入操作。
const int elem_size = 8;
int ia[elem_size] = {12,8,43,0,6,21,3,7};
vector<int> ivec(ia,ia+elem_size);
int ia2[elem_size];
// vector<int> ivec2(elem_size);
// 原例是上面的,但通过本节学习,已经不需要提前定义容器了
vector<int> ivec2;
cout<<"less than elem_size = "<<elem_size<<endl;
// 因为adapter不能用在array上,因此这里不能对ia2用iterator inserter
filter(ia,ia+elem_size,ia2,elem_size,less<int>());
cout<<"greater than elem_size = "<<elem_size<<endl;
// 由于没设定ivec2的大小,所以使用原来的ivec2.begin(),赋值操作会产生运行时错误。但我们将ivec2传给back_iterator,元素的赋值操作几倍替换为插入操作。因为只在末端插入效率较高,因此选用back_inserter
filter(ivec.begin(),ivec.end(),back_inserter(ivec2),elem_size,greater<int>());
3.10 使用 iostream Iterator
假设一个新任务:从标准输入设备读取一串string元素,将他们存到vector内,并进行排序,最后再将这些字符串写回标准输出设备
string word;
vector<string> text;
// 符合直觉的实现方式
while(cin>>word)
text.push_back(word);
sort(text.begin(),text.end());
for(int ix=0;ix<text.size();++ix)
cout << text[ix] <' ';
// 以下使用iostream Iterator解决问题
标准库定义有供输入输出使用的iostream iterator类,称为istream_iterator
和ostream_iterator
,分别支持单一类型的元素读取和写入。使用这两个iterator class之前,须包含iterator头文件:#include <iterator>
如何从利用istream_iterator从标准输入设备读取字符串?首先需要一对iterator,first和last来标示元素范围
istream_iterator<string> is(cin);
提供了一个first iterator,其将is定义为一个“绑至标准输入设备”的istream_iterator
还需要一个last iterator来表示“要读取的元素的下一个位置”,对标准输入设备而言,end-of-file即代表last,而只需在定义istream_iterator时不为它指定istream对象,它便代表了end-of-file,例如
istream_iterator<string> eof;
下例将他们和储存字符串元素的vector一起传给泛型算法copy(),由于不知道该为vector保留多少空间,因此选用了上节提到的back_inserter:
copy(is, eof, back_inserter(text));
现在需要一个ostream_iterator,标示字符串元素的输出位置。一旦没有元素需要输出就停止输出操作。
以下代码将os定义为一个“绑定至标准输出设备”的ostream_iterator,此标准输出设备供我们输出类型为string的元素
ostream_iterator<string> os(cout, " ");
上述的第二个参数可以是C-style字符串,也可以是字符串常量,表示各个元素被输出时的分隔符。
可能的使用方式为:
copy(text.begin(), text.end(), os)
copy()会将储存在text中的每个元素一一写到由os表示的ostream上
完整实现如下:
int main()
{
istream_iterator<string> is(cin);
istream_iterator<string> eof;
vector<string> text;
copy(is, eof, back_inserter(text));
sort(text.begin(), text.end());
// sort(text.begin(), text.end(),greater<string>());
ostream_iterator<string> os(cout, " ");
copy(text.begin(), text.end(), os);
}
现在基本已经完成了,然而还有一个之前遇到过的问题——我们常常并不是要从标准输入设备读数据,也不是要写到标准输出设备上,而是希望从文件中读取,写到文件去,how to do it?
只需把istream_iterator绑定至ifstream object,将ostream_iterator绑定至ofstream object即可:
#include<fstream>
int main()
{
ifstream in_file(".vscode\\chapter3\\input_file.txt");
ofstream out_file(".vscode\\chapter3\\output_file.txt");
if(!in_file || !out_file)
{
cerr << "Unable to open the files.\n";
return -1;
}
// 改动1
istream_iterator<string> is(in_file);
istream_iterator<string> eof;
vector<string> text;
copy(is, eof, back_inserter(text));
sort(text.begin(), text.end());
// sort(text.begin(), text.end(),greater<string>());
// 改动2
ostream_iterator<string> os(out_file, " ");
copy(text.begin(), text.end(), os);
}