Golang中指针的使用让我依然困惑

Golang中指针的使用让我依然困惑 首先我要说明,我没有任何信息学背景,但我确实喜欢编程,而且我喜欢用Go语言来编程 🙂

我并非新手,我已经用Go语言(虽然是断断续续地)编程4-5年了,鉴于我已经成功创建了多个运行良好的应用程序,我相信我对Go生态系统有很好的了解。

然而,有些东西我真的不明白,那就是指针。需要澄清的是,我理解指针是什么,但我真的无法领会应该在何处/何时/为何使用它们。

为什么有人会这样做:

i, j := 42, 2701

p := &i         // 指向 i
fmt.Println(*p) // 通过指针读取 i
*p = 21         // 通过指针设置 i
fmt.Println(i)  // 查看 i 的新值

p = &j         // 指向 j
*p = *p / 37   // 通过指针除以 j
fmt.Println(j) // 查看 j 的新值

而不是这样做?

i, j := 42, 2701

fmt.Println(i) // 直接读取 i
i = 21         // 直接设置新的 i 值
fmt.Println(i)  // 查看 i 的新值


j = j / 37   // 直接除以 j
fmt.Println(j) // 查看 j 的新值

另外,是否有人能用不那么简单的例子向我解释,在哪些情况下指针比直接调用变量更有优势?


更多关于Golang中指针的使用让我依然困惑的实战教程也可以访问 https://www.itying.com/category-94-b0.html

5 回复

顺便提一下……指针的使用与否取决于个人的想象力。这就像你何时、何地以及如何使用“+”运算符一样。wink

更多关于Golang中指针的使用让我依然困惑的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html


我也是个新手,但在我看来,这其中的区别就像包裹和包裹地址之间的区别一样。一张纸的重量要轻得多。 🙂

我在这里找到了一个解释

关于在何处/何时/为何使用指针,我所见过的最佳解释是在 Bill Kennedy 的这个视频课程中。

Ultimate Go: Advanced Concepts | Ardan Labs

Ultimate Go: Advanced Concepts | Ardan Labs

本课程构建了层层基础知识,将使您对 Go 编程语言有更深入的理解。

价格不菲,但质量上乘。

感谢详细的解释,事情开始变得清晰了 🙂

为了确保我完全理解你的第一个案例:

  1. 获取/设置存储在特定内存中的一组数据,而不是到处复制粘贴(例如,接受非指针类型的函数通常会作为副本复制,并不直接修改原始数据)。

这是否意味着如果我这样做:

func Calculate(i, j int) (int, int) {
	fmt.Println(i) // 读取 i,是副本吗?
	i = 21         // 为 i 设置新值,是设置到它的副本里吗?
	fmt.Println(i) // 查看 i 的新值,是从它的副本里吗?

	j = j / 37     // 除以 j
	fmt.Println(j) // 查看 j 的新值

	return i, j // 这些是副本对吗?它们会被 GC 处理吗?
}

func main() {
	i := 42
	j := 2701
	i, j = Calculate(i, j)

	fmt.Printf("After caculate i is: %v\n", i) // 你不会得到 42
	fmt.Printf("After caculate j is: %v\n", j) // 你不会得到 2701
}

ijCalculate 函数内部使用时是被复制的吗?这意味着 i = 21j = j / 37 实际上是在内存中分配新的地址,而不是直接修改已经分配的那些地址?

如果是这种情况,如果 Calculate 函数如下所示会发生什么:

func Calculate(i, j int) (int, int) {
	m := i / 2        
	n := j / 37

	return m, n
}

mn 肯定是分配了内存的。那么 ij 呢,它们在这里被复制了吗?

在Go中指针的核心价值在于共享内存避免复制,特别是在处理大型数据结构或需要跨作用域修改变量时。以下是几个实际场景的示例:

1. 修改函数外的变量

func increment(n *int) {
    *n++
}

func main() {
    x := 10
    increment(&x)
    fmt.Println(x) // 输出 11
}

如果不使用指针,函数内的修改不会影响原始变量。

2. 处理大型结构体

type BigData struct {
    data [1000000]int
}

func process(b *BigData) {
    b.data[0] = 100
    // 操作结构体...
}

func main() {
    var b BigData
    process(&b) // 只传递指针(8字节),避免复制整个结构体
}

传递指针避免内存复制,提升性能。

3. 实现链表等数据结构

type Node struct {
    value int
    next  *Node
}

func main() {
    head := &Node{value: 1}
    head.next = &Node{value: 2}
    // 修改链表节点
    current := head
    for current != nil {
        current.value *= 2
        current = current.next
    }
}

4. 允许nil值表示"无数据"

type Config struct {
    timeout *int // nil表示未设置
}

func (c *Config) GetTimeout() int {
    if c.timeout == nil {
        return 30 // 默认值
    }
    return *c.timeout
}

5. 方法接收器指针

type Counter struct {
    count int
}

func (c *Counter) Increment() {
    c.count++ // 修改原始对象
}

func (c Counter) Value() int {
    return c.count // 只读操作,无需指针
}

实际示例:JSON解析到现有变量

type User struct {
    Name string `json:"name"`
    Age  int    `json:"age"`
}

func main() {
    u := &User{}
    data := []byte(`{"name":"Alice","age":30}`)
    json.Unmarshal(data, u) // 必须传递指针才能修改u
    fmt.Printf("%+v\n", u)
}

指针在以下情况特别必要:

  • 需要修改函数参数时
  • 处理大型结构体/数组时
  • 实现引用语义(如链表、树)
  • 区分零值和未设置值
  • 方法需要修改接收器状态时

你的简单示例中确实不需要指针,但在实际项目中,指针是处理共享状态和优化内存的关键工具。

回到顶部