lambda表达式是C++11新推出来的一个概念,非常好用,但如同C++的其他知识点一样,坑也很多。
想要比较全面的介绍lambda,放到一篇博客中会比较臃肿,故决定用两篇介绍。本篇重点介绍lambda的简单应用,另一篇着重介绍lambda的坑。
进入正题!
lambda可以理解成局部函数,即函数中定义一个函数,与STL算法搭配使用,会极其方便。可以这么说,有了lambda,STL如虎添翼。
但是!lambda也可以定义成全局的,即不在函数内定义。但这种做法有违了lambda的设计初衷,直接用普通函数就好。
其语法格式如下:
[捕获列表] (参数列表) -> 返回值类型 { 函数体 }
其中,仅有“[]{}”是必须的,其他全部可以省略不写。lambda的使用,和函数类似,先要定义,然后调用。
简单的示例代码如下:
auto lam_global = []{std::cout << "全局" << std::endl; }; //函数体外定义的lambda,虽然合法,但不建议这么做,用普通函数即可
int _tmain(int argc, _TCHAR* argv[])
{
using namespace std;
auto lam_empty = []{}; //最简单的Lambda表达式,什么都不做
auto lam_print = []{cout << "简单lambda" << endl; };
lam_empty();
lam_print();
lam_global();
system("pause");
return 0;
}
执行结果如下:
上面用的是auto关键字定义的变量名来接受lambda表达式,用函数指针也可以:
using Fun = void(*)(void);
Fun fun = []{cout << "yes" << endl; };
fun();
建议统一用auto,交由编译器完成类型推断!
下面一一解释各部分。
1、参数列表和普通函数的参数列表一样,可以是值、引用、const引用等,也是作为形参;
2、如果是非const引用,函数体内部可以修改引用的值;
3、与普通函数不同,lambda表达式的参数不能有默认值。
示例代码如下:
int _tmain(int argc, _TCHAR* argv[])
{
using namespace std;
auto lam_printStr = [](const string& str){cout << str << endl; }; //const 引用
auto lam_printNum = [](int& iValue, double dValue){cout << dValue << endl; ++iValue; }; //既有值传递,也有引用传递
//auto lam_defualtValue = [](int iValue = 10){}; //非法,无法通过编译,参数不能有默认值
lam_printStr("lambda");
int iCount = 0;
lam_printNum(iCount, 12.12); //执行结束,iCount会被+1
cout << iCount << endl; //为1
system("pause");
return 0;
}
执行结果如下:
1、可以使用参数列表中传递的参数;
2、可以直接访问静态局部变量,以及全局变量;
3、不能直接访问非静态局部变量;
4、可以使用捕捉列表里捕捉的局部变量;
5、可以在函数体中对全局变量和静态局部变量修改。
第一点上面已经介绍过。
第二点,静态的局部变量、全局变量以及其他的函数,都可以在lambda表达式的函数体中直接访问。
第三点和第四点相辅相成,因为有了第三点,才会有第四点。
请看示例代码:
std::string g_strGlobal("全局变量");
int _tmain(int argc, _TCHAR* argv[])
{
using namespace std;
static string s_strLocal = "静态局部变量";
string strTemp = "非静态局部变量";
//函数体中可以直接访问局部静态变量、全局变量以及参数列表中的形参
auto lam_print = [](char chSperator){
cout << g_strGlobal << chSperator << s_strLocal << endl;
g_strGlobal = "global"; //修改全局变量
s_strLocal = "local"; //修改静态局部变量
};
//auto lam_printLocal = [](){cout << strTemp << endl;}; //编译不过,函数体无法直接访问非静态局部变量
lam_print('/');
cout << g_strGlobal << endl;
cout << s_strLocal << endl;
system("pause");
return 0;
}
执行结果如下:
函数体中不能直接访问非静态局部变量,有没有其他方式访问呢?肯定有,就是接下来要说的捕捉列表,也是lambda表达式中必须的成分之一。
有关捕捉列表,有很多坑及注意事项,将在另一篇文章中深挖:
https://blog.csdn.net/yedawei_1/article/details/109508875
这里仅介绍捕捉列表的简单使用。
1、捕捉列表中直接写非静态变量的变量名,函数体中便可访问;
2、与参数列表不同,不需要在变量名前加上类型;
3、如果有多个变量,用","隔开;
4、只能捕捉在lambda定义之前已经定义的非静态局部变量;
5、直接写变量名,为值捕获;想要引用方式捕获,则在变量名前加上"&"。
将上述代码略作修改,将局部变量strTemp以值捕获的方式放到捕捉列表,在函数体中便可访问。如下:
std::string g_strGlobal("全局变量");
int _tmain(int argc, _TCHAR* argv[])
{
using namespace std;
static string s_strLocal = "静态局部变量";
string strTemp = "非静态局部变量";
auto lam_print = [strTemp](char chSperator){cout << g_strGlobal << chSperator << s_strLocal << chSperator << strTemp<< endl; };
lam_print('/');
system("pause");
return 0;
}
执行结果如下:
如果是引用捕获,在函数体中,是可以修改变量的值的;如果是值捕获,直接在函数体中修改,会编译报错(有修改方式,请参见另一篇文章)。
示例代码如下:
std::string g_strGlobal("全局变量");
int _tmain(int argc, _TCHAR* argv[])
{
using namespace std;
static string s_strLocal = "静态局部变量";
string strTemp1 = "非静态局部变量1";
string strTemp2 = "非静态局部变量2";
auto lam_print = [strTemp1, &strTemp2/*, strTemp3*/](char chSperator) //这里的strTemp3还未定义,无能捕获,会编译报错
{
cout << g_strGlobal << chSperator << s_strLocal;
cout << chSperator << strTemp1 << chSperator << strTemp2 << endl;
//strTemp1 = "strTemp1"; //会编译不过
strTemp2 = "local"; //修改引用捕获的非静态局部变量的值
};
string strTemp3 = "非静态局部变量3";
lam_print('/');
cout << strTemp2 << endl;
system("pause");
return 0;
}
执行结果如下:
这里定义了两个非静态的局部变量strTemp1和strTemp2,strTemp1为值捕获,strTemp2为引用捕获。
在函数体中,对strTemp2进行修改,在lambda表达式执行成功后打印strTemp2,值为lambda表达式中的修改的值,修改成功。
至此,lambda表达式的基础内容已差不多。至于返回值,在另一篇文章中讨论。
下面看看lambda表达式在STL中运用。
for_each算法在另一篇文章中有介绍:https://blog.csdn.net/yedawei_1/article/details/105843348
改算法会忽视第三个一元谓词的返回值,如果有lambda表达式,很容易做到直接更改原始容器中的元素。
例如,想要将原始容器中所有小于0的元素改为正的,可以这么做:
int _tmain(int argc, _TCHAR* argv[])
{
using namespace std;
list lstInt = { -10, 15, 20, -3, -45 };
for_each(lstInt.begin(), lstInt.end(), [](int& i){
if (i < 0)
i = -i;
});
for (int i : lstInt)
cout << i << " ";
cout << endl;
system("pause");
return 0;
}
执行结果:
sort函数(std::sort和list.sort),都有接受一个二元谓词的版本。但也有限值,只能接受两个参数,如果想要引入比较因子,有些麻烦,lambda恰恰就能实现要求。
比如,想要对一个list
假如原始元素为{2,5,8,1,3},比较因子为4,则排序后应该为{2,1,3,5,8}。
实现代码如下:
int _tmain(int argc, _TCHAR* argv[])
{
using namespace std;
//定义一个打印函数
auto fun_print = [](const list& lst){
for_each(lst.begin(), lst.end(), [](int i){ cout << i << " "; });
cout << endl;
};
int iSign = 9; //最初的比较因子
auto fun_compare = [&iSign](int iLeft, int iRight){ //按照引用捕获的方式获取比较因子
if (iLeft <= iSign && iRight > iSign)
return true;
else
return false;
};
list lstInt = { 2, 18, 17, 13, 12, 8, 10, 1, 11, 45 };
lstInt.sort(fun_compare);
fun_print(lstInt);
iSign = 12; //改变比较因子
lstInt.sort(fun_compare);
fun_print(lstInt);
system("pause");
return 0;
}
执行结果如下:
这段代码,定义了一个打印容器中所有元素的lambda表达式fun_print,注意,里面嵌套了另一个lambda表达式。
lambda表达式可以嵌套使用!
接着,定义了一个比较因子iSign,以及自定义lambda表达式作为比较谓词。
注意,fun_compare中用的是引用捕获。这里必须用引用捕获,不能用值捕获,否则后面比较因子的改变会无效。另一篇文章中有详细解释。
后面就是调用和实现。
除了这些外,lambda还有非常多的用途,掌握后会使得代码更加简练、易读。