Golang Go语言在struct中使用指针的坑

发布于 1周前 作者 eggper 来自 Go语言

Golang Go语言在struct中使用指针的坑

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 的情况?


更多关于Golang Go语言在struct中使用指针的坑的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html

10 回复

没有细看,盲猜是在说 例如 for _, v := range sourceListv 共用一块内存空间了
其实这在实现上是合理的,不然 for 循环会导致很多的内存分配。

更多关于Golang Go语言在struct中使用指针的坑的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html


for i, v := range sourceList 的 v 是 sourceList 的一份 copy
也许你想写成 rows = append(rows, Row{Id: &sourceList[i].Id, Open: &sourceList[i].Open})

  • v 是 sourceList[i] 的一份 copy

问题一:这就和定义了 []*Source ,for _, v := range sourceList 往里面 append &v 是一类问题。
for range 是将元素的副本复制给 v (值传递),但循环变量是复用的,地址不会变

问题二:struct 中用指针需要区分情况,比如判断参数是零值还是没传。
问题三:就是用指针,gin 框架的 issue 里面有提到,用指针来区分 0 值和参数不存在

是你 for 的问题。

我想的也是循环时 v 分配到了同一块内存空间导致的。
请问这种情况要怎么 “for” 才不会分配到了同一块内存空间?

我想到的就是把 v 赋值到一个临时变量再 append ,例如:
<br>vv := v<br>rows = append(rows, Row{Id: &amp;<a target="_blank" href="http://vv.Id" rel="nofollow noopener">vv.Id</a>, Open: &amp;vv.Open})<br>
请问有其它方法吗?

for _, v := range sourceList {
改成
for i := range sourceList {

然后之前用 v 的地方改成用 sourceList[i]
就行了。

这不是我之前遇到的问题么

有点迷, 我复制了你的代码
<br>sourceList.v:<br> &amp;v.%p: 0xc00000c0a0<br> &amp;v: &amp;{1 true}<br> v: {1 true}<br> &amp;<a target="_blank" href="http://v.Id" rel="nofollow noopener">v.Id</a>: 0xc00000c0a0<br> <a target="_blank" href="http://v.Id" rel="nofollow noopener">v.Id</a>: 1<br> &amp;v.Open: 0xc00000c0a8<br> v.Open: true<br>sourceList.v:<br> &amp;v.%p: 0xc00000c0a0<br> &amp;v: &amp;{2 false}<br> v: {2 false}<br> &amp;<a target="_blank" href="http://v.Id" rel="nofollow noopener">v.Id</a>: 0xc00000c0a0<br> <a target="_blank" href="http://v.Id" rel="nofollow noopener">v.Id</a>: 2<br> &amp;v.Open: 0xc00000c0a8<br> v.Open: false<br><br>rows, len [{0xc00000c0a0 0xc00000c0a8} {0xc00000c0a0 0xc00000c0a8}] 2<br>rows.v:<br> &amp;v.%p: 0xc000042260<br> <a target="_blank" href="http://v.Id" rel="nofollow noopener">v.Id</a>: 0xc00000c0a0<br> *<a target="_blank" href="http://v.Id" rel="nofollow noopener">v.Id</a>: 2<br> v.Open: 0xc00000c0a8<br> *v.Open: false<br>rows.v:<br> &amp;v.%p: 0xc000042260<br> <a target="_blank" href="http://v.Id" rel="nofollow noopener">v.Id</a>: 0xc00000c0a0<br> *<a target="_blank" href="http://v.Id" rel="nofollow noopener">v.Id</a>: 2<br> v.Open: 0xc00000c0a8<br> *v.Open: false<br><br>
go version go1.17.1 windows/amd64

在Go语言中,struct中使用指针确实可以带来一些灵活性和性能上的优势,但同时也存在一些需要注意的“坑”。以下是一些常见的注意点:

  1. 内存管理:使用指针意味着你需要手动管理内存,特别是要避免内存泄漏。如果指针指向的内存不再需要,记得将其置为nil或者通过其他方式释放(虽然Go有垃圾回收机制,但良好的内存管理习惯仍然重要)。

  2. 空指针解引用:在使用指针访问其指向的值之前,一定要检查指针是否为nil,否则会导致运行时panic。

  3. 并发安全性:如果struct的指针被多个goroutine访问,需要确保访问是安全的,通常需要使用互斥锁(sync.Mutex)或其他同步机制。

  4. 值传递与引用传递:struct本身是按值传递的,但如果你传递的是struct的指针,那么实际上传递的是引用。这会影响函数的参数修改行为,可能导致意外的数据变化。

  5. 性能考量:对于小型struct,直接使用值可能比使用指针更高效,因为避免了额外的指针解引用开销和可能的内存分配。而对于大型struct或需要频繁修改的数据结构,使用指针可能更合适。

总之,在Go语言的struct中使用指针时,需要权衡灵活性、性能、安全性和代码可读性。通过谨慎设计,可以有效避免潜在的问题,并充分利用指针带来的优势。

回到顶部