引言
本想使用Java来说明值传递和引用传递,这里有两个弊端:
- Java无法获取值或对象的地址,不能很好地阐述值传递和引用传递
- 如果使用JVM的内存模型来讲解,很多人不是真正知道JVM 内存模型,自然是看不懂的。
综上两个问题,故而使用golang来阐述问题,因为go语言可以直接展示值的内存地址😹🤣😹🤣😹🤣😹🤣
概念
值传递:方法的形参获取到实参的一份副本,它的生命周期随方法的结束而结束。
引用传递:方法的形参获取到实参的地址,操作形参也就是操作地址,即操作实参。
这里有些拗口,下面以数据交换
为例,来说明这个问题。
备注, go语言中有指针的概念:
- *表示指针,指向内存地址
- &获取值的内存地址
值传递
package main
func swapValue( a ,b int) {
a,b = b,a
println("inner func output : a = %d,b= %d",a,b)
println("inner address a =%T,address b =%T",&a,&b)
}
func main() {
a := 10
b := 20
println("outer address a =%T,address b =%T",&a,&b)
swapValue(a,b)
println("after address a =%T,address b =%T",&a,&b)
println("after func output : a = %d,b= %d",a,b)
}
输出结果为:
你会清楚的看到,在main中执行方法swapValue之前的a
的内存地址为0xc000032748
,b
的内存地址为0xc000032738
,在方法swapValue中的 a
的内存地址为0xc000030740
,b
的内存地址为0xc000030730
,方法swapValue结束之后, a
和 b
的内存地址不变,仍为0xc000032748
和0xc000032738
。
这就很好的说明了,在值传递的过程中,方法swapValue只是拿到了实参【a和b】的副本,并在方法swapValue内部创建该副本的内存地址,形参【a和b】指向该副本的内存地址,等到方法体结束之后,这部分【方法体内】的内存即被回收掉。
同时,你还会发现,在方法体内部,a
和 b
的值确实交换了,值分别为 20 和 10 ,说明它们指向的是副本的内存地址,因为方法swapValue执行之前,a
和 b
的值分别为10 和 20。
同时,也能看到即便在方法体内的值交换了,但输出的值还是没有交换,因为在当方法结束之后,副本所占的内存即被回收。
引用传递
package main
func swapRef(a *int,b *int) {
*a,*b = *b,*a
println("inner func output : a = %d,b= %d",*a,*b)
println("inner address a =%T,address b =%T",a,b)
}
func main() {
a := 10
b := 20
println("outer address a =%T,address b =%T",&a,&b)
swapRef(&a,&b)
println("after address a =%T,address b =%T",&a,&b)
println("after func output : a = %d,b= %d",a,b)
}
你会清楚的看到,在main中执行方法swapRef之前的a
的内存地址为0xc000030738
,b
的内存地址为0xc000030730
,当程序执行到 swapRef(&a,&b) 方法时,即传递 a
和 b
的内存地址,因而,在方法swapRef中的 a
的内存地址为0xc000030738
,b
的内存地址为0xc000030730
,方法swapRef结束之后, a
和 b
的内存地址不变,仍为0xc000030738
和0xc000030730
,但存储的值发生了改变。
这就很好的说明了,在引用传递的过程中,方法swapRef拿到了实参【a和b】的内存地址, a
和 b
在进行数值交换时,即交换了a
和 b
内存所存储的数值。
因而,不仅在方法体内部,a
和 b
的值交换了,值分别为 20 和 10 ,在方法体结束之后,main方法中的数值也已交换了。
补充例子
代码
package main
import (
"fmt"
)
type Book struct {
title string
author string
name string
bookId int
}
func output(book Book,msg string) {
fmt.Println(msg,"title=",book.title,",subject=",book.name)
}
func changeBookNameWithoutPtr(book Book) {
book.title="测试book title"
book.name = " 测试 book name"
output(book,"执行方法体内的输出结果:")
}
func changeBookNameWithPtr(book *Book) {
book.title="测试 book title"
book.name = " 测试 book name"
output(book,"执行方法体内的输出结果:")
}
当执行这个方法 changeBookNameWithoutPtr
时,如下面的main函数:
func main() {
book :=Book{title: "go language",name: "go language study"}
output(book,"执行方法前的输出结果:")
changeBookNameWithoutPtr(book)
output(book,"执行方法后的输出结果:")
}
其输出结果,如图所示:
当执行这个方法 changeBookNameWithPtr
,如下面的main函数:
func main() {
book :=Book{title: "go language",name: "go language study"}
output(book,"执行方法前的输出结果:")
changeBookNameWithPtr(&book)
output(book,"执行方法后的输出结果:")
}
其输出结果,如图所示 :
说明
值传递即执行changeBookNameWithoutPtr该方法,方法体内的book的title和name的值改变了,但是并没有改变main函数中book的title和name的值。
引用传递即执行changeBookNameWithPtr该方法,方法体内的book的title和name的值改变了,同时,也改变main函数中book的title和name的值。
结论
值传递会获取到实参值的一份副本,并方法体中开辟一块内存来存储副本,操作形参即是操作副本的内存,当方法体结束之后,这块内存也就被回收了,因而,对实参值没有影响。
引用传递实际上就是传递实参的内存地址,在方法体中操作形参也就是操作内存地址,因而,形参的改变回影响到实参。
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/99233.html