type
status
date
slug
summary
tags
category
icon
password
Property
大多数算法会比较容器中的元素。默认情况下算法使用的是
<
或 ==
操作符。算法还定义了允许我们提供自己的操作来替换默认操作符的版本。比如
sort
算法使用的是 <
操作符。但序列可能不是按照<
进行排序,或者有些类型没有<
操作符,在这两种情况下都需要重载默认的 sort
行为。向算法传递函数
对于字符串序列进行排序可以是先按照长度进行排序,对于长度一样的再按照字典顺序进行排序。为了这样做需要使用第二个版本的
sort
,这个版本的 sort
是重载过的,它有第三个参数称之为谓词(predicate)。谓词
谓词是一个可以被调用然后返回一个值的表达式,返回值可以作为条件使用。标准库使用的谓词可以是一元谓词(只有一个参数),也可以是二元谓词(两个参数)。接受谓词参数的算法对输入序列中的元素上调用这个谓词。因而,必须要可以将元素类型转为谓词的参数类型。
接受二元谓词参数的
sort
版本将给定谓词替换 <
操作用于比较元素。提供给 sort
算法的谓词必须满足一些限制,现在只需要知道这个操作必须在每一个元素之间提供稳定的顺序。单纯比较字符串的长度就是其中一个例子:排序算法
当使用长度进行排序时,还希望保持相同长度的元素之间的字典排序。为了达到这样的效果,使用
stable_sort
算法,稳定排序将维持相等元素的原有顺序。通过调用
stable_sort
可以保持相同长度的字符串的字典顺序:lambda 表达式
传递给算法的谓词必须有一个或两个参数,但是有时想传递多于算法的谓词需要的参数。
为了查找字符串序列中大于等于给定长度的字符串:
使用
find_if
标准库算法来查找一个元素大于等于给定长度的。find_if
接收一对迭代器表示输入范围,不同于 find
的是它接收的第三个参数是谓词。find_if
算法在每个元素上调用给定的谓词。它将返回第一个使得谓词的返回值为非 0 值的元素迭代器,或者当无法找到这样的元素时返回尾部迭代器。由于
find_if
接收的是一元谓词,任何传递给find_if
的函数必须只有一个参数。所以想要传递可变的size
参数给谓词必须使用lambda
表达式。lambda
可以传递任何可调用对象给算法。如果可以给对象或表达式运用调用操作符,它就是可调用的(callable)。也就是说如果 e 是可调用的表达式,那么可以写作
e(args)
其中 args 是一个逗号分隔的零个或多个参数的列表。之前使用过的可调用对象就是函数和函数指针。还有两种可调用对象:重载了调用操作符的类,以及
lambda
表达式。lambda
表达式表示可调用的代码单元。可以被视作匿名内联函数。与任何函数一样,lambda
有返回类型,参数列表和函数体。与函数不同的是,lambda
可以被定义在函数中,其有如下形式:[capture list]
: 捕获列表,该列表总是出现在lambda
函数的开始位置,编译器根据[]
来判断接下来的代码是否为lambda
函数,捕捉列表能够捕捉上下文中的变量供lambda
函数使用。
(parameter list)
:参数列表。与普通函数的参数列表一致,如果不需要参数传递,则可以连同()
一起省略
mutable
:默认情况下,lambda
函数总是一个const
函数,mutable
可以取消其常量性。使用该修饰符时,参数列表不可省略
>return type
:返回值类型。用追踪返回类型形式声明函数的返回值类型,没有返回值时此部分可省略。返回值类型明确情况下,也可省略,由编译器对返回类型进行推导。
{statement}
:函数体。在该函数体内,除了可以使用其参数外,还可以使用所有捕获到的变量。
可以忽略参数列表和返回类型,但是必须总是包含捕获列表和函数体:
使用
lambda
的方式与任何函数调用都是一样的:省略
lambda
中的括号和参数列表表示其参数列表为空。如果省略返回值类型,lambda
通过函数体中语句进行推断。如果函数体中只有一个
return
语句,那么返回类型就是被返回的表达式的类型。如果包含了除了返回语句之外的任何语句,且未指定返回类型,则返回void
。传递参数给lambda
与普通的函数调用一样,
lambda
调用中的实参也是用于初始化其形参的。实参和形参的类型必须匹配。与普通函数不同的是,lambda
没有默认实参。因而,调用lambda
给足实参。一旦形参被初始化,函数体就开始执行。如下
lambda
需要传递参数:空的捕获列表表示不使用任何外围函数的局部变量。
lambda
的参数与常规函数的参数一样是const string
的引用。可以重写 stable_sort
的调用从而使用 lambda
:当
stable_sort
需要比较元素值时,它将调用给定的lambda
表达式。使用捕获列表
上面使用的
find_if
中的 lambda
表达式中的捕获列表中的 sz
。尽管lambda
被定义在函数中,只有被指定了想要使用的外围函数本地变量才能使用。lambda
通过捕获列表来指定想要使用的变量。捕获列表中包含了 lambda
用于访问外围函数本地变量的信息。这样
find_if
使用的 lambda
表达式捕获了 sz
,并且只有一个参数,函数体中将给定string
的长度与捕获值sz
进行比较:在
[]
中是逗号分隔的捕获列表,里边的名字是外围函数中定义的本地变量。由于这个lambda
捕获了sz
,函数体就可以使用sz
,而没有捕获words
就不能访问此变量。注:
lambda
只能使用出现在捕获列表中的外围函数本地变量。for_each
算法
for_each
算法有一个可调用对象并在输入范围内的每个元素上调用此对象:注:这里并没有捕获
cout
,捕获列表只用于捕获外围函数中定义的非静态变量。lambda
可以自由使用定义在函数外的变量,此处cout
并不是一个本地变量;这个名字定义在 iostream
头文件中。只要包含了 iostream
头文件,这个 lambda
就可以使用 cout
。捕获列表只用于捕获外围函数中定义的非静态变量,
lambda
可以自由使用本地static
变量或者定义在函数体外的变量。lambda 捕获和返回
当定义
lambda
时,编译器为此lambda
产生一个匿名的新的类类型。当向一个函数传递一个lambda
时,同时定义了一个新类型和该类型的一个对象:传递的参数就是此编译器生成的类类型的未命名对象。类似的,当使用auto
定义一个用lambda
初始化的变量时,定义了一个从lambda
生成的类型对象。默认情况下,从
lambda
中生成的类都包含一个对应该lambda
所捕获的变量的数据成员。类似任何普通类的数据成员,lambda
的数据成员也在lambda
对象创建时被初始化。以下是所有的捕获方式:
值捕获
与参数传递一样,可以通过值或引用捕获变量。目前使用到的是值捕获。值捕获中的变量必须是可拷贝的。与参数不同的是,捕获变量是在
lambda
创建时拷贝的,而不是在调用时:由于在创建
lambda
时已经拷贝了值,因而接下来对捕获变量的修改不会影响 lambda
中对应的值引用捕获
可以定义以引用捕获变量的
lambda
:v1
前的&
表示 v1
是引用捕获的。被引用捕获的变量与其它引用是一样的。当在 lambda
函数体中使用引用捕获的变量时,使用的是这个引用绑定的对象。这个例子中返回的 v1
其实是其绑定的外围函数中的本地变量的值。使用引用捕获的变量跟常规的使用引用变量一样,当
lambda
被调用时需要确保引用捕获的变量确实存在。被 lambda
捕获的变量是本地变量,这些变量在函数完成时将消失。如果在函数结束后还可以调用 lambda
,那么将是严重的编程错误。引用捕获有时是必须的,比如不可复制的对象:
可以从函数中返回
lambda
,函数可以直接返回可调用对象或者函数返回一个具有可调用对象数据成员类对象。如果函数返回 lambda
,那么与函数不能返回本地变量的引用一样,这个 lambda
一定不能包含引用捕获。注:当进行引用捕获时,一定要保证当
lambda
执行时变量是存在的。建议:保持 lambda 的捕获尽量的简单
lambda
捕获的信息在创建时保存,在执行时进行访问。保证这些信息的可用性是程序员自己的责任。按值的方式捕获 int ,string 和其它非指针变量是很简单的。这个时候,我们只需要关心在捕获时变量是否有我们需要的值。如果捕获指针或迭代器或者按引用捕获,我们需要保证绑定到迭代器,指针和引用的对象在 lambda 执行时依然存在。甚至需要保证这个对象值是合理的。在 lambda 创建和执行之间的代码可能会改变按引用捕获的对象值。可能在创建时的值的确是我们想要的,但是当执行时其值已经变得很不一样了。
为了避免由捕获带来的问题尽可能缩小捕获的变量,并且尽可能避免捕获指针,迭代器和引用。
隐式捕获
相比于显式列举需要捕获的变量,可以让编译器对
lambda
函数体的代码进行推断。为了引导编译去推断捕获列表,在捕获列表中使用 &
或 =
,其中 &
告知编译器按引用捕获,=
告知编译按值捕获。如以下是重写的 find_if
:如果想让其中的一些变量按值捕获,可以混用隐式和显式捕获:
可变Lambdas
默认情况下,
lambda
不会改变按值捕获的变量值,如果想要改变捕获变量的值,必须在参数列表后加上关键词 mutable
。有 mutable
关键词的 lambda
不能省略参数列表:按引用捕获的对象是否可以改变仅仅依赖于引用的对象是
const
还是非 const
的:指定 lambda 的返回类型
目前编写的
lambda
只有一个 return
语句。因而不需要指定返回类型。如果 lambda
函数体中包含了任何不是 return
语句的话,那么 lambda
将被推断为返回 void
。推断为返回 void
的 lambda
不会返回任何值:transform
将输入序列中的每个元素通过调用 lambda
进行转换,并将结果存储到其目的迭代器所表示的序列中。目的序列和输入序列可以是同一个。由于这里的 lambda
中语句多于一条,必须指定其返回值。参数绑定
lambda
表达式对于只用于一两个地方的简单操作非常有用。如果需要将相同的操作用于多个地方的话,应该定义函数而不是重复相同的 lambda
表达式。同样,如果操作比较复杂的话,最好是定义函数。对于没有捕获列表的
lambda
表达式很容器用函数来替换。然而,想用函数来替换捕获了本地变量的 lambda
就有难度了:想将
check_size
函数用于 find_if
肯定是不行的,find_if
要求函数只能接收一个参数。为了能够传递 check_size
必须要进行参数绑定。标准库 bind 函数
使用定义了
functional
头文件中的 bind
函数。bind
可以被认为是通用的函数适配器(function adaptor)。它接收一个可调用对象并产生一个新的可调用对象,其中改变了原始函数的参数列表。bind
的通用形式是:其中
newCallable
是一个可调用对象,arg_list
是逗号分隔的参数并与给定的 callable
中的参数一一对应。也就是说,当调用 newCallable
时,newCallable
将调用 callable
,并传递参数列表 arg_list
。参数列表
arg_list
可以包含形式为 _n
的名字,其中 n 是一个整数。这种形式的参数称之为 “占位符”(placeholder),表示 newCallable
的形参列表。意味着当调用 newCallable
并传递给其多个参数,这些参数会被填值到 _1
_2
_3
并以 arg_list
的顺序传递给 callable
可调用对象。传递给 newCallable
的参数个数与占位符的个数一样多。将 sz 绑定到 check_size
上
以下是给
check_size
绑定一个固定的 sz 参数:这个例子中
bind
只有一个占位符,意味着 check6
有一个参数。占位符出现在 arg_list
的第一个位置,意味着 check6
的参数对应于 check_size
的第一个参数。这个参数的类型是 const string&
,意味着 check6
中的参数也是 const string&
。那么当调用 check6
时就必须传递类型为 string
的参数,check6
会将其传递给 check_size
的第一个参数。arg_list
中的第二个参数是值 6,这个值被绑定到 check_size
的第二个参数上,无论任何时候调用 check6
,它都会将 6 传递给 check_sz
的第二个参数:使用
bind
可以替换原来的 lambda
版本的 find_if
:bind
的调用生成了一个新的可调用对象,并将 check_size
的第二个参数绑定到值 sz
上。当 find_if
调用此对象时将会用 string
和 sz
调用 check_size
。使用 placeholders 名称空间
名字
_n
定义在名称空间 placeholders
中,这个名称空间本身被嵌套在 std
名称空间中。用using
声明可以简写占位符的名字: 如果嫌麻烦的话,就在函数中使用
using
指令(using directive):using
指令将使得 std::placeholders
中的名字都可见,这样就不需要再使用完全限定名了。与
bind
函数一样,placeholders
名称空间定义在 functional
头文件中。bind 的参数
bind
函数可以用于固定参数的值。甚至,bind
还可以用于重排序参数:将产生一个有两个参数的新的可调用对象 g。其中 g 的第一个参数被传递给 f 的第五个参数,g 的第二个参数将被传递给 f 的第四个参数。效果相当于
g(_1, _2)
与 f(a, b, _2, c, _1)
一样,当调用 g(X, Y)
时与 f(a, b, Y, c, X)
效果一样。还有
bind
可以对一个函数的参数进行重排序:在第一个调用中,sort 调用的正常的 isShorter ,其含义是较短的字符串在前面。第二个调用中,sort 调用的反向的 isShorter ,其含义是较长的字符串在前面。
绑定引用形参
通常调用
bind
时,非占位符参数时拷贝到生成的调用对象中的。然而有时我们希望这个参数是按引用绑定的:由于 os 不能被拷贝,所以不能直接 bind 这个参数。如果想要以引用方式进行绑定需要调用 ref 函数:
ref
将返回一个对象其本身是可以被拷贝的,然而它的内部包含了给定参数对象的引用。同时还有一个 cref
函数用于生成一个类以容纳 const
引用。ref
和 cref
都定义在 functional
头文件中。向后兼容:绑定参数
早期的
C++
版本对于绑定参数到函数有诸多限制,并且更加复杂。标准库定义了 bind1st
和 bind2nd
,在新标准下应该使用 bind
函数。