7.Go语言基础之函数

1.函数

1.1函数定义

Go语言中定义函数使用fun关键字,具体格式为
func 函数名(参数)(返回值){
    函数体
}

其中:
1.函数名:由字母,数字,下划线组成。但函数名的第一个字母不能是数字。在同一个包内,函数名不能重名。
2.参数:参数由变量名和变量类型组成。
3.返回值:可以只写返回值类型,多个返回值必须用()包裹,并用,分隔。
4.函数体:实现指定功能的代码块。

定义一个求两个数和的函数:
func intSum(x int, y int) int {
    return x + y
}

函数的参数和返回值是可选的,我们也可以不传参也没有返回值。
func sayHello() {
    fmt.Println("Hello vita")
}

1.2函数的调用

定义了函数后,可以通过 函数名()的方式调用函数。
调用有返回值的函数时,可以不接收返回值。

package main

import "fmt"

func sayHello(){
    fmt.Println("hello vita")
}
func main() {
    sayHello()
}

结果:
hello vita

1.3参数

1.3.1类型简写

函数的参数中如果相邻变量的类型相同,则可以省略类型。

package main

func intSum(x,y int)int{
    return x+y
}
func main() {
    intSum(1,2)
}

intSum函数有两个参数,x,y的类型都是int,因此可以省略x的类型,因为y后面有类型说明。

1.3.2可变参数

可变参数即参数数量不固定。Go语言中的可变参数通过在参数后面加...来标识。
注意:可变参数通常要作为函数的最后一个参数。

package main

import "fmt"

func intSum(x ...int)int{
    fmt.Println(x)//x是一个切片
    sum:=0
    for _,value :=range x{
        sum+=value
    }
    return sum
}
func main() {
    sum1 := intSum(1,2,3,4,5)
    sum2 := intSum(1,2,3,4,5,6,7)
    sum3 := intSum(1,2,3,4,5,6,7,8,9)
    fmt.Println(sum1)
    fmt.Println(sum2)
    fmt.Println(sum3)

}

结果:
[1 2 3 4 5]
[1 2 3 4 5 6 7]
[1 2 3 4 5 6 7 8 9]
15
28
45

1.3.3固定参数搭配可变参数

可变参数放在固定参数的后面

package main

import "fmt"

func intSum(x int,y ...int)int{
    sum:=x
    for _,value :=range y{
        sum+=value
    }
    return sum
}
func main() {
    sum1 := intSum(1,2,3,4,5)
    sum2 := intSum(1,2,3,4,5,6,7)
    sum3 := intSum(1,2,3,4,5,6,7,8,9)
    fmt.Println(sum1)
    fmt.Println(sum2)
    fmt.Println(sum3)

}

结果:
15
28
45

1.4返回值

1.4.1多返回值

Go语言中支持多返回值,函数如果有多个返回值,必须使用()把所有返回值包裹起来。

package main

func calc(x,y int)(int,int){
    sum :=x+y
    sub:=x-y
    return sum,sub
}
func main() {
    calc(3,2)

}

1.4.2返回值命名

函数定义时,可以给返回值命名,并在函数体中直接使用这些变量,最后通过return关键字返回。
package main

func calc(x,y int)(sum int,sub int){
    sum =x+y
    sub=x-y
    return 
}
func main() {
    calc(3,2)
}

2.函数进阶

2.1全局变量

全局变量是定义在函数外部的变量,他在程序整个运行周期内都有效。在函数中可以访问到全局变量。

package main

import "fmt"

var num int64=100
func testGlobalVar(){
    fmt.Printf("num=%d\n",num)
}
func main() {
    testGlobalVar() //num=100
}

2.2局部变量

函数内定义的变量,无法在函数外使用。
func testLocalVar() {
    //定义一个函数局部变量x,仅在该函数内生效
    var x int64 = 100
    fmt.Printf("x=%d\n", x)
}

func main() {
    testLocalVar()
    fmt.Println(x) // 此时无法使用变量x
}
如果局部变量和全局变量重名,优先访问局部变量。

