C++11中引入lambda,与operator() 和 函数(函数指针)统称为可调用对象。而operator()和lambda也实现了闭包。
闭包的概念
闭包是带有上下文的函数,这个函数带有状态,也就是重载了()的类。什么叫 “带有状态” 呢? 意思是这个闭包有属于自己的变量,这些个变量的值是创建闭包的时候设置的,并在调用闭包的时候,可以访问这些变量。
函数是代码,状态是一组变量,将代码和一组变量捆绑,就形成了闭包,内部包含 static 变量的函数不是闭包,因为这个 static 变量不能捆绑。闭包的状态捆绑,必须发生在运行时。
lambda和函数指针
lambda虽然也叫匿名函数,但是其可以捕捉变量 ,所以和普通函数还是不一样的。
using Func = int(int);
int test(int x) { cout << x << endl; return 0; }
auto lamb = [](int x) ->int { cout << x << endl; return 0; };
void fun(Func f, int x) { f(x); }
int main() {
fun(test, 5); // 输出5
fun(lamb, 10); // 输出10
}
但是,如果lambda捕捉列表不为空时,则lambda无法转成函数指针。
那么就引来一个问题,sort函数传入第三个参数时,可以传函数指针、仿函数、lambda。
class Test {
public:
bool operator()(int& a, int& b) { return a < b; }
};
static bool cmp(int& a, int& b) { return a < b; }
int main() {
vector<int> arr{ 1, 3, 2, 5, 4, 7, 2 };
int temp = 5;
auto lamb = [temp](int& a, int& b) { return a < b; };
Test t;
//sort(arr.begin(), arr.end(), cmp); // 输出1 2 2 3 4 5 7
//sort(arr.begin(), arr.end(), lamb); // 输出1 2 2 3 4 5 7
sort(arr.begin(), arr.end(), t); // 输出1 2 2 3 4 5 7
for (auto i : arr) {
cout << i << " ";
}
}
从上面可以看出,即使lambda捕捉列表不为空时,传入sort函数的效果依旧和函数指针、仿函数效果一样。
C++11引入了 function来统一它们。
function
function是一个可调用对象的包装器,是一个类模板,可以容纳除类成员(函数)指针之外的所以可调用对象。它的实例可以对任何可以调用的目标实体进行存储、复制和调用操作。
void test() { cout << 5 << endl; }
class Test {
public:
void operator()() { cout << 5 << endl; }
};
int main() {
int x = 5;
auto lamb = [x]() {cout << x << endl; };
function<void()> func1{ test };
function<void()> func2{ Test() };
function<void()> func3{ lamb };
func1(); // 输出5
func2(); // 输出5
func3(); // 输出5
}
但是不能直接将重载函数的名字存入function类型的对象中:
void test(int x) { cout << x << endl; }
void test(double x) { cout << x << endl; }
int main() {
function<void(int x)> func{ test }; // error 无法确定需要哪个重载实例
}
// 解决方法,可以通过函数指针和lambda来解决二义性
void test(int x) { cout << x << endl; }
void test(double x) { cout << x << endl; }
void (*Func)(int x) = test; // 函数指针
int main() {
function<void(int x)> func1{ Func };
function<void(int x)> func2{ [](double x) ->void {cout << x << endl; } };
}
bind
像find_if这个函数,由于其只能接受一个一元谓词,但是需要长度这个信息,所以就引入了lambda表达式。而上面function也提到了,C++11通过function将lambda、函数指针、仿函数统一起来了。所以可不可以用函数指针来实现find_if,这就引入了bind的概念(bind1st和bind2nd已被弃用)
bind用来将可调用对象与其参数一起进行绑定。绑定后的结果可以使用function来进行保存。有两大作用:
- 将可调用对象与其参数一起绑定成一个仿函数
- 将多元(参数个数为n,n>1)可调用对象转成一元、二元、…(n-1)元可调用对象,即只绑定部分参数。
调用bind的一般形式为:
auto newCallable = bind(callable, arg_list);
// 其中,newCallable本身是一个可调用对象,arg_list是一个逗号分隔的参数列表,对应callable的参数。即,
// 当我们调用newCallable时,newCallable会调用callable,并传递给它arg_list中的参数
// arg_list中可能包含形如_n的名字,其中n是一个整数。这些参数是占位符,表示newCallable的参数,他们
// 占据了传递给newCallable的参数位置,数值n表示生成的可调用对象中参数的位置。
// 名字_n都定义在一个名为placeholders的名空间中,而这个名空间又在std中。
实现find_if。
// 通过lambda实现
int main() {
vector<string> arr{ "hello", "world!", "C++" };
auto lamb = [sz = arr.size()](string& s) { return s.size() < 4; };
auto it = find_if(arr.begin(), arr.end(), lamb);
if (it != arr.end()) cout << *it << endl;
}
// 通过bind实现
using std::placeholders::_1;
bool cmp(string& s, int size) { return s.size() < 4; }
int main() {
vector<string> arr{ "hello", "world!", "C++" };
function<bool(string)> f = bind(cmp, _1, arr.size());
auto it = find_if(arr.begin(), arr.end(), f);
if (it != arr.end()) cout << *it << endl;
}
使用bind来替代bind1st和bind2nd
// g++实现bind1st和bind2nd
int main() {
vector<int> arr{1, 2, 3, 4, 5};
// 找出比3小的个数
int count = count_if(arr.begin(), arr.end(), bind1st(greater<int>(),3));
cout << count << endl;
// 找出大于等于3的个数
count = count_if(arr.begin(), arr.end(), bind2nd(greater_equal<int>(), 3));
cout << count << endl;
}
// 通过bind实现
int main() {
vector<int> arr{ 1, 2, 3, 4, 5 };
// 找出小于3的个数
int count = count_if(arr.begin(), arr.end(), bind(greater<int>(), _1, 3));
cout << count << endl;
// 找出大于等于3的个数
count = count_if(arr.begin(), arr.end(), bind(greater_equal<int>(), 3, _1));
cout << count << endl;
}
使用组合bind函数
如何找出集合中大于5小于10的元素个数。
(1)首先,需要一个用来判断是否大于5的功能闭包
bind(greater<int>(), _1, 5);
(2)然后,需要一个判断是否小于10的闭包
bing(greater<int>(), 10, _1);
(3)最后用逻辑与把它们连起来
using std::placeholders::_1;
int main() {
vector<int> arr{ 1, 2, 7, 8, 9, 10, 12 };
auto f = bind(logical_and<bool>(), bind(greater<int>(), _1, 5), bind(greater<int>(), 10, _1));
int count = count_if(arr.begin(), arr.end(), f);
cout << count << endl;
}
参考资料
(1)《C++Primer》
(2)《快速上手C++11/14》
(3)《深入应用C++11·代码优化与工程级应用》