Go函数传参:值传递&用明白Go的指针

Go函数传参:值传递&用明白Go的指针,第1张

Go函数传参:值传递&用明白Go的指针

不杠哦,Go里面就是没有引用传递
先拿C++来说,解释一下值传递、指针传递和引用传递的含义
值传递:
形参是实参的拷贝,改变形参的值并不会影响外部实参的值,是将实参的值拷贝到另外的内存地址中才修改。从被调用函数的角度来说,值传递是单向的(实参->形参),参数的值只能传入,不能传出。当函数内部需要修改参数,并且不希望这个改变影响调用者时,采用值传递。
指针传递:
形参为指向实参地址的指针,当对形参的指向 *** 作时,就相当于对实参本身进行的 *** 作;指针传递参数本质上是值传递的方式,它所传递的是一个地址值。指针是一个实体,指向一块内存,它的内容是所指内存的地址;而引用则是某块内存的别名。
引用传递:
形参相当于是实参的“别名”,引用必须在一开始就被初始化,而且其引用的对象在其整个生命周期中是不能被改变的(自始至终只能依附于同一个变量,而指针可以改变其指向的对象)。对形参的 *** 作其实就是对实参的 *** 作,被调函数对形参做的任何 *** 作都影响了主调函数中的实参变量。
看下C++中的三种传递之间的关系:

#include 
using namespace std;

//1.值传递
void change1(int a)
{
    cout << "change1 形参地址:" << &a << endl;
    a = a + 1;
}
//2.指针传递
void change2(int *a)
{
    cout << "change2 形参指针地址:" << &a << endl;
    cout << "change2 指针指向的地址:" << a << endl;
    cout << "change2 指针指向的地址中的值:" << *a << endl;
    *a = *a + 1;
}
//3.引用传递
void change3(int &b)
{
    cout << "change3 形参地址: " << &b << endl;
    cout << "change3 形参地址中的内容: " << b << endl;
    b = b + 1;
}

int main()
{
    int a = 10;
    cout << "实参地址: " << &a << " 值:" << a << endl;
    cout << "值传递:"<< endl;
    change1(a);
    cout << "after change1, a = "<< a << endl;
    cout << "指针传递:" << endl;
    int *p = &a;
    cout << "before change2, 实参指针地址: " << &p << endl;
    change2(p);
    cout << "after change2, a = " << a << endl;
    cout << "引用传递:" << endl;
    change3(a);
    cout << "after change3, a = " << a << endl;
    while(1){}
    return 0;
}

输出:

值传递不用解释,就是实参的一份拷贝,值相等而已,形参和实参完全是两个不同的地址
指针传递本质也是值传递,拷贝的是指针本身:

而引用传递则是直接把实参的别名传递进去了,形参和实参都是同一个地址
C++中有引用传递,也有引用类型

int c = 6;
int &p = c;//p是c的一个别名

go语言中也有引用类型: 指针、slice、map 和 channel。但是,go语言中没有&T类型, 有形如var p = &c的这种表示 , p是指针,等效于var p *int= &c
下面通过测试说明golang中只有值传递和指针传递,两者可以归结为值传递
测试:

package main

import (
	"fmt"
)
type myStruct struct{
   str string
}
func change1(a int) {
   fmt.Printf("change1 形参地址:%v,值:%v\n", &a, a)
   a = a + 1
}
func change2(a *myStruct) {
   fmt.Printf("change2 形参指针地址:%p\n", &a)
   fmt.Printf("change2 形参指针指向的地址:%p\n", a)
   fmt.Printf("change2 形参指针指向的地址的内容:%v\n", *a)
   a.str = "change2"
}
func change3(a *int){
   *a = *a + 1
}
func main() {
   var a1 = 10
   var a2 = myStruct{
      str:"hello",
   }
    fmt.Printf("main a1实参地址:%p, 值:%v\n", &a1, a1)
    fmt.Println("值传递:")
    change1(a1)
    fmt.Printf("after change1, a1 = %v\n", a1)
    fmt.Printf("main a2实参地址:%p,值:%v\n", &a2, a2)
    p := &a2
    fmt.Printf("main a2实参指针地址:%p,值:%p\n", &p, p)
    fmt.Println("引用传递:")
    change2(p)
    fmt.Printf("after change2, a2 = %v\n", a2)
    change3(&a1)
    fmt.Printf("after change3, a1 = %v\n", a1)
}

