Go语言参数传递机制详解:传值、指针与引用类型的关系

苏见山
苏见山
发布时间:2026-10-16| 4人浏览过
原创

在Go语言开发中,理解参数传递机制是避免内存误解的关键。事实上,Go语言只存在“值传递”,但通过传递指针、map或slice等内部包含内存引用的结构体副本,依然可以实现对原始数据的修改。本文通过具体的代码示例和内存分析,厘清传值、指针与引用类型的逻辑关系。

核心结论先行

Go语言的所有函数参数传递均为值传递(Pass by Value)。这意味着无论传入的是基础类型、指针还是复杂类型,接收到的都是原参数的一个副本(Copy)。所谓的“引用传递”在Go中并不存在,但我们可以利用指针、map、slice等引用类型变量的特性,在函数内修改底层数据。

第1步:基础类型与指针的值传递验证

为了理解这一机制,首先观察基础类型及其指针的行为。许多人误认为传递指针就是传引用,实则不然。

func main() {
 i := 10
 ip := &i
 fmt.Printf("原始指针的内存地址是:%p\n", &ip)
 modify(ip)
 fmt.Println("int值被修改了,新值为:", i)
}

func modify(ip *int) {
 fmt.Printf("函数里接收到的指针的内存地址是:%p\n", &ip)
 *ip = 1
}

运行上述代码,输出结果如下:

原始指针的内存地址是:0xc42000c028
函数里接收到的指针的内存地址是:0xc42000c038
int值被修改了,新值为: 1

逻辑解析:

尽管两个地址不同(0xc42000c028 vs 0xc42000c038),证明传递给函数的确实是ip的一个拷贝。然而,这两个指针副本指向同一个内存地址(即变量的地址)。因此,虽然指针本身是拷贝的,但通过解引用*ip修改的是同一块内存中的数据,从而实现了对外部变量的修改。

图示清晰展示了:主函数中的指针ip地址为A,其指向变量i的地址为B;传递给modify函数的指针副本地址为C(也是指向B)。因为B是共享的,所以修改成功。

第2步:易混淆的类型——Map与Slice

Map和Slice在处理上更为特殊,因为它们没有显式的指针符号,却能在函数中直接修改内部元素。

为什么Map可以原地修改?

func main() {
 persons := make(map[string]int)
 persons["张三"] = 19
 
 mp := &persons
 fmt.Printf("原始map的内存地址是:%p\n", mp)
 modify(persons)
 fmt.Println("map值被修改了,新值为:", persons)
}

func modify(p map[string]int) {
 fmt.Printf("函数里接收到map的内存地址是:%p\n", &p)
 p["张三"] = 20
}

再次打印地址发现,传入的map副本地址与原始地址不同,依然是值传递。为什么能修改? 查看Go源码src/runtime/hashmap.go可知:make函数返回的是*hmap指针类型。
因此,实际上map变量存储的是一个指向哈希表结构的指针副本。当我们在函数中操作p["张三"]时,实际上是解引用这个指针副本去访问并修改底层的哈希表数据。

Slice的特殊结构:Header + Data

Slice与Map类似,也是一种引用类型。在Go源码reflect包中,对于Slice类型,Pointer()方法返回的是底层数组首元素的地址。

Slice在内存中表现为一个结构体,包含三个关键字段:

  • Data uintptr: 指向底层数组首地址的指针。
  • Len int: 长度。
  • Cap int: 容量。

当我们传递一个slice到函数时,复制的是这个包含Data、Len、Cap的结构体副本。
- 修改 ages[0]=1:因为Data指针副本指向同一个底层数组,所以可以修改数据。
- 修改 append:如果追加导致扩容,会申请新的内存并改变Data指针,但这只会改变函数内的副本,不会影响到外层切片(除非返回新的切片)。

第3步:Struct的正确修改方式

自定义Struct默认按值传递,无法直接修改字段,必须显式使用指针。

type Person struct {
 Name string
 Age  *int
}

func modify(p Person) {
 // p.Name = "李四" // 错误:无法修改,这是副本
 *p.Age = 20       // 正确:解引用修改底层int
}

若想修改Name字段,必须传递&p(Person指针),然后在函数内使用p.Name = "..."进行修改。

总结

Go语言坚持“一切皆传值”的设计哲学。理解这一点有助于消除“引用类型=传引用”的认知误区。总结如下:

  1. 所有参数传递的都是副本,包括指针、map、slice。
  2. 能否修改原始数据,取决于副本的内容是否含有指向原数据的指针(如*int、map、slice、chan)。
  3. 引用类型 ≠ 传引用:它们只是特殊的传值对象。

以上就是Go语言参数传递机制的详细内容,更多关于Go语言技术进阶的资料请关注本站其它相关文章!

相关标签:

免责声明:本文内容仅供参考,不代表本站观点。转载或引用请注明出处。

Python 相关产品和应用

全部下载
Selenium

Selenium

4.51.0 · Windows

用于浏览器自动化和 Web 测试的 Python 工具。

scikit-learn

scikit-learn

1.9.1 · Windows

常用 Python 机器学习算法与模型评估工具库。

Black

Black

26.10.1 · Windows

强调一致性的 Python 自动代码格式化工具。

Ruff

Ruff

0.17.1 · macOS

由 Rust 编写的高速 Python 代码检查和格式化工具。

pytest

pytest

9.1.1 · Windows

简洁而强大的 Python 自动化测试框架。

FastAPI

FastAPI

0.146.0 · Windows

高性能 Python API 框架,支持类型提示和自动接口文档。

Flask

Flask

3.1.3 · Windows

轻量灵活的 Python Web 应用框架。

Django

Django

6.1.2 · Windows

成熟的 Python Web 框架,适合构建内容站点和业务系统。

Spyder

Spyder

6.1.7 · Windows

面向科学计算与数据分析的 Python 集成开发环境。

SciPy

SciPy

1.18.1 · Windows

面向科学、工程和技术计算的 Python 算法库。