golang 引用类型和指针有什么区别?
·
一句话先总结
-
指针:存的是地址,可以指向任意类型的内存地址,你可以让指针指向别的地方。
-
引用类型:内部也存了地址(底层有指针),但用起来像普通变量,你无法让它“改指向”。
具体区别(多维度对比)
| 维度 | 指针(*T) |
引用类型(slice/map/channel) |
|---|---|---|
| 本质 | 存的是内存地址,就是一个数字 | 是一个结构体/内部包含指针字段 |
| 能否修改指向 | ✅ 可以 p = &otherVar |
❌ 不能,它直接就是“指向底层数据”本身 |
| 创建方式 | new(int) 或 &var |
make([]int, 0) 或字面量 |
| 零值 | nil(不能直接解引用) |
nil(但部分操作可用,如len,append) |
| 解引用/取值 | 需要 *p |
直接使用,如 slice[0],map[key] |
| 函数传参效果 | 传的是地址拷贝,能改原值 | 传的是内部结构体的拷贝,也能改底层数据 |
详细解释 + 举例
1. 指针:明确的“地址值”
指针就像一个门牌号,你可以把这个号码抄给别人,别人按号码能找到你家。
// 指针示例
a := 10 // 一个房间,里面放着10
p := &a // p是房间的门牌号
fmt.Println(*p) // 10, *p 是“顺着门牌号找到里面的东西”
*p = 20 // 通过门牌号改房间里的值
fmt.Println(a) // 20 (原变量a被改了)
// 指针可以改变指向
b := 30
p = &b // p现在指向b了
fmt.Println(*p) // 30
函数传参例子:
func changeByPointer(num *int) {
*num = 100 // 改的是原来的变量
}
func main() {
x := 5
changeByPointer(&x)
fmt.Println(x) // 100 (原来的x被改了)
}
2. 引用类型:伪装成普通值的“指针代理”
引用类型(slice/map/channel)内部已经包含了指针,你不需要用*和&,它用起来就像普通变量,但传递时能共享内部数据。
Slice 举例:
s := []int{1, 2, 3} // s内部有指针指向数组{1,2,3}
// 函数传slice
func changeSlice(s []int) {
s[0] = 100 // ✅ 能改原slice底层数组(类似指针效果)
}
func main() {
s := []int{1, 2, 3}
changeSlice(s)
fmt.Println(s) // [100 2 3] (元素被改了)
// ❌ 但是你不能让外面的s指向一个新slice
// 除非用指针的指针 *[]int
}
但是注意: 引用类型在函数里重新赋值,外面看不到(这是和直接传指针的区别)
func reassignSlice(s []int) {
s = []int{7, 8, 9} // 只改了本地拷贝的s指向
}
func main() {
s := []int{1, 2, 3}
reassignSlice(s)
fmt.Println(s) // [1 2 3] 没变
}
// 如果真想改外面的s指向,还是要传指针
func reallyReassign(s *[]int) {
*s = []int{7, 8, 9}
}
3. 关键区别:能否重新指向
-
指针:你可以让它指向别处,就像门牌号可以贴到另一个房子上。
-
引用类型:它的指向是“固化”在内部的,你不能让
slice变量本身指向另一个底层数组,除非重新赋值。
为什么要有引用类型?
为了方便,避免到处写 *和 &。
-
如果用普通指针操作slice:
(*s)[0] = 100很难看 -
引用类型直接:
s[0] = 100清爽
面试/学习易错点
-
new和make的区别-
new(T)返回*T,一个指针,值为零值 -
make(T)只用于 slice/map/chan,返回 T 本身(但内部已初始化好指针)
-
-
nil 指针不能用
var p *int *p = 10 // panic! 空指针解引用 -
nil slice 可以直接用 append
var s []int // nil slice s = append(s, 1) // ✅ 可以,Go做了处理
记忆口诀
-
指针:要
&取地址,要*取值,可以改指向 -
引用:用起来像值,传参像指针,不能改本体指向
更多推荐


所有评论(0)