输出

关于slice、map、chan等类型的参数传递
但是上述文章中关于slice的描述有不对的地方,简单来说就是,slice本身是个结构体,但它内部第一个元素是一个指针类型,指向底层的具体数组,slice在传递时,形参是拷贝的实参这个slice,但他们底层指向的数组是一样的,拷贝slice时,其内部指针的值也被拷贝了,也就是说指针的内容一样,都是指向同一个数组

type slice struct {
    array unsafe.Pointer
    len   int
    cap   int
}

文章中作者做的实验个人感觉应该把代码改成这样才合适:

func main()  {
	var args =  []int64{1,2,3}
	fmt.Printf("切片args的地址: %p \n",&args)
	fmt.Printf("切片args第一个元素的地址: %p \n",&args[0])
	modifiedNumber(args)
	fmt.Println(args)
}

func modifiedNumber(args []int64)  {
	fmt.Printf("形参切片的地址 %p \n",&args)
	fmt.Printf("形参切片args第一个元素的地址: %p \n",&args[0])
	args[0] = 10
}

输出:

而map和chan使用make函数返回的实际上是 *hmap*hchan指针类型,也就是指针传递。

来测试:【指针到底能不能用明白】

说实话指针这块在当时学c语言的时候就没整明白…

type A struct {
	Ptr1 *B
	Ptr2 *B
	Val  B
	Val2 string
	Val3 int
}

type B struct {
	Str string
}
func main() {
	a := A{
		Ptr1: &B{"ptr-str-1"},
		Ptr2: &B{"ptr-str-2"},
		Val:  B{"val-str"},
		Val2: "main",
		Val3: 0,
	}
	fmt.Printf("main a addr: %p\n", &a)
	fmt.Printf("main Ptr1 addr: %p, point to addr: %p, value: %v\n", &a.Ptr1, a.Ptr1, *a.Ptr1)
	fmt.Printf("main Ptr2 addr: %p, point to addr: %p, value: %v\n", &a.Ptr2, a.Ptr2, *a.Ptr2)
	fmt.Printf("main Val addr: %p, value: %v\n", &a.Val, a.Val)
	fmt.Printf("main Val2 addr: %p, value: %v\n", &a.Val2, a.Val2)
	fmt.Printf("main Val3 addr: %p, value: %v\n\n", &a.Val3, a.Val3)
	demo1(a)
	fmt.Println("after demo1:")
	fmt.Printf("main Ptr1 addr: %p, point to addr: %p, value: %v\n", &a.Ptr1, a.Ptr1, *a.Ptr1)
	fmt.Printf("main Ptr2 addr: %p, point to addr: %p, value: %v\n", &a.Ptr2, a.Ptr2, *a.Ptr2)
	fmt.Printf("main Val addr: %p, value: %v\n", &a.Val, a.Val)
	fmt.Printf("main Val2 addr: %p, value: %v\n", &a.Val2, a.Val2)
	fmt.Printf("main Val3 addr: %p, value: %v\n\n", &a.Val3, a.Val3)
	demo2(&a)
	fmt.Println("after demo2:")
	fmt.Printf("main Ptr1 addr: %p, point to addr: %p, value: %v\n", &a.Ptr1, a.Ptr1, *a.Ptr1)
	fmt.Printf("main Ptr2 addr: %p, point to addr: %p, value: %v\n", &a.Ptr2, a.Ptr2, *a.Ptr2)
	fmt.Printf("main Val addr: %p, value: %v\n", &a.Val, a.Val)
	fmt.Printf("main Val2 addr: %p, value: %v\n", &a.Val2, a.Val2)
	fmt.Printf("main Val3 addr: %p, value: %v\n\n", &a.Val3, a.Val3)
}

