【C++11(二)】lambda表达式以及function包装器

简介: 【C++11(二)】lambda表达式以及function包装器

1. 前言

C++11新增了lambda表达式来解决

特定场景下使用仿函数很麻烦的问题

而function包装器则将C语言中复杂的

函数指针问题给简单化了!

本章重点:

本篇文章着重讲解lambda表达式
的语法使用方法和实用场景以及
function包装器的语法使用以及如何
用包装器一次性搞定函数指针,仿函数
和lambda表达式,最后简单讲解关键字
decltype的使用方法和可变模板参数


2. lambda表达式的提出

在C++98中,对自定义类型进行排序时,

需要自己写仿函数,并传递给sort库函数

但是如果每次要按照自定义类型的不同

成员变量进行排序的话,就要写很多个仿

函数,十分的不方便,C++11给出了一个新玩法:

struct Goods
{
string _name;  // 名字
double _price; // 价格
int _evaluate; // 评价
Goods(const char* str, double price, int evaluate)
  :_name(str)
  , _price(price)
  , _evaluate(evaluate)
{}
};
vector<Goods> v = { { "苹果", 2.1, 5 }, { "香蕉", 3, 4 }, { "橙子", 2.2, 3 }, { "菠萝", 1.5, 4 } };
sort(v.begin(), v.end(), [](Goods g1, Goods g2)->bool
{return g1._price < g2._price; });//按照价格升序
sort(v.begin(), v.end(), [](Goods g1, Goods g2)->bool
{return g1._price > g2._price; });//按照价格降序
sort(v.begin(), v.end(), [](Goods g1, Goods g2)->bool
{return g1._evaluate < g2._evaluate; });//按照评价升序
sort(v.begin(), v.end(), [](Goods g1, Goods g2)->bool
{return g1._evaluate > g2._evaluate; });//按照评价降序

看不懂没关系,现在你只需要知道这种

写法可以代替完美去写仿函数即可.

它的大概意思请看下图:


3. lambda表达式的语法

书写格式:

