鍍金池/ 教程/ GO/ 4.9 指針
4.7 strings 和 strconv 包
13.6 啟動外部命令和程序
?# 11.4 類型判斷:type-switch
12.1 讀取用戶的輸入
10.6 方法
12.2 文件讀寫
13 錯誤處理與測試
9.3 鎖和 sync 包
12.3 文件拷貝
?# 11.7 第一個例子:使用 Sorter 接口排序
?# 11.5 測試一個值是否實現(xiàn)了某個接口
6.4 defer 和追蹤
12.10 XML 數(shù)據(jù)格式
13.10 性能調(diào)試:分析并優(yōu)化 Go 程序
?# 11.1 接口是什么
2.2 Go 環(huán)境變量
2.6 安裝目錄清單
2.5 在 Windows 上安裝 Go
11.11 Printf 和反射
1.2 語言的主要特性與發(fā)展的環(huán)境和影響因素
9.0 包(package)
7.4 切片重組(reslice)
13.2 運行時異常和 panic
10.2 使用工廠方法創(chuàng)建結(jié)構(gòu)體實例
12.8 使用接口的實際例子:fmt.Fprintf
2.4 在 Mac OS X 上安裝 Go
3.8 Go 性能說明
7.2 切片
8.0 Map
3.1 Go 開發(fā)環(huán)境的基本要求
5.6 標(biāo)簽與 goto
6.10 使用閉包調(diào)試
9.5 自定義包和可見性
4.3 常量
?# 11.2 接口嵌套接口
6.5 內(nèi)置函數(shù)
前言
10.8 垃圾回收和 SetFinalizer
2.8 Go 解釋器
13.7 Go 中的單元測試和基準(zhǔn)測試
6.8 閉包
4.9 指針
13.1 錯誤處理
10.1 結(jié)構(gòu)體定義
5.1 if-else 結(jié)構(gòu)
6.6 遞歸函數(shù)
9.9 通過 Git 打包和安裝
2.7 Go 運行時(runtime)
10.7 類型的 String() 方法和格式化描述符
3.7 其它工具
9.6 為自定義包使用 godoc
11.12 接口與動態(tài)類型
13.3 從 panic 中恢復(fù)(Recover)
10.3 使用自定義包中的結(jié)構(gòu)體
11.14 結(jié)構(gòu)體、集合和高階函數(shù)
3.6 生成代碼文檔
9.2 regexp 包
4.1 文件名、關(guān)鍵字與標(biāo)識符
?# 11.6 使用方法集與接口
7.0 數(shù)組與切片
7.1 聲明和初始化
12.11 用 Gob 傳輸數(shù)據(jù)
5.5 Break 與 continue
1.1 起源與發(fā)展
?# 11 接口(Interfaces)與反射(reflection)
6.9 應(yīng)用閉包:將函數(shù)作為返回值
4.2 Go 程序的基本結(jié)構(gòu)和要素
8.6 將 map 的鍵值對調(diào)
6.11 計算函數(shù)執(zhí)行時間
5.0 控制結(jié)構(gòu)
10.5 匿名字段和內(nèi)嵌結(jié)構(gòu)體
4.6 字符串
3.0 編輯器、集成開發(fā)環(huán)境與其它工具
13.8 測試的具體例子
7.6 字符串、數(shù)組和切片的應(yīng)用
8.4 map 類型的切片
3.9 與其它語言進行交互
7.3 For-range 結(jié)構(gòu)
9.7 使用 go install 安裝自定義包
6.0 函數(shù)
9.8 自定義包的目錄結(jié)構(gòu)、go install 和 go test
6.3 傳遞變長參數(shù)
13.9 用(測試數(shù)據(jù))表驅(qū)動測試
11.9 空接口
8.1 聲明、初始化和 make
6.2 函數(shù)參數(shù)與返回值
9.11 在 Go 程序中使用外部庫
3.3 調(diào)試器
4.5 基本類型和運算符
?# 11.8 第二個例子:讀和寫
12.5 用 buffer 讀取文件
總結(jié):Go 中的面向?qū)ο?/span>
11.10 反射包
12.7 用 defer 關(guān)閉文件
9.4 精密計算和 big 包
4.4 變量
6.1 介紹
13.4 自定義包中的錯誤處理和 panicking
12.4 從命令行讀取參數(shù)
9.10 Go 的外部包和項目
8.3 for-range 的配套用法
3.5 格式化代碼
10.4 帶標(biāo)簽的結(jié)構(gòu)體
7.5 切片的復(fù)制與追加
?# 11.3 類型斷言:如何檢測和轉(zhuǎn)換接口變量的類型
5.4 for 結(jié)構(gòu)
4.8 時間和日期
2.3 在 Linux 上安裝 Go
12 讀寫數(shù)據(jù)
6.12 通過內(nèi)存緩存來提升性能
9.1 標(biāo)準(zhǔn)庫概述
12.6 用切片讀寫文件
10 結(jié)構(gòu)(struct)與方法(method)
8.5 map 的排序
12.9 JSON 數(shù)據(jù)格式
13.5 一種用閉包處理錯誤的模式
3.2 編輯器和集成開發(fā)環(huán)境
12.12 Go 中的密碼學(xué)
5.2 測試多返回值函數(shù)的錯誤
6.7 將函數(shù)作為參數(shù)
8.2 測試鍵值對是否存在及刪除元素
3.4 構(gòu)建并運行 Go 程序
2.1 平臺與架構(gòu)
5.3 switch 結(jié)構(gòu)

4.9 指針

