【go语言学习】函数function

2020-09-22  本文已影响0人  Every_dawn

函数是组织好的、可重复使用的、用于执行指定任务的代码块。
Go语言中支持函数、匿名函数和闭包,并且函数在Go语言中属于“一等公民”。

一、函数的声明和调用

1、函数的声明

Go语言中定义函数使用func关键字,具体格式如下:

func funcName(parametername type) (output type) {
    //这里是处理逻辑代码
    //返回多个值
    return valu
}
2、函数的调用

可以通过funcName(parameter)的方式调用函数。调用有返回值的函数时,可以不接收其返回值。

package main

import "fmt"

func main() {
    a := 10
    b := 20
    res := sum(a, b)
    fmt.Printf("%v + %v = %v", a, b, res)
}

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

运行结果

10 + 20 = 30

二、函数的参数

1、参数的使用:

形式参数:定义函数时,用于接收外部传入的数据,叫做形式参数,简称形参。
实际参数:调用函数时,传给形参的实际的数据,叫做实际参数,简称实参。
函数调用:

package main

import "fmt"

func main() {
    a := 10
    b := 20
    res := add(a, b)
    fmt.Printf("%v + %v = %v", a, b, res)
}

func add(a, b int) sum int {
    sum = a + b
    return
}
2、可变参数

可变参数是指函数的参数数量不固定。Go语言中的可变参数通过在参数名后加...来标识。

注意:可变参数通常要作为函数的最后一个参数。

func funcName(arg ...int) {}

arg ...int告诉Go这个函数接受不定数量的参数。注意,这些参数的类型全部是int。在函数体中,变量arg是一个int类型的slice

3、参数的传递

go语言函数的参数也是存在值传递引用传递

数据类型:

package main

import "fmt"

func main() {
    arr1 := [3]int{1, 2, 3}
    fmt.Println("函数调用前,数组的数据:", arr1)
    fun(arr1)
    fmt.Println("函数调用后,数组的数据:", arr1)
}

func fun(arr2 [3]int) {
    fmt.Println("函数中,数组的数据:", arr2)
    arr2[0] = 100
    fmt.Println("函数中,修改后数据的数据:", arr2)
}

运行结果

函数调用前,数组的数据: [1 2 3]
函数中,数组的数据: [1 2 3]
函数中,修改后数据的数据: [100 2 3]
函数调用后,数组的数据: [1 2 3]
package main

import "fmt"

func main() {
    slice1 := []int{1, 2, 3}
    fmt.Println("函数调用前,切片的数据:", slice1)
    fun(slice1)
    fmt.Println("函数调用后,切片的数据:", slice1)
}

func fun(slice2 []int) {
    fmt.Println("函数中,切片的数据:", slice2)
    slice2[0] = 100
    fmt.Println("函数中,修改后切片的数据:", slice2)
}

运行结果

函数调用前,切片的数据: [1 2 3]
函数中,切片的数据: [1 2 3]
函数中,修改后切片的数据: [100 2 3]
函数调用后,切片的数据: [100 2 3]

三、函数的返回值

函数的返回值:
一个函数的执行结果,返回给函数调用处,执行结果就叫函数的返回值。
return语句:
一个函数的定义上有返回值,那么函数中必须有return语句,将执行结果返回给函数的调用处。
函数的返回结果必须和函数定义的一致,类型、数量、顺序。

func add(x, y int) (sum int) {
    sum = x + y
    return
}
func calc(x, y int) (sum, sub int) {
    sum = x + y
    sub = x - y
    return
}
func calc(x, y int) (sum, sub int) {
    sum = x + y
    sub = x - y
    return
}
_, sub := calc(10, 20) //舍弃sum

四、函数的作用域

作用域:变量可以使用的范围。

1、全局变量

全局变量是定义在函数外部的变量,它在程序整个运行周期内都有效。 所有的函数都可以使用,而且共享这一份数据。

package main

import "fmt"

var a = 10

func main() {
    fmt.Println("test调用前,main中访问a:", a)
    test()
    fmt.Println("test调用后,main中访问a:", a)
}

func test() {
    fmt.Println("操作前,test中访问a: ", a)
    a = 20
    fmt.Println("操作后,test中访问a: ", a)
}

运行结果

test调用前,main中访问a: 10
操作前,test中访问a:  10
操作后,test中访问a:  20
test调用后,main中访问a: 20
2、局部变量

一个函数内部定义的变量,就叫做局部变量
局部变量只能在定义的范围内访问操作

package main

import "fmt"

func main() {
    test()
    fmt.Println("main中访问a:", a) //undefined: a
}

func test() {
    a := 20
    fmt.Println("test中访问a: ", a)
}

运行结果

