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

导读:本篇文章讲解 深入理解值传递和引用传递,使用go语言来讲解,希望对大家有帮助,欢迎收藏,转发!站点地址:www.bmabk.com

引言

本想使用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的值。

结论

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

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

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/99233.html

(0)
小半的头像小半

相关推荐

极客之音——专业性很强的中文编程技术网站,欢迎收藏到浏览器,订阅我们!