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
我们可以通过这个例子看到varnew 的区别,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]
 

Ref


© hhmy 2019 - 2023

powered by nobelium