python 闭包 lambda_lambda与闭包与[](){}

之前写过一段时间的python,当时使用map和reduce,于是乎出现了很多这样的东西:

reduce(lambda x, y : x + y, range(100))

后来接触一点点js,因为看到了一个东西叫electron,后端是nodejs,前端是chromium,用web做页面,是原来atom的框架,后来vscode也是用它做的(因为这我还看了一丁丁vue)。所以还出现了这样的东西:

fs.open(file, (err, file) => {

// file operations})

讲道理我挺喜欢这种形状的lambda,比python的高到不知道哪里去,起码有个块了呗。后来知道了它的this绑定会和function(){ // operation } 不一样,但是也没有了解很多(以后有时间看看,感觉js写着蛮舒服)。

然后之前有上一个STL的选修课(超大教室上课,于是乎基本没听,期末作业也贼水),讲了remove_if这种东西,也用到了lambda:

std::vector v{1, 2, 3, 4, 5, 6, 7, 8, 9};

v.erase(std::remove_if(v.begin(), v.end(), [](const int &i) { return i % 2 == 1; }), v.end());

当我才看到这个写法的时候感到一阵疑惑:这个[]里面是个什么玩意儿,有时候写一个=有时候写一个&有时候又什么都不写,很迷。后来到现在才知道这个是捕获类型 。

那么什么是捕获类型呢。

首先要问问,这个匿名函数它究竟是个啥。

那么按照cppreference的说法,他是个闭包:cpprefernce中关于lambda表达式的解释

闭包在wiki上的解释:Inprogramming languages, a closure, also lexical closureor function closure, is a technique for implementing lexically scopedname binding in a language with first-class functions.

闭包是一种“在具有一等公民函数的语言中实现词法作用域名称绑定的技术”(渣翻译)。来看例子:

function wrapper() {

let str = "in wrapper"; // wrapper::str function inner() {

console.log(str); // wrapper::str可以被访问 }

inner();

}

wrapper();

执行结果打印"in wrapper"。

function wrapper() {

let str = "in wrapper"; // wrapper::str function inner() {

let str = "in inner"; // inner::str console.log(str); // inner::str 覆盖 wrapper::str }

inner();

}

wrapper();

执行结果打印"in inner"。

这就是词法作用域:变量属于的作用域是由变量声明的位置决定的,嵌套作用域可以访问外层作用域(如果没有被同名变量覆盖)。如果我们改动一下:

function wrapper() {

let str = "in wrapper"; // wrapper::str function inner() {

console.log(str);

}

return inner;

}

let f = wrapper(); // 离开wrapper作用域f();

执行结果打印"in wrapper" 。

在f()执行时,已经离开了wrapper的作用域,然而"in wrapper"还是被保留了下来。在这个例子中,wrapper()作用域中的局部变量str被绑定到f() 上,在离开了“词法作用域”之后仍然存在。这就是闭包。同时这个例子也展示了闭包的实现: 将上下文保存到函数对象里去。

c++的lambda中,这种保存(即捕获)的默认方式可以通过这个[]来指定:

struct lambda {

int i = 42;

auto f() {

return [=] { return i; }; // OK,以复制捕获lambda }

auto g() {

return [&] { return i; }; // OK,以引用捕获lambda }

// auto h() { // return [] { return i; }; // Error,不捕获lambda,访问不到lambda::i // }};

也可以为变量在默认捕获方式之外指定各自的捕获方式,形式上如同声明变量。引用捕获和复制捕获的区别也就如同字面意思,一个是将捕获的上下文引用过来,一个是复制一份,它们的区别相信写过这个的都知道了:

void swap_copy(int x, int y) { // 传值(复制) int temp = x;

x = y;

y = temp;

}

void swap_ref(int &x, int &y) { // 传引用 int temp = x;

x = y;

y = temp;

}

到现在,我们就知道了,lambda可以用来构建一个闭包,闭包就是一个绑定了词法作用域上下文的对象,当该作用域退出后,有了这个闭包,作用域中的上下文仍然可以访问——它的生存期看上去得以延长。

但是!

cpprefernce有一句话:若以引用隐式或显式捕获非引用实体,且在实体的生存期结束后调用闭包的函数调用运算符,则未定义行为发生。 C++ 闭包不通过被捕获的引用延长生存期

什么意思?

std::function f(int i) {

auto y = [=]() { return i; };

return y;

}

int main() {

auto p = f(3);

std::cout << p() << std::endl;

return 0;

}

结果是个3。在f()中,形参i被复制了一份到y里去,p是个包含被复制的f()的作用域上下文的闭包。如果我们改用引用捕获:

std::function f(int i) {

auto y = [&]() { return i; };

return y;

}

f()的作用域上下文被引用了一份给y,当f()退出以后,p保有了对于f()中i的引用。那么这个结果是啥呢?结果是不知道。 这就是“以引用显示捕获非引用实体,且在实体生存期结束后调用闭包的函数调用运算符”,是个UB。换句话说,这个i在f()调用结束之后就没了,随着f()的返回、调用栈弹出一并清除了。这样调用并不会出现编译错误,也可以运行,但是结果是不确定的,这种情况就不应该出现在代码中。

再次但是!

我在WSL下用g++编译,-std=c++11的情况下,结果就如同预测的一样,是个随机数;而改成-std=c++14之后:

#include

using std::cout;

using std::endl;

auto f(int i) {

auto y = [&] { return i; };

return y;

}

int main() {

auto p = f(3);

cout << p() << endl;

return 0;

}

结果是个3。而如果把返回类型改了:

#include

std::function f(int i) {

auto y = [&] { return i; };

return y;

}

就又是随机数了。

个中原因呢,我也不是很清楚。等一个评论区老哥给我解释一下。

当然,上面关于lambda的种种言论,有所谬误之处,也希望有老哥指出。具体见:Lambda 表达式 (C++11 起)​zh.cppreference.com

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值