# command-line-arguments
.\main.go:7:35: undefined: a

局部变量和全局变量重名,优先访问局部变量。

package main

import "fmt"

var a = 100

func main() {
    test()
}

func test() {
    a := 20
    fmt.Println("test中访问a: ", a)
}

运行结果

test中访问a:  20

另外,ifswitchfor语句中声明的变量也属于局部变量,在代码块外无法访问。

五、函数的本质

函数也是Go语言中的一种数据类型,可以作为另一个函数的参数,也可以作为另一个函数的返回值。

1、函数是一种数据类型
package main

import "fmt"

func main() {
    fmt.Printf("%T\n", fun1) //fun1的类型是func(int, int)
    fmt.Printf("%T\n", fun2) //fun2的类型是func(int, int) int
}

func fun1(a, b int) {
    fmt.Println(a, b)
}

func fun2(c, d int) int {
    fmt.Println(c, d)
    return 0
}

运行结果

func(int, int)
func(int, int) int
2、定义函数类型的变量
var f fun(int, int) int

上面语句定义了一个变量f,它是一个函数类型,这种函数接收两个int类型的参数并且返回一个int类型的返回值。
所有参数和返回值符合条件的函数可以赋值给f变量

package main

import "fmt"

func main() {
    var f func(int, int) int
    f = sum
    res := f(20, 10)
    fmt.Println("20 + 10 = ", res)
    f = sub
    res = f(20, 10)
    fmt.Println("20 - 10 = ", res)
}

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

func sub(a, b int) int {
    return a - b
}

运行结果

20 + 10 =  30
20 - 10 =  10

六、匿名函数

匿名函数就是没有函数名的函数

func (参数) (返回值) {
    函数体
}

匿名函数因为没有函数名,所以没办法像普通函数那样调用,所以匿名函数需要保存到某个变量或者作为立即执行函数。

package main

import "fmt"

func main() {
    // 将匿名函数保存到变量中
    sum := func(a, b int) int {
        return a + b
    }
    // 通过变量调用匿名函数
    res := sum(10, 20)
    fmt.Println("10 + 20 =", res)
    // 自执行函数,匿名函数定义完直接加()执行
    func(c, d int) {
        fmt.Printf("%v + %v = %v\n", c, d, c+d)
    }(10, 20)
}

运行结果

10 + 20 = 30
10 + 20 = 30

七、高阶函数

go语言支持函数式编程:

1、回调函数

一个函数被作为参数传递给另一个函数,那么这个函数就叫做回调函数。
回调函数并不会马上被调用执行,它会在包含它的函数内的某个特定的时间点被“回调”(就像它的名字一样)。

package main

import "fmt"

func main() {
    res := calc(10, 20, add)
    fmt.Println(res)
}
// add是一个func(int, int)int类型的函数,可以作为参数传递给calc函数
func add(a, b int) int {
    return a + b
}

// calc 高阶函数,它有两个int类型的参数和一个func(int, int)int函数类型的参数
// oper 回调函数,它被作为参数传递给calc函数
func calc(a, b int, oper func(int, int) int) int {
    res := oper(a, b)
    return res
}

运行结果

30
2、函数作为返回值
package main

import "fmt"

func main() {
    fun := calc("+")
    res := fun(10, 20)
    fmt.Println("10 + 20 =", res)
}

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

func sub(a, b int) int {
    return a - b
}

func calc(s string) func(int, int) int {
    switch s {
    case "+":
        return sum
    case "-":
        return sub
    default:
        fmt.Println("你传的是个啥玩意!")
        return nil
    }
}

运行结果

10 + 20 = 30
3、闭包

一个外层函数,有内层函数,该内层函数会操作外层函数的局部变量(外层函数的参数,或外层函数定义的变量),并且该内层函数作为外层函数的返回值。
这个内层函数和外层函数的局部变量,统称为闭包结构

局部变量的生命周期会发生改变。正常的局部变量随着函数的调用而创建,随着函数的结束而销毁。
但是闭包结构的外层函数的局部变量并不会随着外层函数的结束而销毁,因为内层函数还要继续使用。

package main

import "fmt"

func main() {
    fun := add()
    res := fun()
    fmt.Println("第一次调用,res=", res)
    res = fun()
    fmt.Println("第二次调用,res=", res)
    res = fun()
    fmt.Println("第二次调用,res=", res)
}

func add() func() int {
    i := 0
    return func() int {
        i++
        return i
    }
}

运行结果

第一次调用,res= 1
第二次调用,res= 2
第二次调用,res= 3

七、defer语句

defer是Go语言中的延迟执行语句,用来添加函数结束时执行的代码,常用于释放某些已分配的资源、关闭数据库连接、断开socket连接、解锁一个加锁的资源。
Go语言机制担保一定会执行defer语句中的代码。

