C++_3——库(functional)与函数对象
这一系列文章的目的是在学习了C++基础后,继续补充一些C++基础和进阶的知识点,包括C++11的相关内容。
以C++11标准为基础。
C++网站:http://www.cplusplus.com/reference/
1 函数对象
任何定义了函数调用操作符的对象都是函数对象(也叫仿函数),C++ 支持创建、操作新的函数对象,同时也提供了许多内置的函数对象,可以像调用函数一样使用。可以自定义函数类如下:
// func.h
class MyType{
public:
void operator()(){
std::cout<<"mmmm\n";
}
void operator()(int bbb){
std::cout<<bbb+10<<"\n";
}
int operator()(char a){
if(isdigit(a)) // isdigit在lacale.h 里面还有很多实用的判断函数
return atoi(&a);
else
return -1;
}
};
// a.cpp
#include "func.h"
int main(){
MyType val;
val(); // print mmmm
val(123); // print 133
std::cout<<val('a')<<std::endl; // print -1
}
2 lambda表达式
利用lambda表达式可以编写内嵌的匿名函数,替换独立函数或者函数对象,不用写一个额外的函数或类或结构体,并且使代码更可读。
-
基本使用
[ 捕获 ] ( 形参 ) -> 返回类型 { 函数体 }
-
捕获指明内部可以访问的外部变量,可以是值捕获,也可以是引用捕获
-
返回类型可以省略,由lambda表达式自动推导结果
-
编译器会将lambda表达式生成为一个定义了函数调用操作符的类/结构体,这个类/结构体是一个闭包类型,因为捕获了封装作用域内的变量。
-
举几个例子
#include <iostream> #include <vector> #include <string> #include <algorithm> int main(){ auto print = [](int s) {std::cout << "value is " << s << std::endl;}; auto lambAdd = [](int aa, int bb) ->int { return aa + bb;}; int iSum = lambAdd(10, 11); print(iSum); // print value is 21 int count = 0; std::vector<std::string> words{ "An", "ancient", "Pond" }; std::for_each(words.cbegin(), words.cend(), // for_each在algorithm.h, 原型为Function for_each (InputIterator first, InputIterator last, Function fn); [&count](const std::string& word) // 引用捕获外部变量count { if(isupper(word[0])) { // isupper在lacale.h 里面还有很多实用的判断函数 std::cout << word << " " << count << std::endl; count++; } }); }
-
能不能修改捕获的变量?
引用捕获,可以修改。
值捕获时,不允许修改变量,这与函数的值传递不一样。若想修改,需要关键字mutabe,修改上面的例子如下。要注意所有修改在lambda表达式之外无效,这就跟函数传递值一样了。#include <iostream> #include <vector> #include <string> #include <algorithm> int main(){ int count = 0; std::vector<std::string> words{ "An", "ancient", "Pond" }; std::for_each(words.cbegin(), words.cend(), [count](const std::string& word) mutable { if(isupper(word[0])) { std::cout << word << " " << count << std::endl; count++; } }); std::cout<<count<<"\n"; }
-
捕获还是直接传参?
考虑传参是否会生成临时变量存储传参值
捕获不会生成临时变量,例外情况如 值捕获使用mutable -
lambda表达式不能=赋值,可以()初始化拷贝
3 functional
-
内置函数对象
#include <algorithm> #include <functional> // std::plus 加法函数对象类,返回两个值求和的结果 int first[]={1,2,3,4,5}; int second[]={10,20,30,40,50}; int results[5]; // transform 将一个/两个容器范围内的元素,根据运算方式计算,将结果存到result std::transform (first, first+5, second, results, std::plus<int>()); // result : 11 22 33 44 55 // std::not_equal_to 判断不等函数对象类 int numbers[]={10,10,10,20,20}; // adjacent_find 依据运算方式,判断范围内相邻元素 int* pt = std::adjacent_find (numbers, numbers+5, std::not_equal_to<int>()) + 1; // *pt : 20 // ...
-
std::function
Class that can wrap any kind of callable element (such as functions and function objects) into a copyable object, and whose type depends solely on its call signature (and not on the callable element type itself).
std::function
对象可以包装多种可调用实体,如普通函数、函数指针、函数对象、lambda表达式、指向成员函数的指针、指向成员变量的指针等,std::function
对象与可调用实体的类型无关,与可调用实体的签名(输入和返回类型)有关,官网举例如下。
// function example #include <iostream> // std::cout #include <functional> // std::function, std::negate // a function: int half(int x) {return x/2;} // a function object class: struct third_t { int operator()(int x) {return x/3;} }; // a class with data members: struct MyValue { int value; int fifth() {return value/5;} }; int main () { std::function<int(int)> fn1 = half; // function std::function<int(int)> fn2 = ½ // function pointer std::function<int(int)> fn3 = third_t(); // function object std::function<int(int)> fn4 = [](int x){return x/4;}; // lambda expression std::function<int(int)> fn5 = std::negate<int>(); // standard function object std::cout << "fn1(60): " << fn1(60) << '\n'; // print fn1(60): 30 std::cout << "fn2(60): " << fn2(60) << '\n'; // print fn1(60): 30 std::cout << "fn3(60): " << fn3(60) << '\n'; // print fn1(60): 20 std::cout << "fn4(60): " << fn4(60) << '\n'; // print fn1(60): 15 std::cout << "fn5(60): " << fn5(60) << '\n'; // print fn1(60): -60 // stuff with members: std::function<int(MyValue&)> value = &MyValue::value; // pointer to data member std::function<int(MyValue&)> fifth = &MyValue::fifth; // pointer to member function MyValue sixty {60}; std::cout << "value(sixty): " << value(sixty) << '\n'; // print value(sixty): 60 std::cout << "fifth(sixty): " << fifth(sixty) << '\n'; // print fifth(sixty): 12 return 0; }
std::function
对象可以swap。std::function
对象可以用nullptr
判断是否为空,为空时调用将抛出std::bad_function_call
异常。
#include <iostream> // std::cout #include <functional> // std::function, std::plus int main () { std::function<int(int,int)> foo,bar; foo = std::plus<int>(); foo.swap(bar); std::cout << "foo is " << (foo ? "callable" : "not callable") << ".\n"; std::cout << "bar is " << (bar!=nullptr ? "callable" : "not callable") << ".\n"; return 0; }
-
bind & placeholder
- bind绑定一个可调用实体(函数对象、函数指针、成员指针),返回基于该实体的函数对象。
- 可调用实体的每个输入参数可以绑定一个值或占位符(placeholder)。绑定值时,返回函数对象调用时,将使用该值。绑定占位符时,返回函数对象调用时,将根据占位符顺序依次传递。
- 可以通过模板指定返回类型。
- 绑定成员指针时,绑定/传入的第一个参数要求是成员所在类的一个实例化对象,如果是成员函数还有其他参数,那么就继续绑定第二个、第三个…参数。
- 简单用法:
// bind example #include <iostream> // std::cout #include <functional> // std::bind // a function: (also works with function object: std::divides<double> my_divide;) double my_divide (double x, double y) {return x/y;} struct MyPair { double a,b; double multiply() {return a*b;} }; int main () { using namespace std::placeholders; // adds visibility of _1, _2, _3,... // binding functions: auto fn_five = std::bind (my_divide,10,2); // returns 10/2 std::cout << fn_five() << '\n'; // 5 // 占位符 auto fn_half = std::bind (my_divide,_1,2); // returns x/2 std::cout << fn_half(10) << '\n'; // 5 // 占位符顺序不同 auto fn_invert = std::bind (my_divide,_2,_1); // returns y/x std::cout << fn_invert(10,2) << '\n'; // 0.2 // 指定返回类型 auto fn_rounding = std::bind<int> (my_divide,_1,_2); // returns int(x/y) std::cout << fn_rounding(10,3) << '\n'; // 3 MyPair ten_two {10,2}; // binding members: auto bound_member_fn = std::bind (&MyPair::multiply,_1); // returns x.multiply() std::cout << bound_member_fn(ten_two) << '\n'; // 20 auto bound_member_data = std::bind (&MyPair::a,ten_two); // returns ten_two.a std::cout << bound_member_data() << '\n'; // 10 return 0; }
- placeholder为占位符,如
std::placeholders::_1
、std::placeholders::_2
、std::placeholders::_3
等等,理论上数字可以一直增加,需要注意的是这些是变量,或者叫对象,是object。判断一个变量是不是占位符可以用std::is_placeholder<decltype(std::placeholders::_1)>::value
4 一些用于理解的引用
既然用函数对象与调用普通函数有相同的效果,为什么还有搞这么麻烦定义一个类来使用函数对象?主要在于函数对象有以下的优势:
- 函数对象可以有自己的状态。我们可以在类中定义状态变量,这样一个函数对象在多次的调用中可以共享这个状态。但是函数调用没这种优势,除非它使用全局变量来保存状态。
- 函数对象有自己特有的类型,而普通函数无类型可言。这种特性对于使用C++标准库来说是至关重要的。这样我们在使用STL中的函数时,可以传递相应的类型作为参数来实例化相应的模板,从而实现我们自己定义的规则。
传递函数指针参数时,使用函数对象替代一些函数指针有利于代码复用和扩展
来自 C++ 仿函数
附录
lambda表达式捕获类型
[] | 默认不捕获任何变量 | ||
[=] | 默认以复制捕获所有变量,少用 | [&] | 默认以引用捕获所有变量,少用 |
[x] | 仅以复制捕获x,其它变量不捕获 | [x…] | 以包展开方式复制捕获参数包变量,也就是可变参数 |
[&x] | 仅以引用捕获x,其它变量不捕获 | [&x…] | 以包展开方式引用捕获参数包变量,也就是可变参数 |
[=, &x] | 默认以复制捕获所有变量,但是x是例外,通过引用捕获 | [&, x] | 默认以引用捕获所有变量,但是x是例外,通过复制捕获 |
[this] | 通过引用捕获当前对象(其实是复制指针) | [*this] | 通过复制方式捕获当前对象 |
更多内置函数对象类型
内置函数对象类 | 操作符重载 | 类似其他内置函数对象类 | |
---|---|---|---|
四则 | std::plus | T operator() (const T& x, const T& y) const {return x+y;} | std::minus std::multiplies std::divides |
大小 | std::not_equal_to | bool operator() (const T& x, const T& y) const {return x!=y;} | std::less_equal std::less std::greater_equal std::greater std::equal_to |
取反 | std::negate | T operator() (const T& x) const {return -x;} | |
取余 | std::modulus | T operator() (const T& x, const T& y) const {return x%y;} | |
逻辑 | std::logical_or | bool operator() (const T& x, const T& y) const {return x||y;} | std::logical_not std::logical_and |
位运算 | std::bit_and | T operator() (const T& x, const T& y) const {return x&y;} | std::bit_xor std::bit_or |