Go语言学习(六)| 函数

mervyn 2018年7月1日12:42:59编程语言 GoGo语言学习(六)| 函数已关闭评论1132

目录

函数的定义

关键字 func 用来声明一个函数文章源自编程技术分享-https://mervyn.life/d0022d6a.html

func functionName(parameter type) returnType {
    // 函数体
}

函数中的参数列表和返回值并非是必须的文章源自编程技术分享-https://mervyn.life/d0022d6a.html

func functionName(parameter type) {
    // 函数体
}

如果有连续若干个参数,它们的类型一致,那么我们无须一一罗列,只需在最后一个参数后添加该类型。例:文章源自编程技术分享-https://mervyn.life/d0022d6a.html

func calc(num, price int) int {
    return price * num
}

多返回值文章源自编程技术分享-https://mervyn.life/d0022d6a.html

Go 语言支持一个函数可以有多个返回值括号,同时可以命名返回值。一旦命名了返回值,可以认为这些值在函数第一行就被声明为变量了。
如果有连续若干个返回值,它们的类型一致,那么我们无须一一罗列,只需在最后一个返回值后添加该类型。文章源自编程技术分享-https://mervyn.life/d0022d6a.html

例:文章源自编程技术分享-https://mervyn.life/d0022d6a.html

package main

func main() {
    s := Factorial(4)
    println(s)
}
func Factorial(x int) int {
    var result int
    if x == 0 {
        result = 1
    } else {
        result = x * Factorial(x-1)
    }
    return result
}

也可以上述例子改为:
例:文章源自编程技术分享-https://mervyn.life/d0022d6a.html

package main

func main() {
    s := Factorial(4)
    println(s)
}
func Factorial(x int) (result int) {
    if x == 0 {
        result = 1
    } else {
        result = x * Factorial(x-1)
    }
    return
}
官方建议:最好命名返回值,因为不命名返回值,虽然代码更加简洁了,但是会造成生成的文档可读性差。

保留函数

  • init 函数(能够应用于所有的package)
  • main 函数(只能应用于package main)。

这两个函数在定义时不能有任何的参数和返回值。Go程序会自动调用 init()main() ,所以你不需要在任何地方调用这两个函数。
每个 package 中的 init 函数都是可选的,但 package main 就必须包含一个main函数。文章源自编程技术分享-https://mervyn.life/d0022d6a.html

变参

接受变参的函数是有着不定数量的参数的。为了做到这点,首先需要定义函数使其接受变参:文章源自编程技术分享-https://mervyn.life/d0022d6a.html

func funcName(arg ...int) {

}

arg ... int 告诉 Go 这个函数接受不定数量的参数。文章源自编程技术分享-https://mervyn.life/d0022d6a.html

注意 这些参数的类型全部是 int。在函数体中,变量 arg 是一个 int 类型的 slice文章源自编程技术分享-https://mervyn.life/d0022d6a.html

例:文章源自编程技术分享-https://mervyn.life/d0022d6a.html

package main

import "fmt"

func main() {
    test("a", "b")
    test("Hello", "world", "你好")
}
func test(arg ...string) {
    for _, val := range arg {
        fmt.Printf("%s\n", val)
    }
}

函数作为值

上例也可以这样:文章源自编程技术分享-https://mervyn.life/d0022d6a.html

package main

import "fmt"

func main() {
    demo := func(arg ...string) { //定义一个匿名函数,并且赋值给demo
        for _, val := range arg {
            fmt.Printf("%s\n", val)
        }
    }
    demo("a", "b")
}

函数作为值也可以用在其他地方,如 map
例:文章源自编程技术分享-https://mervyn.life/d0022d6a.html

package main

import "fmt"

func main() {
    map1 := map[int]func(i int) int{
        1: func(x int) int { return x + 1 },
        2: func(y int) int { return y + 2 },
        3: func(z int) int { return z + 3 },
    }
    fmt.Printf("%d\n", map1[1](1))
    fmt.Printf("%d\n", map1[2](1))
    fmt.Printf("%d\n", map1[3](1))
}

回调函数

例:文章源自编程技术分享-https://mervyn.life/d0022d6a.html

package main

import "fmt"

func main() {
    callback(2, printit)
}
func printit(x int) {
    fmt.Printf("%d\n", x)
}

