
go语言在参数传递方面遵循严格的“值传递”原则。这意味着无论你传递的是基本类型(如int、string)还是复杂类型(如结构体struct、切片slice、映射map、通道channel),函数接收到的都是参数值的一个副本。
对于指针类型,其“值”就是它所指向的内存地址。因此,当你将一个指针变量传递给函数时,实际上是复制了该指针变量所存储的内存地址。函数内部的形参会拥有这个地址的副本,但形参本身是一个独立的变量,位于不同的内存地址。
理解Go语言中指针的关键在于区分以下三个概念:
在函数调用中,当我们将一个指针变量p传递给函数时,函数形参q会得到p的值(即p所指向的内存地址)的一个副本。这意味着p和q虽然指向同一个底层数据,但它们自身是两个独立的变量,位于不同的内存地址。因此:
为了更清晰地说明这一点,我们来看一个简化的示例代码:
立即学习“go语言免费学习笔记(深入)”;
package main
import "fmt"
func byVal(q *int) {
// 3. 在 byVal 函数内部
// q 是形参,其地址 (&q) 与 main 函数中的 p 的地址 (&p) 不同
// q 的值 (q) 与 main 函数中的 p 的值 (p) 相同,都指向 i 的地址
// *q 的值 (*q) 与 main 函数中的 *p 的值 (*p) 相同,都是 i 的值
fmt.Printf("3. byVal -- q %T: &q=%p q=&i=%p *q=i=%v\n", q, &q, q, *q)
// 通过指针 q 修改其指向的底层数据
*q = 4143
fmt.Printf("4. byVal -- q %T: &q=%p q=&i=%p *q=i=%v\n", q, &q, q, *q)
// 将形参 q 设为 nil,这只会影响 q 这个局部变量,不会影响 main 函数中的 p
q = nil
}
func main() {
i := int(42) // i 是一个 int 变量
fmt.Printf("1. main -- i %T: &i=%p i=%v\n", i, &i, i)
p := &i // p 是一个指向 int 变量 i 的指针
fmt.Printf("2. main -- p %T: &p=%p p=&i=%p *p=i=%v\n", p, &p, p, *p)
byVal(p) // 将指针 p 的值(即 i 的地址)传递给 byVal 函数
// 5. 从 byVal 函数返回后
// p 的地址 (&p) 不变
// p 的值 (p) 不变,仍然指向 i 的地址
// *p 的值 (*p) 已被 byVal 函数修改
fmt.Printf("5. main -- p %T: &p=%p p=&i=%p *p=i=%v\n", p, &p, p, *p)
// 6. 最终 i 的值已被修改
fmt.Printf("6. main -- i %T: &i=%p i=%v\n", i, &i, i)
}运行结果示例(内存地址可能不同):
1. main -- i int: &i=0xc000014088 i=42 2. main -- p *int: &p=0xc000006028 p=&i=0xc000014088 *p=i=42 3. byVal -- q *int: &q=0xc000006030 q=&i=0xc000014088 *q=i=42 4. byVal -- q *int: &q=0xc000006030 q=&i=0xc000014088 *q=i=4143 5. main -- p *int: &p=0xc000006028 p=&i=0xc000014088 *p=i=4143 6. main -- i int: &i=0xc000014088 i=4143
输出解读:
这清晰地表明,byVal函数操作的是q(p的副本),但通过q指向的地址,成功修改了main函数中p所指向的原始数据i。
回到原始问题中的代码,main函数和gotest协程中都存在一个名为s的变量。
package main
import ( "runtime" )
type Something struct {
number int
queue chan int
}
func gotest( s *Something, done chan bool ) { // 这里的 s 是 gotest 的形参
println( "from gotest:")
println( &s ) // 打印的是 gotest 局部变量 s 的地址
for num := range s.queue {
println( num )
s.number = num // 修改的是 main 函数中 s 所指向的 Something 对象的 number 字段
}
done <- true
}
func main() {
runtime.GOMAXPROCS(4)
s := new(Something) // 这里的 s 是 main 函数的局部变量
println(&s) // 打印的是 main 局部变量 s 的地址
s.queue = make(chan int)
done := make(chan bool)
go gotest(s, done) // 将 main 函数中 s 的值(即 Something 对象的地址)传递给 gotest
s.queue <- 42
close(s.queue)
<- done
println(&s) // 再次打印 main 局部变量 s 的地址
println(s.number)
}输出显示:
0x4930d4 // main 函数中变量 s 的地址 from gotest: 0x4974d8 // gotest 函数中变量 s 的地址 42 0x4930d4 // main 函数中变量 s 的地址 42
这里的关键在于:
println(&s)打印的是指针变量s本身的内存地址,而不是它所指向的对象的地址。由于main和gotest中的s是不同的变量(即使同名),它们有不同的内存地址,所以打印出来的值不同是完全符合预期的。
Go语言的参数传递机制是纯粹的“值传递”。对于指针类型,传递的是指针变量存储的内存地址副本。这意味着函数内部的形参会拥有与实参相同的指针值(指向相同的底层数据),但形参自身是一个独立的变量,有其独立的内存地址。因此,在函数内部对形参指针进行赋值(如q = nil)不会影响到函数外部的原始指针变量,但通过解引用形参指针来修改其指向的底层数据,则会影响到原始数据。正确理解&variable、variable和*pointer之间的区别,以及善用fmt.Printf进行调试,是掌握Go语言指针和内存管理的关键。
以上就是深入理解Go语言中的指针与值传递:内存地址的奥秘的详细内容,更多请关注php中文网其它相关文章!
每个人都需要一台速度更快、更稳定的 PC。随着时间的推移,垃圾文件、旧注册表数据和不必要的后台进程会占用资源并降低性能。幸运的是,许多工具可以让 Windows 保持平稳运行。
Copyright 2014-2025 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号