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

请教一个 go 的函数问题

  •  
  •   loriann · 2020-10-22 15:48:19 +08:00 · 2734 次点击
    这是一个创建于 1519 天前的主题,其中的信息可能已经有所发展或是发生改变。
    代码片段如下
    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
    zwpaper
        4
    zwpaper  
       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)
    }
    ```
    zwpaper
        7
    zwpaper  
       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   ·   实用小工具   ·   5359 人在线   最高记录 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 23ms · UTC 08:54 · PVG 16:54 · LAX 00:54 · JFK 03:54
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.