• 爱上开源之golang入门至实战第四章函数(Func)(三)


    爱上开源之golang入门至实战第四章函数(Func)(三)

    4.4.3 按值传递(call by value) 按引用传递(call by reference)

    Go 默认使用按值传递来传递参数,也就是传递参数的副本。函数接收参数副本之后,在使用变量的过程中可能对副本 的值进行更改,但不会影响到原来的变量,比如 Function(arg1) 。 如果你希望函数可以直接修改参数的值,而不是对参数的副本进行操作,你需要将参数的地址(变量名前面添加&符 号,比如 &variable)传递给函数,这就是按引用传递,比如 Function(&arg1) ,此时传递给函数的是一个指针。 如果传递给函数的是一个指针,指针的值(一个地址)会被复制,但指针的值所指向的地址上的值不会被复制;我们 可以通过这个指针的值来修改这个值所指向的地址上的值。(指针也是变量类型,有自己的地址和值,通常 指针的值指向一个变量的地址。所以,按引用传递也是按值传递。)

     

     

    1. func exchange(a, b int) {
    2. var temp int
    3. fmt.Printf("Before exchanged: a=%d, b=%d\n", a, b)
    4. temp = b
    5. b = a
    6. a = temp
    7. fmt.Printf("After exchanged: a=%d, b=%d\n", a, b)
    8. }

    上方定义了一个exchange的函数,函数的传入参数是a,b两个int类型的数字, 函数交换两个参数的数值,这个函数的运行结果就可以充分的演示出按值传递的效果, 参数的参数都是副本, 所以执行结束以后,原有的a,b两个原值在函数体里都是在对副本进行的操作,函数体执行结束,a,b继续使用原值,所以执行完exchange以后,a,b值不会发生变化。

    1. a, b := 1, 2
    2. fmt.Printf("Before Func: a=%d, b=%d\n", a, b)
    3. exchange(a, b)
    4. fmt.Printf("After Func: a=%d, b=%d\n", a, b)
    5. ==== OUTPUT ====
    6. Before Func: a=1, b=2
    7. Before exchanged: a=1, b=2
    8. After exchanged: a=2, b=1
    9. After Func: a=1, b=2

    结果输出可以看到,在执行之前a=1, b=2; 当进入函数体,两个原值都是用副本, 所以输出 a=1, b=2; 交换两个副本值,输出a=2, b=1;函数体结束,副本销毁,a,b使用原值,所以输出原值a=1, b=2;

    我们可以改造一下exchange的函数体,来看看传入的两个参数的指针地址,是否和原来的一样。

    1. func exchange2(a, b int) {
    2. var temp int
    3. fmt.Printf("Before exchanged: a[%p]=%d, b[%p]=%d\n", &a, a, &b, b)
    4. temp = b
    5. b = a
    6. a = temp
    7. fmt.Printf("After exchanged: a[%p]=%d, b[%p]=%d\n", &a, a, &b, b)
    8. }

    1. a, b := 1, 2
    2. fmt.Printf("Before Func: a[%p]=%d, b[%p]=%d\n", &a, a, &b, b)
    3. exchange2(a, b)
    4. fmt.Printf("After Func: a[%p]=%d, b[%p]=%d\n", &a, a, &b, b)
    5. ==== OUTPUT ====
    6. Before Func: a[0xc0002f4e08]=1, b[0xc0002f4e10]=2
    7. Before exchanged: a[0xc0002f4e18]=1, b[0xc0002f4e20]=2
    8. After exchanged: a[0xc0002f4e18]=2, b[0xc0002f4e20]=1
    9. After Func: a[0xc0002f4e08]=1, b[0xc0002f4e10]=2

    通过这里的代码测试,我们可以看到在函数调用前,和函数体里,函数体使用的是副本,指针地址不是原地址。 在函数体执行完成以后,变量a,b的地址又重新恢复成以前的指针地址,这就是我们所说的按值传递, 参数传递的是值的副本(拷贝对象)。

    如果我们需要在上述的方法,确实一定要实现交换值的目标,我们可以通过传指针或者返回值覆盖原值的方式来实现; 代码如下:

    传递指针

    1. func exchange3(a, b *int) {
    2.   var temp int
    3.   fmt.Printf("Before exchanged: a[%p]=%d, b[%p]=%d\n", a, *a, b, *b)
    4.   temp = *b  
    5.   *b = *a  // 不能直接b = a 为什么?
    6.   *a = temp
    7.   fmt.Printf("After exchanged: a[%p]=%d, b[%p]=%d\n", a, *a, b, *b)
    8. }
    1. a, b := 1, 2
    2. fmt.Printf("Before Func: a[%p]=%d, b[%p]=%d\n", &a, a, &b, b)
    3. exchange3(&a, &b)
    4. fmt.Printf("After Func: a[%p]=%d, b[%p]=%d\n", &a, a, &b, b)
    5. ==== OUTPUT ====
    6. Before Func: a[0xc0002ece18]=1, b[0xc0002ece20]=2
    7. Before exchanged: a[0xc0002ece18]=1, b[0xc0002ece20]=2
    8. After exchanged: a[0xc0002ece18]=2, b[0xc0002ece20]=1
    9. After Func: a[0xc0002ece18]=2, b[0xc0002ece20]=1

    这段代码要细品;

    品味1: 如何完成值交换的? // 不能直接b = a 为什么?

    品味2:参数都换成了int类型的指针,注意点,指针的地址都是原来的地址?

    返回值覆盖原值

    1. func exchange4(a, b int) (int, int) {
    2.   var temp int
    3.   fmt.Printf("Before exchanged: a=%d, b=%d\n", a, b)
    4.   temp = b
    5.   b = a
    6.   a = temp
    7.   fmt.Printf("After exchanged: a=%d, b=%d\n", a, b)
    8.   return a, b
    9. }
    1. a, b := 1, 2
    2. fmt.Printf("Before Func: a=%d, b=%d\n", a, b)
    3. a, b = exchange4(a, b)
    4. fmt.Printf("After Func: a=%d, b=%d\n", a, b)
    5. ==== OUTPUT ====
    6. Before Func: a=1, b=2
    7. Before exchanged: a=1, b=2
    8. After exchanged: a=2, b=1
    9. After Func: a=2, b=1

    几乎在任何情况下,传递指针(一个32位或者64位的值)的消耗都比传递副本来得少。 调用都是更高效的调用,从基本上来说,至少传递指针的方式,只是一个32位或者64位值的复制,而值传递的方式,就是要对传递的对象进行值拷贝。基本数据类型差异可能不明显,但是对于结构体,或者是数据量大的数组而言,就有很大的差异了。

    在函数调用时,像切片(slice)、字典(map)、接口(interface)、函数(Func)、通道(channel)这样的引用类型都是默认使用引用传递(即使没有显式的指出指针)。

    1. func exchange5(a []int) {
    2.   var temp int
    3.   fmt.Printf("Before exchanged: Array=%v\n", a)
    4.   temp = a[1]
    5.   a[1] = a[0]
    6.   a[0] = temp
    7.   fmt.Printf("After exchanged: Array=%v\n", a)
    8. }

    1. a := make([]int, 0)
    2. a = append(a, 1, 2)
    3. fmt.Printf("Before Func: Array=%v\n", a)
    4. exchange5(a)
    5. fmt.Printf("After Func: Array=%v\n", a)
    6. ==== OUTPUT ====
    7. Before Func: Array=[1 2]
    8. Before exchanged: Array=[1 2]
    9. After exchanged: Array=[2 1]
    10. After Func: Array=[2 1]

     

    变量直接存储值,内存通常在栈中分配

     

    变量存储的是一个地址,这个地址对应的空间才真正存储数据(值); 内存通常在堆上分配,当没有任何变量引用这个地址时,该地址对应的数据空间就成为一个垃圾,由GC来回收

  • 相关阅读:
    部署你自己的导航站-dashy
    页面页脚部分CSS分享
    Greenplum报错could not fork new process for connection: Cannot allocate memory
    Modbus CRC
    【疑难】使用ARM development studio仿真 error:Failed to create Jython interpreter
    在centos7上搭建hadoop大数据平台
    vue_Delete `␍`eslint(prettier/prettier)
    用了TCP协议,就一定不会丢包吗?
    java StringReader类、StringWriter类
    Java反应式编程(1)
  • 原文地址:https://blog.csdn.net/inthirties/article/details/126462736