首页 > 编程语言 >【C++11】lambda表达式

【C++11】lambda表达式

时间:2024-07-21 18:24:48浏览次数:17  
标签:11 return cout int auto 函数 C++ lambda

目录

lambda表达式

function包装器

bind绑定


lambda表达式

我们之前如果要对一组数据排序,我们可以调用sort,并且传一个仿函数对象或者函数指针或者调用库中的类模板并实例化成对象,比如:

bool Less(int a, int b)
{
	return a < b;
}
struct Greater
{
	bool operator()(int a, int b)
	{
		return a > b;
	}
};
int main()
{
	vector<int>f = { 5,8,4,1,6,9,3,2,7,0 };
	sort(f.begin(), f.end(), Greater());//自己实现的仿函数对象
	for (auto e : f)
		cout << e << ' ';
	cout << endl;
	sort(f.begin(), f.end(), Less);//自己实现的函数指针
	for (auto e : f)
		cout << e << ' ';
	cout << endl;
	sort(f.begin(), f.end(), greater<int>());//库中的降序
	for (auto e : f)
		cout << e << ' ';
	cout << endl;
	sort(f.begin(), f.end(), less<int>());//库中的升序
	for (auto e : f)
		cout << e << ' ';
	cout << endl;
	return 0;
}

这是我们之前的写法,如果只是单纯的排序一种数据还是比较容易的,但是实际生活中,一个商品往往有很多的属性需要排序,比如一款手机的价格,销量,用户评价等,不仅需要升序还需要降序,所以就导致我们需要写很多的仿函数,这样无疑是非常不方便的,还有可能因为命名问题导致矛盾的产生,所以,为了避免这种情况,我们可以使用lambda表达式

lambda表达式的书写格式如下:

[捕捉列表](参数列表)mutable->返回值类型{函数体}

其中呢,捕捉列表可以不写,但是方括号必须存在;如果没有参数,参数列表可以跟圆括号一起省略;mutable一般不写;返回值类型可以不写,函数体肯定写。可能比较抽象,我们来举个简单的例子

auto add = [](int a, int b)->int {return a + b; };
	cout << add(1, 2) << endl;

auto swap1 = [](int& a, int& b)->void
	{
		int tmp = a;
		a = b;
		b = tmp;
	};
	int x = 1, y = 2;
	swap1(x, y);

lambda所在的是一条语句,所以最后要加分号

那我们再写一个部分可以省略的lambda表达式

auto func1 = [] {
		cout << "hello world" << endl;
	};
	func1();

有了lambda表达式,那么再处理之前多种数据比较的问题就简单了,比如:

struct fruit {
	const char* name;//水果名字
	int price;//价格
	int sales;//销量
};

int main()
{
	vector<fruit>f = { {"苹果",5,10},{"香蕉",4,25},{"西瓜",10,6},{"葡萄",6,12}};

	sort(f.begin(), f.end(), [](const fruit& f1, const fruit& f2) {return f1.price < f2.price; });
	for (auto& e : f)
		cout << e.price << ' ';
	cout << endl;
	sort(f.begin(), f.end(), [](const fruit& f1, const fruit& f2) {return f1.price > f2.price; });
	for (auto& e : f)
		cout << e.price << ' ';
	cout << endl;
	sort(f.begin(), f.end(), [](const fruit& f1, const fruit& f2) {return f1.sales < f2.sales; });
	for (auto& e : f)
		cout << e.sales<< ' ';
	cout << endl;
	sort(f.begin(), f.end(), [](const fruit& f1, const fruit& f2) {return f1.sales > f2.sales; });
	for (auto& e : f)
		cout << e.sales << ' ';
	cout << endl;
	return 0;
}

lambda表达式就是一个局部的匿名函数对象,像上面这样写也确实比写一堆仿函数要清晰明了一些

讲完了lambda表达式的基本使用,我们就来详细的说一下它各个部分具体的作用

首先是捕捉列表,分为传值捕捉和传引用捕捉

比如说传值捕捉,它捕捉到的是当前对象的拷贝,默认这个拷贝是不能修改的,要想修改就要加mutable(可变的),只要加了这个单词,圆括号及里面的内容不能省略

下面是传引用捕捉,就是可以修改原来的变量的值

下面是传值捕捉所有,就是说,当前域中的所有对象都被捕捉进来了

下面是传引用捕捉所有,其实就是一个书写格式的事情

下面是传引用捕捉当前域所有对象,某些对象传值捕捉

其实lambda表达式的底层就是仿函数,我们用这个代码从汇编的角度看一下

class Rate
{
public:
	Rate(double rate)
		:_rate(rate){}
	double operator()(double money, int year)
	{
		return money * _rate * year;
	}
private:
	double _rate;
};

