5. Go 函数
之前已经展示过几个函数,并且理所应当的应用了它们,也提到了函数的声明,并且简单的描述过函数的调用过程。在这一节将系统的展示 Go 函数的种种特性。
函数声明
Go 函数的声明方式为 func FuncName([parameterlist]) [returnlist] {}
,其中参数列表和返回值列表,在不需要的情况下,都是可以省略的。括号体内的为函数体。
参数列表,参数列表的声明方式为 parameter1 typeA, parameter2 typeB, ......
,如果有两个或者多个参数的 type
则可以合并声明,例如 parameter1 permeter2 ... typeA
,当然如果你需要很多个参数,那么就定义多个参数,如果不确定有多少个参数,那么 Go 其实还定义了变长参数列表,声明方式如 parameter1 typeA, parameter2 ...TypeB
, 如你所想,变长参数,所有可变的参数必须是同一种类型,且必须声明到最后一个参数(如果不这样,谁也不知道这个参数属于谁啊),变长参数列表的实际上 GO 将最后参数的所有数值变成了一个切片,最后通过循环的方式遍历切片。变长参数的好处在于当你担心一个传递一个切片被函数改变时,通过变长参数列表实际上传递了一个复制后的切片,保证原有切片的不可变。
返回值列表,返回列表的声明方式可选,如果不指明返回变量,就直接声明类型就好,(typeA, typeB)
; 如果指明返回值列表,那就类似于参数列表了,(Value1 typeA, Value2 typeB)
, 如果要是同类型,就是 (Value1, Value2 typeA)
,如果声明了返回值,那么在函数中 return Value1, Value2
就可以省略,因为函数知道要返回什么什么。
package main
import (
"fmt"
"math"
)
func main() {
radius := 3.0
C, A := circleCAndA(radius)
// 因为函数有两个返回值,所有可以有两个接收参数,
// 如果对第一个参数不感兴趣,可以使用 _ 代替就像下面的range一样,如果对第二个不感兴趣,那么直接不写就好
fmt.Println("A circle radius:", radius, "Area:", A, "Circ:", C)
fmt.Println("A pentagon with lengths 3, 4, 5, 6, 7, Circ is:", dist(3, 4, 5, 6, 7))
// 函数的返回值可以直接当成其他函数的参数
fmt.Println("A trangle with length 28, 34, 40, Circ is:", dist(28, 34, 40))
// 因为设计的dist是一个支持变长参数的函数,不管是多边形有几个边,都可以直接输入求其周长
polygon := []int{12, 3, 45, 18, 37, 98}
fmt.Println("A polygon with", len(polygon), "edge, has a circ:", dist(polygon...))
// 在这个调用中,对一个六边形,如果已经创建了切片,那么可以使用slice...的方式传递数值,Go会将切片中的数值传递给函数,生成一个新切片
}
func circleCAndA(r float64) (circ, area float64) {
circ = 2 * math.Pi * r
area = math.Pi * r * r
return
}
func dist(length ...int) int {
sum := 0
for _, edge := range length {
sum += edge
}
return sum
}
/*
A circle radius: 3 Area: 28.274333882308138 Circ: 18.84955592153876
A pentagon with lengths 3, 4, 5, 6, 7, Circ is: 25
A trangle with length 28, 34, 40, Circ is: 102
A polygon with 6 edge, has a circ: 213
*/
函数递归
上面谈到的函数的一些用法,还有一些用法需要注意一下。前面谈到,函数可以调用,例如 main()
函数可以调用其他函数,在其他函数中,依然可以继续调用其他函数或者自身。函数调用自身这种方式称之为递归。递归函数必须有退出条件,否则就会一直无限递归下去,后果就是系统资源被耗尽,然后死机,所以编写递归函数需要小心。下面是一个最为常见的递归函数求斐波那契数列。
// f(0) = 1
// f(1) = 1
// f(n) = f(n-1) + f(n-2) n>=2
func fibonacc(i int) int {
if i == 1 || i == 0 {
return 1
}
return fibonacc(i-1) + fibonacc(i-2)
}
for i := 0; i <= 20; i++ {
fmt.Printf("f(%d):\t%d\n", i, fibonacc(i))
}
/* Result
f(0): 1
f(1): 1
f(2): 2
f(3): 3
f(4): 5
f(5): 8
f(6): 13
f(7): 21
f(8): 34
f(9): 55
f(10): 89
f(11): 144
f(12): 233
f(13): 377
f(14): 610
f(15): 987
f(16): 1597
f(17): 2584
f(18): 4181
f(19): 6765
f(20): 10946
*/
函数别名和匿名函数
package main
import "fmt"
func main(){
fibo := func(i int) int {
if i == 1 || i == 0 {
return 1
}
return fibonacc(i-1) + fibonacc(i-2)
}
fmt.Printf("fibo(45):%d\n", fibo(45))
f := fibo
fmt.Printf("f(45):%d\n", f(45))
func(){
fmt.Println("This is Empty func")
}()
}
/* result
fibo(45):1836311903
f(45):1836311903
This is Empty func
*/
由于斐波那契序列定义非常简单,在这个版本的fibo中,直接将其定义在了main
中,甚至可以使用 f:=fibo
给函数起了一个别名,但调用方法相同,最后的函数g,直接在main
定义后加了一对圆括号后就被调用了,没有名称,称之为匿名函数。感觉到没有,其实函数也是一个数据结构类型,不同之处,它可以被调用执行。如何声明一个函数类型呢。type funcType func([parameterlist]) [returnlist]
,然后就可以使用变量一样,定义这个函数的内容了。var FuncName funcType func([parameterlist]) [returnlist] {}
来定义一组具有相同参数和返回值,但不同函数内容的函数组了。
基于此特性,当我们考虑使用一个算法的时候,可以将暂时无法确定的函数延迟定义。例如对于两种自定义的数据类型,Go 是不知道如何比较他们大小的,但只要你能定义出func compare(a,b Type) bool
就可以利用各种算法对新类型的数据进行高效排序了,而不用再重新实现一次算法。
函数作为参数或返回值
既然函数都可以作为数据结构一样来定义了,那么还有什么函数不能的吗。 函数可以作为函数的参数,像数据结构一样传递,更或者作为返回值。
func quickSort(s []int) []int {
if len(s) <= 1 {
return s
}
temp := s[0]
left := 0
right := len(s) - 1
for left != right {
for s[right] >= temp && right > left {
right--
}
if right > left {
s[left] = s[right]
}
for s[left] <= temp && right > left {
left++
}
if right > left {
s[right] = s[left]
}
}
s[left] = temp
quickSort(s[:left])
quickSort(s[left+1:])
return s
}
上面是一个int
类型的快速排序算法,当我们想对如下的学生按照年龄进行排序时,会将一个班的学生切片传递给快速排序算法,但显然上面的排序算法并不支持给学生排序,原因有1. 传递的切片类型不对(这是显然的)2. 即使修改类型为 s []student
也无法正常工作,因为 Go 不会比较两个student的年龄,此时需要传递一个比较函数
type sturdent struct {
name string
age int
}
type compare func (s1,s2 student) bool
var f compare = func (s1,s2 student) bool{
if s1.age >= s2.age{
return true
}
return false
}
func quick(s []student, compare func(s1,s2 student)bool){
//pass
}
由于 Go 语言可以满足了以下特性:1. 定义匿名函数;2. 将函数作为一个值; 3. 将函数作为一个参数; 3. 将函数作为一个返回值; 4. 定义高阶函数。那么也等同于说,Go 中函数是“一等公民” 。