当我们想要在 Go 语言中初始化一个结构时,可能会用到两个不同的关键字 — make
和 new
。因为它们的功能相似,所以初学者可能会对这两个关键字的作用感到困惑,但是它们两者能够初始化的却有较大的不同。
make
的作用是初始化内置的数据结构,包括且仅包括 slice、map 和 Channelnew
的作用是根据传入的类型分配一片内存空间,并返回指向这片内存空间的指针
make()
slice := make([]int, 0, 100)
hash := make(map[int]bool, 10)
ch := make(chan int, 5)
slice
是一个包含data
、cap
和len
的结构体reflect.SliceHeader
;hash
是一个指向runtime.hmap
结构体的指针;ch
是一个指向runtime.hchan
结构体的指针;
Example
Slices can be created with the built-in make
function; this is how you create dynamically-sized arrays.
The make
function allocates a zeroed array and returns a slice that refers to that array:
a := make([]int, 5) // len(a)=5
To specify a capacity, pass a third argument to make
:
b := make([]int, 0, 5) // len(b)=0, cap(b)=5
b = b[:cap(b)] // len(b)=5, cap(b)=5
b = b[1:] // len(b)=4, cap(b)=4
package main
import "fmt"
func main() {
a := make([]int, 5)
printSlice("a", a)
b := make([]int, 0, 5)
printSlice("b", b)
c := b[:2]
printSlice("c", c)
d := c[2:5]
printSlice("d", d)
}
func printSlice(s string, x []int) {
fmt.Printf("%s len=%d cap=%d %v\n",
s, len(x), cap(x), x)
}
Description
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.
官方的这个解释可能会产生 confusion,准确的来说:
slice := make([]int, 0, 100)
hash := make(map[int]bool, 10)
ch := make(chan int, 5)
slice
是一个包含data
、cap
和len
的结构体reflect.SliceHeader
;hash
是一个指向runtime.hmap
结构体的指针;ch
是一个指向runtime.hchan
结构体的指针;
说明:
- 对于
hash := make(map[int]bool, 10)
而言,make
返回的实际上就是一个指向runtime.hmap
的指针; - 即
hash
是一个 指向runtime.hmap
的指针,同时hash
也是map[int]bool
- 即
map[int]bool
等价于一个 指向runtime.hmap
的指针 make's return type is the same as the type of its argument, not a pointer to it
想说的其实是:hash
也是map[int]bool
,而不是*map[int]bool
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. 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 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.
分析
分析1 - slice
分析2 - map
// makemap implements Go map creation for make(map[k]v, hint).
func makemap(t *maptype, hint int, h *hmap) *hmap {
As you see, the type of the value returned from runtime.makemap
is a pointer to a runtime.hmap
structure.
Maps, like channels, but unlike slices, are just pointers to runtime types. As you saw above, a map is just a pointer to a runtime.hmap
structure.
new()
new(T)
allocates zeroed storage for a new item of type T
and returns its address, a value of type *T
. In Go terminology, it returns a pointer to a newly allocated zero value of type T
.
相比与复杂的 make
关键字,new
的功能就很简单了,它接收一个类型作为参数,并返回一个指向该类型的指针:
i := new(int)
// 等价于
var v int
i := &v
//////////////////////////////
var p *[]int = new([]int)
// 等价于
var v []int
p := &v
//////////////////////////////
var p *[]int = new(map[int]int)
// 等价于
var v map[int]int
p := &v
分析1 - int
package main
import (
"fmt"
"sync"
)
func main() {
i := new(int)
var v int
i = &v
v = 5
fmt.Println(i)
fmt.Println(&v)
fmt.Println(&i)
}
// Output
0xc000134010
0xc000134010
0xc00012e018
分析2 - slice
var p *[]int = new([]int)
d := (*reflect.SliceHeader)(unsafe.Pointer(p))
fmt.Println(*d)
*p = append(*p, 1)
d = (*reflect.SliceHeader)(unsafe.Pointer(p))
fmt.Println(*d)
fmt.Println(*p)
fmt.Printf("%p\n", p)
fmt.Printf("%p\n", &p)
*p = make([]int, 5)
d = (*reflect.SliceHeader)(unsafe.Pointer(p))
fmt.Println(*d)
fmt.Println(*p)
fmt.Printf("%p\n", p)
fmt.Printf("%p\n", &p)
// output
{0 0 0}
{824633819288 1 1}
[1]
0xc00000c060
0xc00000e028
{824633835760 5 5}
[0 0 0 0 0]
0xc00000c060
0xc00000e028
分析3 - map
我们知道,如果
var v map[int]int
v[0] = 0
会报错:
panic: assignment to entry in nil map
goroutine 1 [running]:
main.main()
/Users/shiwei/SW/GoPlayground/sw.go:11 +0x35
而由于
var p *[]int = new(map[int]int)
// 等价于
var v map[int]int
p := &v
因此
(*p)[0] = 0
同样也会报错:
panic: assignment to entry in nil map
goroutine 1 [running]:
main.main()
/Users/shiwei/SW/GoPlayground/sw.go:11 +0x35
Comparison Between make
and new
The distinction is a common early point of confusion but seems to quickly become natural. The basic distinction is that new(T)
returns a *T
, a pointer that Go programs can dereference implicitly (the black pointers in the diagrams), while make(T,
args)
returns an ordinary T
, not a pointer. Often that T
has inside it some implicit pointers (the gray pointers in the diagrams). New
returns a pointer to zeroed memory, while make
returns a complex structure.
There is a way to unify these two, but it would be a significant break from the C and C++ tradition: define make(*T)
to return a pointer to a newly allocated T
, so that the current new(Point)
would be written make(*Point)
.
append()
可以通过直接使用append()
来向数组中添加元素,此后数组的 len 和 cap 都会自动变大,比如:
func main() {
var s []int
printSlice(s)
// append works on nil slices.
s = append(s, 0)
printSlice(s)
}
func printSlice(s []int) {
fmt.Printf("len=%d cap=%d %v\n", len(s), cap(s), s)
}
//output
len=0 cap=0 []
len=1 cap=1 [0]
It is common to append new elements to a slice, and so Go provides a built-in append
function. The documentation of the built-in package describes append
.
func append(s []T, vs ...T) []T
The first parameter s
of append
is a slice of type T
, and the rest are T
values to append to the slice.
The resulting value of append
is a slice containing all the elements of the original slice plus the provided values.
If the backing array of s
is too small to fit all the given values a bigger array will be allocated. The returned slice will point to the newly allocated array.
(To learn more about slices, read the Slices: usage and internals article.)
package main
import "fmt"
func main() {
var s []int
printSlice(s)
// append works on nil slices.
s = append(s, 0)
printSlice(s)
// The slice grows as needed.
s = append(s, 1)
printSlice(s)
// We can add more than one element at a time.
s = append(s, 2, 3, 4)
printSlice(s)
}
func printSlice(s []int) {
fmt.Printf("len=%d cap=%d %v\n", len(s), cap(s), s)
}
//output
len=0 cap=0 []
len=1 cap=2 [0]
len=2 cap=2 [0 1]
len=5 cap=8 [0 1 2 3 4]
copy()
Reference
-
https://groups.google.com/g/golang-nuts/c/SjuhSYDITm4/m/jnrp7rRxDQAJ
-
https://draveness.me/golang/docs/part2-foundation/ch05-keyword/golang-make-and-new/
-
https://stackoverflow.com/questions/40680981/are-maps-passed-by-value-or-by-reference-in-go