区分大小写。
new的要求传入一个类型,然后申请一个该类型大小的内存空间,并会初始化为对应的零值,返回指向该内存空间的一个指针。
new是用来分配内存的:
比如我们进行下面这段代码,新建一个指针却不用new给他分配内存:
func main() {
var p *int
*p = 2
fmt.Println(*p)
}
panic: runtime error: invalid memory address or nil pointer dereference
[signal 0xc0000005 code=0x1 addr=0x0 pc=0x40b4f6]
就会panic,这是一个空指针。
如果我们给指针用new初始化一块内存:
func main() {
var p = new(int)
*p = 2
fmt.Println(*p)
}
输出结果就是:
0
2
都是切片、Map、通道Channel都是引用类型,其存储结构比较复杂,不是用new()分配一块内存,并简单的分配零值就行了。所以要用make来分配和初始化引用类型。
//Channel
ch := make(chan string)
go func() {
fmt.Println(<-ch)
}()
ch <- "hello"
close(ch)
//Map
m := make(map[string]int)
m["Hello"] = 2
fmt.Println(m)
//Slice
slice := make([]int, 2, 5)
slice = append(slice, 1)
fmt.Println(slice)
下面是运行结果:
hello
map[Hello:2]
[0 0 1]
总的来说:
不同类型,其零值是不同的,我们可以通过下面的这个例子了解一下:
package main
import "fmt"
func main() {
type stru struct {
s string
}
var ap *[3]int
var ip *int
var sp *string
var tp *stru
ap = new([3]int)
fmt.Println(*ap) //[0 0 0]
ip = new(int)
fmt.Println(*ip) // 0
sp = new(string)
fmt.Println(*sp) //
tp = new(stru)
fmt.Println(*tp) //{}
var a [3]int
var i int
var s string
var t stru
fmt.Println(a, i, s, t) //[0 0 0] 0 {}
}
下面我们来详细看一下复合类型的零值,先来数组:
func main() {
var array [5]int
fmt.Printf("array: %p %#v \n", &array, array)
var array_p = new([5]int)
fmt.Printf("array_p: %p %#v \n", &array_p, array_p)
(*array_p)[3] = 8
fmt.Printf("array_p: %p %#v \n", &array_p, array_p)
}
我们先来看看运行结果:
array: 0xc00000a3f0 [5]int{0, 0, 0, 0, 0}
array_p: 0xc000006030 &[5]int{0, 0, 0, 0, 0}
array_p: 0xc000006030 &[5]int{0, 0, 0, 8, 0}
再来看看切片:
var slice *[]int
fmt.Printf("a: %p %#v \n", &slice, slice)
sliceP := new([]int)
fmt.Printf("av: %p %#v \n", &sliceP, sliceP)
(*sliceP)[2] = 8
fmt.Printf("av: %p %#v \n", &sliceP, sliceP)
输出结果如下:
a: 0xc000006028 (*[]int)(nil)
av: 0xc000006038 &[]int(nil)
panic: runtime error: index out of range [2] with length 0
趁热打铁,再来看看Map的:
package main
import "fmt"
func main() {
var m map[string]string
fmt.Printf("m: %p %#v \n", &m, m)
mP := new(map[string]string)
fmt.Printf("mv: %p %#v \n", &mP, mP)
(*mP)["hello"] = "hello"
fmt.Printf("mv: %p %#v \n", &mP, mP)
}
运行结果如下:
m: 0xc0000ce018 map[string]string(nil)
mv: 0xc0000ce028 &map[string]string(nil)
panic: assignment to entry in nil map
还有通道channel:
cP := new(chan string)
fmt.Printf("cv: %p %#v \n", &cP, cP)
cP <- "hello"
invalid operation: cannot send to non-channel cP (variable of type *chan string)
为什么会报错呢?
这是因为切片、Map、通道Channel都是引用类型,其存储结构比较复杂,不是分配一块内存,并简单的给相关属性分配零值就行了(长度、指针…)。所以要用make来分配和初始化引用类型。
var s []int
if s == nil {
fmt.Println("equal")
} else {
fmt.Println("not equal")
}
//输出结果为: equal
看一看下面这段代码:
package main
import "fmt"
func main() {
var slice = []int{1, 2, 3, 4, 5}
for i, v := range slice {
fmt.Printf("%d,%#v\n", i, &v)
}
}
运行结果如下:
0,(*int)(0xc000016098)
1,(*int)(0xc000016098)
2,(*int)(0xc000016098)
3,(*int)(0xc000016098)
4,(*int)(0xc000016098)
我们再来看看下面:
func main() {
var slice = []int{1, 2, 3, 4, 5}
fmt.Printf("%#v%#v%#v%#v%#v\n", &slice[0], &slice[1], &slice[2], &slice[3], &slice[4])
for i, v := range slice {
fmt.Printf("%#v,%#v\n", &i, &v)
}
fmt.Printf("%#v%#v%#v%#v%#v\n", &slice[0], &slice[1], &slice[2], &slice[3], &slice[4])
}
(*int)(0xc00000a3f0)(*int)(0xc00000a3f8)(*int)(0xc00000a400)(*int)(0xc00000a408)(*int)(0xc00000a410)
(*int)(0xc0000160c0),(*int)(0xc0000160c8)
(*int)(0xc0000160c0),(*int)(0xc0000160c8)
(*int)(0xc0000160c0),(*int)(0xc0000160c8)
(*int)(0xc0000160c0),(*int)(0xc0000160c8)
(*int)(0xc0000160c0),(*int)(0xc0000160c8)
(*int)(0xc00000a3f0)(*int)(0xc00000a3f8)(*int)(0xc00000a400)(*int)(0xc00000a408)(*int)(0xc00000a410)
可以得知,for range的时候,不是读取slice,然后直接输出里面的值。而是在遍历时开辟两块内存以值覆盖的方式将遍历得到的数据放入。由于这个特性,我们如果在for range里面开协程,不能直接把i、v的地址传给协程,也不能直接在for range内保存i,v的地址。如果遍历一个指针切片:
var slice = []int{1, 2, 3, 4, 5}
var slice_p = make([]*int, 0, 5)
for i, v := range slice {
fmt.Println(i, v)
slice_p = append(slice_p, &v)
}
fmt.Println(slice_p)
for i, v := range slice_p {
fmt.Println(i, *v)
fmt.Println(v)
}
那么结果将会是:
0 1
1 2
2 3
3 4
4 5
[0xc000016098 0xc000016098 0xc000016098 0xc000016098 0xc000016098]
0 5
0xc000016098
1 5
0xc000016098
2 5
0xc000016098
3 5
0xc000016098
4 5
0xc000016098
那么解决办法是什么?使用一个临时变量即可。
var slice = []int{1, 2, 3, 4, 5}
var slice_p = make([]*int, 0, 5)
for _, v := range slice {
vv := v
slice_p = append(slice_p, &vv)
}
fmt.Println(slice_p)
for i, v := range slice_p {
fmt.Println(i, *v)
fmt.Println(v)
}
[0xc0000aa058 0xc0000aa070 0xc0000aa078 0xc0000aa080 0xc0000aa088]
0 1
0xc0000aa058
1 2
0xc0000aa070
2 3
0xc0000aa078
3 4
0xc0000aa080
4 5
0xc0000aa088
另外我们也可以发现,当切片是指针切片时,切片里面是什么,for range遍历时还是切片里面的地址,不需要另外开辟。
根据Golang官方文档描述,defer就像一个LIFO的栈,每次执行defer语句,都会将函数”压栈“,函数参数也会被保存下来;如果外层函数(非代码块)退出,最后的defer语句就会执行,也就是栈顶的函数或方法会被执行。
一条return语句,其实不是一条原子指令,其大概可以分为三条指令:
package main
import (
"fmt"
)
func main() {
fmt.Println("return:", banana())
}
func banana() (i int) {
defer func() {
i++
fmt.Println("defer 2:", i)
}()
defer func() {
i++
fmt.Println("defer 1:", i)
}()
return i
}
defer 1: 1
defer 2: 2
return: 2
这是有名返回值的情况,接下我们来看一看匿名返回值的情况:
package main
import (
"fmt"
)
func main() {
fmt.Println("return:", apple())
}
func apple() int {
var i int
defer func() {
i++
fmt.Println("defer 2:", i)
}()
defer func() {
i++
fmt.Println("defer 1:", i)
}()
return i
}
defer 1: 1
defer 2: 2
return: 0
上面这两段代码说明了:defer语句只能访问有名返回值,不能直接访问匿名返回值。
但是如果是下面这种情况:
package main
import (
"fmt"
)
func main() {
fmt.Println("return:", banana())
}
func banana() (i int) {
defer func(i int) {
i++
fmt.Println("defer 2:", i)
}(i)
defer func(i int) {
i++
fmt.Println("defer 1:", i)
}(i)
return i
}
输出结果就为:
defer 1: 1
defer 2: 1
return: 0
这是因为传递给defer后面的匿名函数的是形参的一个复制值,不会影响实参i。