跳转到主内容
趣航编程网 - 趣学编程,启航技术之路!

如何理解Golang指针与值类型区别_Golang内存模型与数据传递机制说明

Go中所有参数都是值传递,传struct时修改副本不影响原变量;需用指针接收者才能修改原值;切片/map/channel是含指针的值类型;是否用指针取决于是否需修改、大小及接口实现。 为什么传 struct 时函数里改了字段,外面却没变? 因为 Go 所有参数都是值传递——
func f(s MyStruct)
传进去的是整个
MyStruct
的副本,函数内修改的只是副本字段,原变量毫发无伤。 想让外部看到修改,必须用指针:
func f(s *MyStruct)
,然后在函数里写
s.Field = "new"
哪怕结构体只有两个
int
字段,只要方法需要改状态(比如
user.IncreaseLoginCount()
),就该用指针接收者 常见错误现象:
user.SetName("Alice")
调用后
user.Name
还是空字符串——十有八九接收者写成了
func (u User) SetName(n string)
,而不是
func (u *User) SetName(n string)
切片、map、channel 为啥“像引用”但又不是引用类型? 它们是「含指针的值类型」:变量本身是值(比如
sliceHeader
结构体),但内部字段存着底层数组/哈希表的地址。所以传参时虽拷贝 header,但底层数据仍共享。
append
可能扩容并返回新 slice,原变量不变;而
map["k"] = v
直接生效,因 map header 里的指针指向同一 hash 表 误以为「map 是引用类型」会导致误判:把 map 当参数传进函数,函数里清空它(
clear(m)
),调用方确实看到空了;但若函数里做了
m = make(map[string]int)
,这只会改副本 header,不影响外层 安全做法:需要保证行为可预测时,显式传
*map
或
*[]T
(虽然极少这么做) nil 指针解引用 panic 和空接口赋值失败,根源都是同一套规则 Go 中一切皆值传递,但「值」的内容决定你能干什么:
nil
是
*T
类型的零值,代表「地址为空」;而空接口
interface{}
存的是(类型, 数据)对,当值为
nil
时,类型信息可能丢失。
var p *string; fmt.Println(*p)
→ 直接 panic:不能解引用
nil
指针
var s *string; var i interface{} = s; fmt.Println(i == nil)
→ 输出
false
!因为
i
存的是 (
*string
,
nil
),类型非空,整体不等于
nil
判断指针是否有效,永远先做
if p != nil
;判断接口是否为 nil,要拆成
if i == nil || reflect.ValueOf(i).IsNil()
(或更稳妥地用类型断言) 什么时候该用指针,什么时候死守值类型? 核心就看三件事:要不要改原值、对象大不大、接不接口。 Python3.6标准库参考手册(PDF版) Python3.6标准库参考手册(PDF版) 下载 立即学习 “ go语言免费学习笔记(深入) ”; 要改原值 → 必须用指针(
*T
参数或接收者) 结构体字段总大小 > 16 字节 (如含
[32]byte
或多个
string
)→ 优先指针,避免复制开销 实现了某个接口?注意方法集:如果只有
func (t *T) Foo()
,那
T{}
值本身无法赋给该接口变量,必须传
&T{}
小而只读的数据(
int
、
time.Time
、轻量
struct{ ID int; Name string }
)→ 传值更清晰,无 nil 风险,GC 压力小 最容易被忽略的一点:即使结构体很小,只要它和指针接收者的方法混用(比如 标准库
sync.Mutex
),就必须统一用指针——否则调用
Lock()
时传值会触发拷贝,锁的不是同一个实例。

相关文章