第五章 函数

2021-02-19  本文已影响0人  Benedict清水

5.1声明函数

普通函数需要声明才能调用。一个函数的声明包括参数和函数名等。

5.1.1 普通函数的声明形式

Go语言的函数声明以func标识,后面紧接着函数名、参数列表、返回参数列表以及函数体,具体形式如下:

func 函数名(参数列表) (返回参数列表){
    函数体
}
func foo(a int, b string)

其中,参数列表中的变量作为函数的局部变量而存在

5.1.2 参数类型的简写

在参数列表中,如有多个参数变量,则以逗号分隔;如果相邻变量是同类型,则可以将类型省略。例如:

func add(a,b int) int{
    return a + b
}

a和b都为int型,因此可以省略a的类型,在b的后面说明,这个类型也是a的类型。

5.1.3 函数的返回值

Go支持多个返回值,多返回值能方便的获得函数执行后的多个返回参数,Go语言经常使用多返回值的最后一个返回参数返回函数执行中可能发生的错误。例如:

conn,err := connectToNetwork()

在这段代码中,connectToNetwork返回两个参数,conn表示连接对象,err返回错误。

1. 同一种类型返回值

如果返回值是同一种类型,则用括号将多个返回值类型括起来,用逗号分隔每个返回值的类型。
使用return语句返回时,值列表的顺序需要与函数声明的返回值类型一致。示例代码如下:

func typedTwoValues() (int, int){
    return 1, 2
}
a, b := typedTwoValues()
fmt.Println(a, b)
2. 带有变量名的返回值

Go 语言支持对返回值进行命名,这样返回值就和参数一样拥有参数变量名和类型。命名的返回值变量的默认值为类型的默认值,即数值为0,字符串为空字符串,布尔为false,指针为nil等。

func namedRetValues() (a, b int) {
    a = 1
    b = 2
    return
}

代码说明:

func namedRetVlues() (a, b int){
    a = 1
    return a,2
}

5.1.4 调用函数

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

result := add(1,1)

5.1.5 函数中的参数传递

学习过其他编程语言的应该会知道函数参数有值传递引用传递两个概念,但Go语言中传入和返回参数在调用和返回时都是使用值传递。

1.在python中有值传递和引用传递,我们来看个例子:
def passFuncParam(strParam, listParam):
    strParam = "no san"
    listParam[0] = 'a'
    print("函数内部:")
    print("strVar:", strParam)
    print("lists:", listParam)


if __name__ == "__main__":
    strVar = "san"
    lists = [1, 2, 3]
    print("函数调用前:")
    print("strVar:", strVar)
    print("lists:", lists)
    passFuncParam(strVar, lists)
    print("函数调用后:")
    print("strVar:", strVar)
    print("lists:", lists)

结果如下:

函数调用前:
strVar: san
lists: [1, 2, 3]
函数内部:
strVar: no san
lists: ['a', 2, 3]
函数调用后:
strVar: san
lists: ['a', 2, 3]

我们可以看到strVar的值并没有改变,而在函数内部更改listParam的值后,函数外的lists的值也改变了。说明strVar是值传递,而lists是引用传递。
我们再看一个Go的例子:

package main

import "fmt"

type Data struct {
    complax []int
    instance InnerData
    ptr *InnerData
}

type InnerData struct {
    a int
}

func passByValue(inFunc Data) Data {
    fmt.Printf("inFunc value: %+v\n", inFunc)
    fmt.Printf("inFunc ptr:%p\n", &inFunc)
    return inFunc
}

func main()  {
    in := Data{
        complax: []int{1,2,3},
        instance: InnerData{a:5},
        ptr: &InnerData{1},
    }
    fmt.Printf("in value:%+v\n", in)

    fmt.Printf("in ptr:%p\n", &in)

    out := passByValue(in)

    fmt.Printf("out value: %+v\n", out)

    fmt.Printf("out ptr:%p\n", &out)
}

运行结果:

in value:{complax:[1 2 3] instance:{a:5} ptr:0xc00001c090}
in ptr:0xc000076180
inFunc value: {complax:[1 2 3] instance:{a:5} ptr:0xc00001c090}
inFunc ptr:0xc000076210
out value: {complax:[1 2 3] instance:{a:5} ptr:0xc00001c090}
out ptr:0xc0000761e0

5.2匿名函数

匿名函数没有函数名,只有函数体,函数可以被作为一种类型被赋值给函数类型的变量,匿名函数也往往以变量的方式被传递。匿名函数经常被用于实现回调函数、闭包等。

5.2.1 定义一个匿名函数

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

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

匿名函数的定义就是没有名字的普通函数的定义。

1.在定义时调用匿名函数

匿名函数可以在声明后调用,例如:

package main

import "fmt"

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

