Go【No-8】函数

8-函数

如果一个动作需要重复执行,那就应该把这个动作封装到函数中。

在 Go 中,函数是一等公民

定义

注意:函数是引用类型!!!

Go 语言中函数的基本形式:

func 函数名(参数列表)(返回值){
    函数体
}
  • 函数名:由字母、数字、下划线组成,但不能数字开头;同一个包内,函数名不能重名
  • 参数:由参数变量和变量类型组成,多个参数使用, 分隔
  • 返回值:由返回变量和变量类型,也可只写变量类型,多个返回值必须用()包裹,并用,分隔
  • 函数体:实现指定功能的代码

eg:求两数之和

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

eg:无参数无返回值的函数

func sayHello() {
    fmt.Println("Hello")
}

调用

通过函数名(参数)的方式调用,如果定义的函数没有要求参数,则调用时不需要给参数

eg:

func main() {
    res := sum(1, 3)
    fmt.Println(res)
}

调用有返回值的函数时可以不接受其返回值。

参数

类型简写

如果相邻变量类型相同,可以只写一个

eg:

func fn(x, y int, p, q string) {
    fmt.Println(x, y, p, q)
}

x 和 y 均为 int 型,x 后面就可省略;p 和 q 都是 string 型,p 后面就可以省略

可变参数

如果参数数量不固定,可以在类型前面加上...,但是只能接收这种类型的参数,且此时要注意类型简写的方法
注意: 和固定参数搭配时,可变参数要放在固定参数后面

func fn(x int, y ...int) {
	fmt.Println(x)            // 1
	fmt.Println(y)            // [2 3 5]
	fmt.Printf("%T \n", y)    // []int
}

fn(1, 2, 3, 5)
// 错误,只能传递int型:fn(1, 2, "B", "C")
// 错误,全都要int型:  fn(1, "A", "B", "C")

可变参数实际上是通过切片实现的

关于类型简写:

// 错误
func fn(x, y ...int) {}

// 正确
func fn(x ...int) {}
func fn(x int, y ...int) {}
func fn(x, z int, y ...int) {}

关于可变参数位置

// 错误
func fn(y ...int, x int) {}
func fn(x int, y ...int, s string) {}

// 正确
func fn(x int, y ...int) {}

返回值

多返回值

Go 中支持多返回值,如果有多个返回值时,必须用()包裹起来

eg:

func calc(x, y int) (int, int) {
	return x + y, x - y
}

调用之后可以接收返回值,也可以不接收,如果指向接收一个,另一个可以用匿名变量 _ 去接

// 两个都接收
sum, sub := calc(10, 5)

// 两个都不接收
calc(10, 5)

// 只接收一个
_, sub := calc(10, 5)
sum, _ := calc(10, 5)

返回值命名

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

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

如果给返回值命名了,但是又 return 了别的变量,以别的变量为准

func calc(x, y int) (sum int, diff int) {
	sum = x + y
	diff = x - y
	mul := x * y
	div := x / y
	return mul, div
}

fmt.Println(calc(10, 5))    // 50 2

函数进阶

函数类型

函数可以有类型,使用关键字type可以定义一个函数类型

eg:

type calculation func(int, int) int

上面的例子定义了一个 calculation 类型,它是一种函数类型,一种接收两个int参数返回一个int值的函数类型。
方式满足上面特点的函数都是 calculation 类型。

eg:

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

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

add() 和 sub() 都是 calculation 类型,都可以赋值给 calculation 类型变量

var c calculation
c = add

res := c(10, 5)
fmt.Println(res)    // 15

s := sub
fmt.Println(s(20, 55))    // -35

函数变量

在 Go 中,函数被看作是第一类值,这意味着函数像变量一样,有类型、有值,普通变量能做的事函数也能做。

例如函数类型的例子,即使不定义函数类型,也可以直接将函数赋值给变量

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

func main() {
    a := add
    fmt.Println(a(10, 20))    // 30
}
  • 调用 nil 的函数变量会导致 panic
  • 函数变量的零值 nil,所以函数变量可以跟 nil 比较,但函数变量之间不能比较。
package main

import "fmt"

type processFunc func(int) bool // 声明一个函数类型

func isOdd(i int) bool { // 定义这种类型的函数
	return i%2 != 0
}

func isEven(i int) bool { // 定义这种类型的函数
	return i%2 == 0
}

func filter(slice []int, f processFunc) []int { // 接收这种类型的函数
	var res []int
	for _, v := range slice {
		if f(v) {
			res = append(res, v)
		}
	}
	return res
}

