C++11学习----lambda表达式,function包装器,bind绑定

目录

一,lambda表达式

1.lambda表达式简介

[2 lambda表达式的五个部分](#2 lambda表达式的五个部分)

3.lambda表达式的使用

4.lambda表达式的类型

5,lambda表达式的本质

二,function包装器

1,function简介

2.为什么要包装可调用对象

3.使用

三,bind绑定器


一,lambda表达式

1.lambda表达式简介

lambda表达式的书写格式如下:
[capture-list] (parameters) mutable -> return-type { statement }

2 lambda表达式的五个部分

1.捕捉列表capture-list:[ ] 这个部分是lambda表达式不可缺少的一个部分,也正是这一部分让我们的lambda表达式成为了lambda表达式。

**2.参数parameters:**这个部分是lambda表达式中可要可不要的一个部分。

**3.mutable:**这个东西相当于一个关键字,因为lambda表达式的参数的属性默认是const的,使用这个mutable能够解除掉lambda表达式的const属性。

**4.返回值return-type:**这个部分也是可写可不写的,因为lambda表达式可以自动推导返回值的类型。

**5.函数体statement:**一定得写。

所以最简单的lambda表达式为:[]{};

3.lambda表达式的使用

对于lambda表达式的捕捉列表有如下的使用方式:

**1.[val]:**直接捕捉变量,直接传入变量名的方式。

**2.[&val]:**以引用的方式捕捉。

**3.[=]:**以传值的方式捕捉所有在lambda父作用域内的变量。

**4.[&]:**以传引用的方式捕捉所有父作用域内的变量。

**5.[this]:**捕捉this指针,主要用于类里面。

示例:

1,

cpp 复制代码
//1,[val]传值方式
int main()
{
	int i = 0, j = 0;
	cout << "adress:" << &i << " " << &j << endl<<endl;

	auto f1 = [i, j] {cout << "adress:" << &i << " " << &j << endl << endl;};
	f1();
	return 0;
}

//

结果:两者的地址不一样,所以是传值,是一种拷贝行为。

cpp 复制代码
adress:006FF904 006FF8F8

adress:006FF8E8 006FF8EC

2,

cpp 复制代码
//1,[&val]传引用方式
int main()
{
	int i = 0, j = 0;
	cout << "adress:" << &i << " " << &j << endl<<endl;

	auto f1 = [&i, &j] {cout << "adress:" << &i << " " << &j << endl << endl;};
	f1();
	return 0;
}

//

结果:两者地址相同,所以是传引用

cpp 复制代码
adress:00BAFA08 00BAF9FC

adress:00BAFA08 00BAF9FC

当然,这两者还可以搭配起来使用变成:auto f1 = [i, &j] {cout << "adress:" << &i << " " << &j << endl << endl;};这样的话,第一个i便是传值,第二个j便是传应用。

3,

cpp 复制代码
//直接以传值的方式捕捉父作用域的变量 [=]
int main()
{
	int i = 0, j = 0;
	cout << "adress:" << &i << " " << &j << endl<<endl;

	auto f1 = [=] {cout << "adress:" << &i << " " << &j << endl << endl;};
	f1();
	return 0;
}

结果:

cpp 复制代码
adress:00F8FD8C 00F8FD80

adress:00F8FD70 00F8FD74

4,

cpp 复制代码
//以传引用的方式捕捉所有的变量[&]
int main()
{
	int i = 0, j = 0;
	cout << "adress:" << &i << " " << &j << endl<<endl;

	auto f1 = [&] {cout << "adress:" << &i << " " << &j << endl << endl;};
	f1();
	return 0;
}

结果:

cpp 复制代码
adress:00B9F9D8 00B9F9CC

adress:00B9F9D8 00B9F9CC

5,

第五种方式便是捕捉this指针,this指针在哪里呢?在类里面。所以捕捉this指针的使用场景便是在类里面使用的。

对于如下场景直接捕捉变量是行不通的:

cpp 复制代码
class A {
public:
	void func()
	{
		auto f2 = [a1,a2] {cout << "a1:" << a1 << " " << "a2:" << " " << a2;};
	}

private:
	int a1 = 1;
	int a2 = 2;
};

原因在于lambda表达式的父作用域是func的函数体,a1,a2不在这个父作用域里面。在这个啥时候就得使用捕捉this指针的方式解决问题了:

cpp 复制代码
class A {
  public:
	void func()
	{
		auto f2 = [this] {cout << "a1:" << a1 << " " << "a2:" << " " << a2;};
	}

private:
	int a1 = 1;
	int a2 = 2;
};

使用:

cpp 复制代码
class A {
public:
	void func()
	{
		auto f2 = [this] {cout << "a1:" << a1 << " " << "a2:" << " " << a2;};
		f2();
	}

private:
	int a1 = 1;
	int a2 = 2;
};


int main()
{
	A a;
	a.func();
	return 0;
}

结果:

cpp 复制代码
a1:1 a2: 2

能用this指针的原因在于,this指针其实是类函数的一个默认的参数。

4.lambda表达式的类型

使用typeid(函数名/变量名).name()便可以知道一个lambda表达式的类型,实验如下:

cpp 复制代码
int main()
{
	auto f1 = [] {};
	auto f2 = [] {};

	cout << typeid(f1).name() << endl << endl;
	cout << typeid(f2).name() << endl << endl;

	return 0;
}

得到的结果如下:

cpp 复制代码
class <lambda_d4e7b29f43988cb7e0ced57656dd48f2>

class <lambda_5e6719a5fcb7a4e6979dcea7af21cec0>

可以看到这两个lambda表达式的类型都是奇怪的东西,都是一个class加上一个lambda+一串数字。这串数字其实是一个UUID。UUID是啥呢?百度如下:

这个UUID表示的是一个唯一通识码。其实在查完以后我还是对此并不了解,但是我可以告诉你在lambda表达式里面如果这两个lambda表达式的UUID不一样,那这两个lambda表达式的类型也是不一样的。**所以因为类型不同,于是f1和f2也是不可以相互赋值的。(不同的lambda表达式不能相互赋值)**如果赋值便会报错:

5,lambda表达式的本质

lambzda表达式的本质其实就是一个仿函数。何以见得呢?这两个万一完全不一样啊?仿函数是在一个类里面实现一个operator(),但是lambda表达式却是一个[]{}的表达式,这两个看起来毫不相干的东西怎么能使一个玩意呢?在这个时候就要透过现象看本质了,拉出汇编代码:

cpp 复制代码
struct com//仿函数
{
public:
	bool operator()(int a,int b)
	{
		return a > b;
	}
};

int main()
{
	int i = 0, j = 1;
	auto f1 = [i, j](int i,int j) {return i > j;};
	f1(i, j);//调用lambda表达式

	com Com;
	Com(i, j);//调用仿函数
	return 0;
}

现在来查看一下汇编代码:

仿函数:

lambda表达式:

从lambda表达式和仿函数的汇编代码的比较可以看出这这两个毫不相干的的两个东西其实底层是一样的!!!

二,function包装器

1,function简介

在这里先介绍一下function包装器的,首先function包装器是定义在头文件functional内的。function包装器是用来包装可调用对象的。这里的可调用对象包括:

1.函数指针 2.仿函数 3.lambda表达式。

2.为什么要包装可调用对象

这三个可调用对象其实各有自己的缺点:

1.函数指针反人类:void(*comback)()

2.仿函数太重了:在使用仿函数时还需要专门去写一个类来对仿函数进行调用。

3.lambda表达式的类型不确定,难以调用。

因为以上的·缺点,导致这三个可调用对象难以调用。所以function包装器应运而生!!!

3.使用

包装器的使用:function<return_type(Parameter type)>

当可调用对象的返回值和参数可以匹配时便可以包装成功。

使用场景如下:

1.逆波兰表达式的普通写法

cpp 复制代码
class Solution {
public:
    int evalRPN(vector<string>& tokens) {

        stack<int>st;

        for(int i = 0;i<tokens.size();i++)
        {
         

            if(tokens[i]=="+"||tokens[i]=="-"||tokens[i]=="*"||tokens[i]=="/")
            {

                int right = st.top();
                st.pop();
                int left = st.top();
                st.pop();
                if(tokens[i]=="+")
                {
                    st.push(left+right);
                }

                if(tokens[i]=="-")
                {
                    st.push(left-right);
                }

                if(tokens[i]=="*")
                {
                    st.push(left*right);
                }
                
                if(tokens[i]=="/")
                {
                    st.push(left/right);
                }
            }
            else
            {
                st.push(stoi(tokens[i]));
            }
        }

        return st.top();

    }
};
控制台

2.加入function包装器后的改进版本:

cpp 复制代码
class Solution {
public:
    int evalRPN(vector<string>& tokens) {
        stack<int> st;
        map<string, function<int(int, int)>> hash = 
        {
            {"+", [](int x, int y)->int{return x + y;}},
            {"-", [](int x, int y)->int{return x - y;}},
            {"*", [](int x, int y)->int{return x * y;}},
            {"/", [](int x, int y)->int{return x / y;}},
        };
        for(auto& e : tokens)
        {
            if(hash.count(e) == 0)
            {
                st.push(stoi(e));
            }
            else
            {
                int right = st.top();
                st.pop();
                int left = st.top();
                st.pop();
                st.push(hash[e](left, right));
            }
        }
        return st.top();
    }
};
 

三,bind绑定器

bind绑定器的作用有两个,一个是调整参数的顺序,一个是调整参数的个数。在这两个作用中,其实有用的是第二个。接下来看看代码实例:

1.调换参数顺序

cpp 复制代码
int Sub(int x, int y)
{
	return x - y;
}

int main()
{
	function<int(int, int)> f1 = bind(Sub,placeholders::_2, placeholders::_1);//传入的第一个参数给第二位,第二个参数给第一位
	cout<<f1(10, 5)<<endl<<endl;

	return 0;
}

结果:

cpp 复制代码
-5

2.减少调用参数

成员函数内有this*类型的参数,每次传参时为了匹配类型都要传入this*类型的参数。为了减少这种操作便可以将plus()绑死减少传参时的麻烦!!!

cpp 复制代码
class Plus
{
	public:
	int Add(int x, int y)
	{
		return x + y;
	}
};

int main()
{
  function<int(int, int)> f2 = bind(&Plus::Add,Plus(), placeholders::_1, placeholders::_2);
  cout << f2(1, 5) << endl;

  return 0;

}
相关推荐
yunhuibin11 分钟前
ffmpeg面向对象——拉流协议匹配机制探索
学习·ffmpeg
hengzhepa21 分钟前
ElasticSearch备考 -- Search across cluster
学习·elasticsearch·搜索引擎·全文检索·es
黑不溜秋的21 分钟前
C++ 语言特性29 - 协程介绍
开发语言·c++
一丝晨光25 分钟前
C++、Ruby和JavaScript
java·开发语言·javascript·c++·python·c·ruby
￴ㅤ￴￴ㅤ9527超级帅39 分钟前
LeetCode hot100---二叉树专题(C++语言)
c++·算法·leetcode
_GR1 小时前
每日OJ题_牛客_牛牛冲钻五_模拟_C++_Java
java·数据结构·c++·算法·动态规划
蜡笔小新星1 小时前
Python Kivy库学习路线
开发语言·网络·经验分享·python·学习
攸攸太上1 小时前
JMeter学习
java·后端·学习·jmeter·微服务
Death2001 小时前
Qt 中的 QListWidget、QTreeWidget 和 QTableWidget:简化的数据展示控件
c语言·开发语言·c++·qt·c#
六点半8881 小时前
【C++】速通涉及 “vector” 的经典OJ编程题
开发语言·c++·算法·青少年编程·推荐算法