1、延迟函数
package main

import "fmt"

func main() {
    a := 1
    b := 2
    c := 3
    d := 4
    //defer a++ //a++ 是一个语句,并非函数或方法,程序报错
    defer fmt.Println("defer", a)
    defer fmt.Println("defer", b)
    defer fmt.Println("defer", c)
    defer fmt.Println("defer", d)
    fmt.Println(a)
    fmt.Println(b)
    fmt.Println(c)
    fmt.Println(d)
}

运行结果

1
2
3
4
defer 4
defer 3
defer 2
defer 1
2、延迟方法

延迟并不仅仅局限于函数。延迟一个方法调用也是完全合法的。

package main

import "fmt"

// Student 学生结构体
type Student struct {
    name string
    city string
}

func (s Student) hello() {
    fmt.Printf("我叫%v, 我来自%v。\n", s.name, s.city)
}

func main() {
    s := Student{
        name: "jack",
        city: "北京市",
    }
    defer s.hello()
    fmt.Print("大家好,")
}

运行结果

大家好,我叫jack, 我来自北京市
3、延迟参数

defer声明时会先计算确定参数的值,defer推迟执行的仅是其函数体。

package main

import "fmt"

func main() {
    a := 1
    defer fun(a)
    a++
    fmt.Println("main中的a =", a)
}

func fun(a int) {
    fmt.Println("fun中的a =", a)
}

运行结果

main中的a = 2
fun中的a = 1
4、defer与return
(1)匿名返回值的情况
package main

import "fmt"

func main() {
    fmt.Println(fun1())
}

func fun1() int {
    var i int
    defer func() {
        i++
    }()
    return i
}

运行结果

0
(2)有名返回值的情况
package main

import "fmt"

func main() {
    fmt.Println(fun2())
}

func fun2() (i int) {
    defer func() {
        i++
    }()
    return i
}

运行结果

1

分析

经典案例

package main

import "fmt"

func main() {
    fmt.Println(f1())
    fmt.Println(f2())
    fmt.Println(f3())
    fmt.Println(f4())
}

func f1() int {
    x := 5
    defer func() {
        x++ // defer 访问的是变量x,访问不到返回值
        // fmt.Println("f1函数defer中的x =", x) //6
    }()
    return x // 返回值 = 5     //返回5
}

func f2() (x int) {
    defer func() {
        x++ //defer 访问x, 可以访问返回值,在RET之前,将返回值修改为6
        // fmt.Println("f2函数defer中的x =", x) //6
    }()
    return 5 // 返回值(x) = 5   //返回6
}

func f3() (y int) {
    x := 5
    defer func() {
        x++ // defer 访问变量x,将变量x修改为6
        // fmt.Println("f3函数defer中的x =", x) //6
    }()
    return x // 返回值(y) = 5   //返回5
}
func f4() (x int) {
    defer func(x int) {
        x++ // 这里修改的defer时传入的x(0),将其修改为1
        // fmt.Println("f4函数defer中的x =", x) //1
    }(x) // defer 语句调用时传入x的值为int类型的默认值0
    return 5 // 返回值(x) = 5   //返回5
}

运行结果

5
6
5
5
5、defer的作用域
package main

import (
    "fmt"
    // "os"
)

func main() {
    fmt.Println("start")
    // panic("崩溃了")              // defer和之后的语句都不再执行
    // os.Exit(1)                  // defer和之后的语句都不再执行
    defer fmt.Println("defer")
    // go func() {
    //  panic("崩溃了")
    // }()                         // defer不被执行
    // panic("崩溃了")             // defer会执行,但后面的语句不再执行
    fmt.Println("over")
    // os.Exit(1)                  // defer不被执行
}

八、内置函数

内置函数 介绍
close 主要用来关闭channel
len 用来求长度,比如string、array、slice、map、channel
new 用来分配内存,主要用来分配值类型,比如int、struct。返回的是指针
make 用来分配内存,主要用来分配引用类型,比如chan、map、slice
append 用来追加元素到数组、slice中
panic和recover 用来做错误处理

panic和recover

Go语言中目前是没有异常机制,但是使用panic/recover模式来处理错误。 panic可以在任何地方引发,但recover只有在defer调用的函数中有效。

package main

import (
    "fmt"
)

func main() {
    fmt.Println("start")
    defer func() {
        err := recover()
        if err != nil {
            fmt.Println("recover")
            fmt.Println("活了")
        }
    }()

    panic("panic")
    fmt.Println("over")
}

运行结果

start
recover
活了
上一篇下一篇

猜你喜欢

热点阅读