func main() {
	s := []int{1, 2, 3, 4, 5, 7}
	fmt.Println("s = ", s) // s =  [1 2 3 4 5 7]

	odd := filter(s, isOdd)   // 调用这种类型的函数
	even := filter(s, isEven) // 调用这种类型的函数

	fmt.Println("Odd slice", odd)   // Odd slice [1 3 5 7]
	fmt.Println("Even slice", even) // Even slice [2 4]
}

匿名函数

没有名字的函数就叫匿名函数
基本语法:

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

匿名函数没有函数名,所以需要变量来保存,或者作为立即执行函数

func main() {
    // 将匿名函数保存到变量
    add := func (x, y int) int {
        return x + y
    }
    add(10, 20)    // 通过变量调用匿名函数
}
func main() {
    // 自执行函数:匿名函数定义完加()直接执行
    func (x, y int) {
        fmt.Println(x + y)
    }(10, 20)
}

高阶函数

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

函数作为参数

// 定义一个函数,使用函数作为参数
// @param x       int                 操作数1
// @param y       int                 操作数2
// @param operate func(int, int) int  操作函数
// @return        int                 经过操作函数操作的结果
func calc (x int, y int, operate func(int, int) int) int {
    return operate(x, y)
}

// 定义一个操作函数
// @param  x 操作数1
// @param  y 操作数2
// @return 两个操作数相加的结果
func add(x, y int) int {
    return x + y
}

func main() {
    // 调用 calc 函数,把 add 函数传进去
    res := calc(10, 20, add)
    fmt.Println(res)    // 30
}
函数作为返回值
func add(x, y int) int {
	return x + y
}

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

// 定义一个函数,根据传入的操作符返回相应的操作函数
// @param s string 操作符
// @return func(int, int) int, error 操作函数
func do(s string) (func(int, int) int, error) {
    switch s {
    case "+":
        return add, nil
    case "-":
        return sub, nil
    default:
        return nil, errors.New("非法操作符")
    }
}

func main() {
    fn, err := do("+")
    if err == nil {
        fmt.Println(fn(10, 20))    // 20
    }
}
闭包

a closure is a record storing a function together with an environment.
闭包是由函数和与其相关的引用环境组合而成的实体 。

闭包

闭包能将局部变量带出其作用域

func adder() func() int {
    var x int
    return func() int {
                x++
                return x
            }
}

func main() {
	i := adder()
	fmt.Println(i()) // 1
	fmt.Println(i()) // 2
	fmt.Println(i()) // 3
}

相当于:

func adder() func() int {
    var x int
    f := func() int {
        x++
        return x
    }
    return f
}

func main() {
	i := adder()
	i() // 1
	i() // 2
	i() // 3
}

等于说,在 adder() 中声明的变量x,本该在adder()调用完就被销毁,却被闭包带出了x的作用域(adder函数内),使得局部变量x没有被销毁。

即局部变量x逃逸了,它的生命周期没有随着它的作用域结束而结束。

defer 语句

defer 就是延迟的意思,被 defer 关键字修饰的函数或方法会延迟执行。

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

// ---------------------
// Output:
start
end
1

程序先执行第一句打印了 start,往下遇到 defer,于是把第二句先压入栈,然后继续执行第三句打印了 end。

再继续往下已经没有,函数准备结束;但是在结束之前需先出栈,也就执行了第二句,打印了 1。最后结束。

这就是 defer 语句。

由于 defer 语句延迟调用的特性,所以 defer 语句能非常方便的处理资源释放的问题。
比如:关闭文件、关闭数据库连接、资源清理、释放锁、记录时间等等。

eg :

func fn() {
   f, err := os.Open("./main.go")    // 打开文件
   defer f.Close()                   // 函数退出之前关闭文件

   if err != nil {
       fmr.Println(err)
       return
   }
}
多个 defer 的执行顺序

当有多个 defer时,按照栈的方式执行。即先defer的后执行。
eg:

func fn() {
    fmt.Println("start")
    defer fmt.Println(1)
    defer fmt.Println(2)
    defer fmt.Println(3)
    fmt.Println("end")
}
// ---------------------
// Output:
start
end
3
2
1
defer 执行时机

Go 中的 return语句并不是原子操作,它分为 给返回值赋值RET指令两步。
defer 语句执行的时机在返回值赋值之后,RET指令执行之前。

在这里插入图片描述

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

TCP404

老板大方~

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值