func callback(y int, f func(int)) {
    f(y)
}

输出结果:2文章源自编程技术分享-https://mervyn.life/d0022d6a.html

例2:文章源自编程技术分享-https://mervyn.life/d0022d6a.html

package main

import "fmt"

func main() {
    callback(2, printit)
}
func printit(x int) (demo int) {
    demo = x + 1
    return
}

func callback(y int, f func(int) int) {
    result := f(y)
    res := fmt.Sprintf("%d", result) + "demo" // Sprintf()将int转换成string
    fmt.Printf("%s\n", res)
}

输出结果:
3demo文章源自编程技术分享-https://mervyn.life/d0022d6a.html

传值和传指针

例3:文章源自编程技术分享-https://mervyn.life/d0022d6a.html

package main

import "fmt"

//简单的一个函数,实现了参数+1的操作
func add1(a *int) int { // 请注意,
    *a = *a + 1 // 修改了a的值
    return *a   // 返回新值
}
func main() {
    x := 3
    fmt.Println("x = ", x)    // 应该输出 "x = 3"
    x1 := add1(&x)            // 调用 add1(&x) 传x的地址
    fmt.Println("x+1 = ", x1) // 应该输出 "x+1 = 4"
    fmt.Println("x = ", x)    // 应该输出 "x = 4"
}

变量在内存中是存放于一定地址上的,修改变量实际是修改变量地址处的内存。只有 add1 函数知道 x 变量所在的地址,才能修改 x 变量的值。所以我们需要将x所在地址 &x 传入函数,并将函数的参数的类型由int改为 *int ,即改为指针类型,才能在函数中修改 x 变量的值。此时参数仍然是按 copy 传递的,只是 copy 的是一个指针。文章源自编程技术分享-https://mervyn.life/d0022d6a.html

函数传递指针的好处:文章源自编程技术分享-https://mervyn.life/d0022d6a.html

  • 传指针使得多个函数能操作同一个对象。
  • 传指针比较轻量级 (8bytes),只是传内存地址,我们可以用指针传递体积大的结构体。如果用参数值传递的话, 在每次 copy 上面就会花费相对较多的系统开销(内存和时间)。所以当你要传递大的结构体的时候,用指针是一个明智的选择。

Go语言中 stringslicemap 这三种类型的实现机制类似指针,所以可以直接传递,而不用取地址后传递指针。(注:若函数需改变 slice 的长度,则仍需要取地址传递指针)文章源自编程技术分享-https://mervyn.life/d0022d6a.html

延迟代码 defer

package main

func ReadWrite() bool {
    file.Open("file")
    // 做一些工作
    if failureX {
        file.Close()
        return false
    }
    if failureY {
        file.Close()
        return false
    }
    file.Close()
    return true
}

在这里有许多重复的代码。为了解决这些,Go 有了 defer 语句。在 defer 后指定的函数会在函数退出前调用。可改为如下代码:文章源自编程技术分享-https://mervyn.life/d0022d6a.html

package main

func ReadWrite() bool {
    file.Open("file")
    defer file.Close() //file.Close() 被添加到了 defer 列表
    // 做一些工作

    if failureX {
        return false // Close() 现在自动调用
    }

    if failureY {
        return false // 这里Close() 也将自动调用
    }

    return true
}

defer 有点类似于 PHP 类中的 __destruct() 析构方法。 defer 也可以这样写:文章源自编程技术分享-https://mervyn.life/d0022d6a.html

defer func(x int) {
    //
}(5)

如果有很多调用 defer ,那么 defer 是采用后进先出模式文章源自编程技术分享-https://mervyn.life/d0022d6a.html

package main

func main() {
    for j := 0; j < 5; j++ {
        defer println(j)
    }
}

输出:
4
3
2
1
0文章源自编程技术分享-https://mervyn.life/d0022d6a.html

例:文章源自编程技术分享-https://mervyn.life/d0022d6a.html

package main

func main() {
    s := test(2)
    println(s)
}
func test(i int) (t int) {
    defer func() {
        t++
    }()
    t = i + 1
    return
}

输出结果:4文章源自编程技术分享-https://mervyn.life/d0022d6a.html

