Golang:指针 - 详细概述。
什么是“指针”?Golang 中的指针示例。* 和 & 运算符。作为函数参数的指针。函数 - 按值和按引用传递参数。
什么是指针?
简而言之,指针是一个变量,它存储另一个存储数据的变量的地址。
指针示例
让我们举一个使用指针的最简单的例子
package main
import "fmt"
func main() {
a := 1
b := &a
fmt.Println("A: ", a)
fmt.Println("B: ", b)
fmt.Println("B: ", *b)
}
这里
- 创建一个名为
a
、类型为整数、值为1
的变量 - 创建一个名为
b
、类型为指向整数的指针(见下文)的变量 - 并输出数据
- 首先只是
a
的值 b
变量的a
的值(即内容)- 最后,我们获取
b
指向的a
的值(我们将在稍后查看*
和&
运算符)
- 首先只是
运行代码
$ go run pointers_example.go
A: 1
B: 0xc0000140e8
B: 1
在第二行,我们看到 b
指针指向的内存地址。
在第三行 - 我们从该内存地址获取了值。
指针可以更全面地初始化,通过指定类型而不是使用 :=
,这样代码看起来就像
...
func main() {
var a int = 1
var b *int = &a
fmt.Println("A: ", a)
fmt.Println("B: ", b)
fmt.Println("B: ", *b)
}
...
在 var b *int = &a
行,我们设置 b
变量是指向整数数据的指针。
同样,可以通过在其数据类型中使用 *string
而不是 *int
来创建指向字符串数据的指针。
...
var c *string
fmt.Printf("The C var type: %T, default value: %v\n", c, c)
var d string = "This is a string"
c = &d
fmt.Printf("The C var type: %T, default value: %v, string value: %s\n", c, c, *c)
...
这里
- 创建了一个名为
c
、类型为指向字符串数据的指针的变量 - 使用
Printf()
的修饰符,显示了c
变量的数据类型 (%T
) 及其值 (%v
) - 创建了一个名为
d
、类型为字符串、值为 “This is a string
” 的变量 - 为
c
变量设置了d
变量的内存地址 - 使用
Printf()
的修饰符,显示了c
变量的数据类型 (%T
)、其值 (%v
) 以及从c
中存储的内存地址获取的值
Run
$ go run pointers_example.go
The C var type: *string, default value: <nil>
The C var type: *string, default value: 0xc0000101e0, string value: This is a string
* 和 & 运算符
我们在前面的示例中已经使用过它们,但让我们仔细看看。
*
运算符是解引用运算符。
这里的解引用意味着我们获取的不是指针(它存储地址)的值,而是从指针指向的内存地址中获取的值。好吧 - 指向的内存地址!
让我们回到之前的例子
...
fmt.Printf("The C var type: %T, default value: %v, string value: %s\n", c, c, *c)
...
这里
default value: %v
与c
- 显示存储在c
变量中的值 -c
指向的内存地址string value: %s
与*c
- 显示从c
变量调用内存后获得的值
&
运算符返回变量的内存地址。
例如,让我们在之前的示例中添加一行,并使用 Printf()
和 %p
修饰符显示地址
...
var c *string
fmt.Printf("The C var type: %T, default value: %v\n", c, c)
var d string = "This is a string"
c = &d
fmt.Printf("The C var type: %T, default value: %v, string value: %s\n", c, c, *c)
fmt.Printf("The D adress: %p\nThe C address: %p\nThe C value: %v\n", &d, &c, c)
...
Check
$ go run pointers_example.go
The C var type: *string, default value: <nil>
The C var type: *string, default value: 0xc0000101e0, string value: This is a string
The D adress: 0xc0000101e0
The C address: 0xc00000e028
The C value: 0xc0000101e0
我们得到了 d
变量地址的 0xc0000101e0
值,c
变量地址的 0xc00000e028
,但 c
本身存储的是 d
变量的地址 - 0xc0000101e0
。
实际上,通过获取 d
变量的地址,可以在 c
指针变量中进行数据初始化。
...
c = &d
...
new() 函数
要使用 var pointername *type
符号定义和初始化指针 - 您可以使用内置的 new()
Go
函数,该函数接受数据类型作为第一个参数,并返回指向为变量数据分配的内存的指针。
...
a := 1
b := new(int)
fmt.Printf("A: %d, B: %v, %v\n", a, b, *b)
b = &a
fmt.Printf("A: %d, B: %v, %v\n", a, b, *b)
...
这里
- 创建了值为
1
的a
变量 - 创建了
b
变量,它将保存new()
函数返回的内存的指针,并且目前保持0
,因为已分配的内存无法保持nil
。 - 将
b
的值更新为a
的地址
Check
$ go run pointers_example.go
A: 1, B: 0xc000014100, 0
A: 1, B: 0xc0000140e8, 1
更改指针的值
好吧,说“更改指针的值”是不正确的,因为指针变量存储的是内存地址 - 而不是值本身。
但是,使用指针,我们可以更改该指针指向的内存位置的值。
例如
...
a := 1
b := &a
fmt.Println("A: ", a)
fmt.Println("B: ", *b)
*b = 2
fmt.Println("B: ", *b)
...
这里
a
变量的值为1
b
变量具有a
的地址- 显示
a
的值 - 显示从
b
指向的地址获取的值 - 我们将此内存中的值更改为
2
- 并显示新值
运行代码
$ go run pointers_example.go
A: 1
B: 1
B: 2
将指针作为函数参数传递
您可以将指针作为参数传递给函数。
例如
package main
import "fmt"
func setVal(b *int) {
*b = 2
}
func main() {
a := 1
b := &a
fmt.Println("Init values")
fmt.Println("A: ", a)
fmt.Println("B: ", *b)
setVal(b)
fmt.Println("Changed values")
fmt.Println("A: ", a)
fmt.Println("B: ", *b)
}
在这里,我们创建了一个名为 a
的 setVal()
函数,它接受一个整数指针作为参数,并将更改该指针传递的地址中的值。
检查一下
$ go run pointers_example.go
Init values
A: 1
B: 1
Changed values
A: 2
B: 2
调用 setVal()
后 - a
和 b
将显示新值。
更进一步 - 我们甚至可以将 a
变量的地址传递给 setVal()
。
...
func setVal(b *int) {
*b = 2
}
func main() {
a := 1
fmt.Println("Init values")
fmt.Println("A: ", a)
setVal(&a)
fmt.Println("Changed values")
fmt.Println("A: ", a)
}
结果是:
$ go run pointers_example.go
Init values
A: 1
Changed values
A: 2
函数:按值和按引用传递参数
这里有点离题,但使用上面的例子,也可以显示按值传递参数和按引用传递参数之间的区别。
让我们更新这个例子
...
func setVal(b *int, c int) {
*b = 2
c = 4
fmt.Printf("B from setVal(). Poiner to: %p, val: %v\n", b, *b)
fmt.Printf("C from setVal(). Addr: %p, val: %v\n", &c, c)
}
func main() {
a := 1
b := &a
c := 3
fmt.Println("Init values")
fmt.Printf("A from main(). Addr: %p, val: %v\n", &a, a)
fmt.Printf("B from main(). Poiner to: %p, val: %v\n", b, *b)
fmt.Printf("C from main(). Addr: %p, val: %v\n", &c, c)
fmt.Println("Changed values")
setVal(b, c)
fmt.Printf("A from main(). Addr: %p, val: %v\n", &a, a)
fmt.Printf("B from main(). Poiner to: %p, val: %v\n", b, *b)
fmt.Printf("C from main(). Addr: %p, val: %v\n", &c, c)
}
这里
- 获取
a
变量的地址及其值 - 获取存储在
b
变量中的地址,然后获取存储在该地址的数据 - 获取
c
变量的地址及其值 - 调用
setVal()
函数,通过b
中的引用传递a
的值,并通过c
- 按值传递 - 在
setVal()
中,获取存储在b
变量中的地址以及存储在那里的值 - 在
setVal()
中,获取c
变量的地址以及存储在该内存区域中的值 - 在
main()
中,获取a
的地址以及存储在那里的值 - 在
main()
中,获取存储在b
变量中的地址以及从那里获取的值 - 在
main()
中,获取c
变量的地址以及存储在那里的值
运行它:
$ go run pointers_example.go
Init values
A from main(). Addr: 0xc0000140e8, val: 1
B from main(). Poiner to: 0xc0000140e8, val: 1
C from main(). Addr: 0xc000014100, val: 3
Changed values
B from setVal(). Poiner to: 0xc0000140e8, val: 2
C from setVal(). Addr: 0xc000014130, val: 4
A from main(). Addr: 0xc0000140e8, val: 2
B from main(). Poiner to: 0xc0000140e8, val: 2
C from main(). Addr: 0xc000014100, val: 3
这里
a
存储在0xc0000140e8
位置,值为1
b
指向同一位置0xc0000140e8
,返回相同的值1
c
存储在0xc000014100
位置,值为3
- 调用了
setVal()
setVal()
中的b
仍然指向0xc0000140e8
位置,其值为2
setVal()
中的c
获得了新的地址0xc000014130
,其中存储着4
main()
中的a
现在保留来自同一0xc0000140e8
位置的2
值main()
中的b
与setVal()
中的情况相同 - 指向同一位置并返回相同的值- 在
main()
中,对于c
变量,没有任何改变,因为setVal()
中的c
有自己的地址0xc000014130
,而main()
中的c
使用0xc000014100
位置。
实际上,这就是你需要了解的全部内容,以便更好地理解指针是什么以及如何使用它们。