Skip to content
字数
1144 字
阅读时间
6 分钟

背景

在看 Golang 语法的时候,遇到了函数传参的一个疑惑点

go
type Point struct{ X, Y int }
func modify(p *Point) {
	(*p).X = 42
	fmt.Println(*p) // {42, 2}
	(*p) = Point{3, 4}
}
func main() {
	p1 := Point{1, 2}
	modify(&p1)
	fmt.Println (p1) // {3, 4}
}

这跟 JavaScript 中的引用传递现象并不一样

js
function modify(p) {
	p.X = 42
	console.log(p) // { X: 42, Y:2 }
	p = {
		X: 3,
		Y: 4
	}
}

let p = {
	X: 1,
	Y: 2
};
modify(p);
console.log(p); // { X: 43, Y: 2 }

然后就梳理了下与 JS 的对比

编程界一般函数传参就两种类型,值传递和引用传递

  • 值传递:将 变量地址 所在的具体值进行传递
  • 引用传递:将 变量地址 进行传递

JavaScript 中的传参机制

如果是原始值,那么就跟原始值变量的复制一样,如果是引用值,那么就跟引用值变量的复制一样

不少人都认为是 "JavaScript 原始值类型按值传递,引用值按引用传递 "

这句话后半句是不对的

值传递

js
let num1 = 6;
let num2 = num1;

在内存中的表现行为:
image.png
num1num2 拥有完全不同的内存地址,但地址内存的值是一样的

引用传递

js
function setName(obj) {
	obj.name = "tom";
}

let person = new Object();
setName(person);
console.log(person.name); // "tom"

很多开发者错误地认为,当在局部作用域中修改对象而变化反映到全局时,就意味着参数是按引用传递的

看下面这个例子

js
function setName(obj) {
	obj.name = "tom";
	obj = new Object();
	obj.name = "jerry";
}

let person = new Object();
setName(person);
console.log(person.name); // "tom"

如果 person 是按引用传递的,那么 person 应该自动将指针改为指向 name 为 "jerry" 的对象。可是,当我们再次访问 person. name 时,它的值是 "tom",这表明函数中参数的值改变之后,原始的引用仍然没变

引用类型传参本质上还是 复制变量的值
image.png

Golang 的传参机制

真正的值类型按值传递,引用类型按引用传递

Go 中函数传参仅有值传递一种方式

对比 JavaScript ,我们就看引用类型

go
type Object struct{ age int }
func setName(obj *Object) {
	(*obj).age = 2
	fmt.Println(*obj) // {2}
	(*obj) = Object{3}
}
func main() {
	person := Object{1}
	setName(&person)
	fmt.Println (person) // {3}
}

官网解释:https://go.dev/doc/faq#pass_by_value

When are function parameters passed by value?
As in all languages in the C family, everything in Go is passed by value. That is, a function always gets a copy of the thing being passed, as if there were an assignment statement assigning the value to the parameter. For instance, passing an int value to a function makes a copy of the int, and passing a pointer value makes a copy of the pointer, but not the data it points to. (See a later section for a discussion of how this affects method receivers.)
Map and slice values behave like pointers: they are descriptors that contain pointers to the underlying map or slice data. Copying a map or slice value doesn't copy the data it points to. Copying an interface value makes a copy of the thing stored in the interface value. If the interface value holds a struct, copying the interface value makes a copy of the struct. If the interface value holds a pointer, copying the interface value makes a copy of the pointer, but again not the data it points to.

翻译一下:

text
像 C 家族中的其他所有语言一样,Go 语言中的所有传递都是传值。
也就是说,函数接收到的永远都是参数的一个副本,就好像有一条将值赋值给参数的赋值语句一样。
例如,传递一个 int 值给一个函数,函数收到的是这个 int 值得副本,传递指针值,获得的是指针值的副本,而不是指针指向的数据。
(请参考 [later section] (https://golang.org/doc/faq#methods_on_values_or_pointers) 来了解这种方式对方法接收者的影响)

Map 和 Slice 的值表现和指针一样:它们是对内部映射或者切片数据的指针的描述符。
复制映射或者切片的值,不会复制它们指向的数据。复制接口的值,会产生一个接口值存储的数据的副本。
如果接口值存储的是一个结构体,复制接口值将产生一个结构体的副本。
如果接口值存储的是指针,复制接口值会产生一个指针的副本,而不是指针指向的数据的副本。

参考

Golang 中函数传参存在引用传递吗?

【Go 进阶】Go 语言到底是值传递,还是引用传递? - 掘金

贡献者

The avatar of contributor named as jiechen jiechen

页面历史

撰写