16.1 函数介绍

Go语言函数基本组成:关键字func、函数名、参数列表、返回值、函数体和返回语句。
func 函数名(参数列表) (返回值列表) {
    // 函数体
return
}

除了main()init()函数外,其它所有类型的函数都可以有参数与返回值。对于函数,一般也可以这么认为:"func" FunctionName Signature [ FunctionBody ] 。"func" 为定义函数的关键字,FunctionName 为函数名,Signature 为函数签名,FunctionBody 为函数体。以下面定义的函数为例:

func FunctionName (a typea, b typeb) (t1 type1, t2 type2)

函数签名由函数参数、返回值以及它们的类型组成,被统称为函数签名。如:

(a typea, b typeb) (t1 type1, t2 type2)

如果两个函数的参数列表和返回值列表的变量类型能一一对应,那么这两个函数就有相同的签名,下面testa与testb具有相同的函数签名。

func testa  (a, b int, z float32) bool
func testb  (a, b int, z float32) (bool)

函数调用传入的参数必须按照参数声明的顺序。而且Go语言没有默认参数值的说法。函数签名中的最后传入参数可以具有前缀为....的类型(...int),这样的参数称为可变参数,并且可以使用零个或多个参数来调用该函数,这样的函数称为变参函数。

func doFix (prefix string, values ...int)

函数的参数和返回值列表始终带括号,但如果只有一个未命名的返回值(且只有此种情况),则可以将其写为未加括号的类型;一个函数也可以拥有多返回值,返回类型之间需要使用逗号分割,并使用小括号 () 将它们括起来。

func testa  (a, b int, z float32) bool
func swap  (a int, b int) (t1 int, t2 int)

函数体中,参数是局部变量,被初始化为调用者传入的值。函数的参数和具名返回值是函数最外层的局部变量,它们的作用域就是整个函数。如果函数的签名声明了返回值,则函数体的语句列表必须以终止语句结束。

func IndexRune(s string, r rune) int {
	for i, c := range s {
		if c == r {
			return i
		}
	}
	return // 必须要有终止语句,如果这里没有return,则会编译错误:missing return at end of function
}

函数重载(function overloading)指的是可以编写多个同名函数,只要它们拥有不同的形参或者不同的返回值,在 Go 语言里面函数重载是不被允许的。

函数也可以作为函数类型被使用。函数类型也就是函数签名,函数类型表示具有相同参数和结果类型的所有函数的集合。函数类型的未初始化变量的值为nil。就像下面:

type  funcType func (int, int) int

上面通过type关键字,定义了一个新类型,函数类型 funcType 。

函数也可以在表达式中赋值给变量,这样作为表达式中右值出现,我们称之为函数值字面量(function literal),函数值字面量是一种表达式,它的值被称为匿名函数,就像下面一样:

f := func() int { return 7 }  

下面代码对以上2种情况都做了定义和调用:

package main
import (
	"fmt"
	"time"
)
type funcType func(time.Time)     // 定义函数类型funcType
func main() {
	f := func(t time.Time) time.Time { return t } // 方式一:直接赋值给变量
	fmt.Println(f(time.Now()))
	var timer funcType = CurrentTime // 方式二:定义函数类型funcType变量timer
	timer(time.Now())
	funcType(CurrentTime)(time.Now())  // 先把CurrentTime函数转为funcType类型,然后传入参数调用
// 这种处理方式在Go 中比较常见
}
func CurrentTime(start time.Time) {
	fmt.Println(start)
}
下一节:Go 语言中函数默认使用按值传递来传递参数,也就是传递参数的副本。函数接收参数副本之后,在使用变量的过程中可能对副本的值进行更改,但不会影响到原来的变量。