go语言所有参数均为值传递,指针传递本质是地址值的副本;修改原值需解引用,安全关键在使用而非传递方式;nil指针解引用会panic,须主动校验;接收者选择取决于是否修改字段及结构体大小;切片/map/channel因含指针字段而表现类似引用但仍是值传递。

Go 语言里没有“引用传递”,所有函数参数都是值传递;所谓“传指针能改原值”,本质是传了 *T 类型的值——它本身是个地址副本,但解引用后能触达原始内存。安全的关键不在“怎么传”,而在“传了之后怎么用”。
为什么 func f(p *int) 能改原值,但 func f(x int) 不能
因为 *int 是一个值类型,它存储的是某个 int 变量的内存地址。函数收到的是这个地址的副本,但副本里存的地址和原指针一样,所以 *p = 100 实际写入的是那个地址指向的位置。
-
func f(x int):传的是int的完整拷贝,修改x只动副本,不影响调用方 -
func f(p *int):传的是指针值的副本,p本身可被重赋值(比如p = nil),但这不影响调用方的指针变量;只有*p才真正操作原始数据 - 误以为“传指针就等于传引用”容易忽略一个事实:你仍可能在函数内把
p指向别处,而调用方完全不知情
nil 指针解引用是第一大 panic 来源
传入 nil 后直接 *p,运行时立即崩溃:panic: runtime error: invalid memory address or nil pointer dereference。这不是编译错误,很容易漏测。
- 所有接受
*T的函数,开头必须检查p == nil,尤其当参数来自外部输入、配置或可选字段时 - 不要依赖文档说“不传 nil”,而要主动防御:
if p == nil { return errors.New("p must not be nil") } - 结构体字段为
*string或*int时,JSON 反序列化可能产生nil,需在业务逻辑前校验
结构体方法该用值接收者还是指针接收者
这不是风格选择,而是语义和性能的硬约束。编译器不会报错,但行为可能不符合预期。
- 要修改字段 → 必须用指针接收者:
func (u *User) SetName(n string) { u.name = n };值接收者改的是副本,调用方完全看不到变化 - 结构体较大(如含
[]byte、嵌套 map、总大小 > 64 字节)→ 指针接收者避免每次调方法都拷贝整块内存 - 小结构体且只读(如
type Point struct{ X, Y int }的func (p Point) Distance() float64)→ 值接收者更自然,也杜绝意外修改 - 混用会导致方法集不一致:值类型变量无法调用指针接收者方法(除非取地址),反之亦然
切片、map、channel 为什么“看起来像引用”却不是引用传递
它们是值类型,但内部含指针字段。传值时拷贝的是 header(ptr、len、cap 或哈希表描述符),不是底层数据。这导致行为割裂:
-
s[0] = x生效:因为s.ptr指向原数组,改的是共享内存 -
s = append(s, x)失效(若扩容):新底层数组地址写入形参s的ptr字段,但调用方变量没变 -
m["k"] = v生效:m副本里的指针仍指向原哈希表,写入发生在共享结构中 -
m = make(map[string]int)失效:只改副本 header,原 map 不受影响 - 想让扩容/重赋值生效?要么返回新值(
s = f(s)),要么传*[]T/*map[K]V(极少见,通常说明设计有问题)
最容易被忽略的点是:指针本身是值,它的生命周期、所有权和并发访问必须由程序员显式管理。传 *T 不等于自动线程安全,也不等于规避拷贝成本——如果 T 很小,传值反而更高效、更清晰。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











