參考教程:
http://www.flysnow.org/2017/03/26/go-in-action-go-type.html
http://www.flysnow.org/2017/10/23/go-new-vs-make.html
1 變數的類型
想弄明白 make 和 new 的區別,首先要分清楚參考型別和實值型別。
1.1 實值型別
golang 中內建未經處理資料類型是實值型別。比如int、float、string、bool。實值型別是不可變的,所以對他們進行操作,一般會返回一個新建立的值。把這些值傳遞給函數時,其實建立的是一個值的副本。這一點在python中也是相同的。作為參數傳遞給函數的都是一個副本。
func main(){ s := "a" modify(s) fmt.Println(s)}func modify(s string)string{ s = "888888888"}
因為修改實值型別,產生的都是它的一個副本,所以實值型別在多線程中是安全的。
1.2 參考型別
切片、map、介面、函數類型以及chan都是參考型別。參考型別的修改可以影響到任何引用到它的變數。
參考型別之所以可以引用,是因為我們在建立參考型別的變數,其實是一個標題值,標題值裡包含一個指標,指向底層的資料結構,在函數中傳遞參考型別時,傳遞的是這個標題值的副本,它所指向的底層結構並沒有被複製傳遞,這也是參考型別傳遞高效的原因。
func main(){ ages := map[string]int{"s":12} modify(ages) fmt.Println(ages)}func modify(m map[string]int){ m["s"] = 777}
函數 modify 的修改,會影響 ages 的值。
1.3 結構類型
用 var 關鍵字聲明一個結構體類型的變數。
type person struct{ age int name string}// 聲明var p person
這種 var 聲明的方式,會對結構體 person 裡的資料類型預設初始化,也就是使用它們類型的零值。
函數傳參是值傳遞,所以對於結構體來說也不例外,結構體傳遞的是其本身以及裡面的值的拷貝。
type Person struct { age int name string}func modify(p Person){ p.age = 100000000000}func main(){ jim := Person{10,"jim"} fmt.Println(jim) modify(jim) fmt.Println(jim)}
以上的輸出都是一樣的,所以我們驗證傳遞的是值的副本。如果上面的例子我們要修改 age 的值可以通過傳遞結構體的指標。
type Person struct { age int name string}// 我覺得這樣比較好理解,*Person 是針對結構體執行個體的操作。(可以想想物件導向的語言,Python / Java)func modify(p *Person){ p.age = 100000000000}func main(){// jim 的類型是 Person jim := Person{10,"jim"}// var jim Person = Person{10, "jim"} fmt.Println(jim)// &jim 指向的是執行個體 modify(&jim)// var h *Person = &jim fmt.Println(jim)// * 號在定義時使用,&在運算時使用。*這種類型在運算時映射的值是&}
1.4 自訂類型
type Duration int64
Go是強型別語言,所以 int64 與 Duration不能直接賦值。
2 變數的聲明
var i intvar s string
變數的聲明可以通過 var 關鍵字,然後就可以在程式中使用了,var 聲明的變數的值是他們預設的零值。
類型 |
預設零值 |
int |
0 |
string |
"" |
參考型別 |
nil |
對參考型別賦值。
*int
,是聲明一個實值型別int
的參考型別。
*i
,是對實值型別的參考型別進行運算。
func main(){ var i *int *i = 10 fmt.Println(*i)}
運行時的 paninc : runtime error: invalid memory address or nil pointer dereference
從這個提示中可以看出,對於參考型別的變數,我們不光是聲明它,還要為它分配記憶體空間,否則我們的值放在哪去呢?這就是上面錯誤提示的原因。
對於實值型別的聲明則不需要,是因為已經預設幫我們分配好了,實值型別都有一個預設零值。
要分配記憶體,就引出來今天的new 和 make。
new
func main(){ var i *int i = new(int) *i = 10 fmt.Println(*i)}
看下 new 這個內建的函數
// The new built-in function allocates memory.The first argument is a type,not a value, and the value returned is a pointer to a newly allocated zero value of that type.func new(Type) *Type
new 是一個分配記憶體的內建函數。new的參數是一個類型,new的傳回值是一個指標。該指標指向參數的預設零值。
func main(){// 這個u 是什麼類型,*user, 為啥?因為new函數返回的是一個指向參數類型預設零值的指標啊。// 這種方式和上文中 u := user{}, &u 的方式實際上是一樣的。 u := new(user) u.lock.Lock() u.name = "sssssssss" u.lock.Unlock() fmt.Println(u)}type user struct { lock sync.Mutex name string age int}
這就是 new,它返回的永遠是類型的指標,指向配置類型的記憶體位址。
make
make 也用於記憶體配置,但是和 new 不同,它只用於 chan / map 和 且切片的記憶體建立,而且它返回的類型就是這三個類型本身,而不是它們的指標類型。因為chan / map / 切片已經是參考型別了,所以沒必要返回他們的指標了。
看一下 make 的文檔
The make built-in function allocates and initializes an object of type slice, map, or chan (only).Like new,the argument is a type, not a value.Unlike new, make's return type is the same as the type of its argument,not a pointer to it. The specification of the result depends on the type:
Slice: the size specifies the length. The capacity of the slice is equal to its length.A second integer argument may be provided to specify a different capacity; it must be no smaller than the length, so make([]int,0,10) allocates a slice of length 0 and capacity 10.
Map: An empty map is allocated with enough space to hold the specified number of elements.The size may be omitted,in which case a small starting size is allocated.
Channel: The channel's buffer is initialized with the specified buffer capacity. If zero, or the size is omitted, the channel is unbuffered.
func make(t Type, size ...IntegerType) Type
make 只為 slice/ map/ chan 初始化,分配記憶體。
和new 類似,第一個參數必須是一個類型。和 new 不同的是,new 返回的是參數類型的指標,指標指向的該參數的預設零值。而make返回的是傳入類型相同,並不是指標。因為 slice / map / chan 本身就是參考型別了。