package main

import "fmt"

//定义全局变量num
var num int64 = 10

func testNum() {
    num := 100
    fmt.Printf("num=%d\n", num) // 函数中优先使用局部变量
}
func main() {
    testNum() // num=100
}
语句块中定义的变量,通常我们会在if条件判断,for循环,switch语句上使用这种定义变量的方式。

func testLocalVar2(x, y int) {
    fmt.Println(x, y) //函数的参数也是只在本函数中生效
    if x > 0 {
        z := 100 //变量z只在if语句块生效
        fmt.Println(z)
    }
    //fmt.Println(z)//此处无法使用变量z
}
for循环语句中定义的变量,也只在for语句块中生效
func testLocalVar3() {
    for i := 0; i < 10; i++ {
        fmt.Println(i) //变量i只在当前for语句块中生效
    }
    //fmt.Println(i) //此处无法使用变量i
}

2.3定义函数类型

我们可以使用type关键字定义一个函数类型,具体格式如下:
type calculation func(int, int) int

上面语句定义了一个calculation类型,它是一种函数类型,接收两个int参数,返回值类型为int。

简单的说,凡是满足这个条件的函数都是calculation类型的函数,例如下面的add和sub是calculation类型。

func add(x, y int) int {
    return x + y
}

func sub(x, y int) int {
    return x - y
}

add和sub都能赋值给calculation类型的变量。
var c calculation
c = add

2.4函数类型变量

我们可以声明函数类型的变量并且为该变量赋值:
func main() {
    var c calculation               // 声明一个calculation类型的变量c
    c = add                         // 把add赋值给c
    fmt.Printf("type of c:%T\n", c) // type of c:main.calculation
    fmt.Println(c(1, 2))            // 像调用add一样调用c

    f := add                        // 将函数add赋值给变量f1
    fmt.Printf("type of f:%T\n", f) // type of f:func(int, int) int
    fmt.Println(f(10, 20))          // 像调用add一样调用f
}

3.高阶函数

高阶函数分为函数作为参数和函数作为返回值。

3.1函数作为参数

package main

import "fmt"

func add(x,y int) int  {
    return x+y
}
func calc(x,y int, op func(int,int) int) int{
    return op(x,y)
}
func main()  {
    ret2 := calc(10,20,add)
    fmt.Println(ret2) //30
}

3.2函数作为返回值

package main

import (
    "errors"
    "fmt"
)

func add(x,y int) int  {
    return x+y
}
func sub(x,y int) int  {
    return x-y
}
func do(s string) (func(int, int) int,error)  {
    switch s {
    case "+":
        return add,nil
    case "-":
        return sub,nil
    default:
        err := errors.New("无法识别的操作符")
        return nil,err

    }
}
func main()  {

    addResult,err := do("+")
    fmt.Println(addResult(1,2),err)
    subResult,err := do("-")
    fmt.Println(subResult(2,1),err)
    informalResult,err := do(".")
    fmt.Println(informalResult,err)

}

结果:
3 
1 
 无法识别的操作符

Process finished with exit code 0

3.3匿名函数

函数可以作为返回值,但是在GO语言中,只能定义匿名函数。
匿名函数就是没有函数名的函数,匿名函数的定义格式如下:

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

匿名函数因为没有函数名,所以不能像普通函数那样调用,所以匿名函数需要保存到某个变量或者作为立即执行函数。
匿名函数多用于实现回调函数和闭包。
package main

import "fmt"

func main()  {
    //将匿名函数保存到变量
    add := func(x,y int) {
        fmt.Println(x+y)
    }
    add(10,20) //通过变量调用匿名函数

    //自执行函数:匿名函数定义完加()直接执行
    func(x,y int){
        fmt.Println(x+y)
    }(10,20)

}

结果:
30
30

Process finished with exit code 0

3.4闭包

闭包,在Python中,是外部函数的返回值是内部函数的应用,内部函数使用了外部函数的变量,这就构成了闭包。
在Go语言中,也是同样的。
adder函数的返回值是一个内部的匿名函数,该匿名函数引用了外部的变量x,并且x变量在f的生命周期内一直有效。
package main

