1 C/C++中左值引用与右值引用
何为左值与右值?
简单一句话概括:左值指的是代码中程序员定义的变量(如int a),右值是编译器编译过程中生成的临时变量。
int a = rand();
上述代码中i为左值,rand()函数返回的随机数会存放在一个临时变量里,这个临时变量就是右值。
右值引用可以减少拷贝构造函数的调用次数以及深拷贝的悬挂指针问题,右值引用常常与lamda表达式合起来用,可以非常方便的定义一个函数。
右值引用部分有个讲的非常好的几篇文章从4行代码看右值引用 (文章中除了右值引用还有指针悬挂,完美转发,移动构造函数等知识);此处不再赘述。
2 Lambda表达式
参考资料C++ 中的 Lambda 表达式(微软);Lamda表达式 C++11
Lambda表达式简介
Lambda表达式完整的声明格式如下:
[capture list] (params list) mutable exception-> return type { function body }
capture 子句(在 C++ 规范中也称为 Lambda 引导。)
参数列表(可选)。(也称为 Lambda 声明符)
mutable 规范(可选)。
exception-specification(可选)。
trailing-return-type(可选)。
Lambda 体。
Lambda表达式用法
捕获外部变量
Lambda表达式通过在最前面的方括号[]来明确指明其内部可以访问的外部变量,这一过程也称过Lambda表达式“捕获”了外部变量。
例子:
int main()
{
int a = 123;
auto f = [a] { cout << a << endl; };
f(); // 输出:123
//或通过“函数体”后面的‘()’传入参数
auto x = [](int a){cout << a << endl;}(123);
}
值捕获
值捕获和参数传递中的值传递类似,被捕获的变量的值在Lambda表达式创建时通过值拷贝的方式传入,因此随后对该变量的修改不会影响影响Lambda表达式中的值。
int main()
{
int a = 123;
auto f = [a] { cout << a << endl; };
a = 321;
f(); // 输出:123
}
引用捕获
int main()
{
int a = 123;
auto f = [&a] { cout << a << endl; };
a = 321;
f(); // 输出:321
}
隐式捕获
上面的值捕获和引用捕获都需要我们在捕获列表中显示列出Lambda表达式中使用的外部变量。除此之外,我们还可以让编译器根据函数体中的代码来推断需要捕获哪些变量,这种方式称之为隐式捕获。隐式捕获有两种方式,分别是[=]和[&]。[=]表示以值捕获的方式捕获外部变量,[&]表示以引用捕获的方式捕获外部变量。
int main()
{
int a = 123;
auto f = [=] { cout << a << endl; }; // 值捕获
auto f2 = [&] {cout << a << endl; }; //引用捕获
a = 321;
f(); // 输出:123
f2();// 输出321
}
4.混合方式
既有值捕获也有引用捕获。
总结
捕获形式 | 说明 |
[] | 不捕获任何外部变量 |
[变量名, …] | 默认以值得形式捕获指定的多个外部变量(用逗号分隔),如果引用捕获,需要显示声明(使用&说明符) |
[this] | 以值的形式捕获this指针 |
[=] | 以值的形式捕获所有外部变量 |
[&] | 以引用形式捕获所有外部变量 |
[=, &x] | 变量x以引用形式捕获,其余变量以传值形式捕获 |
[&, x] | 变量x以值的形式捕获,其余变量以引用形式捕获 |
修改捕获变量
前面我们提到过,在Lambda表达式中,如果以传值方式捕获外部变量,则函数体中不能修改该外部变量,否则会引发编译错误。那么有没有办法可以修改值捕获的外部变量呢?这是就需要使用mutable关键字,该关键字用以说明表达式体内的代码可以修改值捕获的变量。
int main()
{
int a = 123;
auto f = [a]()mutable { cout << ++a; }; // 不会报错
cout << a << endl; // 输出:123
f(); // 输出:124
}
Lambda体内的变量
Lambda 表达式的 Lambda 体是一个复合语句。 它可以包含普通函数或成员函数体中允许的任何内容。 普通函数和 lambda 表达式的主体均可访问以下变量类型:
从封闭范围捕获变量,如前所述。
参数。
本地声明变量。
类数据成员(在类内部声明并且捕获 this 时)。
具有静态存储持续时间的任何变量(例如,全局变量)。
异常规范
你可以使用 noexcept 异常规范来指示 Lambda 表达式不会引发任何异常。 与普通函数一样,如果 Lambda 表达式声明 noexcept 异常规范且 Lambda 体引发异常,Microsoft C++ 编译器将生成警告 C4297,如下所示:
// throw_lambda_expression.cpp
// compile with: /W4 /EHsc
int main() // C4297 expected
{
[]() noexcept { throw 5; }();
}
返回类型
可以指定通过->return type。比如:
int a = [](int x)->int{return x};
也可以让编译器自动推导。比如:
auto x1 = [](int i){ return i; }; // OK: return type is int
编译器可以根据以下规则推断出Lambda表达式的返回类型:
如果function body中存在return语句,则该Lambda表达式的返回类型由return语句的返回类型确定;
如果function body中没有return语句,则返回值为void类型。
Lambda表达式的参数
Lambda表达式的参数和普通函数的参数类似,那么这里为什么还要拿出来说一下呢?原因是在Lambda表达式中传递参数还有一些限制,主要有以下几点:
参数列表中不能有默认参数
不支持可变参数
所有参数必须有参数名
高阶 Lambda 函数
// higher_order_lambda_expression.cpp
// compile with: /EHsc /W4
#include <iostream>
#include <functional>
int main()
{
using namespace std;
// The following code declares a lambda expression that returns
// another lambda expression that adds two numbers.
// The returned lambda expression captures parameter x by value.
auto addtwointegers = [](int x) -> function<int(int)> {
return [=](int y) { return x + y; };
};
// The following code declares a lambda expression that takes another
// lambda expression as its argument.
// The lambda expression applies the argument z to the function f
// and multiplies by 2.
auto higherorder = [](const function<int(int)>& f, int z) {
return f(z) * 2;
};
// Call the lambda expression that is bound to higherorder.
auto answer = higherorder(addtwointegers(7), 8);
// Print the result, which is (7+8)*2.
cout << answer << endl;//30
}
配合使用 Lambda 表达式和模板
由于 lambda 表达式已类型化,因此你可以将其与 C++ 模板一起使用。
下面的示例显示 negate_all 和 print_all 函数。 negate_all 函数将一元 operator- 应用于 vector 对象中的每个元素。 print_all 函数将 vector 对象中的每个元素打印到控制台。
// template_lambda_expression.cpp
// compile with: /EHsc
#include <vector>
#include <algorithm>
#include <iostream>
using namespace std;
// Negates each element in the vector object. Assumes signed data type.
template <typename T>
void negate_all(vector<T>& v)
{
for_each(v.begin(), v.end(), [](T& n) { n = -n; });
}
// Prints to the console each element in the vector object.
template <typename T>
void print_all(const vector<T>& v)
{
for_each(v.begin(), v.end(), [](const T& n) { cout << n << endl; });
}
int main()
{
// Create a vector of signed integers with a few elements.
vector<int> v;
v.push_back(34);
v.push_back(-43);
v.push_back(56);
print_all(v);
negate_all(v);
cout << "After negate_all():" << endl;
print_all(v);
}
/*
output
34
-43
56
After negate_all():
-34
43
-56
*/
配合使用 Lambda 表达式和托管类型 (C++/CLI)
lambda 表达式的捕获子句不能包含具有托管类型的变量。 但是,你可以将具有托管类型的实际参数传递到 lambda 表达式的形式参数列表。 以下示例包含一个 lambda 表达式,它通过值捕获局部非托管变量 ch,并采用 System.String 对象作为其参数。
// managed_lambda_expression.cpp
// compile with: /clr
using namespace System;
int main()
{
char ch = '!'; // a local unmanaged variable
// The following lambda expression captures local variables
// by value and takes a managed String object as its parameter.
[=](String ^s) {
Console::WriteLine(s + Convert::ToChar(ch));
}("Hello");
}
//输出 Hello!
练习代码:
#include<iostream>
#include<functional>
using namespace std;
int main(){
int m = [](int x) { return [](int y) { return y * 2; }(x)+6; }(5);
std::cout << "m:" << m << std::endl; //输出m:16
std::cout << "n:" << [](int x, int y) { return x + y; }(5, 4) << std::endl; //输出n:9
auto gFunc = [](int x)->std::function<int(int)>{return[=](int y) { return x + y; }; };
auto lFunc = gFunc(4);
std::cout << lFunc(5) << std::endl;//9
auto hFunc = [](const function<int(int)>& f, int z) { return f(z) + 1; };
[](int &&a){cout<<a<<endl;}(hFunc(gFunc(7), 8));//16
int a = 111, b = 222;
auto func = [=, &b]()mutable { a = 22; b = 333; std::cout << "a:" << a << " b:" << b << std::endl; };
func();//a:22 b:333
std::cout << "a:" << a << " b:" << b << std::endl;//a:111 b:333
a = 333;
auto func2 = [=, &a] { a = 444; std::cout << "a:" << a << " b:" << b << std::endl; };
func2();//a:444 b:333
auto func3 = [](int x) ->function<int(int)> { return [=](int y) { return x + y; }; };
std::function<void(int x)> f_display_42=[](int x){cout<<(x)<<endl;};
f_display_42(44);//44
}