注意第三行“(100)”,表示对匿名函数进行调动,传递参数为100。

2.将匿名函数赋值给变量

匿名函数体可以被赋值,例如:

package main

import "fmt"

func main()  {
    f := func(data int) {
        fmt.Println("hello,", data)
    }
    f(100)
}

5.2.2 匿名函数用作回调函数

下面的代码实现对切片的遍历操作,遍历中访问每个元素的操作使用匿名函数来实现。

package main

import "fmt"

func visit(list []int, f func(int))  {
    for _, v := range list{
        f(v)
    }
}

func main()  {
    visit([]int{1,2,3,4}, func(v int) {
        fmt.Println(v)
    })
}

5.2.3 使用匿名函数实现操作封装

下面这段代码将匿名函数作为map的键值,通过命令行参数动态调用匿名函数。代码如下:

package main

import "fmt"
import "flag"

var skillParam = flag.String("skill", "", "skill to perform")

func main()  {
    flag.Parse()
    var skill = map[string]func(){
        "fire": func() {
            fmt.Println("chicken fire")
        },
        "run": func() {
            fmt.Println("soldier run")
        },
        "fly": func() {
            fmt.Println("angle fly")
        },
    }
    if f, ok := skill[*skillParam]; ok {
        f()
    } else {
        fmt.Println("skill not found")
    }
}
$go run main.go --skill=run
soldier run

5.3 延迟执行语句

Go语言的defer语句会将其后面跟随的语句进行延迟处理。在defer归属的函数即将返回时,将延迟处理的语句按defer的逆序进行执行,也就是说先被defer的语句最后执行,最后的defer的语句,最先被执行。

5.3.1 多个延迟执行语句的处理顺序

下面的代码是将一些列的数值打印语句按顺序延迟处理,如下:

package main

import "fmt"

func main()  {
    fmt.Println("defer begin")

    defer fmt.Println(1)
    defer fmt.Println(2)
    defer fmt.Println(3)
    fmt.Println("defer end")
}

运行结果如下:

defer begin
defer end
3
2
1

5.3.2使用延迟执行语句在函数退出时释放资源

1. 使用延迟并发解锁

map默认并不是并发安全的,准备一个sync.Mutex互斥量保护map的访问。

package main

import (
    "fmt"
    "sync"
)

var valueByKey = make(map[string]int)

var valueByKeyGuard sync.Mutex

func readValue(key string) int {
    valueByKeyGuard.Lock()

    v := valueByKey[key]

    valueByKeyGuard.Unlock()

    return v
}

func main()  {
    valueByKey["first"] = 1
    valueByKey["second"] = 2
    var readKey = "first"
    fmt.Println(readValue(readKey))
}

下面使用defer语句对上面的语句进行简化。

func readValue(key string) int {
    valueByKeyGuard.Lock()
    defer valueByKeyGuard.Unlock()
    return valueByKey[key]
}
2. 使用延迟释放文件句柄

文件的操作需要经过打开文件、获取和操作文件资源、关闭资源几个过程,如果再操作完毕后不关闭文件资源,进程将一直无法释放文件资源。

package main

import (
    "fmt"
    "os"
)

func fileSize(filename string) int64 {
    f, err := os.Open(filename)
    // 如果打开时发生错误,返回文件大小为0
    if err != nil {
        return 0
    }
    defer f.Close()
    // 取文件状态信息
    info, err := f.Stat()
    // 如果获取信息时发生错误,关闭文件并返回文件大小为0
    if err != nil{
        // defer机制触发,调用close关闭文件
        return 0
    }
    // 取文件大小
    size := info.Size()
    //关闭文件
    // defer机制触发,调用close关闭文件
    //返回文件大小
    return size
}
func main()  {
    size := fileSize("./ch02")
    fmt.Println(size)
}

5.4 处理运行时发生的错误

error类型是go语言的一种内置类型,使用的时候不用特定去import。是Go系统声明的接口类型,代码如下:

type error interface{
    Error() string
}

Error()方法返回错误的具体描述,使用者可以通过这个字符串知道发生了什么错误。

5.4.1自定义一个错误。

在Go语言中,使用errors包进行错误的定义,格式如下:

var err = errors.New("This is an error")
在代码中使用错误定义

下面的代码会定义一个除法函数,当除数为0时,返回一个预定义的除数为0的错误。

package main

import "errors"
import "fmt"

var errDivisionByZero = errors.New("division by zero")

func div(dividend, divisor int) (int,error) {
    // 判断除数为0的情况返回
    if divisor == 0 {
        return 0, errDivisionByZero
    }
    // 正常计算,返回空错误
    return dividend / divisor, nil
}

func main()  {
    fmt.Println(div(1,0))
}

运行结果:

$ go run main.go
0 division by zero
上一篇 下一篇

猜你喜欢

热点阅读