Golang只有二十五個(gè)系統(tǒng)保留關(guān)鍵字,二十幾個(gè)系統(tǒng)內(nèi)置函數(shù),加起來(lái)只有五十個(gè)左右需要記住的關(guān)鍵字,縱觀編程宇宙,無(wú)人能出其右。其中還有一些保留關(guān)鍵字屬于“錦上添花”,什么叫錦上添花?就是從表面上看,就算沒(méi)有,也無(wú)傷大雅,不影響業(yè)務(wù)或者邏輯的實(shí)現(xiàn),比如lambda表達(dá)式之類(lèi),沒(méi)有也無(wú)所謂,但在初始化數(shù)據(jù)結(jié)構(gòu)的時(shí)候,我們無(wú)法避免地,會(huì)談及兩個(gè)內(nèi)置函數(shù):New和Make。
網(wǎng)站建設(shè)哪家好,找創(chuàng)新互聯(lián)公司!專(zhuān)注于網(wǎng)頁(yè)設(shè)計(jì)、網(wǎng)站建設(shè)、微信開(kāi)發(fā)、成都小程序開(kāi)發(fā)、集團(tuán)企業(yè)網(wǎng)站建設(shè)等服務(wù)項(xiàng)目。為回饋新老客戶創(chuàng)新互聯(lián)還提供了桐城免費(fèi)建站歡迎大家使用!
假設(shè)聲明一個(gè)變量:
package main
import "fmt"
func main() {
var a string
fmt.Println(a)
fmt.Println(&a)
}
系統(tǒng)返回:
0x
這里我們使用var關(guān)鍵字聲明了一個(gè)數(shù)據(jù)類(lèi)型是字符串的變量a,然后沒(méi)有做任何賦值操作,于是a的默認(rèn)值變?yōu)橄到y(tǒng)的零值,也就是空,a的內(nèi)存地址已經(jīng)做好了指向,以便存儲(chǔ)a將來(lái)的值。
下面開(kāi)始賦值:
package main
import "fmt"
func main() {
var a string
a = "ok"
fmt.Println(a)
fmt.Println(&a)
}
系統(tǒng)返回:
ok
0x
可以看到a的值和內(nèi)存地址都發(fā)生了改變,整個(gè)初始化過(guò)程,我們并沒(méi)有使用new函數(shù)
下面我們把數(shù)據(jù)類(lèi)型換成指針:
package main
import "fmt"
func main() {
var a *string
fmt.Println(a)
fmt.Println(&a)
}
系統(tǒng)返回:
0xa4018
可以看到由于數(shù)據(jù)類(lèi)型換成了指針,零值變成了nil
接著像字符串?dāng)?shù)據(jù)類(lèi)型一樣進(jìn)行賦值操作:
package main
import "fmt"
func main() {
var a *string
*a = "ok"
fmt.Println(*a)
fmt.Println(&a)
}
系統(tǒng)返回:
panic: runtime error: invalid memory address or nil pointer dereference
是的,空指針異常,為什么?因?yàn)橹羔樖且粋€(gè)引用類(lèi)型,對(duì)于引用類(lèi)型來(lái)說(shuō),系統(tǒng)不僅需要我們要聲明它,還要為它分配內(nèi)存空間,否則我們賦值的變量就沒(méi)地方放,這里系統(tǒng)沒(méi)法為nil分配內(nèi)存空間,所以沒(méi)有內(nèi)存空間就沒(méi)法賦值。
而像字符串這種值類(lèi)型就不會(huì)有這種煩惱,因?yàn)橹殿?lèi)型的聲明不需要我們分配內(nèi)存空間,系統(tǒng)會(huì)默認(rèn)為其分配,為什么?因?yàn)橹殿?lèi)型的零值是一個(gè)具體的值,而不是nil,比如整形的零值是0,字符串的零值是空,空不是nil,所以就算是空,也可以賦值。
那引用類(lèi)型就沒(méi)法賦值了?
package main
import "fmt"
func main() {
var a *string
a = new(string)
*a = "ok"
fmt.Println(*a)
fmt.Println(&a)
}
系統(tǒng)返回:
ok
0x
這里我們使用了new函數(shù),它正是用于分配內(nèi)存,第一個(gè)參數(shù)接收一個(gè)類(lèi)型而不是一個(gè)值,函數(shù)返回一個(gè)指向該類(lèi)型內(nèi)存地址的指針,同時(shí)把分配的內(nèi)存置為該類(lèi)型的零值。
換句話說(shuō),new函數(shù)可以幫我們做之前系統(tǒng)自動(dòng)為值類(lèi)型數(shù)據(jù)類(lèi)型做的事。
當(dāng)然,new函數(shù)不僅僅能夠?yàn)橄到y(tǒng)的基本類(lèi)型的引用分配內(nèi)存,也可以為自定義數(shù)據(jù)類(lèi)型的引用分配內(nèi)存:
package main
package main
import "fmt"
func main() {
type Human struct {
name string
age int
}
var human *Human
human = new(Human)
human.name = "張三"
fmt.Println(*human)
fmt.Println(&human)
}
系統(tǒng)返回:
{張三 0}
0xc018
這里我們自定義了一種人類(lèi)的結(jié)構(gòu)體類(lèi)型,然后聲明該類(lèi)型的指針,由于指針是引用類(lèi)型,所以必須使用new函數(shù)為其分配內(nèi)存,然后,才能對(duì)該引用的結(jié)構(gòu)體屬性進(jìn)行賦值。
說(shuō)白了,new函數(shù)就是為了解決引用類(lèi)型的零值問(wèn)題,nil算不上是真正意義上的零值,所以需要new函數(shù)為其“仙人指路”。
make函數(shù)從功能層面上講,和new函數(shù)是一致的,也是用于內(nèi)存的分配,但它只能為切片slice,字典map以及通道channel分配內(nèi)存,并返回一個(gè)初始化的值。
這顯然有些矛盾了,既然已經(jīng)有了new函數(shù),并且new函數(shù)可以為引用數(shù)據(jù)類(lèi)型分配內(nèi)存,而切片、字典和通道不也是引用類(lèi)型嗎?
大家既然都是引用類(lèi)型,為什么不直接使用new函數(shù)呢?
package main
import "fmt"
func main() {
a := *new([]int)
fmt.Printf("%T, %v\n", a, a == nil)
b := *new(map[string]int)
fmt.Printf("%T, %v\n", b, b == nil)
c := *new(chan int)
fmt.Printf("%T, %v\n", c, c == nil)
}
程序返回:
[]int, true
map[string]int, true
chan int, true
雖然new函數(shù)也可以為切片、字典和通道分配內(nèi)存,但沒(méi)有意義,因?yàn)樗峙湟院蟮牡刂愤€是nil:
package main
import "fmt"
func main() {
a := *new([]int)
fmt.Printf("%T, %v\n", a, a == nil)
b := *new(map[string]int)
fmt.Printf("%T, %v\n", b, b == nil)
c := *new(chan int)
fmt.Printf("%T, %v\n", c, c == nil)
b["123"] = 123
fmt.Println(b)
}
這里使用new函數(shù)初始化以后,為字典變量b賦值,系統(tǒng)報(bào)錯(cuò):
panic: assignment to entry in nil map
提示無(wú)法為nil的字典賦值,所以這就是make函數(shù)存在的意義:
package main
import "fmt"
func main() {
a := *new([]int)
fmt.Printf("%T, %v\n", a, a == nil)
b := make(map[string]int)
fmt.Printf("%T, %v\n", b, b == nil)
c := *new(chan int)
fmt.Printf("%T, %v\n", c, c == nil)
b["123"] = 123
fmt.Println(b)
}
這里字典b使用make函數(shù)進(jìn)行初始化之后,就可以為b進(jìn)行賦值了。
程序返回:
[]int, true
map[string]int, false
chan int, true
map[123:123]
這也是make和new的區(qū)別,make可以為這三種類(lèi)型分配內(nèi)存,并且設(shè)置好其對(duì)應(yīng)基本數(shù)據(jù)類(lèi)型的零值,所以只要記住切片、字典和通道聲明后需要賦值的時(shí)候,需要使用make函數(shù)為其先分配內(nèi)存空間。
有人會(huì)說(shuō),為什么非得糾結(jié)分配內(nèi)存的問(wèn)題?用海象操作符不就可以直接賦值了嗎?
// example1.go
package main
import "fmt"
func main() {
a := map[int]string{}
fmt.Printf("%T, %v\n", a, a == nil)
a[1] = "ok"
fmt.Println(a)
}
程序返回:
map[int]string, false
map[1:ok]
沒(méi)錯(cuò),就算沒(méi)用make函數(shù),我們也可以“人為”的給字典分配內(nèi)存,因?yàn)楹O蟛僮鞣鋵?shí)是聲明加賦值的連貫操作,后面的空字典就是在為變量申請(qǐng)內(nèi)存空間。
但為什么系統(tǒng)還要保留new和make函數(shù)呢?事實(shí)上,這是一個(gè)分配內(nèi)存的時(shí)機(jī)問(wèn)題,聲明之后,沒(méi)有任何規(guī)定必須要立刻賦值,賦值后的變量會(huì)消耗系統(tǒng)的內(nèi)存資源,所以聲明以后并不分配內(nèi)存,而是在適當(dāng)?shù)臅r(shí)候再分配,這也是new和make的意義所在,所謂千石之弓,引而不發(fā),就是這個(gè)道理。
new和make函數(shù)都可以為引用類(lèi)型分配內(nèi)存,起到“仙人指路”的作用,變量聲明后“引而不發(fā)”就是使用它們的時(shí)機(jī),make函數(shù)作用于創(chuàng)建 slice、map 和 channel 等內(nèi)置的數(shù)據(jù)結(jié)構(gòu),而 new函數(shù)作用是為類(lèi)型申請(qǐng)內(nèi)存空間,并返回指向內(nèi)存地址的指針。