按值传参和引用传参

go中默认按值来进行参数传递,就是传递传入参数的一个副本。函数只对副本操作,不会对原值有任何影响。

  1. package main
  2. import "fmt"
  3. func add(a, b int) int {
  4. a *= 2
  5. b *= 3
  6. return a + b
  7. }
  8. func main() {
  9. x, y := 1, 2
  10. z := add(x, y)
  11. fmt.Printf("add(%d, %d) = %d\n", x, y, z)
  12. }

当我们把 x、y 变量作为参数传递到 add 函数时,这两个变量会拷贝出一个副本赋值给 a、b 变量作为参数,因此,在 add 函数中调整 a、b 变量的值并不会影响原变量 x、y 的值,所以上述代码的输出是:

add(1, 2) = 8

如果想要实在在函数中修改原有参数的值,可以通过引用传参来完成。传入函数内的不再是参数的服务。而是传递存储有变量值地址的指针。所以原变量的值也会被修改(这种情况下,传递的是变量地址值的拷贝,所以从本质上来说还是按值传参):

package main

import "fmt"

func add(a, b *int) int {
    *a *= 2
    *b *= 3
    return *a + *b
}

func main() {
    x, y := 1, 2
    z := add(&x, &y)
    fmt.Printf("add(%d, %d) = %d\n", x, y, z)
}

在函数调用时,像切片(slice)、字典(map)、接口(interface)、通道(channel)这样的引用类型 默认使用引用传参(即使没有显示的指出指针,类似 PHP 中把对象实例作为函数参数)。

多返回值及返回值命名

Go 语言函数与其他编程语言一大不同之处在于支持多返回值,这在处理程序出错的时候非常有用,比如,如果上述 add 函数只支持非负整数相加,传入负数则会报错,换做是在其他语言如 PHP 中,我们需要对返回结果做各种判断,才能实现预期的效果,在 Go 语言中,只需要通过在返回值中多返回一个错误信息即可:

package main

import (
    "errors"
    "fmt"
)

func add1(a, b *int) (int, error) {
    if *a < 0 || *b < 0 {
        err := errors.New("error1")
        return 0, err
    }
    *a *= 2
    *b *= 3
    return *a + *b, nil
}

func add(a, b *int) int {
    *a *= 2
    *b *= 3
    return *a + *b
}

func main() {
    x, y := 1, 2
    z := add(&x, &y)
    fmt.Printf("add(%d, %d) = %d\n", x, y, z)

    z, err := add1(&x, &y)
    if err != nil {
        fmt.Println(err.Error())
        return
    }
    fmt.Printf("add(%d, %d) = %d\n", x, y, z)
}

此外,在设置多返回值时,还可以对返回值进行变量命名,这样,我们就可以在函数中直接对返回值变量进行赋值,而不必每次都按照指定的返回值格式返回多个变量了:

func add2(a, b *int) (c int, err error) {
    if *a < 0 || *b < 0 {
        err := errors.New("error1")
        return 0, err
    }
    *a *= 2
    *b *= 3
    c = *a + *b
    return
}

这种机制避免了每次进行 return 操作时都要关注函数需要返回哪些返回值,为开发者节省了精力,尤其是在复杂的函数中。