int main()
{
	double rate = 1.35;
	Rate r1(rate);
	r1(1000,3);

	auto func1 = [=](double money, int year)->double {return money * rate * year; };
	func1(1000, 3);
	return 0;
}

r1 和func1进行运算的时候汇编分别是这样的:

我们可以看到lambda表达式底层就是调用的仿函数,并且这个仿函数的类型是前面的一大串,这个串这么长就是为了保证它是唯一的,所以lambda表达式间不能相互赋值,因为它们的类型不同

就是因为lambda表达式的类型复杂,所以我们才用auto接受,但是我们知道decltype可以知道类型,所以它们两个之间可以这么用:

class Date
{
public:
	bool operator>(const Date& d)const
	{
		if (_y != d._y)
		{
			return _y > d._y;
		}
		else
		{
			if (_m != d._m)
			{
				return _m > d._m;
			}
			else
			{
				if (_d != d._d)
				{
					return _d > d._d;
				}
				else
				{
					return false;
				}
			}
		}
	}
private:
	int _y = 0;
	int _m = 0;
	int _d = 0;
};
int main()
{
	auto DateGreater = [](const Date& d1, const Date& d2)
	{
		return d1>d2;
	};
	cout << typeid(DateGreater).name() << endl;
	//lambda对象禁掉默认构造,所以这里传一个对象用拷贝构造
	priority_queue<Date, vector<Date>, decltype(DateGreater)>p1(DateGreater);
	return 0;
}

function包装器

包装器实际上是一个类模板,可以将可调用对象(函数指针,仿函数,lambda表达式)进行再封装,就是说,将上面的一系列可调用对象在封装一层,这样它们的类型就统一了,这样调用就更好调用了

它也是用的可变模板参数,它写的形式比较特殊,我们后面会写

那我们来比较一下不用function类模板和用的区别:我们建一个函数模板,函数模板对于参数相同,返回值相同的函数指针,仿函数,lambda表达式会形成三个具体的函数,但是function因为只需要形成一个类,所以就只会形成一个函数

template<class F,class V>
V useF(F f, V val)
{
	static int count = 0;
	cout << "count: " << ++count << endl;
	cout << "&count:" << &count << endl;
	return f(val);
}
double f(double i)
{
	return i / 2;
}
struct Functor
{
	double operator()(double d)
	{
		return d / 3;
	}
};
int main()
{
	cout << useF(f, 11.1) << endl;//函数指针
	cout << useF(Functor(), 11.1) << endl;//函数对象
	cout << useF([](double d)->double {return d / 4; }, 11.1) << endl;//lambda表达式

	return 0;
}

结果是

证明确实生成了三个不同的函数

下面再用function,要包头文件<functional>

#include<iostream>
#include<functional>
using namespace std;
template<class F,class V>
V useF(F f, V val)
{
	static int count = 0;
	cout << "count: " << ++count << endl;
	cout << "&count:" << &count << endl;
	return f(val);
}
double f(double i)
{
	return i / 2;
}
struct Functor
{
	double operator()(double d)
	{
		return d / 3;
	}
};
int main()
{
	function<double(double)>fc1 = f;
	cout << useF(fc1, 11.1) << endl;
	function<double(double)>fc2 = Functor();
	cout << useF(fc2, 11.1) << endl;
	function<double(double)>fc3 = [](double d)->double {return d / 4; };
	cout << useF(fc3, 11.1) << endl;

	return 0;
}

这个写的形式就是<返回值类型(参数类型)>,这个的结果是

证明确实只生成了一个函数

它有什么作用呢?我们之前写过逆波兰表达式求值,我们之前都是僵硬的编码,有了function就简单了,我们可以把运算符号和运算方法写到一起放到一个hash表中

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

下面是包装函数,分为普通函数,static静态函数,普通成员函数

class Plus
{
public:
	static int plusi(int a, int b)
	{
		return a + b;
	}
	double plusd(double a, double b)
	{
		return a + b;
	}
};
int f(int a, int b)
{
	return a + b;
}
int main()
{
	Plus plus;
	function<int(int, int)>fc1 = f;//普通函数
	cout << fc1(1, 1) << endl;
	function<int(int, int)>fc2 = Plus::plusi;//静态成员函数
	cout << fc2(1, 1) << endl;

	function<double(Plus*, double, double)>fc3 = &Plus::plusd;//非静态成员函数
	cout << fc3(&plus,1, 1) << endl;

	function<double(Plus, double, double)>fc4 = &Plus::plusd;//非静态成员函数
	cout << fc4(Plus(), 1, 1) << endl;
}

bind绑定

bing是一个函数模板,对于可调用对象来说,它可以调整参数的顺序和个数

