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

在 Go 的 struct 中使用指针的坑

  •  
  •   WadeLaunch 2021-11-26 21:37:57 +08:00 2251 次点击
    这是一个创建于 1494 天前的主题,其中的信息可能已经有所发展或是发生改变。
    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  
       2021-11-26 23:47:20 +08:00
    没有细看,盲猜是在说 例如 `for _, v := range sourceList`的 `v` 共用一块内存空间了
    其实这在实现上是合理的,不然 for 循环会导致很多的内存分配。
    soap520
        2
    soap520  
       2021-11-26 23:47:20 +08:00
    for i, v := range sourceList 的 v 是 sourceList 的一份 copy
    也许你想写成 rows = append(rows, Row{Id: &sourceList[i].Id, Open: &sourceList[i].Open})
    soap520
        3
    soap520  
       2021-11-26 23:48:05 +08:00
    @soap520 * v 是 sourceList[i] 的一份 copy
    tiedan
        4
    tiedan  
       2021-11-27 01:02:02 +08:00
    问题一:这就和定义了 []*Source ,for _, v := range sourceList 往里面 append &v 是一类问题。
    for range 是将元素的副本复制给 v (值传递),但循环变量是复用的,地址不会变

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

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

    然后之前用 v 的地方改成用 sourceList[i]
    就行了。
    superfatboy
        8
    superfatboy  
       2021-12-03 11:21:26 +08:00
    这不是我之前遇到的问题么
    xpyusrs
        9
    xpyusrs  
       2021-12-13 23:17:02 +08:00
    有点迷, 我复制了你的代码
    ```
    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     Solana     1165 人在线   最高记录 6679       Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 28ms UTC 17:37 PVG 01:37 LAX 09:37 JFK 12:37
    Do have faith in what you're doing.
    ubao msn 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