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

在 Go 的 struct 中使用指针的坑

  •  
  •   WadeLaunch · 215 天前 · 1147 次点击
    这是一个创建于 215 天前的主题,其中的信息可能已经有所发展或是发生改变。
    go version: go1.17.3 linux/amd64
    
    package main
    
    import (
    	"fmt"
    )
    
    // 注意 Row 的字段是指针类型
    type Row struct {
    	Id   *int
    	Open *bool
    }
    
    type Source struct {
    	Id   int
    	Open bool
    }
    
    func main() {
    	sourceList := []Source{
    		{Id: 1, Open: true},
    		{Id: 2, Open: false},
    	}
    
    	var rows []Row
    	for _, v := range sourceList {
    		fmt.Println("sourceList.v:")
    		fmt.Printf("      &v.%%p: %p\n", &v)
    		fmt.Println("         &v:", &v)
    		fmt.Println("          v:", v)
    		fmt.Println("      &v.Id:", &v.Id)
    		fmt.Println("       v.Id:", v.Id)
    		fmt.Println("    &v.Open:", &v.Open)
    		fmt.Println("     v.Open:", v.Open)
    
    		rows = append(rows, Row{Id: &v.Id, Open: &v.Open})
    	}
    
    	fmt.Println("\nrows, len", rows, len(rows))
    
    	for _, v := range rows {
    		fmt.Println("rows.v:")
    		fmt.Printf("     &v.%%p: %p\n", &v)
    		fmt.Println("      v.Id:", v.Id)
    		fmt.Println("     *v.Id:", *v.Id)
    		fmt.Println("    v.Open:", v.Open)
    		fmt.Println("   *v.Open:", *v.Open)
    	}
    }
    

    先不看下面的答案,猜猜 rows 的结果是什么?

    先不看下面的答案,猜猜 rows 的结果是什么?

    先不看下面的答案,猜猜 rows 的结果是什么?

    答案揭晓:

    sourceList.v:
          &v.%p: 0xc0000aa000
             &v: &{1 true}
              v: {1 true}
          &v.Id: 0xc0000aa000
           v.Id: 1
        &v.Open: 0xc0000aa008
         v.Open: true
    sourceList.v:
          &v.%p: 0xc0000aa000
             &v: &{2 false}
              v: {2 false}
          &v.Id: 0xc0000aa000
           v.Id: 2
        &v.Open: 0xc0000aa008
         v.Open: false
    
    rows, len [{0xc0000aa000 0xc0000aa008} {0xc0000aa000 0xc0000aa008}] 2
    rows.v:
         &v.%p: 0xc00008c240
          v.Id: 0xc0000aa000
         *v.Id: 2
        v.Open: 0xc0000aa008
       *v.Open: false
    rows.v:
         &v.%p: 0xc00008c240
          v.Id: 0xc0000aa000
         *v.Id: 2
        v.Open: 0xc0000aa008
       *v.Open: false
    

    问题一:为什么 rows 两行值是一样的?(我自己有一个答案,可能是错的,先不写出来,想先问问大家的看法)

    问题二:初学 Go ,请问在 struct 中用指针是不推荐的用法吗?还是我不会用?

    这个用法是在某个框架中看到的,用指针可能是为了通过 if Row.Id != nil 来区分请求参数不存在(domain/path?name=x)与请求参数的值是 0 (domain/path?name=x&id=0) 的情况。

    问题三:怎么区分这种参数不存在与参数值是 0 的情况?

    9 条回复    2021-12-13 23:17:02 +08:00
    zzn
        1
    zzn  
       215 天前
    没有细看,盲猜是在说 例如 `for _, v := range sourceList`的 `v` 共用一块内存空间了
    其实这在实现上是合理的,不然 for 循环会导致很多的内存分配。
    soap520
        2
    soap520  
       215 天前
    for i, v := range sourceList 的 v 是 sourceList 的一份 copy
    也许你想写成 rows = append(rows, Row{Id: &sourceList[i].Id, Open: &sourceList[i].Open})
    soap520
        3
    soap520  
       215 天前
    @soap520 * v 是 sourceList[i] 的一份 copy
    tiedan
        4
    tiedan  
       215 天前
    问题一:这就和定义了 []*Source ,for _, v := range sourceList 往里面 append &v 是一类问题。
    for range 是将元素的副本复制给 v (值传递),但循环变量是复用的,地址不会变

    问题二:struct 中用指针需要区分情况,比如判断参数是零值还是没传。
    问题三:就是用指针,gin 框架的 issue 里面有提到,用指针来区分 0 值和参数不存在
    fgwmlhdkkkw
        5
    fgwmlhdkkkw  
       214 天前 via Android
    是你 for 的问题。
    WadeLaunch
        6
    WadeLaunch  
    OP
       214 天前
    @zzn @tiedan 我想的也是循环时 v 分配到了同一块内存空间导致的。
    @fgwmlhdkkkw 请问这种情况要怎么 “for” 才不会分配到了同一块内存空间?

    我想到的就是把 v 赋值到一个临时变量再 append ,例如:
    ```
    vv := v
    rows = append(rows, Row{Id: &vv.Id, Open: &vv.Open})
    ```
    请问有其它方法吗?
    djFFFFF
        7
    djFFFFF  
       213 天前
    for _, v := range sourceList {
    改成
    for i := range sourceList {

    然后之前用 v 的地方改成用 sourceList[i]
    就行了。
    superfatboy
        8
    superfatboy  
       208 天前
    这不是我之前遇到的问题么
    xpyusrs
        9
    xpyusrs  
       198 天前
    有点迷, 我复制了你的代码
    ```
    sourceList.v:
    &v.%p: 0xc00000c0a0
    &v: &{1 true}
    v: {1 true}
    &v.Id: 0xc00000c0a0
    v.Id: 1
    &v.Open: 0xc00000c0a8
    v.Open: true
    sourceList.v:
    &v.%p: 0xc00000c0a0
    &v: &{2 false}
    v: {2 false}
    &v.Id: 0xc00000c0a0
    v.Id: 2
    &v.Open: 0xc00000c0a8
    v.Open: false

    rows, len [{0xc00000c0a0 0xc00000c0a8} {0xc00000c0a0 0xc00000c0a8}] 2
    rows.v:
    &v.%p: 0xc000042260
    v.Id: 0xc00000c0a0
    *v.Id: 2
    v.Open: 0xc00000c0a8
    *v.Open: false
    rows.v:
    &v.%p: 0xc000042260
    v.Id: 0xc00000c0a0
    *v.Id: 2
    v.Open: 0xc00000c0a8
    *v.Open: false

    ```
    go version go1.17.1 windows/amd64
    关于   ·   帮助文档   ·   API   ·   FAQ   ·   我们的愿景   ·   广告投放   ·   感谢   ·   实用小工具   ·   1088 人在线   最高记录 5497   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 25ms · UTC 22:00 · PVG 06:00 · LAX 15:00 · JFK 18:00
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.