请教一个 go 的函数问题 - V2EX
V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
The Go Programming Language
http://golang.org/
Go Playground
Go Projects
Revel Web Framework
loriann
V2EX    Go 编程语言

请教一个 go 的函数问题

  •  a href="Javascript:" Onclick="downVoteTopic(717494);" class="vote">
  •   loriann 2020-10-22 15:48:19 +08:00 3131 次点击
    这是一个创建于 1816 天前的主题,其中的信息可能已经有所发展或是发生改变。
    代码片段如下
    type St struct {
    Val int
    }
    type f Func(s *St)
    func Set(i int) f {
    return func(s *St){
    s.Val = i
    }
    }

    func main(){
    f := Set(100)
    var ss St
    f(&ss)

    // ss.Val = 100 ????
    }

    最后我好奇的是,这个 100 是怎么设置进去的,同样的代码逻辑我用 c++ s.Val 的结果完全是个随机数
    第 1 条附言    2020-10-22 16:23:22 +08:00

    我把c++ 代码贴出来吧,有什么错误欢迎指正

    class St { public: St(int i) :_i(i) { } void Set(int i) { _i = i; } int Get() { return _i; } private: int _i; }; typedef void f(St* s); std::function<void(St*)> Set(int i) { return [&](St* s) { s->Set(i); }; } int main() { auto f = Set(100); St s(0); f(&s); std::cout << s.Get() << std::endl; return 0; } 
    28 条回复    2020-10-22 18:32:29 +08:00
    cmdOptionKana
        1
    cmdOptionKana  
       2020-10-22 15:56:25 +08:00
    不懂 C++,想问一下 C++有没有闭包的概念?

    另外 Go 会自动初始化空值,比如 var ss St 之后,ss.Val 会自动初始化为 0,这里 C++ 是类似的规定吗?
    kuro1
        2
    kuro1  
       2020-10-22 15:56:55 +08:00
    f 是个 function 啊
    loriann
        3
    loriann  
    OP
       2020-10-22 15:57:46 +08:00
    c++ 没有闭包。ss.Val 是 0 我能理解,但是不太理解为什么 ss.Val 是 100
    ikw
        4
    ikw  
       2020-10-22 15:58:31 +08:00
    贴代码可以用 markdown 语法,```
    或者 gist

    这样的代码太难看了

    另外,可以把 C++ 是随机数的代码也贴出来对比一下
    di1012
        5
    di1012  
       2020-10-22 15:59:30 +08:00
    ss 的地址指到了 s
    kuro1
        6
    kuro1  
       2020-10-22 15:59:35 +08:00
    给你换种写法,就是个赋值而已

    ```
    type St struct {
    Val int
    }

    func f1(s *St, v int) {
    s.Val = v
    }

    func TestA(t *testing.T) {
    var ss St
    f1(&ss, 100)

    fmt.Println(ss.Val)
    }
    ```
    ikw
        7
    ikw  
       2020-10-22 15:59:59 +08:00
    Orenoid
        8
    Orenoid  
       2020-10-22 16:00:13 +08:00
    就通过传值设置进去的啊,你的疑问点是在于闭包?
    Mitt
        9
    Mitt  
       2020-10-22 16:00:16 +08:00 via iPhone
    @loriann 因为闭包保存的是你闭包环境,你的闭包环境下 i 是 100,所以你执行它的时候设置的就是 100
    loriann
        10
    loriann  
    OP
       2020-10-22 16:01:17 +08:00
    @kuro1 嗯,你这样写我能理解。其实我想问的是 100 这个值存到哪里去了。它为什么能正确设置
    kuro1
        11
    kuro1  
       2020-10-22 16:04:15 +08:00
    @loriann 闭包
    kuro1
        12
    kuro1  
       2020-10-22 16:07:04 +08:00
    https://tour.golang.org/moretypes/25

    经典闭包
    ```
    package main

    import "fmt"

    func adder() func(int) int {
    sum := 0
    return func(x int) int {
    sum += x
    return sum
    }
    }

    func main() {
    pos, neg := adder(), adder()
    for i := 0; i < 10; i++ {
    fmt.Println(
    pos(i),
    neg(-2*i),
    )
    }
    }

    ```
    loriann
        13
    loriann  
    OP
       2020-10-22 16:11:54 +08:00
    @kuro1 非常感谢,闭包这个东西以前没碰到过,正在理解中。。。。
    hdbzsgm
        14
    hdbzsgm  
       2020-10-22 16:14:43 +08:00
    c++ capture list ???
    catror
        15
    catror  
       2020-10-22 16:30:41 +08:00 via Android
    C++你把引用捕获改为赋值捕获就是 100 了
    loriann
        16
    loriann  
    OP
       2020-10-22 16:34:24 +08:00
    @catror 真是大神,佩服
    lin07hui
        17
    lin07hui  
       2020-10-22 16:34:45 +08:00
    Set(100) --> i = 100 --> f(&ss) --> s = &ss --> ss.Val = s.Val = i = 100
    akatquas
        18
    akatquas  
       2020-10-22 16:57:14 +08:00
    go 中关于闭包的一些实现过程可以看这个, [go 闭包的实现]( https://tiancaiamao.gitbooks.io/go-internals/content/zh/03.6.html)

    同时,把你的代码复制到,https://godbolt.org/ , 选择 go 语言,再选择 386gc tip,对照汇编结果来理解。
    zunceng
        19
    zunceng  
       2020-10-22 17:07:53 +08:00
    @catror 正解
    楼主 c++ lambda 引用了一个临时变量
    loriann
        20
    loriann  
    OP
       2020-10-22 17:10:12 +08:00
    @akatquas 谢谢,应该可以理解的更快一点了
    zunceng
        21
    zunceng  
       2020-10-22 17:10:49 +08:00
    c++要把对象的作用域 生命周期在脑子里想的很清楚
    我感觉这是最难地方 比语法规则难多了
    loriann
        22
    loriann  
    OP
       2020-10-22 17:14:38 +08:00
    @catror 好吧,c++ capture 换成 = 号后我又有一个疑问,这个值是什么时候复制的,是在调 Set 的时候还是调 f 的时候???
    linjunyi22
        23
    linjunyi22  
       2020-10-22 17:37:11 +08:00
    闭包,Set 执行完后返回的是一个函数,再执行把指针传进去,那肯定把 ss 的值给改了
    catror
        24
    catror  
       2020-10-22 18:04:00 +08:00 via Android
    @loriann 两步都有复制。调用 Set 的时候,复制了 100 到一个地方存下来。调用 f 的时候,再把存下来的值复制给 s._i
    catror
        25
    catror  
       2020-10-22 18:09:44 +08:00 via Android
    @loriann 在 lambda 表达式的内外,参数 i 的意义是不一样的。在表达式的内部:1. 引用捕获的时候,它是参数 i 的引用,调用 f 的时候,参数 i 已经没有了,所以最终得到的结果是随机的; 2. 赋值捕获的时候,它是参数 i 的复制
    catror
        26
    catror  
       2020-10-22 18:19:22 +08:00
    可能还是代码更直观点一点,Set 可以写成下面这样
    ```cpp
    std::function<void(St*)> Set(int i) {
    // 赋值捕获
    return [j=i](St* s) {
    s->Set(j);
    };
    // // 引用捕获
    // return [&j=i](St* s) {
    // s->Set(j);
    // };
    }
    ```
    loriann
        27
    loriann  
    OP
       2020-10-22 18:24:00 +08:00
    @catror 嗯,我有点明白了,执行 Set 的时候把 i 复制到了 j,执行 f 的时候函数引用的就是捕获的值 j,也就是 i 的一个复制
    katsusan
        28
    katsusan  
       2020-10-22 18:32:29 +08:00
    Go 里的闭包函数在内存里是 code+data 形式表现的。

    以你 Set 函数返回的 f 为例,在编译期就可以分析出它应该包含内部匿名函数的入口点地址和 i 的值,
    在 x64 上就是 16 字节空间,也就是 main 栈上的 f 指向一个 16 字节的空间,执行 f=Set(100)的赋值后
    第二个 8 字节就赋值为 100 。

    执行 f(&ss)时参照 https://golang.org/s/go11func,在 x86_64 下 R0 用的是 DX 寄存器。

    MOV …, R0

    MOV 0(R0), R1 //R0+0=>R1,即函数入口地址

    CALL R1 # called code can access “data” using R0,比如例子里的 100 就是 8(R0)
    关于     帮助文档     自助推广系统     博客     API     FAQ     Solana     2764 人在线   最高记录 6679       Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 27ms UTC 12:32 PVG 20:32 LAX 05:32 JFK 08:32
    Do have faith in what you're doing.
    ubao snddm index pchome yahoo rakuten mypaper meadowduck bidyahoo youbao zxmzxm asda bnvcg cvbfg dfscv mmhjk xxddc yybgb zznbn ccubao uaitu acv GXCV ET GDG YH FG BCVB FJFH CBRE CBC GDG ET54 WRWR RWER WREW WRWER RWER SDG EW SF DSFSF fbbs ubao fhd dfg ewr dg df ewwr ewwr et ruyut utut dfg fgd gdfgt etg dfgt dfgd ert4 gd fgg wr 235 wer3 we vsdf sdf gdf ert xcv sdf rwer hfd dfg cvb rwf afb dfh jgh bmn lgh rty gfds cxv xcv xcs vdas fdf fgd cv sdf tert sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf shasha9178 shasha9178 shasha9178 shasha9178 shasha9178 liflif2 liflif2 liflif2 liflif2 liflif2 liblib3 liblib3 liblib3 liblib3 liblib3 zhazha444 zhazha444 zhazha444 zhazha444 zhazha444 dende5 dende denden denden2 denden21 fenfen9 fenf619 fen619 fenfe9 fe619 sdf sdf sdf sdf sdf zhazh90 zhazh0 zhaa50 zha90 zh590 zho zhoz zhozh zhozho zhozho2 lislis lls95 lili95 lils5 liss9 sdf0ty987 sdft876 sdft9876 sdf09876 sd0t9876 sdf0ty98 sdf0976 sdf0ty986 sdf0ty96 sdf0t76 sdf0876 df0ty98 sf0t876 sd0ty76 sdy76 sdf76 sdf0t76 sdf0ty9 sdf0ty98 sdf0ty987 sdf0ty98 sdf6676 sdf876 sd876 sd876 sdf6 sdf6 sdf9876 sdf0t sdf06 sdf0ty9776 sdf0ty9776 sdf0ty76 sdf8876 sdf0t sd6 sdf06 s688876 sd688 sdf86