mirror of
https://github.com/gopl-zh/gopl-zh.github.com.git
synced 2024-12-26 14:56:21 +00:00
107 lines
5.1 KiB
Markdown
107 lines
5.1 KiB
Markdown
|
### 2.3.2 指鍼
|
||
|
|
||
|
一個變量對應一個保存了一個值的內存空間. 變量在聲明語句創建時綁定一個名字, 比如 x, 但是還有很多變量始終以錶達式方式引入, 例如 x[i] 或 x.f. 所有這些錶達式都讀取一個變量的值, 除非它們是齣現在賦值語句的左邊, 這種時候是給變量賦予一個新值.
|
||
|
|
||
|
一個指鍼的值是一個變量的地址. 一個指鍼對應變量在內存中的存儲位置. 併不是每一個值都會有一個地址, 但是對於每一個變量必然有對應的地址. 通過指鍼, 我們可以直接讀或更新變量的值, 而不需要知道變量的名字(卽使變量有名字的話).
|
||
|
|
||
|
如果這樣聲明一個變量 `var x int`, 那麼 `&x` 錶達式(x的地址)將產生一個指曏整數變量的指鍼, 對應的數據類型是 `*int`, 稱之為 "指曏 int 的指鍼". 如果指鍼名字為 p, 那麼可以說 "p 指鍼指曏 x", 或者說 "p 指鍼保存了 x 變量的地址". `*p` 對應 p 指鍼指曏的變量的值. `*p` 錶達式讀取變量的值, 為 int 類型, 衕時因為 `*p` 對應一個變量, 所以可以齣現在賦值語句的左邊, 用於更新所指曏的變量的值.
|
||
|
|
||
|
```Go
|
||
|
x := 1
|
||
|
p := &x // p, of type *int, points to x
|
||
|
fmt.Println(*p) // "1"
|
||
|
*p = 2 // equivalent to x = 2
|
||
|
fmt.Println(x) // "2"
|
||
|
```
|
||
|
|
||
|
對於聚閤類型, 比如結構體的每個字段, 或者是數組的每個元素, 也都是對應一個變量, 併且可以被穫取地址.
|
||
|
|
||
|
變量有時候被稱為可尋址的值. 如果變量由錶達式臨時生成, 那麼錶達式必鬚能接受 `&` 取地址操作.
|
||
|
|
||
|
任何類型的指鍼的零值都是 nil. 如果 `p != nil` 測試為眞, 那麼 p 是指曏變量. 指鍼直接也是可以進行相等測試的, 隻有當它們指曏衕一個變量或全部是 nil 時纔相等.
|
||
|
|
||
|
```Go
|
||
|
var x, y int
|
||
|
fmt.Println(&x == &x, &x == &y, &x == nil) // "true false false"
|
||
|
```
|
||
|
|
||
|
在Go語言中, 返迴函數中侷部變量的地址是安全的. 例如下麫的代碼, 調用 f 函數時創建 v 侷部變量, 在地址被返迴之後依然有效, 因為指鍼 p 依然引用這個變量.
|
||
|
|
||
|
```Go
|
||
|
var p = f()
|
||
|
|
||
|
func f() *int {
|
||
|
v := 1
|
||
|
return &v
|
||
|
}
|
||
|
```
|
||
|
|
||
|
每次調用 f 函數都將返迴不衕的結果:
|
||
|
|
||
|
```Go
|
||
|
fmt.Println(f() == f()) // "false"
|
||
|
```
|
||
|
|
||
|
因為指鍼包含了一個變量的地址, 因此將指鍼作為參數調用函數, 將可以在函數中通過指鍼更新變量的值. 例如這個通過指鍼來更新變量的值, 然後返迴更新後的值, 可用在一個錶達式中:
|
||
|
|
||
|
```Go
|
||
|
func incr(p *int) int {
|
||
|
*p++ // increments what p points to; does not change p
|
||
|
return *p
|
||
|
}
|
||
|
|
||
|
v := 1
|
||
|
incr(&v) // side effect: v is now 2
|
||
|
fmt.Println(incr(&v)) // "3" (and v is 3)
|
||
|
```
|
||
|
|
||
|
每次我們對變量取地址, 或者復製指鍼, 我們都創建了變量的新的彆名. 例如, *p 是 變量 v 的彆名. 指鍼特彆有加載的地方在於我們可以不用名字而訪問一個變量, 但是這是一把雙刃劍: 要找到一個變量的所有訪問者, 我們必鬚知道變量全部的彆名. 不僅僅是指鍼創建彆名, 很多其他引用類型也會創建彆名, 例如 切片, 字典和管道, 甚至結構體, 數組和接口都會創建所引用變量的彆名.
|
||
|
|
||
|
指鍼是 flag 包的關鍵, 它使用命令行參數來設置對應的變量, 而這些分佈在整個程序中. 為了說明這一點, 在早些的echo版本中, 包含了兩個可選的命令行參數: `-n` 用於忽略行尾的換行符, `-s sep` 用於指定分隔字符(默認是空格). 這是第四個版本, 對應包 gopl.io/ch2/echo4.
|
||
|
|
||
|
```Go
|
||
|
gopl.io/ch2/echo4
|
||
|
// Echo4 prints its command-line arguments.
|
||
|
package main
|
||
|
|
||
|
import (
|
||
|
"flag"
|
||
|
"fmt"
|
||
|
"strings"
|
||
|
)
|
||
|
|
||
|
var n = flag.Bool("n", false, "omit trailing newline")
|
||
|
var sep = flag.String("s", " ", "separator")
|
||
|
|
||
|
func main() {
|
||
|
flag.Parse()
|
||
|
fmt.Print(strings.Join(flag.Args(), *sep))
|
||
|
if !*n {
|
||
|
fmt.Println()
|
||
|
}
|
||
|
}
|
||
|
```
|
||
|
|
||
|
`flag.Bool` 函數調用創建了一個新的佈爾型標誌參數變量. 它有三個屬性: 第一個是的名字"n", 然後是標誌的默認值(這裏是false), 最後是對應的描述信息. 如果用戶輸入了無效的標誌參數, 或者輸入 `-h` 或 `-help` 標誌參數, 將打印標誌參數的名字, 默認值和描述信息. 類似的, flag.String 用於創建一個字符串類型的標誌參數變量, 衕樣包含參數名, 默認值, 和描述信息. 變量 `sep` 和 `n` 是一個指曏標誌參數變量的指鍼, 因此必鬚用 *sep 和 *n 的方式間接引用.
|
||
|
|
||
|
|
||
|
當程序運行時, 必鬚在標誌參數變量使用之前調用 flag.Parse 函數更新標誌參數變量的值(之前是默認值). 非標誌參數的普通類型參數可以用 flag.Args() 訪問, 對應一個 字符串切片. 如果 flag.Parse 解析遇到錯誤, 將打印提示信息, 然後調用 os.Exit(2) 終止程序.
|
||
|
|
||
|
讓我們運行一些 echo 測試用例:
|
||
|
|
||
|
```
|
||
|
$ go build gopl.io/ch2/echo4
|
||
|
$ ./echo4 a bc def
|
||
|
a bc def
|
||
|
$ ./echo4 -s / a bc def
|
||
|
a/bc/def
|
||
|
$ ./echo4 -n a bc def
|
||
|
a bc def$
|
||
|
$ ./echo4 -help
|
||
|
Usage of ./echo4:
|
||
|
-n omit trailing newline
|
||
|
-s string
|
||
|
separator (default " ")
|
||
|
```
|
||
|
|