## 什么是指針
程序運行時的數據是存放在內存中的,而內存會被抽象為一系列具有連續編號的存儲空間,那么每一個存儲在內存中的數據都會有一個編號,這個編號就是內存地址。有了這個內存地址就可以找到這個內存中存儲的數據,而內存地址可以被賦值給一個指針。
可以總結為:在編程語言中,指針是一種數據類型,用來存儲一個內存地址,該地址指向存儲在該內存中的對象。這個對象可以是字符串、整數、函數或者你自定義的結構體。
## 指針的聲明和定義
在 Go 語言中,獲取一個變量的指針非常容易,使用取地址符 & 就可以,比如下面的例子:
```
func main() {
strVal := "PHP is the best language in the world!"
strPtr := &strPtr//取地址
fmt.Println("strPtrr變量的值為:",strPtrr)
fmt.Println("strPtr變量的內存地址為:",strPtr)
}
```
> 指針類型非常廉價,只占用 4 個或者 8 個字節的內存大小。
以上示例中 strPtr 指針的類型是 *string,用于指向 string 類型的數據。在 Go 語言中使用類型名稱前加 `*` 的方式,即可表示一個對應的指針類型。比如 int 類型的指針類型是 *int,float64 類型的指針類型是 *float64,自定義結構體 A 的指針類型是 *A。總之,指針類型就是在對應的類型前加 * 號。
此外,除了可以通過簡短聲明的方式聲明一個指針類型的變量外,也可以使用 var 關鍵字聲明, `var intP *int `
> 通過 var 聲明的指針變量是不能直接賦值和取值的,因為這時候它僅僅是個變量,還沒有對應的內存地址,它的值是 nil。
和普通類型不一樣的是,指針類型還可以通過內置的 new 函數來聲明,如下所示:
```
intP := new(int)
```
內置的 new 函數有一個參數,可以傳遞類型給它。它會返回對應的指針類型,比如上述示例中會返回一個 *int 類型的 intP。
## 指針的操作
在 Go 語言中指針的操作無非是兩種:一種是獲取指針指向的值,一種是修改指針指向的值。
要獲取指針指向的值,只需要在指針變量前加 * 號即可:
```
strVal := *strPtr
fmt.Println("strPtr指針指向的值為:", strVal)
```
修改指針指向的值也非常簡單
```
*strPtr = "hello world" //修改指針指向的值
fmt.Println("strPtr指針指向的值為:",*strPtr) // hello world
fmt.Println("strPtr變量的值為:", str) // hello world
```
通過打印結果可以看到,不光 strPtr 指針指向的值被改變了,變量 strVal 的值也被改變了,這就是指針的作用。因為變量 strVal 存儲數據的內存就是指針 strPtr 指向的內存,這塊內存被 strPtr 修改后,變量 strVal 的值也被修改了。
通過 var 關鍵字直接定義的指針變量是不能進行賦值操作的,因為它的值為 nil,也就是還沒有指向的內存地址。比如下面的示例:
```
var intP *int
*intP =10
```
運行的時候會提示 `invalid memory address or nil pointer dereference`。這時候該怎么辦呢?其實只需要通過 new 函數給它分配一塊內存就可以了,如下所示:
```
var intPtr *int = new(int)
或者
//intPtr := new(int)
```
## 指針接收者
對于是否使用指針類型作為接收者,有以下幾點參考:
1. 如果接收者類型是 map、slice、channel 這類引用類型,不使用指針;
2. 如果需要修改接收者,那么需要使用指針;
3. 如果接收者是比較大的類型,可以考慮使用指針,因為內存拷貝廉價,所以效率高。
## 什么情況下使用指針
從以上指針的詳細分析中,可以總結出指針的兩大好處:
1. 可以修改指向數據的值;
2. 在變量賦值,參數傳值的時候可以節省內存。
不過 Go 語言作為一種高級語言,在指針的使用上還是比較克制的。它在設計的時候就對指針進行了諸多限制,比如指針不能進行運行,也不能獲取常量的指針。所以在思考是否使用時,我們也要保持克制的心態。
使用指針的建議:
1. 不要對 map、slice、channel 這類引用類型使用指針;
2. 如果需要修改方法接收者內部的數據或者狀態時,需要使用指針;
3. 如果需要修改參數的值或者內部數據時,也需要使用指針類型的參數;
4. 如果是比較大的結構體,每次參數傳遞或者調用方法都要內存拷貝,內存占用多,這時候可以考慮使用指針;
5. 像 int、bool 這樣的小數據類型沒必要使用指針;
6. 如果需要并發安全,則盡可能地不要使用指針,使用指針一定要保證并發安全;
7. 指針最好不要嵌套,也就是不要使用一個指向指針的指針,雖然 Go 語言允許這么做,但是這會使你的代碼變得異常復雜。