func demo1(a A) {
	fmt.Printf("demo1 a addr:%p\n", &a)
	// Update a value of a pointer and changes will persist
	a.Ptr1.Str = "demo1-ptr-str1"
	fmt.Printf("demo1 Ptr1 addr: %p, point to addr: %p, value: %v\n", &a.Ptr1, a.Ptr1, *a.Ptr1)
	// Use an entirely new B object and changes won't persist
	a.Ptr2 = &B{"demo1-ptr-str-2"}
	fmt.Printf("demo1 Ptr2 addr: %p, point to addr: %p, value: %v\n", &a.Ptr2, a.Ptr2, *a.Ptr2)
	//a.Val.Str = "new-val-str"
	a.Val = B{"demo1-val-str"}
	fmt.Printf("demo1 Val addr: %p, value: %v\n", &a.Val, a.Val)
	fmt.Printf("demo1 Val2 before addr: %p, value: %v\n", &a.Val2, a.Val2)
	a.Val2 = "demo1"
	fmt.Printf("demo1 Val2 after addr: %p, value: %v\n", &a.Val2, a.Val2)
	fmt.Printf("demo1 Val3 before addr: %p, value: %v\n", &a.Val3, a.Val3)
	a.Val3 = 1
	fmt.Printf("demo1 Val3 after addr: %p, value: %v\n", &a.Val3, a.Val3)
}
func demo2(a *A) {
	fmt.Printf("demo2 a addr:%p\n", &a)
	fmt.Printf("demo2 a point to addr:%p\n", a)
	//在go语言中通过指针去访问指针所对应的地址处的值时,*允许不写。
	a.Ptr1.Str = "demo2-ptr-str1"
	fmt.Printf("demo1 Ptr1 addr: %p, point to addr: %p, value: %v\n", &a.Ptr1, a.Ptr1, *a.Ptr1)
	a.Ptr2 = &B{"demo2-ptr-str-2"}
	fmt.Printf("demo2 Ptr2 addr: %p, point to addr: %p, value: %v\n", &a.Ptr2, a.Ptr2, *a.Ptr2)
	//a.Val.Str = "new-val-str"
	a.Val = B{"demo2-val-str"}
	fmt.Printf("demo2 Val addr: %p, value: %v\n", &a.Val, a.Val)
	a.Val2 = "demo2"
	fmt.Printf("demo2 Val2 addr: %p, value: %v\n", &a.Val2, a.Val2)
	a.Val3 = 2
	fmt.Printf("demo2 Val3 addr: %p, value: %v\n", &a.Val3, a.Val3)
}

输出

这种是针对复杂聚合类型中的参数传递,demo1可以说是值传递,demo2是指针传递,画图分析如下:

demo1

其实就是完整的拷贝了一份原结构体对象,只是原对象里面有指针对象,拷贝的时候一起拷贝了,值是一样的,也就指向了同一个地方,那么内部对该指向的地址的内容进行修改的话,肯定会影响外部;但是该指针可以修改成指向其他地方,此时和外部实参就没有关系了

在函数内对结构体内参数修改之后:【发生改动的地方用红色标出】

demo2

这里指针传递其实就是传递了指向实参对象的一个指针的值,那么对该指针指向地址的内容进行修改就相当于对原对象进行修改,而在go语言中通过指针去访问指针所对应的地址处的值时,*允许不写。

修改后

ok啦!应该算是完全搞懂啦!

欢迎分享,转载请注明来源:内存溢出

原文地址: http://outofmemory.cn/langs/990173.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2022-05-21
下一篇 2022-05-21

发表评论

登录后才能评论

评论列表(0条)

保存