文章目錄
Go語言中的指針
區別於C/C++中的指針,Go語言中的指針不能進行偏移和運算,是安全指針。
要搞明白Go語言中的指針需要先知道3個概念:指針地址、指針類型和指針取值。
任何程序數據載入內存後,在內存都有他們的地址,這就是指針。而爲了保存一個數據在內存中的地址,我們就需要指針變量。
比如,“慎獨”這句話是我的座右銘,我想把它寫入程序中,程序一啓動這句話是要加載到內存(假設內存地址0x123456),我在程序中把這段話賦值給變量A,把內存地址賦值給變量B。這時候變量B就是一個指針變量。通過變量A和變量B都能找到我的座右銘。
Go語言中的指針不能進行偏移和運算,因此Go語言中的指針操作非常簡單,我們只需要記住兩個符號:&(取地址)和*(根據地址取值)。
指針地址和指針類型 |
每個變量在運行時都擁有一個地址,這個地址代表變量在內存中的位置。Go語言中使用&字符放在變量前面對變量進行“取地址”操作。 Go語言中的值類型(int、float、bool、string、array、struct)都有對應的指針類型,如:*int
、*int64<
、*string
等。
取變量指針的語法如下:
ptr := &v // v的類型爲T
其中:
- v:代表被取地址的變量,類型爲T
- ptr:用於接收地址的變量,ptr的類型就爲*T,稱做T的指針類型。*代表指針。
舉個例子:
func main() {
a := 10
b := &a
fmt.Printf("a:%d ptr:%p\n", a, &a) // a:10 ptr:0xc00001a078
fmt.Printf("b:%p type:%T\n", b, b) // b:0xc00001a078 type:*int
fmt.Println(&b) // 0xc00000e018
}
我們來看一下b := &a
的圖示:
指針的零值 |
指針的零值是 nil
。
package main
import (
"fmt"
)
func main() {
a := 25
var b *int
if b == nil {
fmt.Println("b is", b)
b = &a
fmt.Println("b after initialization is", b)
}
}
上面的程序中,b
初始化爲 nil
,接着將 a
的地址賦值給 b
。程序會輸出:
b is <nil>
b after initialisation is 0x1040a124
指針取值 |
在對普通變量使用&操作符取地址後會獲得這個變量的指針,然後可以對指針使用*操作,也就是指針取值,代碼如下。
func main() {
//指針取值
a := 10
b := &a // 取變量a的地址,將指針保存到b中
fmt.Printf("type of b:%T\n", b)
c := *b // 指針取值(根據指針去內存取值)
fmt.Printf("type of c:%T\n", c)
fmt.Printf("value of c:%v\n", c)
}
輸出如下:
type of b:*int
type of c:int
value of c:10
總結: 取地址操作符&
和取值操作符是一對互補操作符,
&
取出地址,根據地址取出地址指向的值。
變量、指針地址、指針變量、取地址、取值的相互關係和特性如下:
- 對變量進行取地址(&)操作,可以獲得這個變量的指針變量。
- 指針變量的值是指針地址。
- 對指針變量進行取值(*)操作,可以獲得指針變量指向的原變量的值。
指針傳值示例:
func modify1(x int) {
x = 100
}
func modify2(x *int) {
*x = 100
}
func main() {
a := 10
modify1(a)
fmt.Println(a) // 10
modify2(&a)
fmt.Println(a) // 100
}
new和make |
我們先來看一個例子:
func main() {
var a *int
*a = 100
fmt.Println(*a)
var b map[string]int
b["武漢一枝花"] = 100
fmt.Println(b)
}
執行上面的代碼會引發panic,爲什麼呢? 在Go語言中對於引用類型的變量,我們在使用的時候不僅要聲明它,還要爲它分配內存空間,否則我們的值就沒辦法存儲。而對於值類型的聲明不需要分配內存空間,是因爲它們在聲明的時候已經默認分配好了內存空間。要分配內存,就引出來今天的new和make。 Go語言中new和make是內建的兩個函數,主要用來分配內存。
| new
new是一個內置的函數,它的函數簽名如下:
func new(Type) *Type
其中,
- Type表示類型,new函數只接受一個參數,這個參數是一個類型
- *Type表示類型指針,new函數返回一個指向該類型內存地址的指針。
new函數不太常用,使用new函數得到的是一個類型的指針,並且該指針對應的值爲該類型的零值。舉個例子:
func main() {
a := new(int)
b := new(bool)
fmt.Printf("%T\n", a) // *int
fmt.Printf("%T\n", b) // *bool
fmt.Println(*a) // 0
fmt.Println(*b) // false
}
本節開始的示例代碼中var a *int
只是聲明瞭一個指針變量a但是沒有初始化,指針作爲引用類型需要初始化後纔會擁有內存空間,纔可以給它賦值。應該按照如下方式使用內置的new函數對a進行初始化之後就可以正常對其賦值了:
func main() {
var a *int
a = new(int)
*a = 10
fmt.Println(*a)
}
| make
make也是用於內存分配的,區別於new,它只用於slice、map以及chan的內存創建,而且它返回的類型就是這三個類型本身,而不是他們的指針類型,因爲這三種類型就是引用類型,所以就沒有必要返回他們的指針了。make函數的函數簽名如下:
func make(t Type, size ...IntegerType) Type
make函數是無可替代的,我們在使用slice、map以及channel的時候,都需要使用make進行初始化,然後纔可以對它們進行操作。這個我們在上一章中都有說明,關於channel我們會在後續的章節詳細說明。
本節開始的示例中var b map[string]int
只是聲明變量b是一個map類型的變量,需要像下面的示例代碼一樣使用make函數進行初始化操作之後,才能對其進行鍵值對賦值:
func main() {
var b map[string]int
b = make(map[string]int, 10)
b["武漢一枝花"] = 100
fmt.Println(b)
}
| new與make的區別
- 二者都是用來做內存分配的。
- make只用於slice、map以及channel的初始化,返回的還是這三個引用類型本身;
- 而new用於類型的內存分配,並且內存對應的值爲類型零值,返回的是指向類型的指針。
【對比python】
python中沒有指針的概念