【Golang】内置函数

Posted by 西维蜀黍 on 2020-03-15, Last Modified on 2021-10-16

当我们想要在 Go 语言中初始化一个结构时,可能会用到两个不同的关键字 — makenew。因为它们的功能相似,所以初学者可能会对这两个关键字的作用感到困惑,但是它们两者能够初始化的却有较大的不同。

  • make 的作用是初始化内置的数据结构,包括且仅包括 slice、map 和 Channel
  • new 的作用是根据传入的类型分配一片内存空间,并返回指向这片内存空间的指针

make()

slice := make([]int, 0, 100)
hash := make(map[int]bool, 10)
ch := make(chan int, 5)
  1. slice 是一个包含 datacaplen 的结构体 reflect.SliceHeader
  2. hash 是一个指向 runtime.hmap 结构体的指针;
  3. 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)
  1. slice 是一个包含 datacaplen 的结构体 reflect.SliceHeader
  2. hash 是一个指向 runtime.hmap 结构体的指针;
  3. 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