在 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 后的值。