函数入门


在 Go 中要定义函数,是使用func来定义,其基本格式如下:

func funcName(param1 type1, param2 type2) (return1 type1, return2 type2) {
    // 一些代码...
    return value1, value2
}

定义函数

可以看到,Go 定义函数时,参数的类型定义同样地是放在名称之后,如果多个参数有同样的类型,那么只要最右边同类型的名称右方加上类型就可以了,比较特别的地方在于,可以有两个以上的返回值,且返回值可以设定名称。

来看个简单的函数定义,以下是个求最大公因数的函数定义:

package main

import "fmt"

func Gcd(m, n int) int {
    if n == 0 {
        return m
    } else {
        return Gcd(n, m%n)
    }
}

func main() {
    fmt.Printf("Gcd of 10 and 4: %d\n", Gcd(10, 4)) // 2
}

当只有一个返回值且没有定义名称时,返回值的定义可以不用使用(),返回值的名称可以在函数中使用,返回值名称设定的值,会自动于函数return时返回,例如:

package main

import "fmt"

func Gcd(m, n int) (gcd int) {
    if n == 0 {
        gcd = m
    } else {
        gcd = Gcd(n, m%n)
    }
    return
}

func main() {
    fmt.Printf("Gcd of 10 and 4: %d\n", Gcd(10, 4)) // 2
}

官方的建议是要定义返回值名称,令程序可读性更高(当然程序会变得啰嗦一些),对那些公开给包外使用的函数(也就是首字大写的函数),最好是定义返回值名称。

多个返回值

Go 中允许多个返回值,例如,定义一个函数,可搜寻 slice 的元素中是否指定的子字符串,若有就返回元素索引位置与字符串,若无就返回 -1 与空字符串:

package main

import "fmt"
import "strings"

func FirstMatch(elems []string, substr string) (int, string) {
    for index, elem := range elems {
        if strings.Contains(elem, substr) {
            return index, elem
        }
    }
    return -1, ""
}

func main() {
    names := []string{"Justin Lin", "Monica Huang", "Irene Lin"}
    if index, name := FirstMatch(names, "Huang"); index == -1 {
        fmt.Println("找不到任何东西")
    } else {
        fmt.Printf("在索引 %d 找到 \"%s\"\n", index, name)
    }
}

返回多值时,指定给变量时必须依顺序,若不需要某个返回值,可以使用_略过:

_, name := FirstMatch(names, "Huang")

另一种多值返回的场合之一是错误处理,例如:

package main

import "fmt"
import "errors"

func Div(x, y int) (int, error) {
    if y == 0 {
        return 0, errors.New("division by zero")
    }
    return x / y, nil
}

func main() {
    if result, err := Div(10, 5); err == nil {
        fmt.Printf("10 / 5 = %d\n", result)
    } else {
        fmt.Println(err)
    }
}

若函数签署上有返回error,应透过检查其是否为nil来确认执行时是否有错误发生,这是 Go 的错误处理风格之一,例如,os.Open的函数签署是:

func Open(name string) (file *File, err error)

透过os.Open开启文件时的一个基本范例就是:

file, err := os.Open("file.go")
if err != nil {
    log.Fatal(err)
}

可变参数

在调用方法时,若方法的实参个数事先无法决定该如何处理?在 Go 中支持不定长度实参(Variable-length Argument),可以轻松的解决这个问题。直接来看示范:

package main

import "fmt"

func Sum(numbers ...int) int {
    var sum int
    for _, number := range numbers {
        sum += number
    }
    return sum
}

func main() {
    fmt.Println(Sum(1, 2))          // 3
    fmt.Println(Sum(1, 2, 3))       // 6
    fmt.Println(Sum(1, 2, 3, 4))    // 10
    fmt.Println(Sum(1, 2, 3, 4, 5)) // 15
}

可以看到,要使用不定长度实参,定义参数时要于类型关键字前加上...,此参数本质上是个 slice,因此可以使用for range来遍历元素,可接受可变长度的参数只能有一个,而必须是最后一个参数。

虽然可接受可变长度实参的参数,本质上是个 slice,然而,若已经有个 slice,并不能直接传递给它,而必须使用...展开,否则会发生错误:

package main

import "fmt"

func Sum(numbers ...int) int {
    var sum int
    for _, number := range numbers {
        sum += number
    }
    return sum
}

func main() {
    numbers := []int{1, 2, 3, 4, 5}
    fmt.Println(Sum(numbers...)) // 15
}

函数与指针

Go 语言有指针,因此,在变量传递就多了一种选择,直接来看个例子,以下的执行结果会显示 1:

package main

import "fmt"

func add1To(n int) {
    n = n + 1
}

func main() {
    number := 1
    add1To(number)
    fmt.Println(number) // 1
}

这应该没有问题,因为传递的是变量值n,函数中n的值加上 1 之后,再指定回给n,这对main中的number变量毫无影响,因此函数结束后,显示number的值,仍旧是 1。

那么来看下面这个例子:

package main

import "fmt"

func add1To(n *int) {
    *n = *n + 1
}

func main() {
    number := 1
    add1To(&number)
    fmt.Println(number) // 2
}

这次使用了&number获取number的地址值再传递给n,也就是传递了变量地址值n,函数中使用*n获取地址处的值,加上 1 后再将值存回原地址处,因此,透过main函数中的number获取的值,也会是加 1 后的值。


展开阅读全文