參考教程:
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 的區(qū)別短蜕,首先要分清楚引用類型和值類型。
1.1 值類型
golang 中內(nèi)置原始數(shù)據(jù)類型是值類型坡倔。比如int若专、float、string窖杀、bool漓摩。值類型是不可變的,所以對他們進(jìn)行操作入客,一般會返回一個(gè)新創(chuàng)建的值管毙。把這些值傳遞給函數(shù)時(shí),其實(shí)創(chuàng)建的是一個(gè)值的副本桌硫。這一點(diǎn)在python中也是相同的夭咬。作為參數(shù)傳遞給函數(shù)的都是一個(gè)副本。
func main(){
s := "a"
modify(s)
fmt.Println(s)
}
func modify(s string)string{
s = "888888888"
}
因?yàn)樾薷闹殿愋兔傻亩际撬囊粋€(gè)副本卓舵,所以值類型在多線程中是安全的。
1.2 引用類型
切片膀钠、map掏湾、接口、函數(shù)類型以及chan都是引用類型肿嘲。引用類型的修改可以影響到任何引用到它的變量融击。
引用類型之所以可以引用,是因?yàn)槲覀冊趧?chuàng)建引用類型的變量雳窟,其實(shí)是一個(gè)標(biāo)頭值砚嘴,標(biāo)頭值里包含一個(gè)指針,指向底層的數(shù)據(jù)結(jié)構(gòu),在函數(shù)中傳遞引用類型時(shí)际长,傳遞的是這個(gè)標(biāo)頭值的副本耸采,它所指向的底層結(jié)構(gòu)并沒有被復(fù)制傳遞,這也是引用類型傳遞高效的原因工育。
func main(){
ages := map[string]int{"s":12}
modify(ages)
fmt.Println(ages)
}
func modify(m map[string]int){
m["s"] = 777
}
函數(shù) modify 的修改虾宇,會影響 ages 的值。
1.3 結(jié)構(gòu)類型
用 var 關(guān)鍵字聲明一個(gè)結(jié)構(gòu)體類型的變量如绸。
type person struct{
age int
name string
}
// 聲明
var p person
這種 var 聲明的方式计寇,會對結(jié)構(gòu)體 person 里的數(shù)據(jù)類型默認(rèn)初始化第喳,也就是使用它們類型的零值。
函數(shù)傳參是值傳遞,所以對于結(jié)構(gòu)體來說也不例外夷磕,結(jié)構(gòu)體傳遞的是其本身以及里面的值的拷貝撮慨。
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)
}
以上的輸出都是一樣的纹腌,所以我們驗(yàn)證傳遞的是值的副本肖抱。如果上面的例子我們要修改 age 的值可以通過傳遞結(jié)構(gòu)體的指針。
type Person struct {
age int
name string
}
// 我覺得這樣比較好理解瓦侮,*Person 是針對結(jié)構(gòu)體實(shí)例的操作艰赞。(可以想想面向?qū)ο蟮恼Z言,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 指向的是實(shí)例
modify(&jim)
// var h *Person = &jim
fmt.Println(jim)
// * 號在定義時(shí)使用肚吏,&在運(yùn)算時(shí)使用方妖。*這種類型在運(yùn)算時(shí)映射的值是&
}
1.4 自定義類型
type Duration int64
Go是強(qiáng)類型語言,所以 int64 與 Duration不能直接賦值罚攀。
2 變量的聲明
var i int
var s string
變量的聲明可以通過 var 關(guān)鍵字党觅,然后就可以在程序中使用了,var 聲明的變量的值是他們默認(rèn)的零值斋泄。
類型 | 默認(rèn)零值 |
---|---|
int | 0 |
string | "" |
引用類型 | nil |
對引用類型賦值杯瞻。
*int
,是聲明一個(gè)值類型int
的引用類型是己。
*i
,是對值類型的引用類型進(jìn)行運(yùn)算又兵。
func main(){
var i *int
*i = 10
fmt.Println(*i)
}
運(yùn)行時(shí)的 paninc : runtime error: invalid memory address or nil pointer dereference
從這個(gè)提示中可以看出任柜,對于引用類型的變量卒废,我們不光是聲明它,還要為它分配內(nèi)存空間宙地,否則我們的值放在哪去呢摔认?這就是上面錯(cuò)誤提示的原因。
對于值類型的聲明則不需要宅粥,是因?yàn)橐呀?jīng)默認(rèn)幫我們分配好了参袱,值類型都有一個(gè)默認(rèn)零值。
要分配內(nèi)存,就引出來今天的new 和 make抹蚀。
new
func main(){
var i *int
i = new(int)
*i = 10
fmt.Println(*i)
}
看下 new 這個(gè)內(nèi)置的函數(shù)
// 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 是一個(gè)分配內(nèi)存的內(nèi)置函數(shù)剿牺。new的參數(shù)是一個(gè)類型,new的返回值是一個(gè)指針环壤。該指針指向參數(shù)的默認(rèn)零值晒来。
func main(){
// 這個(gè)u 是什么類型,*user, 為啥郑现?因?yàn)閚ew函數(shù)返回的是一個(gè)指向參數(shù)類型默認(rèn)零值的指針啊湃崩。
// 這種方式和上文中 u := user{}, &u 的方式實(shí)際上是一樣的。
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,它返回的永遠(yuǎn)是類型的指針接箫,指向分配類型的內(nèi)存地址攒读。
make
make 也用于內(nèi)存分配,但是和 new 不同辛友,它只用于 chan / map 和 且切片的內(nèi)存創(chuàng)建薄扁,而且它返回的類型就是這三個(gè)類型本身,而不是它們的指針類型瞎领。因?yàn)閏han / map / 切片已經(jīng)是引用類型了泌辫,所以沒必要返回他們的指針了。
看一下 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 初始化九默,分配內(nèi)存震放。
和new 類似,第一個(gè)參數(shù)必須是一個(gè)類型驼修。和 new 不同的是殿遂,new 返回的是參數(shù)類型的指針,指針指向的該參數(shù)的默認(rèn)零值乙各。而make返回的是傳入類型相同墨礁,并不是指針。因?yàn)?slice / map / chan 本身就是引用類型了耳峦。