文章目录
- 重载函数调用运算符
- lambda
- lambda等价于函数对象
- lambda等价于类
- 标准库函数对象
- 可调用对象与function
- 可调用对象
- function
- 函数重载与function
重载函数调用运算符
函数调用运算符必须是成员函数。 一个类可以定义多个不同版本的调用运算符,互相之间应该在参数数量or类型上有所区别。如果类定义了函数调用运算符 ()
,则该类的对象称作函数对象,因为可以调用这种对象,所以我们说这些对象的行为像函数一样。
class absInt {
public:int operator()(int val)const {return val < 0 ? -val : val;}
};
lambda
lambda等价于函数对象
对于作为最后一个实参的 lambda
的表达式来说:
//根据单词的长度对其进行排序,对于长度相同的单词按照字母表顺序排序
stable_sort (words.begin(), words.end(),[](const string &a, const string &b){return a.size () < b.size();});
其等价于下面这个类的一个未命名对象:
class ShorterString {
public:bool operator()(const string &s1, const string &s2) const{ return s1.size() < s2.size(); }
};
默认情况下,lambda
不能改变它捕获的变量,因此类的函数调用运算符是一个 const
成员函数。
那么我们可以重写并调用 stable_sort
:
stable_sort(words.begin(), words.end(), ShorterString());
lambda等价于类
- 当一个
lambda表达式
通过引用捕获变量时,将由程序负责确保lambda
执行时所引用的对象确实存在。因此,编译器可以直接使用该引用而无须在lambda产生的类
中将其存储为数据成员。 - 过值捕获的变量被拷贝到
lambda
中。因此,这种lambda
产生的类必须为每个值捕获的变量建立对应的数据成员,同时创建构造函数,令其使用捕获的变量的值来初始化数据成员。
举个例子,有这样一个 lambda
,它的作用是找到第一个长度不小于给定值的 string
对象:
//获得第一个指向满足条件元素的迭代器,该元素满足 size() >= sz
auto wc = find_if(words.begin(), words.end(),[sz] (const string &a){ return a.size() >= sz;});
等价于这样的类:
class SizeComp{size_t sz; // 该数据成员对应通过值捕获的变量
public:SizeComp (size_t n): sz(n) {} // 该形参对应捕获的变量// 该调用运算符的返回类型、形参和函数体都与lambda一致bool operator()(const string &s) const { return s.size()>= sz; }
由于这个类不含默认构造函数,因此想要调用它必须提供一个实参,也就起到了 值捕获 的作用。
标准库函数对象
表示运算符的函数对象类常用来替换算法中的默认运算符。
例如,在默认情况下排序算法使用 operator<
将序列按照升序排列。如果要执行降序排列的话,我们可以传入一个 greater
类型的对象。该类将产生一个调用运算符并负责执行待排序类型的大于运算:
// sv是一个vecotr<string>,greater<string>()是一个临时的函数对象
sort(sv.begin(), sv.end(), greater<string>());
标准库函数对象适用性是很宽的,甚至适用于指针:
而实际上,STL
中也正是这样做的:关联容器使用 less<key_type>
对元素排序,因此我们可以定义一个指针的 set
或者在 map
中使用指针作为关键值而无须直接声明 less
。
可调用对象与function
可调用对象
C++语言中有几种可调用的对象:函数、函数指针、lambda表达式、bind创建的对象、重载了函数调用运算符的类。
和其他对象一样,可调用的对象也有类型。例如,每个lambda有它自己唯一的(未命名)类类型;函数及函数指针的类型则由其返回值类型和实参类型决定,等等。
然而,两个不同类型的可调用对象却可能共享同一种调用形式(call signature)。 调用形式指明了调用返回的类型以及传递给调用的实参类型。一种调用形式对应一个函数类型,例如:
int(int, int)
是一个函数类型,它接受两个 int
、返回一个 int
。
用具体的例子来讲,即:
上面三个可调用对象共享一种调用形式—— int(int, int)
对于调用形式相同的可调用对象,我们可以定义一个函数表(function table)存储他们的“指针”,对他们进行统一管理。 当程序需要执行特定的 +
、%
、/
时,从表中查找该操作对应的可调用对象。
在 C++
中,函数表很容易通过 map
实现:
// string表示 + 或 % 或 /
// 函数指针 作为 value
map<string, int(*)(int, int)> m;
// 我们可以将 add的指针 加入到 m 中
m.insert({"+", add}); // {"+", add}是一个pair
// 但不可以将 mod 或者 divide 存入 m
m.insert({"%", mod}); // error:mod不是一个函数指针,lambda有自己的类类型
function
function
模板的运用示例:
function<int(int, int)>
// 声明了一个function类型,表示接受两个int、返回一个int的可调用对象
// 用function<int(int, int)>表示上面的可调用对象
function<int(int, int)> f1 = add; // 函数指针
function<int(int, int)> f2 = divide(); // 函数对象类的对象
function<int(int, int)> f3 = [](ing i, int j) {return i%j}; // lambdacout << f1(4, 2) << endl; // 打印6
cout << f2(4, 2) << endl; // 打印2
cout << f3(4, 2) << endl; // 打印0
function
也可以和 map
搭配使用:
初始化:
使用:
函数重载与function
我们不能直接将重载函数的名字存入 function
类型的对象中:
int add(int i, int j) { return i + j; }
A add(const A&, const A&);
map<string, function<int(int, int)>> m;
m.insert( {"+", add} ); // ERROR:哪个add?
解决上述二义性问题的一条途径是存储函数指针而非函数名字:
int (*fp)(int, int) = add;
m.insert( {"+", fp} );
新版本标准库中的 function 类与旧版本中的 unary_function 和 binary_function 没有关联。后两个类已经被更通用的 bind 函数替代了。