import "fmt"

func adder() func(int) int  {
    var x int
    return func(y int) int {
        x += y
        return x
    }
}
func main()  {
    f := adder()
    fmt.Println(f(10))//10
    fmt.Println(f(20))//30
    fmt.Println(f(30))//60

    f1 := adder()
    fmt.Println(f1(40))//40
    fmt.Println(f1(50))//90
}

结果:
10
30
60
40
90

Process finished with exit code 0
闭包进阶示例1
package main

import "fmt"

func adder2(x int) func(int) int  {
    return func(y int) int {
        x += y
        return x
    }
}
func main()  {
    f := adder2(10)
    fmt.Println(f(10))//20
    fmt.Println(f(20))//40
    fmt.Println(f(30))//70

    f1 := adder2(20)
    fmt.Println(f1(40))//60
    fmt.Println(f1(50))//110
}

结果:
20
40
70
60
110

Process finished with exit code 0
闭包进阶示例2:
package main

import (
    "fmt"
    "strings"
)

func makeSuffixFunc(suffix string) func(string) string  {
    return func(name string) string {
        if !strings.HasSuffix(name,suffix){
            return name + suffix
        }
        return name
    }
}
func main()  {
    jpgFunc := makeSuffixFunc(".jpg")
    textFunc := makeSuffixFunc(".txt")
    fmt.Println(jpgFunc("test"))
    fmt.Println(textFunc("test"))
}

结果:
test.jpg
test.txt

Process finished with exit code 0
闭包进阶示例3:
package main

import "fmt"

func calc(base int) (func(int) int,func(int) int)  {
    add := func(i int) int {
        base += i
        return base
    }
    sub := func(i int) int {
        base -= i
        return base
    }
    return add,sub
}
func main()  {
    f1,f2 := calc(10)
    fmt.Println(f1(1),f2(2))
    fmt.Println(f1(3),f2(4))
    fmt.Println(f1(5),f2(6))
}

结果:
11 9
12 8
13 7

Process finished with exit code 0

4.defer语句

4.1延迟调用特性

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

import "fmt"

func main()  {
    fmt.Println("start")
    defer fmt.Println(1)
    defer fmt.Println(2)
    defer fmt.Println(3)
    fmt.Println("end")
}

结果:
start
end
3
2
1

Process finished with exit code 0
由于defer语句延迟调用的特性,所以defer语句能非常方便的处理资源释放问题。比如:资源清理、文件关闭、解锁及记录时间等。

4.2defer执行时机

在Go语言的函数中,return语句在底层并不是原子操作,它分为给返回值赋值和RET指令两步。
defer语句执行时机就在赋值操作后,RET指令执行前。具体如图:

7.Go语言基础之函数

创新互联建站专业为企业提供沙湾网站建设、沙湾做网站、沙湾网站设计、沙湾网站制作等企业网站建设、网页设计与制作、沙湾企业网站模板建站服务,10余年沙湾做网站经验,不只是建网站,更提供有价值的思路和整体网络服务。

4.3defer经典案例

package main

import "fmt"

// 关于defer的面试题
func f1() int {
    x := 5
    defer func() {
        x++
    }()
    return x // 1. 返回值=5  2. x++ 3. RET指令  ==> 5
}

func f2() (x int) {
    defer func() {
        x++
    }()
    return 5 // 1. (汇编)返回值=x(5) 2. x++  3.(汇编)RET ==> 6
}

func f3() (y int) {
    x := 5
    defer func() {
        x++
    }()
    return x // 1. (汇编)返回值=y(5)  2. x++  3.(汇编)RET  ==> 5
}
func f4() (x int) {
    defer func(x int) {
        x++
    }(x)
    return 5 //1. (汇编)返回值=x(5)  2. x++(函数内部的x)  3.(汇编)RET  ==> 5
}
func main() {
    fmt.Println(f1())
    fmt.Println(f2())
    fmt.Println(f3())
    fmt.Println(f4())
}

