GO面试题:new 和 map 的区别

在 Go 语言中,newmake 都是用于内存分配的内建函数,但它们的适用类型、行为方式和返回值有本质区别。

核心区别

特性 new make
适用类型 任意类型(包括结构体、整数、数组等) 仅限 slicemapchannel 三种内置引用类型
返回值 指向该类型的指针*T 返回该类型本身(T,不是指针),且已初始化
内存行为 分配内存并置零值,不初始化内部结构 分配内存并初始化内部结构(如 slice 的底层数组、map 的哈希桶、channel 的缓冲区等)
使用场景 获取一个可指向零值对象的指针 创建可立即使用的 slice、map、channel

详细说明

1. new 函数

  • 签名:func new(Type) *Type
  • 作用:分配足够存储 Type 值的内存,将其所有位初始化为零值(zero value),并返回该内存地址的指针。
  • 结果:指针指向一个零值对象,适合需要指针但暂时不需要填充具体数据的场景。
p := new(int)      // p 是 *int,指向 0
fmt.Println(*p)    // 输出 0
*p = 42
type Person struct {
    Name string
    Age  int
}
p := new(Person)   // p 是 *Person,Name="", Age=0

2. make 函数

  • 签名:make(Type, size ...) Type(具体参数依类型而定)
  • 作用:只用于 slicemapchannel,不仅分配内存,还会初始化它们的内部数据结构(如 slice 的 lencap 和底层数组,map 的哈希桶等),返回已初始化的该类型值(非指针)。
  • 结果:直接可用的引用类型值。
// slice: 类型, len, cap
s := make([]int, 3, 5)   // len=3, cap=5,底层数组有5个元素,前3个为0

// map: 类型, 可指定容量(hint)
m := make(map[string]int) // 等价于 map[string]int{}

// channel: 类型, 可指定缓冲区大小
ch := make(chan int, 10)  // 缓冲区容量为10

为什么需要两个函数?

  • new 只负责分配内存并返回指针,不关心类型的内部结构。对于 slice、map、channel 这种需要复杂初始化的类型,仅仅置零(例如 nil slice、nil map、nil channel)是不够的,必须调用 make 完成内部数据结构的初始化才能正常使用。
  • 例如:var m map[string]intnew(map[string]int) 得到的 nil map 无法直接写入数据(会 panic),而 make(map[string]int) 得到的空 map 可以安全写入。
var m1 map[string]int   // nil map
m1["a"] = 1             // panic: assignment to entry in nil map

m2 := make(map[string]int)
m2["a"] = 1             // 正常

使用new初始化map等内置引用类型后,该如何进行读写操作

使用 new(map[string]int) 得到的是一个指向 nil map 的指针(*map[string]int 类型)。这个指针本身不是 nil,但它指向的 map 是 nil,因此无法直接读写(会 panic)。要让它变得可用,必须让该指针指向一个已初始化的 map。

如下示例:

package main

import "fmt"

func main() {
	fmt.Println("Hello, 世界")

	v := new(int)
	fmt.Println(v, *v)

	m1 := new(map[string]int)
	fmt.Println("m1:", m1, "| *m1:", *m1)
	fmt.Println("m1 == nil:", m1 == nil)   // false(指针本身不是 nil)
	fmt.Println("*m1 == nil:", *m1 == nil) // true(指向的 map 是 nil)

	// `m1`是一个指向nil的map的指针,这个指针本身不是 nil,但它指向的 map 是 nil,因此无法直接读写(会 panic)。要让它变得可用,必须让该指针指向一个已初始化的 map。
	// 当你写 *m1["key"] 时,编译器会先执行 m1["key"],但 m1 的类型是 *map[string]int(指向 map 的指针),而指针类型不支持索引操作,所以会编译报错:invalid operation: cannot index m1 (variable of type *map[string]int)
	// m1["a"] = 1 // 写操作会引发错误:invalid operation: cannot index m1 (variable of type *map[string]int)
	// fmt.Println(m1["a"]) // 读操作也会引发错误,原因同上。

	// 为了让编译器先解引用指针得到 map,再对 map 进行索引,必须用括号改变运算顺序:`(*m1)["key"]`
	// 类似的情况也出现在通过指针访问结构体字段时:(*p).field,但 Go 提供了语法糖 p.field,会自动解引用。可惜 map 索引没有这种语法糖,所以必须显式写括号。

	// (*m1)["a"] = 1 // panic: assignment to entry in nil map
	fmt.Println("未初始化m1 | (*m1)[\"a\"] = ", (*m1)["a"]) // 可以读

	// 必须初始化
	*m1 = make(map[string]int) // 现在 *m1 是一个空 map
	(*m1)["a"] = 1             // 可以进行读写操作
	fmt.Println("已初始化m1 | (*m1)[\"a\"] =", (*m1)["a"])
}

输出:

Hello, 世界
0x14000098030 0
m1: &map[] | *m1: map[]
m1 == nil: false
*m1 == nil: true
未初始化m1 | (*m1)["a"] =  0
已初始化m1 | (*m1)["a"] = 1
posted @ 2026-04-03 12:38  川川籽  阅读(9)  评论(0)    收藏  举报