Go 函数

admin
admin 2020年03月15日
  • 在其它设备中阅读本文章

函数 是组织好的、可重复使用的、用来实现单一或相关联功能的代码段,其可以提高应用的模块性和代码的重复利用率。Go 语言支持普通函数、匿名函数和闭包,从设计上对函数进行了优化和改进,让函数使用起来更加方便。
在 Go 语言中,函数的基本组成为:关键字 func、函数名、参数列表、返回值、函数体和返回语句,每一个程序都包含很多的函数,函数是基本的代码块。
编写多个函数的主要目的是将一个需要很多行代码的复杂问题分解为一系列简单的任务来解决,而且,同一个任务(函数)可以被多次调用,有助于代码重用(事实上,好的程序是非常注意 DRY 原则的,即不要重复你自己(Don't Repeat Yourself),意思是执行特定任务的代码只能在程序里面出现一次)。

一、函数相关概念

1、函数签名(function signature)和函数类型

一个 函数类型 的字面表示形式由一个 func 关键字和一个 函数签名 字面表示表示形式组成。一个 函数签名 由一个 输入参数类型列表 和一个 输出结果类型列表 组成。参数名称和结果名称可以出现函数签名的字面表示形式中,但是它们并不重要。

func (int, string, string) (int, int, bool) // 标准函数字面形式
func (a int, b string, c string) (int, int, bool)
func (x int, _ string, z string) (int, int, bool)
func (int, string, string) (x int, y int, z bool)
func (int, string, string) (a int, b int, _ bool)

上面列出的函数类型字面形式表示同一个函数类型。参数列表必须用一对小括号 () 括起来,即使此列表为空。如果一个函数类型一个结果列表为空,则它可以在函数类型的字面形式中被省略掉。当一个结果列表含有最多一个结果,则此结果列表的字面形式在它不包含结果名称的时候可以不用括号 () 括起来。

// 这三个函数类型字面形式是等价的。
func () (x int)
func () (int)
func () int
// 这两个函数类型字面形式是等价的。
func (a int, b string) ()
func (a int, b string)

2、变长参数和变长参数函数类型

一个函数的最后一个参数可以是一个 变长参数。一个函数可以最多有一个变长参数。一个变长参数的类型总为一个切片类型。变长参数在声明的时候必须在它的(切片)类型的元素类型前面前置三个点…

func (values ...int64) (sum int64)
func (sep string, tokens ...string) string

一个变长函数类型和一个非变长函数类型绝对不可能是同一个类型。

3、函数原型(function prototype)

一个 函数原型 由一个 函数名称 和一个 函数类型 组成。它的字面形式由一个 func 关键字、一个函数名 和一个 函数签名 字面形式组成。

func Double(n int) (result int)

换句话说,一个函数原型可以看作是一个不带函数体的函数声明;或者说一个函数声明由一个函数原型和一个函数体组成。

4、函数值

函数类型的值称为 函数值 。在字面上,函数类型的零值也使用预定义的 nil 来表示。 内置函数和 init 函数不可被用做函数值
当我们声明了一个函数的时候,我们实际上同时声明了一个不可修改的函数值。此函数值用此函数的名称来标识。此函数值的类型的字面表示形式为此函数的原型刨去函数名部分。
任何函数值都可以被当作普通声明函数来调用。调用一个 nil 函数来开启一个协程将产生一个致命的不可恢复的错误,此错误将使整个程序崩溃。在其它情况下调用一个 nil 函数将产生一个可恢复的恐慌。

二、函数特性

1、同一个包中可以同名的函数

一般来说,同一个包中声明的函数的名称不能重复,但有两个例外:
同一个包内可以声明若干个原型为 func ()的名称为 init 的函数。
多个函数的名称可以被声明为空标识符_。这样声明的函数不可被调用。

2、某些函数调用是在编译时刻被估值的

大多数函数调用都是在运行时刻被估值的。但 unsafe 标准库包中的函数的调用都是在编译时刻估值的。另外,某些其它内置函数(比如 len 和 cap 等)的调用在所传实参满足一定的条件的时候也将在编译时刻估值。详见在编译时刻估值的函数调用。

3、所有的函数调用的传参均属于值复制

和赋值一样,传参也属于值复制。

4、不含函数体的函数声明

我们可以使用 Go 汇编(Go assembly)来实现一个 Go 函数。Go 汇编代码放在后缀为.a 的文件中。一个使用 Go 汇编实现的函数依旧必须在一个 *.go 文件中声明,但是它的声明必须不能含有函数体。换句话说,一个使用 Go 汇编实现的函数的声明中只含有它的原型。

5、某些有返回值的函数可以不必返回

如果一个函数有返回值,则它的函数体内的最后一条语句必须为一条终止语句。Go 中有多种终止语句,return 语句只是其中一种。所以一个有返回值的函数的体内不一定需要一个 return 语句。

6、某些函数调用的返回结果不可被舍弃

自定义函数的调用结果都是可以被舍弃掉的。大多数内置函数(除了 recover 和 copy)的调用结果都是不可被舍弃的。调用结果不可被舍弃的函数是不可以被用做延迟调用函数和协程起始函数的,比如 append 函数。

7、有返回值的函数的调用是一种表达式

一个有且只有一个返回值的函数的每个调用总可以被当成一个单值表达式使用。比如,它可以被内嵌在其它函数调用中当作实参使用,或者可以被当作其它表达式中的操作数使用。 如果一个有多个返回结果的函数的一个调用的返回结果没有被舍弃,则此调用可以当作一个多值表达式使用在两种场合:

  • 此调用可以在一个赋值语句中当作源值来使用,但是它不能和其它源值掺和到一块。
  • 此调用可以内嵌在另一个函数调用中中当作实参来使用,但是它不能和其它实参掺和到一块。

三、普通函数声明(定义)和调用

1、普通函数声明

函数声明包括函数名、形式参数列表、返回值列表(可省略)以及函数体。

func 函数名(形式参数列表)(返回值列表){
    函数体
}

形式参数列表描述了函数的参数名以及参数类型,这些参数作为局部变量,其值由参数调用者提供,返回值列表描述了函数返回值的变量名以及类型,如果函数返回一个无名变量或者没有返回值,返回值列表的括号是可以省略的。
每一次函数在调用时都必须按照声明顺序为所有参数提供实参(参数值),在函数调用时,Go 语言没有默认参数值,也没有任何方法可以通过参数名指定形参,因此形参和返回值的变量名对于函数调用者而言没有意义。
在函数中,实参通过值传递的方式进行传递,因此函数的形参是实参的拷贝,对形参进行修改不会影响实参,但是,如果实参包括引用类型,如指针、slice(切片)、map、function、channel 等类型,实参可能会由于函数的间接引用被修改。

func add(x, y int) int {
    return x + y
}

2、调用函数

函数在定义后,可以通过调用的方式,让当前代码跳转到被调用的函数中进行执行,调用前的函数局部变量都会被保存起来不会丢失,被调用的函数运行结束后,恢复到调用函数的下一行继续执行代码,之前的局部变量也能继续访问。

函数内的局部变量只能在函数体中使用,函数调用结束后,这些局部变量都会被释放并且失效。

Go 语言的函数调用格式如下:
返回值变量列表 = 函数名 (参数列表)

下面是对各个部分的说明:
函数名:需要调用的函数名。
参数列表:参数变量以逗号分隔,尾部无须以分号结尾。
返回值变量列表:多个返回值使用逗号分隔。

例如,加法函数调用样式如下:

result := add(1,1)

四、匿名函数

Go 语言支持匿名函数,即在需要使用函数时再定义函数,匿名函数没有函数名只有函数体,函数可以作为一种类型被赋值给函数类型的变量,匿名函数也往往以变量方式传递,这与 C 语言的回调函数比较类似,不同的是,Go 语言支持随时在代码里定义匿名函数。匿名函数是指不需要定义函数名的一种函数实现方式,由一个不带函数名的函数声明和函数体组成。

1、定义一个匿名函数

匿名函数的定义格式如下:

func(参数列表)(返回参数列表){
    函数体
}

匿名函数的定义就是没有名字的普通函数定义。
1) 在定义时调用匿名函数
匿名函数可以在声明后调用,例如:

func(data int) {
    fmt.Println("hello", data)
}(100)

注意函数定义后的 (100),表示对匿名函数进行调用,传递参数为 100。
2) 将匿名函数赋值给变量
匿名函数可以被赋值,例如:

// 将匿名函数体保存到f()中
f := func(data int) {
    fmt.Println("hello", data)
}
// 使用f()调用
f(100)

匿名函数的用途非常广泛,它本身就是一种值,可以方便地保存在各种容器中实现回调函数和操作封装。