第22章 函数对象与Lambda表达式 函数指针 函数指针的概念 函数指针是指向函数的指针,它可以存储函数的地址,并通过该地址调用函数。
函数指针的声明与使用 1 2 3 4 5 6 7 8 9 10 11 12 13 int (*addPtr)(int , int );int add (int a, int b) { return a + b; } addPtr = add; int result = addPtr (1 , 2 );
函数指针作为参数 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 void process (int a, int b, int (*operation)(int , int )) { int result = operation (a, b); std::cout << "Result: " << result << std::endl; } int add (int a, int b) { return a + b; } int subtract (int a, int b) { return a - b; } int main () { process (10 , 5 , add); process (10 , 5 , subtract); return 0 ; }
函数指针作为返回值 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 int add (int a, int b) { return a + b; } int subtract (int a, int b) { return a - b; } int (*getOperation (char op))(int , int ) { if (op == '+' ) { return add; } else if (op == '-' ) { return subtract; } return nullptr ; } int main () { auto operation = getOperation ('+' ); if (operation) { std::cout << operation (10 , 5 ) << std::endl; } return 0 ; }
函数指针的类型别名 使用typedef或using为函数指针类型创建别名,提高代码可读性:
1 2 3 4 5 6 7 8 typedef int (*Operation) (int , int ) ;using Operation = int (*)(int , int );Operation addPtr = add;
函数对象 函数对象的概念 函数对象(Function Object),也称为仿函数(Functor),是一种具有函数行为的对象。它是一个类的实例,该类重载了函数调用运算符operator()。
函数对象的定义与使用 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 class Add {public : int operator () (int a, int b) const { return a + b; } }; class Subtract {public : int operator () (int a, int b) const { return a - b; } }; int main () { Add add; int result1 = add (10 , 5 ); Subtract subtract; int result2 = subtract (10 , 5 ); return 0 ; }
函数对象作为参数 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 class Add {public : int operator () (int a, int b) const { return a + b; } }; class Subtract {public : int operator () (int a, int b) const { return a - b; } }; void process (int a, int b, const Add& operation) { int result = operation (a, b); std::cout << "Result: " << result << std::endl; } void process (int a, int b, const Subtract& operation) { int result = operation (a, b); std::cout << "Result: " << result << std::endl; } template <typename Operation>void process (int a, int b, Operation operation) { int result = operation (a, b); std::cout << "Result: " << result << std::endl; } int main () { Add add; Subtract subtract; process (10 , 5 , add); process (10 , 5 , subtract); return 0 ; }
带状态的函数对象 函数对象可以存储状态,这是函数指针无法做到的:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 class Counter {public : Counter () : count (0 ) {} int operator () () { return ++count; } int getCount () const { return count; } private : int count; }; int main () { Counter counter; std::cout << counter () << std::endl; std::cout << counter () << std::endl; std::cout << counter () << std::endl; std::cout << "Total: " << counter.getCount () << std::endl; return 0 ; }
标准库中的函数对象 C++标准库提供了一系列函数对象,定义在<functional>头文件中:
算术运算 :std::plus, std::minus, std::multiplies, std::divides, std::modulus, std::negate比较运算 :std::equal_to, std::not_equal_to, std::greater, std::less, std::greater_equal, std::less_equal逻辑运算 :std::logical_and, std::logical_or, std::logical_not1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 #include <functional> int main () { std::plus<int > add; std::cout << add (10 , 5 ) << std::endl; std::minus<int > subtract; std::cout << subtract (10 , 5 ) << std::endl; std::greater<int > greater; std::cout << greater (10 , 5 ) << std::endl; std::less<int > less; std::cout << less (10 , 5 ) << std::endl; return 0 ; }
Lambda表达式 Lambda表达式的概念 Lambda表达式是C++11引入的一种匿名函数的表达方式,它允许在需要函数的地方直接定义函数,而不需要单独声明函数。
Lambda表达式的语法 1 [capture](parameters) -> return_type { body }
capture :捕获列表,指定哪些外部变量可以在lambda体内使用parameters :参数列表,与普通函数的参数列表类似return_type :返回类型,可以省略,由编译器自动推导body :函数体,包含要执行的代码Lambda表达式的基本使用 1 2 3 4 5 6 7 8 9 10 11 auto add = [](int a, int b) { return a + b; };int result = add (10 , 5 ); auto subtract = [](int a, int b) -> int { return a - b; };int result2 = subtract (10 , 5 ); auto hello = []() { std::cout << "Hello, Lambda!" << std::endl; };hello ();
捕获列表 Lambda表达式可以通过捕获列表访问外部变量:
值捕获 :[var] 或 [=]引用捕获 :[&var] 或 [&]混合捕获 :[var, &other] 或 [=, &var] 或 [&, var]1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 int x = 10 ;auto addX = [x](int y) { return x + y; };x = 20 ; int result = addX (5 ); int y = 10 ;auto addY = [&y](int z) { return y + z; };y = 20 ; int result2 = addY (5 ); int a = 1 , b = 2 ;auto sum = [=]() { return a + b; };auto sumRef = [&]() { return a + b; };auto mixed = [a, &b]() { return a + b; };
Lambda表达式作为参数 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 #include <algorithm> #include <vector> int main () { std::vector<int > nums = {1 , 2 , 3 , 4 , 5 }; std::sort (nums.begin (), nums.end (), [](int a, int b) { return a > b; }); std::for_each(nums.begin (), nums.end (), [](int num) { std::cout << num << " " ; }); std::cout << std::endl; return 0 ; }
Lambda表达式作为返回值 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 #include <functional> std::function<int (int , int ) > getOperation (char op) { if (op == '+' ) { return [](int a, int b) { return a + b; }; } else if (op == '-' ) { return [](int a, int b) { return a - b; }; } else if (op == '*' ) { return [](int a, int b) { return a * b; }; } else if (op == '/' ) { return [](int a, int b) { return a / b; }; } return nullptr ; } int main () { auto add = getOperation ('+' ); std::cout << add (10 , 5 ) << std::endl; auto multiply = getOperation ('*' ); std::cout << multiply (10 , 5 ) << std::endl; return 0 ; }
泛型Lambda表达式 C++14引入了泛型Lambda表达式,允许使用自动类型推导:
1 2 3 4 5 6 auto add = [](auto a, auto b) { return a + b; };int result1 = add (10 , 5 ); std::string result2 = add ("Hello, " , "Lambda!" ); double result3 = add (3.14 , 2.71 );
可变Lambda表达式 默认情况下,值捕获的变量在Lambda体内是不可修改的。使用mutable关键字可以修改值捕获的变量:
1 2 3 4 5 6 7 8 9 int x = 10 ;auto increment = [x]() mutable { x++; return x; }; std::cout << increment () << std::endl; std::cout << increment () << std::endl; std::cout << x << std::endl;
Lambda表达式的捕获初始化 C++14引入了Lambda表达式的捕获初始化,允许在捕获列表中初始化捕获的变量:
1 2 3 4 5 6 7 8 int x = 10 ;auto add = [y = x + 5 ](int z) { return y + z; }; std::cout << add (5 ) << std::endl;
函数对象、函数指针与Lambda表达式的比较 性能比较 函数指针 :调用开销较小,但不支持状态函数对象 :调用开销与函数指针相当,支持状态Lambda表达式 :编译时会被转换为函数对象,性能与函数对象相当使用场景 函数指针 :当需要一个简单的函数回调,且不需要状态时函数对象 :当需要一个可重用的、带状态的函数对象时Lambda表达式 :当需要一个临时的、一次性的函数对象时函数包装器 std::functionstd::function是C++11引入的一种通用函数包装器,定义在<functional>头文件中,它可以包装:
普通函数 函数指针 成员函数 函数对象 Lambda表达式 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 #include <functional> int add (int a, int b) { return a + b; } class Multiply {public : int operator () (int a, int b) const { return a * b; } }; int main () { std::function<int (int , int )> f1 = add; std::cout << f1 (10 , 5 ) << std::endl; Multiply multiply; std::function<int (int , int )> f2 = multiply; std::cout << f2 (10 , 5 ) << std::endl; std::function<int (int , int )> f3 = [](int a, int b) { return a - b; }; std::cout << f3 (10 , 5 ) << std::endl; return 0 ; }
std::bindstd::bind是C++11引入的一个函数适配器,定义在<functional>头文件中,它可以将函数与参数绑定,生成一个新的可调用对象:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 #include <functional> int add (int a, int b, int c) { return a + b + c; } int main () { auto add5 = std::bind (add, 5 , std::placeholders::_1, std::placeholders::_2); std::cout << add5 (10 , 15 ) << std::endl; auto add10 = std::bind (add, std::placeholders::_1, 10 , std::placeholders::_2); std::cout << add10 (5 , 15 ) << std::endl; return 0 ; }
应用示例 1. 排序 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 #include <algorithm> #include <vector> #include <string> struct Person { std::string name; int age; }; int main () { std::vector<Person> people = { {"Alice" , 25 }, {"Bob" , 30 }, {"Charlie" , 20 } }; std::sort (people.begin (), people.end (), [](const Person& a, const Person& b) { return a.age < b.age; }); for (const auto & person : people) { std::cout << person.name << " (" << person.age << ")" << std::endl; } return 0 ; }
2. 查找 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 #include <algorithm> #include <vector> int main () { std::vector<int > nums = {1 , 2 , 3 , 4 , 5 , 6 , 7 , 8 , 9 , 10 }; auto it = std::find_if (nums.begin (), nums.end (), [](int num) { return num > 5 ; }); if (it != nums.end ()) { std::cout << "First number greater than 5: " << *it << std::endl; } return 0 ; }
3. 转换 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 #include <algorithm> #include <vector> #include <string> #include <sstream> int main () { std::vector<int > nums = {1 , 2 , 3 , 4 , 5 }; std::vector<std::string> strs (nums.size()) ; std::transform (nums.begin (), nums.end (), strs.begin (), [](int num) { std::stringstream ss; ss << num; return ss.str (); }); for (const auto & str : strs) { std::cout << str << " " ; } std::cout << std::endl; return 0 ; }
4. 过滤 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 #include <algorithm> #include <vector> int main () { std::vector<int > nums = {1 , 2 , 3 , 4 , 5 , 6 , 7 , 8 , 9 , 10 }; std::vector<int > evenNums; std::copy_if (nums.begin (), nums.end (), std::back_inserter (evenNums), [](int num) { return num % 2 == 0 ; }); for (const auto & num : evenNums) { std::cout << num << " " ; } std::cout << std::endl; return 0 ; }
5. 函数组合 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 #include <functional> template <typename F, typename G>auto compose (F f, G g) { return [f, g](auto x) { return f (g (x)); }; } int main () { auto add5 = [](int x) { return x + 5 ; }; auto multiply2 = [](int x) { return x * 2 ; }; auto add5ThenMultiply2 = compose (multiply2, add5); std::cout << add5ThenMultiply2 (10 ) << std::endl; auto multiply2ThenAdd5 = compose (add5, multiply2); std::cout << multiply2ThenAdd5 (10 ) << std::endl; return 0 ; }
最佳实践 1. 优先使用Lambda表达式 对于一次性使用的小函数,优先使用Lambda表达式,它更加简洁、直观。
2. 合理使用捕获列表 值捕获 :当需要使用变量的当前值,且不希望变量的变化影响Lambda表达式时引用捕获 :当需要使用变量的最新值,或变量较大时混合捕获 :当需要值捕获某些变量,引用捕获其他变量时3. 避免过度使用Lambda表达式 对于复杂的、需要重复使用的逻辑,应该定义为普通函数或函数对象,而不是Lambda表达式。
4. 注意Lambda表达式的生命周期 引用捕获 :确保被引用的变量在Lambda表达式的生命周期内有效值捕获 :注意值捕获的变量的副本大小,避免捕获过大的对象5. 合理使用std::function 当需要存储或传递不同类型的可调用对象时,使用std::function。
总结 函数对象、函数指针和Lambda表达式是C++中实现回调和函数式编程的重要工具。它们各有优缺点,适用于不同的场景:
函数指针 :简单、直接,但不支持状态函数对象 :支持状态,可重用性好,但需要显式定义类Lambda表达式 :简洁、直观,支持捕获外部变量,适用于一次性使用的场景在现代C++中,Lambda表达式已经成为实现回调和函数式编程的首选工具,它结合了函数指针的简洁性和函数对象的灵活性。
通过本章的学习,读者应该掌握函数指针、函数对象和Lambda表达式的基本用法,能够在实际编程中合理选择和使用这些工具,提高代码的可读性和可维护性。