值传递的意义和几个例子
1 简介内存管理机制
Go 语言的内存管理是 自动垃圾回收(GC)+ 编译期逃逸分析(Escape Analysis)+ 栈内存分配优化 的组合设计。
- 栈内存分配
在函数内部创建的局部变量,Go 会优先分配在栈上。栈的生命周期随着函数调用结束而回收,效率很高。
编译器会通过 逃逸分析 判断变量是否可能被函数外部引用:
若不会逃逸,则分配在栈上。
若可能逃逸(如被返回或闭包引用),则分配在堆上,由 GC 管理。
- 垃圾回收(GC)
Go 使用的是 并发标记-清除(Concurrent Mark and Sweep)+ 三色标记算法 的改进版:
GC 在应用运行时并发执行,尽量减少 STW(Stop The World)的时间。
Go 1.8 之后的 GC 目标是将 STW 降到 亚毫秒级,适合大规模并发程序。
- 内存复用与优化
Go runtime 会维护对象池、内存页(span)等结构,减少频繁的堆分配开销。
2 为什么 Go 的函数一定是值传递?
Go 的函数参数传递机制是 严格的值传递(call by value),原因在于语言设计的简洁性与一致性:
在调用函数时,实参会被复制一份给形参。
如果参数是基础类型(int、string 等),就是值本身的拷贝。
如果参数是引用类型(切片、map、channel、指针),则拷贝的是“引用(指针)”,因此函数内部修改底层数据会影响外部。
这样保证了 所有函数调用的语义一致:永远是值拷贝。
Python 则是 对象引用传递(call by object reference),容易让初学者混淆“值传递”和“引用传递”的概念。
例子:
func changeSlice(s []int) {
s[0] = 99 // 修改了底层数组,外部可见
s = append(s, 100) // 修改了形参 s,不影响外部
}
这里 s 作为参数时被 值拷贝,但它包含一个指针指向底层数组,所以能修改数组内容。但 append 后的 s 是新拷贝,外部不受影响。
3 代码示例
a := 43
fmt.Println(a) // 43
fmt.Println(&a) // 0x20818a220
var b = &a
fmt.Println(b) // 0x20818a220
fmt.Println(*b) // 43
*b = 42 // b says, "The value at this address, change it to 42"
fmt.Println(a) // 42
上面的代码使 b 成为指向存储 int 的内存地址的指针,b 的类型为 “int pointer”
其中 *int – * 是类型的一部分 – b 是 *int 类型
这很有用
我们可以传递一个内存地址而不是一堆值(我们可以传递一个引用)
然后我们仍然可以更改存储在该内存地址中的任何内容的值
这使我们的程序性能更高
我们不必传递大量数据
我们只需要传递地址
当我们传递一个内存地址时,我们传递了一个值
与 C 系列中的所有语言一样,Go 中的所有内容都是通过值传递的。
也就是说,函数总是获取所传递事物的副本,
就好像有一个赋值语句将值分配给参数一样。
例如,将 int 值传递给函数会复制 int,
传递指针值会复制指针,但不会复制它指向的数据。
如果你将变量传递给函数,该函数总是会得到它的副本。永远记住。
如果被调用方修改了 parameter variable,则效果对调用方不可见。
越能认识到 Go 总是通过值传递,再加上支持指针的额外表现力,你就能创建出更清晰的程序
4 内存管理与 Python3 的区别
特性 Go Python3
内存分配 编译器逃逸分析,局部变量优先分配在栈上;堆内存由 GC 管理 所有对象(即使是整数、字符串)都在堆上分配
垃圾回收 并发三色标记清除 GC(低延迟) 引用计数为主,循环引用通过分代 GC 处理
对象模型 静态类型,编译期决定内存布局 动态类型,所有对象都是 PyObject,内存占用较大
性能影响 内存管理趋近于 C/C++ 效率,适合高并发 引用计数 + 动态类型使得内存管理灵活但开销较高
5 小结:
Go 的设计目标是 高并发、高性能、低延迟,所以强调栈分配和低开销 GC。
Python 的设计目标是 易用性和灵活性,所以所有对象堆分配,引用计数简单直观,但性能相对较低。
Go 的内存管理依赖逃逸分析、栈优先分配和低延迟 GC,追求性能与并发;Python 则依赖堆分配、引用计数和分代 GC,追求灵活性。
Go 的函数参数始终是值传递,保持语义简单一致;而是否能“修改外部”取决于是否传入了引用类型。
- 点赞
- 收藏
- 关注作者
评论(0)