Go Learn - Pointer, Address
Outline
Go 是一种值传递语言。换句话说,我们传递的是函数参数的值 。从技术角度来看,当我们使用参数调用函数时,Go 编译器严格使用参数的值 ,而不是参数本身。由于这个特性(按值传递),函数中发生的更改只会保留在函数内部。想象一下,一位老师带着一群学生,需要学生们完成一张练习题。老师会保留练习题的原件,并复印一份给学生书写,但学生不会直接在老师的复印件上书写。
但是,我们确实有能力从不同的作用域更改值。为此,我们需要利用:
- 地址
- 指针
- 取消引用
现在我们知道了需要参考哪些主题,让我们快速解决这个问题并进入正题!
地址⌗
想象一下在课堂上听课的情景。当我们听到一个重要的细节时,我们会把它记在笔记本上,以便日后参考。正是这种将重要信息存储在某个地方的想法,让我们声明了变量。但是,计算机不是把信息写在笔记本上,而是在内存中留出一些空间来存储值。计算机分配的空间称为地址 。每个地址都被标记为唯一的数值。每次使用变量时,我们所做的就是检索存储在变量地址中的值。要查找变量的地址,我们使用 & 运算符,后跟变量名,如下所示:
x := "My very first address"
fmt.Println(&x) // Prints 0x414020
当我们看到 0x 前缀时,这意味着该数字是十六进制格式的,这是一种表示 16 位数字的方式。因此, 0x414020 实际上是 x 的十六进制地址。
指针⌗
上面我们学习了地址,现在我们来学习如何存储它们。在 Go 中,指针为我们完成这项工作, 即存储地址的变量:
var pointerForInt *int
在上面的例子中, pointerForInt 将存储一个 int 数据类型变量的地址。进一步细分, * 运算符表示该变量将存储一个地址,而 int 部分表示该地址包含一个整数值。初始化 pointerForInt 后,我们可以像这样为其赋值:
var pointerForInt *int
minutes := 525600
pointerForInt = &minutes
fmt.Println(pointerForInt) // Prints 0xc000018038
注意,在我们的例子中, minutes 的值为 525600 ,它是一个整数类型。由于我们已将 pointerForInt 初始化为一个保存整数值地址的指针,因此我们可以将 minutes 的地址( &minutes )赋值给 pointerForInt 。打印出 pointerForInt ,我们得到另一个十六进制数: 0xc000018038 。
我们也可以像其他变量一样隐式声明指针:
minutes := 55
pointerForInt := &minutes
取消引用⌗
我们知道地址是存储值的地方,指针允许我们跟踪地址。但是,如果我们想让地址存储不同的值怎么办?其实,我们可以使用指针来访问地址并更改其值!此操作称为解引用或间接引用 。我们需要在指针上再次使用 * 运算符,然后分配一个新值,如下所示:
lyrics := "Moments so dear"
pointerForStr := &lyrics
*pointerForStr = "Journeys to plan"
fmt.Println(lyrics) // Prints: Journeys to plan
在我们的例子中,我们有变量: lyrics ,其值为 “Moments so dear” ; pointerForStr ,它是一个指向 lyrics 的指针。然后,我们在 pointerForStr 上使用 * 运算符来取消引用它,并赋值一个新值 “Journeys to plan” 。当我们检查 lyrics 的值时,它现在是 “Journeys to plan” !
在不同的作用域内改变值⌗
利用我们对地址、指针以及解引用的了解,让我们回到最初的问题:如何在不同的作用域中更改变量的值?让我们再看一下代码:
func addHundred(num int) {
num += 100
}
func main() {
x := 1
addHundred(x)
fmt.Println(x) // Prints 1
}
即使我们调用 addHundred(x) , x 的值也不会改变!这是为什么呢?记住,Go 是一种值传递语言。当我们调用 addHundred(x) 时,我们给 addHundred() 传入了一个值 1 。我们实际上并没有提供 x 的地址让 addHundred() 去修改存储在那里的值。如果我们想使用函数改变 x 的值,我们首先需要改变我们的函数:
func addHundred (numPtr *int) {
*numPtr += 100
}
我们的新函数现在有一个指向整数的指针参数。通过将指针的值(即地址)传递给 addHundred() ,我们也可以取消引用该地址,并将它的值加 100 但是,既然 addHundred() 需要一个指针作为参数,我们还需要修改 main() 函数!完整代码如下:
func addHundred (numPtr *int) {
*numPtr += 100
}
func main() {
x := 1
addHundred(&x)
fmt.Println(x) // Prints 101
}
最后一步是向 addHundred() 提供 x 的地址。这样, x 就变成了 101 !
总结与回顾⌗
现在我们可以更改变量值,即使变量超出作用域!本文涵盖了以下概念:
- Go 是一种按值传递的语言。
- 地址是存储值的地方。
- 要查找变量的地址,请在变量前使用
&运算符。 - 指针是存储地址的变量。
- 指针特定于它可以存储什么类型的地址。
*运算符可用于为指针分配其地址所保存的值的类型。*运算符还可用于取消引用指针并分配新值。