不像 Java 和 .NET,Go 語言為程序員提供了控制數(shù)據(jù)結(jié)構(gòu)的指針的能力;但是,你不能進行指針運算。通過給予程序員基本內(nèi)存布局,Go 語言允許你控制特定集合的數(shù)據(jù)結(jié)構(gòu)、分配的數(shù)量以及內(nèi)存訪問模式,這些對構(gòu)建運行良好的系統(tǒng)是非常重要的:指針對于性能的影響是不言而喻的,而如果你想要做的是系統(tǒng)編程、操作系統(tǒng)或者網(wǎng)絡(luò)應(yīng)用,指針更是不可或缺的一部分。

由于各種原因,指針對于使用面向?qū)ο缶幊痰默F(xiàn)代程序員來說可能顯得有些陌生,不過我們將會在這一小節(jié)對此進行解釋,并在未來的章節(jié)中展開深入討論。

程序在內(nèi)存中存儲它的值,每個內(nèi)存塊(或字)有一個地址,通常用十六進制數(shù)表示,如:0x6b08200xf84001d7f0。

Go 語言的取地址符是 &,放到一個變量前使用就會返回相應(yīng)變量的內(nèi)存地址。

下面的代碼片段(示例 4.9 pointer.go)可能輸出 An integer: 5, its location in memory: 0x6b0820(這個值隨著你每次運行程序而變化)。

var i1 = 5
fmt.Printf("An integer: %d, it's location in memory: %p\n", i1, &i1)

這個地址可以存儲在一個叫做指針的特殊數(shù)據(jù)類型中,在本例中這是一個指向 int 的指針,即 i1:此處使用 *int 表示。如果我們想調(diào)用指針 intP,我們可以這樣聲明它:

var intP *int

然后使用 intP = &i1 是合法的,此時 intP 指向 i1。

(指針的格式化標(biāo)識符為 %p

intP 存儲了 i1 的內(nèi)存地址;它指向了 i1 的位置,它引用了變量 i1。

一個指針變量可以指向任何一個值的內(nèi)存地址 它指向那個值的內(nèi)存地址,在 32 位機器上占用 4 個字節(jié),在 64 位機器上占用 8 個字節(jié),并且與它所指向的值的大小無關(guān)。當(dāng)然,可以聲明指針指向任何類型的值來表明它的原始性或結(jié)構(gòu)性;你可以在指針類型前面加上 號(前綴)來獲取指針?biāo)赶虻膬?nèi)容,這里的 號是一個類型更改器。使用一個指針引用一個值被稱為間接引用。

當(dāng)一個指針被定義后沒有分配到任何變量時,它的值為 nil

一個指針變量通??s寫為 ptr

注意事項

在書寫表達式類似 var p *type 時,切記在 號和指針名稱間留有一個空格,因為 `- var ptype` 是語法正確的,但是在更復(fù)雜的表達式中,它容易被誤認(rèn)為是一個乘法表達式!

符號 可以放在一個指針前,如 `intP`,那么它將得到這個指針指向地址上所存儲的值;這被稱為反引用(或者內(nèi)容或者間接引用)操作符;另一種說法是指針轉(zhuǎn)移。

對于任何一個變量 var, 如下表達式都是正確的:var == *(&var)

現(xiàn)在,我們應(yīng)當(dāng)能理解 pointer.go 的全部內(nèi)容及其輸出:

示例 4.21 pointer.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

我們可以用下圖來表示內(nèi)存使用的情況:

http://wiki.jikexueyuan.com/project/the-way-to-go/images/4.4.9_fig4.4.png?raw=true" alt="" />

程序 string_pointer.go 為我們展示了指針對string的例子。

它展示了分配一個新的值給 *p 并且更改這個變量自己的值(這里是一個字符串)。

示例 4.22 string_pointer.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 也會隨之更改。

內(nèi)存示意圖如下:

http://wiki.jikexueyuan.com/project/the-way-to-go/images/4.4.9_fig4.5.png?raw=true" alt="" />

注意事項

你不能得到一個文字或常量的地址,例如:

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 語言這些低級(系統(tǒng))語言一樣,都有指針的概念。但是對于經(jīng)常導(dǎo)致 C 語言內(nèi)存泄漏繼而程序崩潰的指針運算(所謂的指針?biāo)惴?,如?code>pointer+2,移動指針指向字符串的字節(jié)數(shù)或數(shù)組的某個位置)是不被允許的。Go 語言中的指針保證了內(nèi)存安全,更像是 Java、C# 和 VB.NET 中的引用。

因此 c = *p++ 在 Go 語言的代碼中是不合法的。

指針的一個高級應(yīng)用是你可以傳遞一個變量的引用(如函數(shù)的參數(shù)),這樣不會傳遞變量的拷貝。指針傳遞是很廉價的,只占用 4 個或 8 個字節(jié)。當(dāng)程序在工作中需要占用大量的內(nèi)存,或很多變量,或者兩者都有,使用指針會減少內(nèi)存占用和提高效率。被指向的變量也保存在內(nèi)存中,直到?jīng)]有任何指針指向它們,所以從它們被創(chuàng)建開始就具有相互獨立的生命周期。

另一方面(雖然不太可能),由于一個指針導(dǎo)致的間接引用(一個進程執(zhí)行了另一個地址),指針的過度頻繁使用也會導(dǎo)致性能下降。

指針也可以指向另一個指針,并且可以進行任意深度的嵌套,導(dǎo)致你可以有多級的間接引用,但在大多數(shù)情況這會使你的代碼結(jié)構(gòu)不清晰。

如我們所見,在大多數(shù)情況下 Go 語言可以使程序員輕松創(chuàng)建指針,并且隱藏間接引用,如:自動反向引用。

對一個空指針的反向引用是不合法的,并且會使程序崩潰:

示例 4.23 testcrash.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 語言中 * 號的所有用法。

鏈接