深入理解值传递和引用传递,使用go语言来讲解

引言

本想使用Java来说明值传递和引用传递,这里有两个弊端:

  1. Java无法获取值或对象的地址,不能很好地阐述值传递和引用传递
  2. 如果使用JVM的内存模型来讲解,很多人不是真正知道JVM 内存模型,自然是看不懂的。

综上两个问题,故而使用golang来阐述问题,因为go语言可以直接展示值的内存地址😹🤣😹🤣😹🤣😹🤣

概念

值传递:方法的形参获取到实参的一份副本,它的生命周期随方法的结束而结束。

引用传递:方法的形参获取到实参的地址,操作形参也就是操作地址,即操作实参。

这里有些拗口,下面以数据交换为例,来说明这个问题。

备注, go语言中有指针的概念:

  1. *表示指针,指向内存地址
  2. &获取值的内存地址

值传递

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的内存地址为0xc000032748b的内存地址为0xc000032738,在方法swapValue中的 a的内存地址为0xc000030740b的内存地址为0xc000030730,方法swapValue结束之后, ab的内存地址不变,仍为0xc0000327480xc000032738

这就很好的说明了,在值传递的过程中,方法swapValue只是拿到了实参【a和b】的副本,并在方法swapValue内部创建该副本的内存地址,形参【a和b】指向该副本的内存地址,等到方法体结束之后,这部分【方法体内】的内存即被回收掉。

同时,你还会发现,在方法体内部,ab的值确实交换了,值分别为 20 和 10 ,说明它们指向的是副本的内存地址,因为方法swapValue执行之前,ab的值分别为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的内存地址为0xc000030738b的内存地址为0xc000030730,当程序执行到 swapRef(&a,&b) 方法时,即传递 ab的内存地址,因而,在方法swapRef中的 a的内存地址为0xc000030738b的内存地址为0xc000030730,方法swapRef结束之后, ab的内存地址不变,仍为0xc0000307380xc000030730,但存储的值发生了改变。

这就很好的说明了,在引用传递的过程中,方法swapRef拿到了实参【a和b】的内存地址, ab在进行数值交换时,即交换了ab内存所存储的数值。

因而,不仅在方法体内部,ab的值交换了,值分别为 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的值。

结论

值传递会获取到实参值的一份副本,并方法体中开辟一块内存来存储副本,操作形参即是操作副本的内存,当方法体结束之后,这块内存也就被回收了,因而,对实参值没有影响。

引用传递实际上就是传递实参的内存地址,在方法体中操作形参也就是操作内存地址,因而,形参的改变回影响到实参。

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

互联网全栈开发实战

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值