Essential C++ Chapter3 学习记录(泛型编程风格)

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> &lt)
{
    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的真伪值取反,需要注意的是

not1unary function object的真伪值取反

not2binary 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_iteratorostream_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);
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值