在 Go 的 struct 中使用指针的坑

2021-11-26 21:37:57 +08:00
 WadeLaunch
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 的情况?

1574 次点击
所在节点    Go 编程语言
9 条回复
zzn
2021-11-26 23:47:20 +08:00
没有细看,盲猜是在说 例如 `for _, v := range sourceList`的 `v` 共用一块内存空间了
其实这在实现上是合理的,不然 for 循环会导致很多的内存分配。
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
2021-11-26 23:48:05 +08:00
@soap520 * v 是 sourceList[i] 的一份 copy
tiedan
2021-11-27 01:02:02 +08:00
问题一:这就和定义了 []*Source ,for _, v := range sourceList 往里面 append &v 是一类问题。
for range 是将元素的副本复制给 v (值传递),但循环变量是复用的,地址不会变

问题二:struct 中用指针需要区分情况,比如判断参数是零值还是没传。
问题三:就是用指针,gin 框架的 issue 里面有提到,用指针来区分 0 值和参数不存在
fgwmlhdkkkw
2021-11-27 10:17:17 +08:00
是你 for 的问题。
WadeLaunch
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
2021-11-28 20:14:16 +08:00
for _, v := range sourceList {
改成
for i := range sourceList {

然后之前用 v 的地方改成用 sourceList[i]
就行了。
superfatboy
2021-12-03 11:21:26 +08:00
这不是我之前遇到的问题么
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

这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。

https://www.v2ex.com/t/818257

V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。

V2EX is a community of developers, designers and creative people.

© 2021 V2EX