A=捕捉列表, B=参数列表, C=返回值
`[A] (B)-> C {函数体}

lambda表达式各部分说明:

  • 捕捉列表总是出现在lambda函数的开始位置,编译器根据[]来判断接下来的代码是否为lambda表达式,捕捉列表能够捕捉上下文中的变量供lambda函数使用
  • 参数列表,与普通函数的参数列表一致,如果不需要传递参数,则可以连同()一起省略
  • 返回值类型,没有返回值时此部分可省略,返回值类型明确情况下,也可省略,由编译器对返回类型进行推导。
  • 函数体,在该函数体内,除了可以使用其参数外,还能使用捕捉列表中的变量
  • 参数列表和返回值类型都是可选部分,而捕捉列表和函数体可以为空,因此C++11中最简单的lambda表达式为:[]{};,该lambda表达式不能做任何事情。

事实上,可以把lambda表达式看作一个
class类,被捕捉或定义的变量可以看作
是类中的成员变量,但是lambda表达式
有一个特性是它默认有const属性,相当于
这个类的所有成员变量都是const修饰的
,无法被修改,要想修改它,要在返回值前加
上一个关键字: mutable

利用lambda表达式实现一个swap函数:

int x = 3;
int y = 5;
auto myswap = [](int& x,int&y)mutable->void
{
  int tmp = x;
  x = y;
  y = tmp;
};
myswap(x,y);

实际上lambda表达式的返回值是一个函数对象

在sort中传参正是要一个函数对象,而在这里需要

的是用函数对象来充当一个函数,也就是使用(x,y)


4. lambda表达式的捕捉列表

lambda表达式的捕捉列表[ ]可以

捕捉父作用域的变量供自己使用

下面请看它的捕捉规则:

首先捕捉分为值捕捉和引用捕捉

int a = 10;
char* b = "xxxxxxxxxxx"
vector<double> v{1.11,2.22};
auto it = [a,&b,c]()->bool{return b+="abcd";};
//以值传递的方式捕捉a和c,引用捕捉b

其实可以发现,lambda表达式的使用
方法和仿函数及其相似实际在底层
编译器对于lambda表达式的处理方式
完全就是按照函数对象的方式处理的
即:如果定义了一个lambda表达式,
编译器会自动生成一个类,
在该类中重载了operator()


5. function包装器

C++中的function本质是一个类模板

也是一个包装器,请看下面的代码:

ret = func(x);
// 上面func可能是什么呢?那么func可能是函数名?函数指针?函数对象(仿函数对象)?
//也有可能是lamber表达式对象?所以这些都是可调用的类型!如此丰富的类型
//可能会导致模板的效率低下!
template<class F, class T>
T useF(F f, T x)
{
  static int count = 0;
  cout << "count:" << ++count << endl;
  cout << "count:" << &count << endl;
  return f(x);
}
double f(double i)
{
  return i / 2;
}
struct Functor
{
  double operator()(double d)
  {
    return d / 3;
  }
};
int main()
{
// 函数名
 cout << useF(f, 11.11) << endl;
 // 函数对象
 cout << useF(Functor(), 11.11) << endl;
 // lamber表达式
 cout << useF([](double d)->double{ return d/4; }, 11.11) << endl;
 return 0;
}

我们会发现useF函数模板实例化了三份

但是不管func是什么东西,都可以用

function来定义,这也就提高了效率

function包装器的使用方法:

第一个int代表返回值类型
括号里面用逗号分割的是参数类型


6. function包装器使用场景

function的使用场景非常多,博主

结合了自己学习操作系统的编码

经验来给大家做几个分享:

  1. 创建线程时用function:

在Linux下创建线程时,我们使用
pthread_create函数时要传入此
线程要调用的线程函数对象,这里
配合function使用起来非常方便

pthread_t tid;
pthread_create(&tid,nullptr,[](void* args)->void*
{
  //函数体
},nullptr);
  1. 线程池内部的处理方法用function

在编写线程池时,每一个线程被创建
出来可能会执行不同的任务,也就是
执行不同的函数,但所有函数的参数
与返回值都一样,这是就可以使用一个
数组保存函数方法,而数组中的元素
类型就是function定义的对象类型!

//func_t是一种函数类型,此类型的函数的返回值和参数都是int
typedef function<int(int,int)> func_t;
//将不同的函数方法插入到数组中,使用时去数组找!
vector<func_t> Task;

这里旨在告诉大家,function的使用场景

很多,即使你现在还没有接触过它,你也

应该掌握它!!!


7. decltype关键字用法

关键字decltype可以将变量的

类型声明为表达式指定的类型

使用场景以及用法:

// decltype的一些使用使用场景
template<class T1, class T2>
void F(T1 t1, T2 t2)
{
  decltype(t1 * t2) ret;
  cout << typeid(ret).name() << endl;
}
int main()
{
const int x = 1;
double y = 2.2;
decltype(x * y) ret; // ret的类型是double
decltype(&x) p;      // p的类型是int*
cout << typeid(ret).name() << endl;
cout << typeid(p).name() << endl;
F(1, 'a');
return 0;
}

你可能会觉得decltype关键字很鸡肋

因为有auto可以自动推导类型了,还要

decltype干啥?不错!auto固然好用,但是

有些场景下你想要一个具体的类型时,

比如vector的元素类型时,你不能用auto

decltype([](int x)->int{return 2*x+10;}) it;

8. 可变参数模板讲解

下面是一个基本可变参数的函数模板

// Args是一个模板参数包,args是一个函数形参参数包
// 声明一个参数包Args...args,这个参数包中可以包含0到任意个模板参数。
template <class ...Args>
void ShowList(Args... args)
{}

上面的参数args前面有省略号,所以它就是一个可变模版参数,我们把带省略号的参数称为“参数包”,它里面包含0~N(N>=0)个模版参数。我们无法直接获取参数包args中的每个参数的,只能通过展开参数包的方式来获取参数包中的每个参数,这是使用可变模版参数的一个主要特点,也是最大的难点,即如何展开可变模版参数。由于语法不支持使用args[i]这样方式获取可变参数,所以我们的用一些奇招来一一获取参数包的值

递归函数的方式展开参数包:

// 递归终止函数
template <class T>
void ShowList(const T& t)
{
  cout << t << endl;
}
// 展开函数
template <class T, class ...Args>
void ShowList(T value, Args... args)
{
  cout << value <<" ";
  ShowList(args...);
}
int main()
{
  ShowList(1);
  ShowList(1, 'A');
  ShowList(1, 'A', std::string("sort"));
  return 0;
}

对于可变模板参数的认知到这儿就差不多了
这属于是了解认知的范畴,下次看见了不会懵逼


9. 总结

本篇文章介绍了两个C++11十分

常用的内容,lambda表达式和包装器

function是需要同学们掌握并且能熟练

编写的,后面的decltype和可变模板参数

属于了解内容,保证你下次看见这个的

时候不会懵逼


🔎 下期预告:智能指针详解🔍


相关文章
|
2月前
|
存储 编译器 C语言
从C语言到C++_34(C++11_下)可变参数+ lambda+function+bind+笔试题(下)
从C语言到C++_34(C++11_下)可变参数+ lambda+function+bind+笔试题
39 5
|
12天前
|
存储 C++
【C++】string类的使用③(非成员函数重载Non-member function overloads)
这篇文章探讨了C++中`std::string`的`replace`和`swap`函数以及非成员函数重载。`replace`提供了多种方式替换字符串中的部分内容,包括使用字符串、子串、字符、字符数组和填充字符。`swap`函数用于交换两个`string`对象的内容,成员函数版本效率更高。非成员函数重载包括`operator+`实现字符串连接,关系运算符(如`==`, `&lt;`等)用于比较字符串,以及`swap`非成员函数。此外,还介绍了`getline`函数,用于按指定分隔符从输入流中读取字符串。文章强调了非成员函数在特定情况下的作用,并给出了多个示例代码。
|
23天前
|
算法 编译器 C++
C++一分钟之—Lambda表达式初探
【6月更文挑战第22天】C++的Lambda表达式是匿名函数的快捷方式,增强函数式编程能力。基本语法:`[capture](params) -&gt; ret_type { body }`。例如,简单的加法lambda:`[](int a, int b) { return a + b; }`。Lambda可用于捕获外部变量(值/引用),作为函数参数,如在`std::sort`中定制比较。注意点包括正确使用捕获列表、`mutable`关键字和返回类型推导。通过实践和理解这些概念,可以写出更简洁高效的C++代码。
34 13
|
21天前
|
C++
C++语言的lambda表达式
C++从函数对象到lambda表达式以及操作参数化
|
2月前
|
程序员 编译器 C++
C++中的函数重载(Function Overloading)
C++中的函数重载(Function Overloading)
22 2
|
1月前
|
计算机视觉 C++
【见微知著】OpenCV中C++11 lambda方式急速像素遍历
【见微知著】OpenCV中C++11 lambda方式急速像素遍历
27 0
|
1月前
|
C++
C++ lambda表达式
C++ lambda表达式
|
1月前
|
算法 编译器 C++
C++多态与虚拟:函数重载(Function Overloading)
重载(Overloading)是C++中的一个特性,允许不同函数实体共享同一名称但通过参数差异来区分。例如,在类`CPoint`中,有两个成员函数`x()`,一个返回`float`,另一个是设置`float`值。通过函数重载,我们可以为不同数据类型(如`int`、`float`、`double`)定义同名函数`Add`,编译器会根据传入参数自动选择正确实现。不过,仅返回类型不同而参数相同的函数不能重载,这在编译时会导致错误。重载适用于成员和全局函数,而模板是另一种处理类型多样性的方式,将在后续讨论中介绍。
|
23天前
|
JavaScript 前端开发
JavaScript函数是代码复用的关键。使用`function`创建函数
【6月更文挑战第22天】JavaScript函数是代码复用的关键。使用`function`创建函数,如`function sayHello() {...}`或`function addNumbers(num1, num2) {...}`。调用函数如`sayHello()`执行其代码,传递参数按值进行。函数可通过`return`返回值,无返回值默认为`undefined`。理解函数对于模块化编程至关重要。
23 4
|
17天前
|
运维 负载均衡 Serverless
函数计算产品使用问题之yaml如果写多个function,可不可以yaml在构建的时候能构建多个函数
函数计算产品作为一种事件驱动的全托管计算服务,让用户能够专注于业务逻辑的编写,而无需关心底层服务器的管理与运维。你可以有效地利用函数计算产品来支撑各类应用场景,从简单的数据处理到复杂的业务逻辑,实现快速、高效、低成本的云上部署与运维。以下是一些关于使用函数计算产品的合集和要点,帮助你更好地理解和应用这一服务。