开始先上一段在C++ Weekly上看到的一段代码。
#include <iostream>
#include <functional>
int main(int argc, char* argv[]) {
[out = std::ref(std::cout << "Hello ")](){out.get() << "World!\n";}();
return 0;
}
接下来我们使用如下命令编译这段程序。
g++ hello.cpp -o hello -std=c++14
./hello
你会得到"Hello World!"的输出,然而想要搞懂这段代码,还要从C++11说起。
C++11引入了lambda表达式可以用来创建匿名函数。
// 定义一个匿名函数,但是匿名函数后续无法使用
[](){cout << "hello world!";};
// 定义并使用匿名函数
[](){cout << "hello world!";}(); // "hello world!"
匿名函数体内使用外部变量需要"捕获"。
// 可以得到与上面代码相同的结果
string str = "hello world!";
[str](){cout << str;}(); // "hello world!"
但是通过值捕获的str在匿名函数体内是只读的,你需要使用引用捕获的方式来解决这个问题。
string str = "hello world!";
[&str](){
str = "hello lambda!";
cout << str; // "hello lambda!"
}();
cout << str; // "hello lambda"
关于lambda更多的内容我们就不再介绍,这里只铺垫了一些本文需要知道的一些知识。
那么我们现在应该了解了文章开始的那段代码正是通过lambda表达式来实现的,下面我们由浅入深,一步一步来讲解。
// 向cout写入"hello ",并为它创建一个引用
ostream& out = cout << "hello "; // "hello "
// 值捕获的本质是拷贝构造,但是ios_base的拷贝构造函数是私有的,所以下面这行代码会报错
// error [out](){};
// 我们需要使用引用捕获
[&out](){cout << "world!";}(); // "world!"
上面的程序会输出"hello world!".我们也可以把两行代码并到一行来写。
[&out = cout << "hello "](){cout << "world!";}();
由于lambda capture initializers是C++14标准提供的,所以我们需要使用如下命令编译我们的程序。
g++ hello.cpp -o hello -std=c++14
./hello
C++11中引入了std::ref用于取某个变量的引用,它包含在头文件functional中,所以上面的代码可以改成下面的形式。
[out = ref(cout << "hello ")](){cout << "world!";}();
编译执行,我们可以得到输出"hello world!",需要注意的是,要自己手动编译执行,亲测在dev-cpp中会报错。而这里ref返回的对象相当于ostream&&,我们可以调用ref.get()得到ostream&类型。
[out = ref(cout << "hello ")](){out.get() << "world!";}();
lambda表达式是C++11带来的一颗语法糖(即不通过该形式也可达到同样的效果),但考虑到代码的可读性,在较复杂的一些情况还是不要使用lambda表达式为好。