
大家接觸 Go 語言肯定對 new 跟 make 不陌生,但是什麼時候要使用 new 什麼時候用 make,也許是很多剛入門的開發者比較不懂,本篇就簡單筆記 new 跟 make 的差異及使用時機。
使用 new 關鍵字
Go 提供兩種方式來分配記憶體,一個是 new 另一個是 make,這兩個關鍵字做的事情不同,應用的類型也不同,可能會造成剛入門的朋友一些混淆,但是這兩個關鍵字使用的規則卻很簡單,先來看看如何使用 new 關鍵字。new(T) 宣告會直接拿到儲存位置,並且配置 Zero Value (初始化),也就是數字型態為 0,字串型態就是 ""。底下是範例程式
1
2
3
4
5
6
7
8
9
10
| package main
import "fmt"
func main() {
foo := new(int)
fmt.Println(foo)
fmt.Println(*foo)
fmt.Printf("%#v", foo)
}
|
執行後可以看到底下結果
1
2
3
4
| $ go run main.go
0xc00001a110
0
(*int)(0xc00001a110)
|
上面的做法比較少人用,比較多人用在 struct 上面,由於 new 的特性,直接可以用在 struct 做初始化,底下是範例程式
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
| package main
import (
"bytes"
"fmt"
"sync"
)
type SyncedBuffer struct {
lock sync.Mutex
buffer bytes.Buffer
foo int
bar string
}
func main() {
p := new(SyncedBuffer)
fmt.Println("foo:", p.foo)
fmt.Println("bar:", p.bar)
fmt.Printf("%#v\n", p)
}
|
上面可以看到透過 new 快速的達到初始化,但是有個不方便的地方就是,如果開發者要塞入特定的初始化值,透過 new 是沒辦法做到的,所以大多數的寫法會改成如下,範例連結
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
| package main
import (
"bytes"
"fmt"
"sync"
)
type SyncedBuffer struct {
lock sync.Mutex
buffer bytes.Buffer
foo int
bar string
}
func main() {
p := &SyncedBuffer{
foo: 100,
bar: "foobar",
}
fmt.Println("foo:", p.foo)
fmt.Println("bar:", p.bar)
fmt.Printf("%#v\n", p)
}
|
或者是大部分會寫一個新的 Func 做初始化設定,範例程式如下
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
| package main
import (
"bytes"
"fmt"
"sync"
)
type SyncedBuffer struct {
lock sync.Mutex
buffer bytes.Buffer
foo int
bar string
}
func NewSynced(foo int, bar string) *SyncedBuffer {
return &SyncedBuffer{
foo: foo,
bar: bar,
}
}
func main() {
p := NewSynced(100, "foobar")
fmt.Println("foo:", p.foo)
fmt.Println("bar:", p.bar)
fmt.Printf("%#v\n", p)
}
|
但是 new 如果使用在 slice, map 及 channel 身上的話,其初始的 Value 會是 nil,請看底下範例:
1
2
3
4
5
6
7
8
9
10
11
12
| package main
import (
"fmt"
)
func main() {
p := new(map[string]string)
test := *p
test["foo"] = "bar"
fmt.Println(test)
}
|
底下結果看到 panic
1
2
3
4
5
6
7
| $ go run main.go
panic: assignment to entry in nil map
goroutine 1 [running]:
main.main()
/app/main.go:10 +0x4f
exit status 2
|
初始化 map 拿到的會是 nil,故通常在宣告 slice, map 及 channel 則會使用 Go 提供的另一個宣告方式 make。
使用 make 關鍵字
make 與 new 不同的地方在於,new 回傳指標,而 make 不是,make 通常只用於在宣告三個地方,分別是 slice, map 及 channel,如果真的想要拿到指標,建議還是用 new 方式。底下拿 map 當作範例
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
| package main
import "fmt"
func main() {
var p *map[string]string
// new
p = new(map[string]string)
*p = map[string]string{
"bar": "foo",
}
people := *p
people["foo"] = "bar"
fmt.Println(people)
fmt.Println(p)
// make
foobar := make(map[string]string)
foobar["foo"] = "bar"
foobar["bar"] = "foo"
fmt.Println(foobar)
}
|
上面例子可以看到 p 宣告為 map 指標,new 初始化 map 後則需要獨立寫成 map[string]string{},才可以正常運作,如果是透過 make 方式就可以快速宣告完成。通常是這樣,我自己在開發,幾乎很少用到 new,反到是在宣告 slice, map 及 channel 時一定會使用到 make。記住,用 make 回傳的不會是指標,真的要拿到指標,請使用 new 的方式,但是程式碼就會變得比較複雜些。
心得
總結底下 make 跟 new 的區別
make 能夠分配並且初始化所需要的記憶體空間跟結構,而 new 只能回傳指標位置make 只能用在三種類型 slice, map 及 channelmake 可以初始化上述三種格式的長度跟容量以便提供效率跟減少開銷
參考文章
See also