5. Go 函数

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 中函数是“一等公民” 。

你可能感兴趣的:(5. Go 函数)