Golang指针相关的问题探讨

Golang指针相关的问题探讨 我是新手,有几个问题。关于这段代码片段,我有一些疑问。

package main

type Dummy struct {
	Name  *string `json:"name"`
	Image *string `json:"image"`
}

func dummyFunc() Dummy {
	row := database.DbConn.QueryRow("SELECT name, image FROM mytable WHERE id=5")

	var dummy Dummy
	row.Scan(&dummy.Name, &dummy.Image)

	return dummy
}

我运行了这段代码,令我惊讶的是,dummy.Namedummy.Image 的值竟然能够被设置,但我不明白这是如何实现的。我在调试器中再次确认了 NameImage 是 nil。希望有人能为我解释一下。另一个问题是,在一个指针类型前面加上 & 是什么意思?那就像是地址的地址吗?这怎么可能呢?我有点困惑。非常感谢。


更多关于Golang指针相关的问题探讨的实战教程也可以访问 https://www.itying.com/category-94-b0.html

3 回复

谢谢Jarrod。这样解释就明白了。

更多关于Golang指针相关的问题探讨的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html


Name *string 意味着 Name 是一个指针类型,指向一个 string 的位置。

&dummy.Name 表示指向变量 dummy 的指针,而 dummyDummy 的一个 “接口类型”

接口类型 包含一个指针类型和一个值类型。

你的理解是正确的,如果你对 var *string 类型的变量使用 &var,那将得到一个指向指针的指针。但你现在操作的是指向一个包含指针的值类型的指针。

在某些情况下,Go 和一些库会进行一些魔法操作,自动隐式地解引用指针,这有时仍然会让我感到困惑。但幸运的是,当我尝试使用别人混合了指针和值类型的代码时,JetBrains GoLand 通常会建议正确的修复方法使其正常工作。

这是一个很好的问题,涉及到Go中指针和database/sql包的工作机制。

首先,你的代码能够工作是因为database/sql包在Scan()方法内部处理了指针的指针。当你调用row.Scan(&dummy.Name, &dummy.Image)时:

  1. dummy.Namedummy.Image 已经是 *string 类型(指向string的指针)
  2. 加上 & 后,你传递的是 **string 类型(指向指针的指针)
  3. Scan() 方法内部会分配新的字符串内存,并将指针指向这个内存

这里是一个简化的示例来说明这个过程:

package main

import (
	"fmt"
)

type Dummy struct {
	Name  *string
	Image *string
}

// 模拟Scan函数的行为
func mockScan(namePtr **string, imagePtr **string) {
	// 分配新的字符串
	nameValue := "John Doe"
	imageValue := "profile.jpg"
	
	// 将指针指向新分配的内存
	*namePtr = &nameValue
	*imagePtr = &imageValue
}

func main() {
	var dummy Dummy
	
	// 传递指针的地址,这样mockScan可以修改指针本身
	mockScan(&dummy.Name, &dummy.Image)
	
	fmt.Printf("Name: %s\n", *dummy.Name)   // 输出: Name: John Doe
	fmt.Printf("Image: %s\n", *dummy.Image) // 输出: Image: profile.jpg
}

关于你的第二个问题:在指针类型前面加上 & 确实是"地址的地址"。这在Go中是合法的,因为:

  1. 指针本身也是一个变量,存储在内存中
  2. 指针变量也有自己的内存地址
  3. 你可以获取任何变量的地址,包括指针变量

示例:

package main

import "fmt"

func main() {
	var x int = 10
	var ptr *int = &x      // ptr是指向int的指针
	var pptr **int = &ptr  // pptr是指向指针的指针
	
	fmt.Printf("x的值: %d\n", x)           // 10
	fmt.Printf("ptr指向的值: %d\n", *ptr)    // 10
	fmt.Printf("pptr指向的指针指向的值: %d\n", **pptr) // 10
	
	// 修改原始值
	**pptr = 20
	fmt.Printf("修改后x的值: %d\n", x)      // 20
}

在你的数据库代码中,Scan()方法需要接收**string(指针的指针)的原因是:

  • 如果只传递*stringScan()只能修改指针指向的值
  • 但你的结构体字段初始是nil,需要Scan()能够分配内存并设置指针指向新内存
  • 所以需要传递指针的地址,让Scan()能够修改指针本身

这就是为什么即使dummy.Namedummy.Image初始为nil,代码仍然能正常工作的原因。Scan()方法内部处理了内存分配和指针设置。

回到顶部