new 分配;make 初始化
new(T) 返回 *T 指向一个零值 T
make(T) 返回初始化后的 T,只能用于 slice,map,channel。
先整明白go里面的几大变量“**类型**”(不严谨,只是个人在使用的时候常用到的结构的一个划分)
(1) 值类型: 基本类型 + struct
(2) 引用类型:主要是map, slice,chan 这三个引用(make创建内存的)
(3)指针类型:那就多了, *int64, *struct…
值类型:分配内存空间,并赋该类型的零值
引用类型:不会分配内存,默认就是nil
例子:
//值类型
var i int
fmt.Println(i) //i=0
//引用类型
var j *int
fmt.Println(j)
*j = 10 //invalid memory address or nil pointer dereference
//也就是说,空指针还没有内存分配,是不能使用的。
func new(Type) *Type
new(T) 返回 *T 指向一个零值 T 返回指针类型(一般不用)
值类型和引用类型:分配内存空间,并赋该类型的零值,返回一个指向该类型内存地址的指针
实际在工程使用中,通常是直接声明指针使用,不需要new操作。
例子:
var j *int
j = new(int) // 让j里面的内容指向一块分配好的内存地址,地址里面设置int的零值:0
fmt.Println(j)
fmt.Println(*j)
*j = 10
fmt.Println(*j)
func make(t Type, size ...IntegerType) Type
make(T) 返回初始化后的 T 返回引用类型
make用于map, slice,chan 的内存创建,因为他们三个是引用类型,直接返回这三个类型本身(引用类型),不赋零值
例子:
var c chan int //声明管道类型变量c,此时c还是nil,不可用;
fmt.Printf("%#v \n",c) //(chan int)(nil)
c = make(chan int)
fmt.Printf("%#v", c) //(chan int)(0xc000062060)
声明管道类型变量c,此时c还是nil,不可用;
通过make来分配内存并初始化,c就获得了内存可以使用了。
make(T, args) 返回的值通过函数传递参数之后可以直接修改,即 map,slice,channel 通过函数穿参之后在函数内部修改将影响函数外部的值。
func modifySlice(s []int) {
s[0] = 1
}
s2 := make([]int, 3)
fmt.Printf("%#v", s2) //[]int{0, 0, 0}
modifySlice(s2)
fmt.Printf("%#v", s2) //[]int{1, 0, 0}
make(T, args) 返回的是引用类型,在函数内部可以直接更改原始值,对 map 和 channel 也是如此。
func modifyMap(m map[int]string) {
m[0] = "string"
}
func modifyChan(c chan string) {
c <- "string"
}
m2 := make(map[int]string)
if m2 == nil {
fmt.Printf("m2 is nil --> %#v \n ", m2)
} else {
fmt.Printf("m2 is not nill --> %#v \n ", m2) //map[int]string{}
}
modifyMap(m2)
fmt.Printf("m2 is not nill --> %#v \n ", m2) // map[int]string{0:"string"}
c2 := make(chan string)
if c2 == nil {
fmt.Printf("c2 is nil --> %#v \n ", c2)
} else {
fmt.Printf("c2 is not nill --> %#v \n ", c2)
}
go modifyChan(c2)
fmt.Printf("c2 is not nill --> %#v ", <-c2) //"string"
以下代码演示了 struct 初始化的过程,可以说明不使用 new 一样可以完成 struct 的初始化工作。
type Foo struct {
name string
age int
}
//声明初始化
var foo1 Foo
fmt.Printf("foo1 --> %#v\n ", foo1) //main.Foo{age:0, name:""}
foo1.age = 1
fmt.Println(foo1.age)
//struct literal 初始化
foo2 := Foo{}
fmt.Printf("foo2 --> %#v\n ", foo2) //main.Foo{age:0, name:""}
foo2.age = 2
fmt.Println(foo2.age)
//指针初始化
foo3 := &Foo{}
fmt.Printf("foo3 --> %#v\n ", foo3) //&main.Foo{age:0, name:""}
foo3.age = 3
fmt.Println(foo3.age)
//new 初始化
foo4 := new(Foo)
fmt.Printf("foo4 --> %#v\n ", foo4) //&main.Foo{age:0, name:""}
foo4.age = 4
fmt.Println(foo4.age)
//声明指针并用 new 初始化
var foo5 *Foo = new(Foo)
fmt.Printf("foo5 --> %#v\n ", foo5) //&main.Foo{age:0, name:""}
foo5.age = 5
fmt.Println(foo5.age)
foo1 和 foo2 是同样的类型,都是 Foo 类型的值,
foo1 是通过 var 声明,Foo 的 filed 自动初始化为每个类型的零值,
foo2 是通过字面量的完成初始化。
foo3,foo4 和 foo5 是一样的类型,都是 Foo 的指针 Foo。*但是所有 foo 都可以直接使用 Foo 的 filed,读取或修改,为什么?
如果 x 是可寻址的,&x 的 filed 集合包含 m,x.m 和 (&x).m 是等同的,go 自动做转换,也就是 foo1.age 和 foo3.age 调用是等价的,go 在下面自动做了转换。
因而可以直接使用 struct literal 的方式创建对象,能达到和 new 创建是一样的情况而不需要使用 new。