【golang】值类型与引用类型
先给一个特别“接地气”的说法:
值类型:变量里直接装的是数据本身
引用类型:变量里装的是“地址/引用”,真正的数据在别处
下面慢慢展开 👇
1. 值类型(Value Type)是什么?
特点可以总结成一句:
赋值 / 传参 = 拷贝一份完整的数据
也就是说,当你把一个值类型赋值给另一个变量时,两个变量就互不影响。
通俗理解
像是复印文件:
A 有一份文件
你给 B 复印了一份
A 改自己的那份,不会影响 B 手里的那份
在 Go 里的典型值类型
基本类型:
int、float64、bool、string(字符串有点特殊,但语义上当成值)结构体:
struct数组:
[N]int也包括:
complex等
Go 示例
a := 10
b := a // 拷贝了一份 10
b = 20
fmt.Println(a) // 10
fmt.Println(b) // 20
type Person struct {
Name string
Age int
}
p1 := Person{"Tom", 18}
p2 := p1 // 拷贝一份 Person
p2.Age = 20
fmt.Println(p1.Age) // 18
fmt.Println(p2.Age) // 20
2. 引用类型(Reference Type)是什么?
一句话:
赋值 / 传参 = 拷贝“引用”(指针),多个变量指向同一块底层数据
所以,当你通过一个变量修改数据时,其它引用同一数据的变量也会“看到”变化。
通俗理解
像是几个人共用一台电脑:
A、B 都拿到这台电脑的“开机密码”(引用)
只有一台电脑(底层数据)
A 改了桌面壁纸,B 一看,壁纸也变了(因为看到的是同一台电脑)
在 Go 里的“引用类型”(准确叫:带引用语义的类型)
严格说,Go 里所有东西传参都是值拷贝,只是有些类型的“值里”本身就包含指针/引用,所以叫“引用类型”更像是说它的使用语义:
切片:
[]T映射:
map[K]V通道:
chan T函数:
func(...) ...指针:
*T
这些类型的“值”内部都会持有对底层数据的引用,所以看起来像是“引用传递”。
Go 示例:切片(引用语义)
s1 := []int{1, 2, 3}
s2 := s1 // 拷贝了一份“切片头”,但指向同一个底层数组
s2[0] = 100
fmt.Println(s1) // [100 2 3]
fmt.Println(s2) // [100 2 3]
s1 和 s2 里各有一份“切片描述结构”,但它们都指向同一块数组内存,所以改元素会互相影响。
Go 示例:map(引用语义)
m1 := map[string]int{"a": 1}
m2 := m1 // 复制了一个引用
m2["a"] = 100
fmt.Println(m1["a"]) // 100
fmt.Println(m2["a"]) // 100
3. 再精确一点:Go 中“值传递 + 引用语义”
很多人会说“Go 是值传递还是引用传递?”
标准答案是:Go 只有值传递。
区别在于“传的这个值里面有没有引用”:
传一个
int:只是一个纯粹的数值,和引用无关。
传一个
[]int(切片):拷贝的是“切片头”(里面包含指向底层数组的指针),
所以多个切片变量共享底层数组 → 表现为“引用语义”。
传一个
*T(指针):拷贝的是指针本身,但指针都指向同一个对象 → 引用语义。
你可以理解为:
是否“引用类型”取决于:这个类型的值里,有没有指向其它数据的“地址/引用”。
4. 如何快速区分 & 使用建议
在 Go 里可以简单记:
值类型(拷贝语义)
int,float,bool,string,struct,array
→ 赋值、传参不会影响原始变量引用语义类型
slice,map,chan,func,pointer
→ 赋值、传参后,多个变量操作的是同一份底层数据
常见设计习惯
大多数业务场景:
集合类用:
[]T/map[K]V
结构体较大,又不想频繁拷贝:
用
*MyStruct(指针)传参