Go 的 new 和 make 区别
date
Nov 21, 2022
slug
new-and-make
status
Published
tags
Go
summary
type
Post
简介
先上结论
new(T)
返回T类型的指针,指向T类型的零值make(T)
返回初始化后的T的引用,只能用于slice、map、channel
在Go里面,你几乎很少会遇到必须使用new的场景
make
make用来初始化
slice、map、channel
,并返回这些结构的引用如果我们使用
var
来声明一个结构体,将会是nil值,对nil值操作可能会引起panic,只有赋值之后才能使用,下面我们看看这个测试用例func TestMake(t *testing.T) {
var m1 map[int]string
fmt.Printf("%#v\n", m1)
m2 := make(map[int]string)
fmt.Printf("%#v\n", m2)
var c1 chan string
fmt.Printf("%#v\n", c1)
c2 := make(chan string)
fmt.Printf("%#v\n", c2)
}
输出结果:
map[int]string(nil)
map[int]string{}
(chan string)(nil)
(chan string)(0x140000c0240)
可以看到确实是符合我们上面的分析
new
new(T)
适用的T范围更广,它返回T类型的指针,指向T类型的零值比如说这个例子:
func TestNew(t *testing.T) {
var p1 *int
a := 1
// fmt.Println(*p1) p1 is nil, panic
p1 = &a
fmt.Println(*p1)
p2 := new(int)
fmt.Println(*p2)
p2 = &a
fmt.Println(*p2)
}
输出:
1
0
1
我们可以通过这个例子看到
var
和new
的区别,var
一个指针此时是nil的,未经赋值直接使用会panic,而new
就不会有这种问题出现实际上我们很少会用到
new
因为它总是可以被其它操作替代func TestNew2(t *testing.T) {
var car1 *Car
InnerCar := Car{}
car1 = &InnerCar
var car2 *Car = new(Car)
car3 := &Car{} // 最简洁的方式
car4 := new(Car)
fmt.Println(car1.id)
fmt.Println(car2.id)
fmt.Println(car3.id)
fmt.Println(car4.id)
}
输出结果:
0
0
0
0
你可以看到上面四种Car都是指针,它们输出结果都是一致的,其中
&Car
是最简洁的声明方式了C/C++语言的选手可能会感到疑惑,为什么不用
(&Car3).id
也能拿到id
的值呢?这是因为Go从语言层面做了优化:
如果x是指针,&x里面属性集合包含了m,那么x.m和(&x).m是等价的,Go会自动转换
扩展
既然提到了指针,我们来聊聊指针相关的东西吧
指针比较原则
Go定义了指针比较原则:
- 只有同类型的指针才能比较
如果你在1.18之前尝试对不同类型的指针比较,就会报错,提示你:
./slice_test.go:120:17: invalid operation: p4 == p5 (mismatched types *int and *string)
note: module requires Go 1.18
- 指针指向同一个对象,或者都是nil的时候比较结果是
true
- 否则比较结果都是
false
比如这个例子
func TestNil(t *testing.T) {
var p1 *int
var p2 *int
a := 1
p3 := new(int)
p3 = &a
p4 := &a
fmt.Println(p1 == p2) // true, p1 and p2 is nil
fmt.Println(p2 == p3) // false, p2 is nil, p3 non-nil
fmt.Println(p4 == p3) // true, p3 and p4 指向同一个对象
}
传引用?还是传值?
先说结论:Go是值传递,所有类型数据都会拷贝一个副本到函数里面。
但是Go又把数据类型分为两种:
- 引用类型:指针、slice、map、channel、接口、函数等
- 非引用类型:int、string、float、bool、数组和struct
引用类型传递的时候依然会拷贝一个副本,但是这个副本和引用指向了同一个对象,因此函数内的修改会影响原引用数据
而非引用类型就是普通的值传递了,不会影响到原有的数据
func TestModify(t *testing.T) {
a := 1
modifyInt(a)
fmt.Println(a) // 没有改变
b := make([]int, 3)
modifySlice(b)
fmt.Println(b) // 被改变了
}
输出结果:
-1
1
[1 0 0]
[1 0 0]