跳转到主要内容

在本教程中,我们将介绍 Go 中的指针以及如何在自己的 Go 程序中使用它们。我们将介绍最佳实践,并介绍一些最常见的指针用例。

在本教程结束时,您将对指针以及如何使用它们有一个扎实的理解。

介绍


在 Go 中,当我们调用一个函数并将一堆参数传递给该函数时,该语言会创建参数的副本,然后在所述函数中使用这些副本。例如:

main.go

package main

import "fmt"

func myTestFunc(a int) {
    a += 3
    fmt.Println(a)
}

func main() {
    a := 2
    myTestFunc(a)
    fmt.Println(a) // prints out 2
}

在上面的代码中,myTestFunc 接收一个整数变量,并对其进行复制,以便在函数体的上下文中使用。我们对 myTestFunc 中的 a 所做的任何更改都只会保留在 myTestFunc 函数的主体中。

现在,假设我们想调用 myTestFunc 并更新原来的 a 变量并将 3 添加到它?

在这个特定的例子中,我们可以更改函数签名,以便它接收指针而不是引用。这意味着我们在 myTestFunc 函数中对 a 所做的任何更改都将针对原始变量而不是副本!

main.go

package main

import "fmt"

func myTestFunc(a *int) {
    a += 3
    fmt.Println(a)
}

func main() {
    a := 2
    myTestFunc(a)
    fmt.Println(a) // prints out 5
}

当我们运行上面的代码时,我们应该看到 myTestFunc 已经正确更新了 a 的原始值并添加了 3:

$ go run main.go

5
5


定义指针


现在让我们退后一步,看看使用指针的基础知识。

我们将从查看如何在 Go 代码中定义指针开始。为了定义一个指针,我们可以在我们声明变量的地方使用 * 符号,它将把变量变成一个指针变量。

main.go

package main

import "fmt"

func main() {
    var age *int

    fmt.Println(age)
    fmt.Println(&age)
}

当我们尝试运行它时?我们应该看到以下内容:

$ go run main.go

<nil>
0xc00000e018


第一个值代表我们的指针变量age的值。第二个代表这个变量的地址。

给指针赋值


问题 - 如果您尝试为年龄分配值会发生什么?

就目前而言,我们的年龄变量为零。让我们尝试将其设置为 26,看看会发生什么:

main.go

package main

import "fmt"

func main() {
    var age *int

    *age = 26

    fmt.Println(age)
    fmt.Println(&age)
}

这实际上会导致编译器恐慌。

$ go run main.go

panic: runtime error: invalid memory address or nil pointer dereference
[signal SIGSEGV: segmentation violation code=0x1 addr=0x0 pc=0x1092f6e]

goroutine 1 [running]:
main.main()
        /Users/elliot/Documents/Projects/TutorialEdge/Projects/Go/go-pointers-tutorial/main.go:8 +0x3e
exit status 2

这样做的原因是我们需要使用内置函数 new 来分配足够的内存来容纳 int 类型的值。现在让我们看看它的实际效果:

package main

import "fmt"

func main() {
    var age *int
	age = new(int)
	*age = 26

    fmt.Println(*age)
    fmt.Println(&age)
}

重要提示 - 如果我们希望更简洁一点,我们可以删除上面示例中 main 函数的第一行并将 age = new(int) 修改为 age := new(int) 。

可空性(Nullability)


重要提示 - 在 Go 代码中使用指针的一大优势是它们可以为空。

如果我们有一个具有指针返回值的函数,我们可以利用指针可以为空这一事实。

以第一个函数为例:

func testFunc(id string) (Guitarist, error) {
    result, err := getSongs(id)
    if err != nil {
        return Guitarist{}, err
    }

    return result, nil
}

在上面的代码中,我们必须填充一个空的 Guitarist{} 结构来返回错误。然而,如果我们用一个指针返回值来定义这个函数,那么我们可以像这样简化上面的代码:

func testFunc(id string) (*Guitarist, error) {
    result, err := getSongs(id)
    if err != nil {
        return nil, err
    }

    return result, nil
}

传递变量

在教授 Go 时,我经常看到开发人员在如何将指针变量传递给接受值接收器的函数时遇到了困难。让我们看一个例子:

main.go

package main

import "fmt"

func YearsUntilRetirement(age int) {
    fmt.Println(100 - age)
}

func main() {
	var age *int
	age = new(int)
	*age = 26
    
    YearsUntilRetirement(age)
}

当我们尝试运行它时,我们应该看到它抱怨我们不能将类型 *int 传递给我们的函数 YearsUntilRetirement。

$ go run main.go

## command-line-arguments
./main.go:13:25: cannot use age (type *int) as type int in argument to YearsUntilRetirement


为了传递这个指针的值,我们可以通过将它作为 *age 传递给我们的函数来取消引用,如下所示:

main.go

package main

import "fmt"

func YearsUntilRetirement(age int) {
    fmt.Println(100 - age)
}

func main() {
	var age *int
	age = new(int)
	*age = 26
    
    YearsUntilRetirement(*age)
}

现在,当我们运行它时,我们应该看到我们的 Go 应用程序执行了!

$ go run main.go

74


结论


太棒了,所以在本教程中,我们介绍了 Go 中指针的基础知识,以及如何在自己的 Go 应用程序中使用和滥用它们!

希望您发现本教程有用且有趣,如果您这样做了,或者您有任何其他意见或问题,请随时在下面的评论部分告诉我!

文章链接