背景介紹
學(xué)習(xí)Go的時候医咨,遇到了 make
和 new
的區(qū)別問題芥喇。網(wǎng)上查看了很多文檔靴迫,大體都是4個區(qū)別(見下文),但是缺少更詳細(xì)的說明和更豐富的代碼樣例。比如:
- zero value of the type(零值)
- 聲明(declare)霜第、分配內(nèi)存空間(allocate)、初始化(initialize)户辞、賦值泌类,各自的作用
- 用 new 去給slice、map底燎、chan進(jìn)行內(nèi)存分配刃榨,會是什么結(jié)果?
下面將先介紹官方文檔里make
和new
的定義双仍,然后講解它們的不同點(diǎn)枢希,最后
Talk is cheap. Show me the code.
make
func make(t Type, size ...IntegerType) Type
The make built-in function allocates and initializes an object of type slice, map, or chan (only). Like new, the first 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:
make() 方法只給類型slice、map朱沃、chan分配內(nèi)存空間苞轿,并初始化一個對象。它的第一個參數(shù)是一個類型逗物,第二個參數(shù)是一個可變長參數(shù)搬卒,返回的是這個類型本身。
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. For example, make([]int, 0, 10) allocates an underlying array
of size 10 and returns a slice of length 0 and capacity 10 that is
backed by this underlying array.
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.
- 對于 slice(切片) 類型翎卓,第一個 size 表示的是切片的長度契邀,第二個 size 表示的是切片的容量。如果只給了一個size參數(shù)失暴,則切片的容量等于其長度坯门;
- 對于 map(字典)類型,只有一個size锐帜,表示給map分配一個多大的空間田盈,如果忽略這個參數(shù),則自動分配一個小空間(一般不需要這個參數(shù)缴阎,因?yàn)闀ap會自動擴(kuò)展)允瞧;
- 對于 chan(管道)類型,只有一個size蛮拔,表示管道的緩沖區(qū)述暂,無參數(shù)就是無緩沖區(qū)。
new
func new(Type) *Type
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.
new() 方法分配內(nèi)存空間建炫。它的第一個參數(shù)是一個類型畦韭,然后返回指向該類型的一個內(nèi)存空間的指針。
make vs. new
make | new | |
---|---|---|
1 | 只用于slice肛跌、map艺配、chan | 任意結(jié)構(gòu)察郁,包括slice、map转唉、chan |
2 | 傳入的參數(shù)包括一個類型皮钠,還有一個size | 只接收一個類型參數(shù),沒有size |
3 | 返回的是類型本身 | 返回的是類型的指針 |
4 | 分配內(nèi)存空間赠法,進(jìn)行初始化 | 分配內(nèi)存空間麦轰,不進(jìn)行初始化 |
代碼樣例
上面總結(jié)了make
和new
的一些不同點(diǎn),下面將針對這些不同點(diǎn)砖织,以及最上面提到的一些問題款侵,進(jìn)行代碼實(shí)驗(yàn)。
zero value of the type(零值)
零值是指創(chuàng)建一個變量后(也就是分配了內(nèi)存空間之后)侧纯,并沒有給該變量一個初始值新锈,那么Go會自動用零值對該變量進(jìn)行初始化。
Type | 零值 |
---|---|
int, int32, int64 | 0 |
float32, float64 | 0.0 |
string | "" |
bool | false |
pointer, function, interface, slice, channel, map | nil |
數(shù)組, struct | 遞歸初始化為零值 |
注意:數(shù)組的零值是遞歸初始化數(shù)組元素類型的零值眶熬,而切片的零值是nil
Go的零值在這篇文章中被吐槽了壕鹉。
type TT struct {
A string
B int64
C struct {
Cd string
Ce string
Cf []float64
}
G bool
H float32
}
func main() {
var i *[]int
i = new([]int)
fmt.Println(i, len(*i), cap(*i)) // &[] 0 0
var istring *[]string
istring = new([]string)
fmt.Println(istring, len(*istring), cap(*istring)) // &[] 0 0
var tt = new(TT)
fmt.Println(tt) // &{ 0 { []} false 0}
}
declare、allocate聋涨、initialize、賦值
// 1. 聲明 declare
var i *int
var tt *TT
// 2. 分配內(nèi)存 allocate
i = new(int)
tt = new(TT)
// 聲明 + 分配內(nèi)存
var i = new(int)
var i *int = new(int)
i := new(int)
var tt = new(TT)
var tt *TT = new(TT)
tt := new(TT)
tt := &TT{}
// 3. 初始化 initialize
// 初始化就是第一次賦值
var arr = make([]int, 3) // arr => [0, 0, 0]
// 4. 賦值
i = 4
tt = TT{A: "a", B: 12, ...}
// 聲明 + 賦值
i := TT{A: "a", B: 12, ...}
用new去給 slice负乡、map牍白、chan進(jìn)行內(nèi)存分配
用new對map、chan進(jìn)行內(nèi)存分配抖棘,效果和make差不多茂腥。
但是對slice進(jìn)行內(nèi)存分配時:
var i *[]int
i = new([]int)
fmt.Println(i, len(*i), cap(*i)) // &[] 0 0
j := make([]int, 3)
fmt.Println(j, len(j), cap(j)) // [0 0 0] 3 3
本來 slice的零值是 nil,但是 make 會做一次初始化切省,就變成了 [0 0 0]了最岗。
而且 make 的第二個參數(shù)是不可以省略的,但是可以設(shè)置為0朝捆,這樣的話:
j := make([]int, 0)
fmt.Println(j, len(j), cap(j)) // [] 0 0
總結(jié)
-
make
只用于 slice般渡、map、channel 的內(nèi)存分配芙盘,new
用于各種類型驯用; -
new
返回指針 -
make
比new
多一些參數(shù)
相對而言,make
的功能和使用場景都是很明確的儒老,而 new
的話:
- 如果是基礎(chǔ)類型蝴乔,如int,string,不需要使用 new
- 如果是基礎(chǔ)類型的指針,在使用該指針(打印花枫,賦值等)前蚌铜,必須先分配內(nèi)存空間睛蛛,就可以用到 new 了捉捅。但是如果直接給從一個變量那里取地址賦值給它厅目,就又不需要 new 了凭迹。
var pInt *int
pInt = new(int)
等價于
var i int = 10
var pInt *int = &i
fmt.Println(*pInt) // 10
- 如果是數(shù)組曙聂、切片晦炊、結(jié)構(gòu)體、map類型的指針宁脊,也是類似:
var s *[3]int
s = new([3]int)
等價于
var s *[3]int = &[3]int{}
var s *[]int
s = new([]int)
等價于
var s *[]int = &[]int{}
var t *TT
t = new(TT)
等價于
var t *TT = new(TT)
等價于
var t *TT = &TT{}
var mp = new(map[string]string)
等價于
mp := map[string]string{}
注:chan 不能這么玩(也可能是我沒找對姿勢)
那么問題來了断国,為啥要有 new
呢?
如果有了解的朋友榆苞,希望能指點(diǎn)一下稳衬,多謝!
PS. 有一個思路是坐漏,多看看源碼薄疚,看看哪里用到了new,以及是怎么使用new的赊琳,或許能對new有更深入的了解街夭。
參考文檔
- http://docs.studygolang.com/doc/effective_go.html#allocation_new
- http://docs.studygolang.com/doc/effective_go.html#allocation_make
- http://docs.studygolang.com/doc/faq#new_and_make
- https://studygolang.com/articles/3496
- http://www.jb51.net/article/126703.htm
-
https://github.com/astaxie/build-web-application-with-golang/blob/master/zh/02.2.md
(這個文檔也對 make 和 new 進(jìn)行了對比,也解釋了零值)