• 8. Go的函数


    Go的函数

    函数定义

    函数是组织好的、可重复使用的、用于执行指定任务的代码块

    Go语言支持:函数、匿名函数和闭包

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

    func 函数名(参数)(返回值) {
        函数体
    }
    
    • 1
    • 2
    • 3

    其中:

    • 函数名:由字母、数字、下划线组成。但函数名的第一个字母不能是数字。在同一个包内,函数名也不能重名

    示例

    // 求两个数的和
    func sumFn(x int, y int) int{
    	return x + y
    }
    // 调用方式
    sunFn(1, 2)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

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

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

    func sunFn2(x ...int) int {
    	sum := 0
    	for _, num := range x {
    		sum = sum + num
    	}
    	return sum
    }
    // 调用方法
    sunFn2(1, 2, 3, 4, 5, 7)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    方法多返回值,Go语言中函数支持多返回值,同时还支持返回值命名,函数定义时可以给返回值命名,并在函数体中直接使用这些变量,最后通过return关键字返回

    // 方法多返回值
    func sunFn4(x int, y int)(sum int, sub int) {
    	sum = x + y
    	sub = x -y
    	return
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    函数类型和变量

    定义函数类型

    我们可以使用type关键字来定义一个函数类型,具体格式如下

    type calculation func(int, int) int
    
    • 1

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

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

    type calc func(int, int) int
    // 求两个数的和
    func sumFn(x int, y int) int{
    	return x + y
    }
    func main() {
        var c calc
        c = add
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    方法作为参数

    /**
    	传递两个参数和一个方法
     */
    func sunFn (a int, b int, sum func(int, int)int) int {
    	return sum(a, b)
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    或者使用switch定义方法,这里用到了匿名函数

    // 返回一个方法
    type calcType func(int, int)int
    func do(o string) calcType {
    	switch o {
    		case "+":
    			return func(i int, i2 int) int {
    				return i + i2
    			}
    		case "-":
    			return func(i int, i2 int) int {
    				return i - i2
    			}
    		case "*":
    			return func(i int, i2 int) int {
    				return i * i2
    			}
    		case "/":
    			return func(i int, i2 int) int {
    				return i / i2
    			}
    		default:
    			return nil
    
    	}
    }
    
    func main() {
    	add := do("+")
    	fmt.Println(add(1,5))
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30

    匿名函数

    函数当然还可以作为返回值,但是在Go语言中,函数内部不能再像之前那样定义函数了,只能定义匿名函数。匿名函数就是没有函数名的函数,匿名函数的定义格式如下

    func (参数)(返回值) {
        函数体
    }
    
    • 1
    • 2
    • 3

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

    func main() {
    	func () {
    		fmt.Println("匿名自执行函数")
    	}()
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5

    Golang中的闭包

    全局变量和局部变量

    全局变量的特点:

    • 常驻内存
    • 污染全局

    局部变量的特点

    • 不常驻内存
    • 不污染全局

    闭包

    • 可以让一个变量常驻内存
    • 可以让一个变量不污染全局

    闭包可以理解成 “定义在一个函数内部的函数”。在本质上,闭包就是将函数内部 和 函数外部连接起来的桥梁。或者说是函数和其引用环境的组合体。

    • 闭包是指有权访问另一个函数作用域中的变量的函数
    • 创建闭包的常见的方式就是在一个函数内部创建另一个函数,通过另一个函数访问这个函数的局部变量

    注意:由于闭包里作用域返回的局部变量资源不会被立刻销毁,所以可能会占用更多的内存,过度使用闭包会导致性能下降,建议在非常有必要的时候才使用闭包。

    // 闭包的写法:函数里面嵌套一个函数,最后返回里面的函数就形成了闭包
    func adder() func() int {
    	var i = 10
    	return func() int {
    		return i + 1
    	}
    }
    
    func main() {
    	var fn = adder()
    	fmt.Println(fn())
    	fmt.Println(fn())
    	fmt.Println(fn())
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    最后输出的结果

    11
    11
    11
    
    • 1
    • 2
    • 3

    另一个闭包的写法,让一个变量常驻内存,不污染全局

    func adder2() func(y int) int {
    	var i = 10
    	return func(y int) int {
    		i = i + y
    		return i
    	}
    }
    
    func main() {
    	var fn2 = adder2()
    	fmt.Println(fn2(10))
    	fmt.Println(fn2(10))
    	fmt.Println(fn2(10))
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    defer语句

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

    // defer函数
    fmt.Println("1")
    defer fmt.Println("2")
    fmt.Println("3")
    fmt.Println("4")
    
    • 1
    • 2
    • 3
    • 4
    • 5

    defer将会延迟执行

    1
    3
    4
    2
    
    • 1
    • 2
    • 3
    • 4

    如果有多个defer修饰的语句,将会逆序进行执行

    // defer函数
    fmt.Println("1")
    defer fmt.Println("2")
    defer fmt.Println("3")
    fmt.Println("4")
    
    • 1
    • 2
    • 3
    • 4
    • 5

    运行结果

    1
    4
    3
    2
    
    • 1
    • 2
    • 3
    • 4

    如果需要用defer运行一系列的语句,那么就可以使用匿名函数

    func main() {
    	fmt.Println("开始")
    	defer func() {
    		fmt.Println("1")
    		fmt.Println("2")
    	}()
    	fmt.Println("结束")
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    运行结果

    开始
    结束
    1
    2
    
    • 1
    • 2
    • 3
    • 4

    defer执行时机

    在Go语言的函数中return语句在底层并不是原子操作,它分为返回值赋值和RET指令两步。而defer语句执行的时机就在返回值赋值操作后,RET指令执行前,具体如下图所示
    在这里插入图片描述

    panic/revocer处理异常

    Go语言中是没有异常机制,但是使用panic / recover模式来处理错误

    • panic:可以在任何地方引发
    • recover:只有在defer调用函数内有效
    func fn1() {
    	fmt.Println("fn1")
    }
    
    func fn2() {
    	panic("抛出一个异常")
    }
    func main() {
        fn1()
    	fn2()
        fmt.Println("结束")
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    上述程序会直接抛出异常,无法正常运行

    fn1
    panic: 抛出一个异常
    
    • 1
    • 2

    解决方法就是使用 recover进行异常的监听

    func fn1() {
    	fmt.Println("fn1")
    }
    
    func fn2() {
    	// 使用recover监听异常
    	defer func() {
    		err := recover()
    		if err != nil {
    			fmt.Println(err)
    		}
    	}()
    	panic("抛出一个异常")
    }
    func main() {
        fn1()
    	fn2()
        fmt.Println("结束")
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19

    异常运用场景

    模拟一个读取文件的方法,这里可以主动发送使用panic 和 recover

    func readFile(fileName string) error {
    	if fileName == "main.go" {
    		return nil
    	} else {
    		return errors.New("读取文件失败")
    	}
    }
    
    func myFn () {
    	defer func() {
    		e := recover()
    		if e != nil {
    			fmt.Println("给管理员发送邮件")
    		}
    	}()
    	err := readFile("XXX.go")
    	if err != nil {
    		panic(err)
    	}
    }
    
    func main() {
    	myFn()
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24

    内置函数

    内置函数介绍
    close主要用来关闭channel
    len用来求长度,比如string、array、slice、map、channel
    new用来分配内存、主要用来分配值类型,比如 int、struct ,返回的是指针
    make用来分配内存,主要用来分配引用类型,比如chan、map、slice
    append用来追加元素到数组、slice中
    panic\recover用来处理错误
  • 相关阅读:
    [Vulnhub] basic_pentesting_1
    linux下doc转docx
    【C++】加了<string.h>还是报“strlen:找不到标识符”的错误
    beego框架自学笔记1
    Linux命令记录
    【Java 基础篇】Java可变参数:灵活处理不定数量的方法参数
    python 从一道作业题到制作一个图形界面的“诈金花”游戏
    Python3数据分析与挖掘建模(1)python数据分析的流程与概述
    LeetCode通关:连刷十四题,回溯算法完全攻略
    快速开发项目-VScode插件
  • 原文地址:https://blog.csdn.net/wangzhicheng987/article/details/126699056