int Sub(int a, int b)
{
	return a - b;
}
int main()
{
	int x = 10;
	int y = 20;
	cout << Sub(x, y) << endl;
	auto f1 = bind(Sub, placeholders::_2, placeholders::_1);
	cout << f1(x, y) << endl;
	return 0;
}

这里的_2和_1分别对应实参中的第一个和第二个参数,就是说:bing中和f1中的参数是按顺序对应的,而_2在Sub里又是第二个参数,这样就调整了参数顺序

auto fc4 = bind(&Plus::plusd, Plus(), placeholders::_1, placeholders:: _2);
	cout << fc4(2, 3) << endl;;
	auto fc5 = bind(&Plus::plusd, Plus(), placeholders::_1, 20);
	cout << fc5(2) << endl;

这样就可以绑定部分参数,改变函数参数的个数

标签:11,return,cout,int,auto,函数,C++,lambda
From: https://blog.csdn.net/2201_76024104/article/details/140583515

相关文章

  • C++学习之路——第一天(结构体、C++程序从编写到运行)
    引子:兜兜转转还是你~C++数据类型int占用4个字节(32位机)char占用1个字节(8位机)short占用2个字节(16位机)long占用4个字节(32位机)float占用4个字节(32位机)double占用8个字节(64位机)longdouble占用16个字节(64位机)define和const的区别define在......
  • C++多线程编程中的锁详解
    在现代软件开发中,多线程编程是提升应用程序性能和响应能力的重要手段。然而,多线程编程也带来了数据竞争和死锁等复杂问题。为了确保线程间的同步和共享数据的一致性,C++标准库提供了多种锁机制。1.std::mutexstd::mutex是最基础的互斥锁,用于保护共享数据,防止多个线程同时访问......
  • P3522 [POI2011] TEM-Temperature
    原题链接题解尽量直观地理解单调队列的作用首先,对于合法的一段,有如下性质A满足:当前的最高温度大于等于前面的最大的最低温度该性质对于段内每一个数都满足,所以对于第\(i\)天,我们可以找其前面的第一天\(j\)的最低温度大于\(i\)的最高温度,同时还要满足\((j,i]\)内......
  • 湖南(市场调研公司)源点咨询 市场研究中11种数据分析技术
    湖南源点市场调研认为,不同的营销问题适合用不同的分析工具。在众多工具中做出选择,调研人员必须了解每种工具的优缺点。接下来将介绍11种可能用到的工具。(1)多元回归分析:分析一个数值型因变量和多个数值型自变量关系的分析方法。使用时必须认真考虑正态性、线性和同方差性......
  • 1186. 删除一次得到子数组最大和 Medium
    给你一个整数数组,返回它的某个 非空 子数组(连续元素)在执行一次可选的删除操作后,所能得到的最大元素总和。换句话说,你可以从原数组中选出一个子数组,并可以决定要不要从中删除一个元素(只能删一次哦),(删除后)子数组中至少应当有一个元素,然后该子数组(剩下)的元素总和是所有子数组之中......
  • RV1126中AI例程解析
    本次以基于rockx组件的ssd目标检测为例官方提供的例程中有2个C++文件及2个头文件其中:atk_ssd_object_recognize.cpp是一个使用Rockchip嵌入式平台进行图像处理的示例程序。整体功能是初始化视频输入、进行图像处理(RGA),然后将结果输出到显示终端。以下是代码的详细解释:引入......
  • C++ PDF PoDoFo库使用教程
    #include<podofo/podofo.h>#include<iostream>//AllPoDoFoclassesarememberofthePoDoFonamespace.//usingnamespacestd;usingnamespacePoDoFo;PdfFont*getFont(PdfDocument&doc);//Base14+othernon-Base14fontsforcomparis......
  • DAY11
    for循环packagecom.drumk.struct;/*1*1=11*2=22*2=41*3=32*3=63*3=91*4=42*4=83*4=124*4=161*5=52*5=103*5=154*5=205*5=251*6=62*6=123*6=184*6=245*6=306*6=361*7=72*7=143*7=214*7=285*7=356*7=427*7=491*8=82*8=163*8=244*8=325*8=40......
  • C++字体库开发之fontconfig使用五
    代码 #include<cassert>#include<algorithm>#include"fontconfig/fontconfig.h"#include<stdexcept>#include<iostream>#defineHAS_FALLBACK_CONFIGURATIONenumFontStyle:uint16_t{Regular=0,Italic=0x01......
  • Chapter 11 Paython数据容器:序列
    欢迎大家订阅【Python从入门到精通】专栏,一起探索Python的无限可能!文章目录前言一、序列的定义二、序列的切片前言在Python中,数据容器是组织和管理数据的重要工具,序列作为其中一种基本的数据结构,具有独特的特性和广泛的应用。本章详细介绍了序列的定义及其切片......