例2:文章源自编程技术分享-https://mervyn.life/d0022d6a.html

package main

func main() {
    s := test(2)
    println(s)
}
func test(i int) (t int) {
    defer func(x int) {
        t += x
    }(5)
    t = i + 1
    return
}

输出结果:8文章源自编程技术分享-https://mervyn.life/d0022d6a.html

在 Go 中函数也是一种变量,可以通过 type 来定义它,它的类型就是所有拥有相同的参数,相同的返回值的一种类型文章源自编程技术分享-https://mervyn.life/d0022d6a.html

例:文章源自编程技术分享-https://mervyn.life/d0022d6a.html

package main

import "fmt"

type testInt func(int) bool //声明了一个函数类型
func main() {
    slice := []int{1, 2, 4, 5, 7}
    fmt.Println("slice = ", slice)
    odd := filter(slice, isOdd) //函数当做值来传递了
    fmt.Println("Odd elements of slice are: ", odd)
    even := filter(slice, isEven) //函数当做值来传递了
    fmt.Println("Even elements of slice are: ", even)
}

func isOdd(integer int) bool {
    if integer%2 == 0 {
        return false
    }
    return true
}

func isEven(integer int) bool {
    if integer%2 == 0 {
        return true
    }
    return false
}

//声明的函数在这个地方当做了一个参数
func filter(slice []int, f testInt) []int {
    var result []int
    for _, value := range slice {
        if f(value) {
            result = append(result, value)
        }
    }
    return result
}

返回结果:
slice = [1 2 3 4 5 7]
Odd elements of slice are: [1 3 5 7]
Even elements of slice are: [2 4]文章源自编程技术分享-https://mervyn.life/d0022d6a.html

Panic和Recover

panic

panic 是用来表示非常严重的不可恢复的错误的。
在 Go 语言中这是一个内置函数,接收一个 interface{} 类型的值(也就是任何值了)作为参数。
panic 的作用就像我们平常接触的异常。不过Go可没有 try…catch ,所以, panic 一般会导致程序挂掉(除非 recover )。所以,Go语言中的异常,那真的是异常了。
但是,关键的一点是,即使函数执行的时候panic了,函数不往下走了,运行时并不是立刻向上传递 panic ,而是到defer那,等defer的东西都跑完了, panic 再向上传递。所以这时候 defer 有点类似 try-catch-finally 中的 finally。文章源自编程技术分享-https://mervyn.life/d0022d6a.html

recover

Go语言提供了 recover 内置函数,前面提到,一旦 panic ,逻辑就会走到 defer 那,那我们就在 defer 那等着,调用 recover 函数将会捕获到当前的 panic (如果有的话),被捕获到的 panic 就不会向上传递了,于是,世界恢复了和平。你可以干你想干的事情了。文章源自编程技术分享-https://mervyn.life/d0022d6a.html

不过要注意的是, recover 之后,逻辑并不会恢复到 panic 那个点去,函数还是会在 defer 之后返回。文章源自编程技术分享-https://mervyn.life/d0022d6a.html

weinxin
我的微信公众号
微信扫一扫
mervyn
go中string和int、floatx相互转换 Go

go中string和int、floatx相互转换

日常开发中经常用到字符串和数字之间的相关转换,下面总结下常用的类型转换方式。 字符串转数字 string转int i, err := strconv.Atoi(str) if err != nil{ ...
python多版本及依赖包管理 Python

python多版本及依赖包管理

本文主要讲述如何通过 pyenv 来管理不同版本的 python ,以及如何使用 Pipenv 在同一个python版本实现项目之间依赖包的隔离。 pyenv Linux下安装 curl https:...
PHP 将16进制字符转换成汉字 PHP

PHP 将16进制字符转换成汉字

项目代码提供给外部的api,有些参数是中文的。发现有些客户在请求接口的时候,参数的值被转成了16进制,从而导致接口无法正常解析。 此时可以采用如下方法进行转移: <?php $param = &...
Go 方法指针接收者和值接收者 Go

Go 方法指针接收者和值接收者

Go 语言可以给自定义的类型添加一个方法。这里的方法其实也是函数,跟函数的区别在于在 func 关键字和函数名中间增加了一个参数,可以认为该类型也作为了参数传递入了函数中,例: package mai...