【C++】C++中的lambda表达式和函数对象
目录结构:
contents structure
[-]
lambda表达式是C++11中引入的一项新技术,利用lambda表达式可以编写内嵌的匿名函数,用以替换独立函数或者函数对象,并且使代码更可读。但是从本质上来讲,lambda表达式只是一种语法糖,因为所有其能完成的工作都可以用其它稍微复杂的代码来实现。但是它简便的语法却给C++带来了深远的影响。如果从广义上说,lamdba表达式产生的是函数对象。在类中,可以重载函数调用运算符(),此时类的对象可以将具有类似函数的行为,我们称这些对象为函数对象(Function Object)或者仿函数(Functor)。相比lambda表达式,函数对象有自己独特的优势。下面我们开始具体讲解这两项黑科技。 1 lambda表达式我们先从简答的例子开始,我们定义一个可以输出字符串的lambda表达式,表达式一般都是从方括号[]开始,然后结束于花括号{},花括号里面就像定义函数那样,包含了lamdba表达式体: // 定义简单的lambda表达式 auto basicLambda = [] { cout << "Hello,world!" << endl; }; // 调用 basicLambda(); // 输出:Hello,world! 上面是最简单的lambda表达式,没有参数。如果需要参数,那么就要像函数那样,放在圆括号里面,如果有返回值,返回类型要放在->后面,即拖尾返回类型,当然你也可以忽略返回类型,lambda会帮你自动推断出返回类型: // 指明返回类型 auto add = [](int a,int b) -> int { return a + b; }; // 自动推断返回类型 auto multiply = [](int a,int b) { return a * b; }; int sum = add(2,5); // 输出:7 int product = multiply(2,5); // 输出:10
int main() { int x = 10; auto add_x = [x](int a) { return a + x; }; // 复制捕捉x auto multiply_x = [&x](int a) { return a * x; }; // 引用捕捉x cout << add_x(10) << " " << multiply_x(10) << endl; // 输出:20 100 return 0; }
class ClosureType { public: // ... ReturnType operator(params) const { body }; }
int main() { int x = 10; auto add_x = [x](int a) mutable { x *= 2; return a + x; }; // 复制捕捉x cout << add_x(10) << endl; // 输出 30 return 0; }
class ClosureType { public: // ... ReturnType operator(params) { body }; }
auto a = [] { cout << "A" << endl; }; auto b = [] { cout << "B" << endl; }; a = b; // 非法,lambda无法赋值 auto c = a; // 合法,生成一个副本
ClosureType& operator=(const ClosureType&) = delete; 但是没有禁用复制构造函数,所以你仍然可以用一个lambda表达式去初始化另外一个lambda表达式而产生副本。并且lambda表达式也可以赋值给相对应的函数指针,这也使得你完全可以把lambda表达式看成对应函数类型的指针。 std::function<int(int)> add_x(int x) { return [&](int a) { return x + a; }; }
std::function<int(int)> add_x(int x) { return [=](int a) { return x + a; }; }
class Filter { public: Filter(int divisorVal): divisor{divisorVal} {} std::function<bool(int)> getFilter() { return [=](int value) {return value % divisor == 0; }; } private: int divisor; };
// 类的方法,下面无法编译,因为divisor并不在lambda捕捉的范围 std::function<bool(int)> getFilter() { return [divisor](int value) {return value % divisor == 0; }; }
std::function<bool(int)> getFilter() { return [this](int value) {return value % this->divisor == 0; }; }
std::function<bool(int,int)> wrapper = [](int x,int y) { return x < y; };
int value = 3; vector<int> v {1,3,5,2,6,10}; int count = std::count_if(v.beigin(),v.end(),[value](int x) { return x > value; });
vector<int> v(10); int a = 0; int b = 1; std::generate(v.begin(),[&a,&b] { int value = b; b = b + a; a = value; return value; }); // 此时v {1,1,2,3,5,8,13,21,34,55}
class Person { public: Person(const string& first,const string& last): firstName{first},lastName{last} {} Person() = default; string first() const { return firstName; } string last() const { return lastName; } private: string firstName; string lastName; }; int main() { vector<Person> vp; // ... 添加Person信息 // 按照姓名排序 std::sort(vp.begin(),vp.end(),[](const Person& p1,const Person& p2) { return p1.last() < p2.last() || (p1.last() == p2.last() && p1.first() < p2.first()); }); // ... return 0; }
// 完整语法 [ capture-list ] ( params ) mutable(optional) constexpr(optional)(c++17) exception attribute -> ret { body } // 可选的简化语法 [ capture-list ] ( params ) -> ret { body } [ capture-list ] ( params ) { body } [ capture-list ] { body }
2 lambda c++14新特性在C++14中,lambda又得到了增强,一个是泛型lambda表达式,一个是lambda可以捕捉表达式。这里我们对这两项新特点进行简单介绍。 2.1 lambda捕捉表达式前面讲过,lambda表达式可以按复制或者引用捕获在其作用域范围内的变量。而有时候,我们希望捕捉不在其作用域范围内的变量,而且最重要的是我们希望捕捉右值。所以C++14中引入了表达式捕捉,其允许用任何类型的表达式初始化捕捉的变量。看下面的例子: // 利用表达式捕获,可以更灵活地处理作用域内的变量 int x = 4; auto y = [&r = x,x = x + 1] { r += 2; return x * x; }(); // 此时 x 更新为6,y 为25 // 直接用字面值初始化变量 auto z = [str = "string"]{ return str; }(); // 此时z是const char* 类型,存储字符串 string
auto myPi = std::make_unique<double>(3.1415); auto circle_area = [pi = std::move(myPi)](double r) { return *pi * r * r; }; cout << circle_area(1.0) << endl; // 3.1415
2.2 泛型lambda表达式从C++14开始,lambda表达式支持泛型:其参数可以使用自动推断类型的功能,而不需要显示地声明具体类型。这就如同函数模板一样,参数要使用类型自动推断功能,只需要将其类型指定为auto,类型推断规则与函数模板一样。这里给出一个简单例子: auto add = [](auto x,auto y) { return x + y; }; int x = add(2,3); // 5 double y = add(2.5,3.5); // 6.0 ? 3 函数对象函数对象是一个广泛的概念,因为所有具有函数行为的对象都可以称为函数对象。这是一个高级抽象,我们不关心对象到底是什么,只要其具有函数行为。所谓的函数行为是指的是可以使用()调用并传递参数: function(arg1,arg2,...); // 函数调用
class X { public: // 定义函数调用符 ReturnType operator()(params) const; // ... };
X f; // ... f(arg1,arg2); // 等价于 f.operator()(arg1,arg2);
// T需要支持输出流运算符 template <typename T> class Print { public: void operator()(T elem) const { cout << elem << ‘ ‘ ; } }; int main() { vector<int> v(10); int init = 0; std::generate(v.begin(),[&init] { return init++; }); // 使用for_each输出各个元素(送入一个Print实例) std::for_each(v.begin(),Print<int>{}); // 利用lambda表达式:std::for_each(v.begin(),[](int x){ cout << x << ‘ ‘;}); // 输出:0,4,6,7,9 return 0; }
// for_each的类似实现 namespace std { template <typename Iterator,typename Operation> Operation for_each(Iterator act,Iterator end,Operation op) { while (act != end) { op(*act); ++act; } return op; } }
class IntSequence { public: IntSequence(int initVal) : value{ initVal } {} int operator()() { return ++value; } private: int value; }; int main() { vector<int> v(10); std::generate(v.begin(),IntSequence{ 0 }); /* lambda实现同样效果 int init = 0; std::generate(v.begin(),[&init] { return ++init; }); */ std::for_each(v.begin(),[](int x) { cout << x << ‘ ‘; }); //输出:1,9,10 return 0; }
class MeanValue { public: MeanValue(): num{0},sum{0} {} void operator()(int e) { ++num; sum += num; } double value() { return static_cast<double>(sum) / static_cast<double>(num); } private: int num; int sum; }; int main() { vector<int> v{ 1,7 }; MeanValue mv = std::for_each(v.begin(),MeanValue{}); cout << mv.value() << endl; // output: 2.5 return 0; }
vector<int> v{3,4,9,5}; // 升序排序 std::sort(v.begin(),v.end()); // output: 2,9 // 降序排列 std::sort(v.begin(),std::greater<int>{}); // output: 9,2 更多有关函数对象的信息大家可以参考这里。 4 函数适配器从设计模式来说,函数适配器是一种特殊的函数对象,是将函数对象与其它函数对象,或者特定的值,或者特定的函数相互组合的产物。由于组合特性,函数适配器可以满足特定的需求,头文件<functional>定义了几种函数适配器: ? 4.1 绑定器(binder)绑定器std::bind是最常用的函数适配器,它可以将函数对象的参数绑定至特定的值。对于没有绑定的参数可以使用std::placeholers::_1,std::placeholers::_2等标记。我们从简单的例子开始,比如你想得到一个减去固定树的函数对象: auto minus10 = std::bind(std::minus<int>{},std::placeholders::_1,10); cout << minus10(20) << endl; // 输出10
// 逆转参数顺序 auto vminus = std::bind(std::minus<int>{},std::placeholders::_2,std::placeholders::_1); cout << vminus(20,10) << endl; // 输出-10
// 定义一个接收一个参数,然后将参数加10再乘以2的函数对象 auto plus10times2 = std::bind(std::multiplies<int>{},std::bind(std::plus<int>{},10),2); cout << plus10times2(4) << endl; // 输出: 28 // 定义3次方函数对象 auto pow3 = std::bind(std::multiplies<int>{},std::bind(std::multiplies<int>{},std::placeholders::_1),std::placeholders::_1); cout << pow3(3) << endl; // 输出:27
// 大写转换函数 char myToupper(char c) { if (c >= ‘a‘ && c <= ‘z‘) return static_cast<char>(c - ‘a‘ + ‘A‘); return c; } int main() { string s{ "Internationalization" }; string sub{ "Nation" }; auto pos = std::search(s.begin(),s.end(),sub.begin(),sub.end(),std::bind(std::equal_to<char>{},std::bind(myToupper,std::placeholders::_2))); if (pos != s.end()) { cout << sub << " is part of " << s << endl; } // 输出:Nation is part of Internationalization return 0; }
void f(int& n1,int& n2,const int& n3) { cout << "In function: " << n1 << ‘ ‘ << n2 << ‘ ‘ << n3 << ‘n‘; ++n1; ++n2; // ++n3; //无法编译 } int main() { int n1 = 1,n2 = 2,n3 = 3; auto boundf = std::bind(f,n1,std::ref(n2),std::cref(n3)); n1 = 10; n2 = 11; n3 = 12; cout << "Before function: " << n1 << ‘ ‘ << n2 << ‘ ‘ << n3 << ‘n‘; boundf(); cout << "After function: " << n1 << ‘ ‘ << n2 << ‘ ‘ << n3 << ‘n‘; // Before function : 10 11 12 // In function : 1 11 12 // After function : 10 12 12 return 0; }
class Person { public: Person(const string& n) : name{ n } {} void print() const { cout << name << endl; } void print2(const string& prefix) { cout << prefix << name << endl; } private: string name; }; int main() { vector<Person> p{ Person{"Tick"},Person{"Trick"} }; // 调用成员函数print std::for_each(p.begin(),p.end(),std::bind(&Person::print,std::placeholders::_1)); // 此处的std::placeholders::_1表示要调用的Person对象,所以相当于调用arg1.print() // 输出:Tick Trick std::for_each(p.begin(),std::bind(&Person::print2,"Person: ")); // 此处的std::placeholders::_1表示要调用的Person对象,所以相当于调用arg1.print2("Person: ") // 输出:Person: Tick Person: Trick return 0; }
vector<int> data{ 1,4 }; auto func = std::bind([](const vector<int>& data) { cout << data.size() << endl; },std::move(data)); func(); // 4 cout << data.size() << endl; // 0
4.2 std::mem_fn()适配器当想调用成员函数时,你还可以使用std::mem_fn函数,此时你可以省略掉用于调用对象的占位符: vector<Person> p{ Person{ "Tick" },Person{ "Trick" } }; std::for_each(p.begin(),std::mem_fn(&Person::print)); // 输出: Trick Trick Person n{ "Bob" }; std::mem_fn(&Person::print2)(n,"Person: "); // 输出:Person: Bob
class Foo { public: int data = 7; void display_greeting() { cout << "Hello,world.n"; } void display_number(int i) { cout << "number: " << i << ‘n‘; } }; int main() { Foo f; // 调用成员函数 std::mem_fn(&Foo::display_greeting)(f); // Hello,world. std::mem_fn(&Foo::display_number)(f,20); // number: 20 // 调用数据成员 cout << std::mem_fn(&Foo::data)(f) << endl; // 7 return 0; }
? 原文链接:https://www.jianshu.com/p/d686ad9de817 (编辑:李大同) 【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容! |