4.4defer面试题

package main

import "fmt"

func calc(index string, a, b int) int {
    ret := a + b
    fmt.Println(index, a, b, ret)
    return ret
}

func main() {
    x := 1
    y := 2
    defer calc("AA", x, calc("A", x, y))
    x = 10
    defer calc("BB", x, calc("B", x, y))
    y = 20
}

提示:defer注册要延迟执行的函数时该函数所有的参数都需要确定其值
结果:
A 1 2 3
B 10 2 12
BB 10 12 22
AA 1 3 4

Process finished with exit code 0

5.内置函数

7.Go语言基础之函数

5.1panic/recover

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

import "fmt"

func funcA()  {
    fmt.Println("func A")
}
func funcB()  {
    panic("func B")
}
func funcC()  {
    fmt.Println("func C")
}

func main() {
    funcA()
    funcB()
    funcC()
}

7.Go语言基础之函数

在程序运行期间,funcB中引发了panic,导致程序崩溃,异常退出了。
这个时候,我们可以通过recover将程序恢复回来,继续往后执行。
package main

import "fmt"

func funcA()  {
    fmt.Println("func A")
}
func funcB()  {
    defer func() {
        err := recover()
        if err != nil{
            fmt.Println("recover in B")
        }
    }()
    panic("func B")
}
func funcC()  {
    fmt.Println("func C")
}

func main() {
    funcA()
    funcB()
    funcC()
}

结果:
func A
recover in B
func C

Process finished with exit code 0
注意:
1.recover()必须搭配defer使用。
2.2.defer一定要在可能引发panic的语句之前定义。

6.练习题

/*
你有50枚金币,需要分配给以下几个人:Matthew,Sarah,Augustus,Heidi,Emilie,Peter,Giana,Adriano,Aaron,Elizabeth。
分配规则如下:
a. 名字中每包含1个'e'或'E'分1枚金币
b. 名字中每包含1个'i'或'I'分2枚金币
c. 名字中每包含1个'o'或'O'分3枚金币
d: 名字中每包含1个'u'或'U'分4枚金币
写一个程序,计算每个用户分到多少金币,以及最后剩余多少金币?
程序结构如下,请实现 ‘dispatchCoin’ 函数
*/
var (
    coins = 50
    users = []string{
        "Matthew", "Sarah", "Augustus", "Heidi", "Emilie", "Peter", "Giana", "Adriano", "Aaron", "Elizabeth",
    }
    distribution = make(map[string]int, len(users))
)

func main() {
    left := dispatchCoin()
    fmt.Println("剩下:", left)
}
package main

import (
    "fmt"
    "strings"
)

var (
    coins = 50
    users = []string{
        "Matthew", "Sarah", "Augustus", "Heidi", "Emilie", "Peter", "Giana", "Adriano", "Aaron", "Elizabeth",
    }
    distribution = make(map[string]int, len(users))
)

func dispatchCoin()  int{
    count := 0
    for _,value := range users{

        eCount := strings.Count(strings.ToLower(value),"e")
        iCount := strings.Count(strings.ToLower(value),"i")
        oCount := strings.Count(strings.ToLower(value),"o")
        uCount := strings.Count(strings.ToLower(value),"u")
        personCount := eCount*1+iCount*2+oCount*3+uCount*4
        fmt.Printf("Pserson:%s,coins:%d\n",value,personCount)
        count += personCount

    }
    return coins-count

}
func main() {
    left := dispatchCoin()
    fmt.Println("剩下:", left)
}

结果:
Pserson:Matthew,coins:1
Pserson:Sarah,coins:0
Pserson:Augustus,coins:12
Pserson:Heidi,coins:5
Pserson:Emilie,coins:6
Pserson:Peter,coins:2
Pserson:Giana,coins:2
Pserson:Adriano,coins:5
Pserson:Aaron,coins:3
Pserson:Elizabeth,coins:4
剩下: 10

Process finished with exit code 0

分享文章:7.Go语言基础之函数
文章网址:http://scyanting.com/article/jopjoi.html