Files
the-way-to-go_ZH_CN/eBook/04.9.md
WenKang Tan 3c1df47a86 Update 04.9.md (#755)
Fix: "a literal" 应翻译为术语 ”字面量“ 而不是 “文字”
2020-01-22 23:36:54 +08:00

143 lines
6.4 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 4.9 指针
不像 Java 和 .NETGo 语言为程序员提供了控制数据结构的指针的能力但是你不能进行指针运算。通过给予程序员基本内存布局Go 语言允许你控制特定集合的数据结构、分配的数量以及内存访问模式,这些对构建运行良好的系统是非常重要的:指针对于性能的影响是不言而喻的,而如果你想要做的是系统编程、操作系统或者网络应用,指针更是不可或缺的一部分。
由于各种原因,指针对于使用面向对象编程的现代程序员来说可能显得有些陌生,不过我们将会在这一小节对此进行解释,并在未来的章节中展开深入讨论。
程序在内存中存储它的值,每个内存块(或字)有一个地址,通常用十六进制数表示,如:`0x6b0820``0xf84001d7f0`
Go 语言的取地址符是 `&`,放到一个变量前使用就会返回相应变量的内存地址。
下面的代码片段(示例 4.9 [pointer.go](examples/chapter_4/pointer.go))可能输出 `An integer: 5, its location in memory: 0x6b0820`(这个值随着你每次运行程序而变化)。
```go
var i1 = 5
fmt.Printf("An integer: %d, it's location in memory: %p\n", i1, &i1)
```
这个地址可以存储在一个叫做指针的特殊数据类型中,在本例中这是一个指向 int 的指针,即 `i1`:此处使用 *int 表示。如果我们想调用指针 intP我们可以这样声明它
```go
var intP *int
```
然后使用 `intP = &i1` 是合法的,此时 intP 指向 i1。
(指针的格式化标识符为 `%p`
intP 存储了 i1 的内存地址;它指向了 i1 的位置,它引用了变量 i1。
**一个指针变量可以指向任何一个值的内存地址** 它指向那个值的内存地址,在 32 位机器上占用 4 个字节,在 64 位机器上占用 8 个字节,并且与它所指向的值的大小无关。当然,可以声明指针指向任何类型的值来表明它的原始性或结构性;你可以在指针类型前面加上 * 号(前缀)来获取指针所指向的内容,这里的 * 号是一个类型更改器。使用一个指针引用一个值被称为间接引用。
当一个指针被定义后没有分配到任何变量时,它的值为 `nil`
一个指针变量通常缩写为 `ptr`
**注意事项**
在书写表达式类似 `var p *type` 时,切记在 * 号和指针名称间留有一个空格,因为 `- var p*type` 是语法正确的,但是在更复杂的表达式中,它容易被误认为是一个乘法表达式!
符号 * 可以放在一个指针前,如 `*intP`,那么它将得到这个指针指向地址上所存储的值;这被称为反引用(或者内容或者间接引用)操作符;另一种说法是指针转移。
对于任何一个变量 var 如下表达式都是正确的:`var == *(&var)`
现在,我们应当能理解 pointer.go 的全部内容及其输出:
示例 4.21 [pointer.go](examples/chapter_4/pointer.go):
```go
package main
import "fmt"
func main() {
var i1 = 5
fmt.Printf("An integer: %d, its location in memory: %p\n", i1, &i1)
var intP *int
intP = &i1
fmt.Printf("The value at memory location %p is %d\n", intP, *intP)
}
```
输出:
An integer: 5, its location in memory: 0x24f0820
The value at memory location 0x24f0820 is 5
我们可以用下图来表示内存使用的情况:
![](images/4.9_fig4.4.png?raw=true)
程序 string_pointer.go 为我们展示了指针对string的例子。
它展示了分配一个新的值给 *p 并且更改这个变量自己的值(这里是一个字符串)。
示例 4.22 [string_pointer.go](examples/chapter_4/string_pointer.go)
```go
package main
import "fmt"
func main() {
s := "good bye"
var p *string = &s
*p = "ciao"
fmt.Printf("Here is the pointer p: %p\n", p) // prints address
fmt.Printf("Here is the string *p: %s\n", *p) // prints string
fmt.Printf("Here is the string s: %s\n", s) // prints same string
}
```
输出:
Here is the pointer p: 0x2540820
Here is the string *p: ciao
Here is the string s: ciao
通过对 *p 赋另一个值来更改“对象”,这样 s 也会随之更改。
内存示意图如下:
![](images/4.9_fig4.5.png?raw=true)
**注意事项**
你不能获取字面量或常量的地址,例如:
```go
const i = 5
ptr := &i //error: cannot take the address of i
ptr2 := &10 //error: cannot take the address of 10
```
所以说Go 语言和 C、C++ 以及 D 语言这些低级(系统)语言一样,都有指针的概念。但是对于经常导致 C 语言内存泄漏继而程序崩溃的指针运算(所谓的指针算法,如:`pointer+2`移动指针指向字符串的字节数或数组的某个位置是不被允许的。Go 语言中的指针保证了内存安全,更像是 Java、C# 和 VB.NET 中的引用。
因此 `c = *p++` 在 Go 语言的代码中是不合法的。
指针的一个高级应用是你可以传递一个变量的引用(如函数的参数),这样不会传递变量的拷贝。指针传递是很廉价的,只占用 4 个或 8 个字节。当程序在工作中需要占用大量的内存,或很多变量,或者两者都有,使用指针会减少内存占用和提高效率。被指向的变量也保存在内存中,直到没有任何指针指向它们,所以从它们被创建开始就具有相互独立的生命周期。
另一方面(虽然不太可能),由于一个指针导致的间接引用(一个进程执行了另一个地址),指针的过度频繁使用也会导致性能下降。
指针也可以指向另一个指针,并且可以进行任意深度的嵌套,导致你可以有多级的间接引用,但在大多数情况这会使你的代码结构不清晰。
如我们所见,在大多数情况下 Go 语言可以使程序员轻松创建指针,并且隐藏间接引用,如:自动反向引用。
对一个空指针的反向引用是不合法的,并且会使程序崩溃:
示例 4.23 [testcrash.go](examples/chapter_4/testcrash.go):
```go
package main
func main() {
var p *int = nil
*p = 0
}
// in Windows: stops only with: <exit code="-1073741819" msg="process crashed"/>
// runtime error: invalid memory address or nil pointer dereference
```
**问题 4.2** 列举 Go 语言中 * 号的所有用法。
## 链接
- [目录](directory.md)
- 上一节:[时间和日期](04.8.md)
- 下一节:[控制结构](05.0.md)