C++primer 第10章 泛型算法
10.1概述
迭代器算法不会依赖容器,体现其泛型,但算法会依赖元素类型的操作,如排序算法sort默认使用<运算符,当然可以用运算符重载来自定义,或者使用sort的带谓词的重载版本,通过函数指针实现自定义。
算法永远不会执行容器的操作,只会对迭代器操作,从而导致算法永远不会改变底层容器的大小,算法可能改变容器中保存的元素,也可能在容器中移动元素,但永远不会直接添加或删减元素( 笔者猜测这可能是迭代器支持的运算符决定的,而默认情况下的迭代器不会具有添加和删除功能,之后我们学习的插入迭代器,通过重载迭代器支持的‘=’运算符使其具有插入功能。)
10.2初始泛型算法
- 泛型算法结构
输入迭代器:只读,不写;单遍扫描,只能递增
输出迭代器:只写,不读;单遍扫描,只能递增
前向迭代器:可读写;多遍扫描,只能递增
双向迭代器:可读写;多变扫描,可递增递减
随机访问迭代器:可读写;多遍扫描,支持全部迭代器运算
除了输出迭代器之外,一个高层类别的跌大气支持低层类别的迭代器的所有操作,c++标准指明了泛型和数值算法的每个迭代器参数的最小类别
- 输入迭代器
支持:
- 用于比较两个迭代器的相等和不相等(==,!=)
- 用于推进迭代器的前置和后置递增操作
- 用于读取元素的解引用运算符(*)
- ->运算符,提取对象成员
输入迭代器只能用于顺序访问,对于一个输入迭代器,*it++保证是有效的,但递增它可能导致其他指向流的迭代器失效(因为流管理一个缓冲区,迭代器递增,会导致原先位置失效),其结果就是,不能保证输入迭代器的状态可以保存下来,因此只能用于单遍扫描;显然istram_iterator是一种输入迭代器。
- 输出迭代器
支持:
用于推进迭代器的前置和后置递增操作
解引用运算符(*),向一个解引用额输出迭代器赋值,就是将值写入它所指向的元素
只能向输出迭代器复制一次,所以只能单遍扫描,ostream_iterator类型是输出迭代器。
- 前向迭代器
只能向一个方向移动,但是可以多次读写同一个元素,所以可以保存前向迭代器的状态,forward_list上的迭代器是前向迭代器。
- 双向迭代器
可以正向反向读写序列中的元素,reverse要求双向迭代器。
- 随机访问迭代器
除了支持双向迭代器的所有功能,还支持:
- 用于比较两个迭代器相对位置的关系运算符(<,>,<=,>=)
- 迭代器和一个整数的加减运算,计算结果是迭代器在序列中前进或后退的给定运算后的位置
- 用于两个迭代器上的减法运算符-,得到两个迭代器的距离
- 下标运算符(iter[n]),与*(iter[n])等价
顺序存储的容器的迭代器,访问内置数组的元素的指针也是
特别值得注意的是:向目标迭代器写入数据,算法都是假定目标空间能够容纳写入的数据,如果是指向容器的迭代器,则使用绑定容器的插入迭代器或是一个ostream_iterator
几种特殊的迭代器
- 插入迭代器
it=t :在it指定的位置插入值t,插入前需绑定容器,,根据迭代器的类型的不同,在赋值时调用push_back,push_front,insert
注意:*it,++it,it++,这些操作虽然存在,只是为了保持使用的一致性,但是不会对it做任何事情,每个操作都返回It,这个可以通过运算符重载实现
类型:
back_inserter:创建一个使用push_back的迭代器
front_inserter:创建一个使用push_front的迭代器
inserter:创建一个使用insert的迭代器,有两个参数,这个参数必须是一个指向给定容器的迭代器,元素被插入给定元素之前
当然,只有容器有相应的操作,插入迭代器才能绑定。
- 流迭代器
isteam_iterator
istream_iterator要读取的类型必须定义了输入运算符,创建时可以绑co定一个流,默认初始化时,创建了一个可以当作尾后值使用的迭代器
下面是一个从标准输入读取数据,存入一个vector的例子:istream_iterator<int> int_iter(cin);//从cin读取int istream_iterator<int> int_eof;//istream尾后迭代器 whihe(int_it!=int_eof) { vec.push_back(*iter++); }
C++标准库中的实现保证的是,在第一次的解引用迭代器之前,从流中读取数据的操作已经完成,这导致值得注意的是如果我们创建了一个istream-iterator,没有使用就销毁了,或者我们正在从两个不同的对象同步读取一个流,合适读取可能就很重要了。
ostream_iterator
我们可以对任何具有输出运算符(<<运算符)的类型定>ostream_iterator。
当创建一个ostream_iterator时,我们可以提供(可选的)第二参数,它是一个字符串,在输出每个元素后都会打印此字符串。此字符串必须是一个C风格字符串(即,一个字符串字面值常量或者一个指向以空字符结尾的字符数组指针)。必须将ostream_iterator绑定到一个指定的流,不允许空的或表示尾后位置的ostream_iterator。
- 反向迭代器
普通迭代器和反向迭代器均反映左闭合区间,为了实现表示相同的范围
[c.rbegin,rit)和[it.base,it.end),导致it.base和rit必须是相邻而不是相等,也导致了一个结果:用一个普通
10.3算法定制操作
算法会有接受谓词的版本,在排序算法中,自定义比较大小函数,例如sort(begin,end,compare),在查找或者替换算法中,自定义相等函数find_if(begin,end,if)
lambda表达式
引入lambda表达式可以解决泛型算法只能接受一元谓词和二元谓词(谓词是一个可调用表达式,其返回结果是一个能用作条件的值)的问题,当然也可以使用bind函数,达到同样的目的,当定义一个lambda表达式时,编译器生成一个lambda对应的新的未命名的类类型,目前可以这样理解,当向一个函数传递了一个lambda表达式,同时定义了一个新类型,和该类型都包含了一个的一个对象,传递的参数卆编译器生成的类类型的未命名的可调用对象,默认情况下,从lambda生成的类都包含了捕获列表中变量对应的数据成员,当然创建时,数据成员拷贝初始化。
lambda表达式形式:
[capture list](parameter list) ->return type{ function body}
可以忽略参数列表和返回类型,当未指定返回类型,如果函数体只有一个return语句,可根据函数体中的代码分析返回类型,否则返回类型为void,捕获列表只用于局部非static变量,lambda可以直接使用局部static变量和它所在函数之外声明的名字;
捕获方式有值捕获,引用捕获;
隐式捕获,编译器自己根据函数体内推断使用了哪些变量,[=]表示值捕获,[&]表示引用捕获,还可以混合使用隐式捕,显示获和显示捕获,捕获列表中的一个元素必须是=或&,显示捕获的变量捕获方式必须和隐式捕获不同