• go语言的函数


    go语言的函数

    函数基础

    简介

    在任何编程语言中,函数就是对功能的封装,是组织好的,可重复使用,用于执行指定功能的代码块。在go语言中,函数属于一等公民。

    定义

    在go语言当中,用func定义一个函数,格式:

    func 函数名 (参数) (返回值) {
        函数体
    }
    
    • 函数名:由数字、字母、下划线组成。但是不能以数字开头,在同一个包内,函数名不能重复。
    • 参数:一个参数由参数变量和参数类型组成,多个参数之间用,分隔。
    • 返回值:可以是返回值类型,也可以是返回值变量和返回值类型组成。多个返回值用()包裹。并,分隔。
    • 函数体:实现指定功能的代码块。

    定义一个求两数之和的函数并调用:

    package main
    
    import (
    	"fmt"
    )
    
    // 定义一个求两数之和的函数
    func add(a int, b int) int {
    	return a + b
    }
    
    func main() {
        // 调用
    	res := add(100, 200)
    	// 300
    	fmt.Println(res)
    }
    

    参数

    参数简写

    当两个相邻参数的类型相同时,可以简写,例如:

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

    可变参数

    有时候我们不知道我们传的参数有多少个,可以通过不固定参数来解决。在参数名后面加...来标识。类似python中的 *args**kwargs

    格式:

    func 函数名 (固定参数, v ... T) (返回值) {
    				函数体
    			}
    
    • v:可变参数变量,类型为[]T ,也就是拥有多个元素的类型切片 ,v 和 t 之间用 ...
    • T:可变参数变量类型,当类型为interface{}的时候,传入的值可以是任意值。
    可变参数为多个字符串
    package main
    
    import (
    	"fmt"
    )
    
    func studentInfo(name string, age int, args ...string) {
    	fmt.Println("固定参数:", name, age)
    	fmt.Println("不固定参数:", args)
    
    	// 通过遍历获取每一个参数
    	for index, arg := range args {
    		fmt.Printf("索引:%d  参数值:%s
    ", index, arg)
    	}
    }
    func main() {
    	studentInfo("江子牙", 21, "江西省", "萍乡市", "莲花县", "坊楼镇", "小江村")
    }
    

    执行结果:

    固定参数: 江子牙 21
    不固定参数: [江西省 萍乡市 莲花县 坊楼镇 小江村]
    索引:0  参数值:江西省
    索引:1  参数值:萍乡市
    索引:2  参数值:莲花县
    索引:3  参数值:坊楼镇
    索引:4  参数值:小江村
    
    可变参数为空接口
    package main
    
    import (
    	"fmt"
    )
    
    func studentInfo(name string, args ...interface{}) {
    	fmt.Println("固定参数", name)
    	fmt.Println("不固定参数", args)
    
    	// 通过遍历获取每一个参数的类型
    	for k, v := range args {
    
    		fmt.Printf("索引:%d  	参数的类型:%T	参数值:%v
    ", k, v, v)
    	}
    
    }
    func main() {
    	studentInfo("江子牙", 22, 99.9, "sex", true, map[string]string{
    		"className": "高三(1)班",
    		"score":     "99",
    	})
    }
    

    执行结果:

    固定参数 江子牙
    不固定参数 [22 99.9 sex true map[className:高三(1)班 score:99]]
    索引:0  	参数的类型:int	参数值:22
    索引:1  	参数的类型:float64	参数值:99.9
    索引:2  	参数的类型:string	参数值:sex
    索引:3  	参数的类型:bool	参数值:true
    索引:4  	参数的类型:map[string]string	参数值:map[className:高三(1)班 score:99]
    

    本质上,可变参数是根据切片来实现的。

    返回值

    多返回值

    go语言是支持多个返回值,如果有多个返回值就要用()包裹。例如:

    package main
    
    import "fmt"
    
    // 定义一个求两数之和、两数之差的函数
    func cacl(a, b int) (int, int) {
    	sum := a + b
    	sub := a - b
    	return sum, sub
    }
    
    func main() {
    	sum, sub := cacl(10, 10)
    	// 两数之和 20
    	fmt.Println("两数之和", sum)
    	// 两数之差 0
    	fmt.Println("两数之差", sub)
    }
    

    返回值命名

    返回值可以和参数一样,由返回值变量和返回值变量类型组成。最后由return关键字返回。

    package main
    
    import "fmt"
    
    // 定义一个求两数之和、两数之差的函数
    func cacl(a, b int) (sum, sub int) {
    	sum = a + b
    	sub = a - b
    	return
    }
    
    func main() {
    	sum, sub := cacl(10, 10)
    	// 两数之和 20
    	fmt.Println("两数之和", sum)
    	// 两数之差 0
    	fmt.Println("两数之差", sub)
    }
    

    函数进阶

    变量作用域

    全局变量

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

    package main
    
    import "fmt"
    
    // 定义两个全局变量a 、b
    var (
    	a = 10
    	b = 10
    )
    
    // 定义一个求两数之和、两数之差的函数
    func cacl() (sum, sub int) {
    	fmt.Println("两个全局变量:",a, b)
    	sum = a + b
    	sub = a - b
    	return
    }
    
    func main() {
    	sum, sub := cacl()
    	// 两数之和 20
    	fmt.Println("两数之和", sum)
    	// 两数之差 0
    	fmt.Println("两数之差", sub)
    }
    
    

    局部变量

    函数内部定义的局部变量函数外部无法访问
    package main
    
    import "fmt"
    
    func testLocalVar() {
    	local := "我是函数内部的局部变量,函数外部无法访问我"
    	fmt.Println(local)
    }
    
    func main() {
    	testLocalVar()
    	//fmt.Println(local) 访问不到
    }
    
    
    全局变量和局部变量同时存在

    如果局部变量和全局变量同名,优先访问局部变量,就近原则。

    package main
    
    import "fmt"
    
    //定义全局变量num
    var num int64 = 10
    
    func testNum() {
    	num := 100
    	//函数中优先使用局部变量
    	fmt.Println("局部变量的num:", num)
    }
    func main() {
    	testNum()
    	fmt.Println("全局变量的num:", num)
    }
    

    函数类型与函数变量

    函数也是一种类型,也可以赋值给一个变量保存起来。

    函数变量
    package main
    
    import "fmt"
    
    func add(a, b int) int {
    	return a + b
    }
    
    func main() {
    	f:= add
    	fmt.Printf("类型:%T
    ", f)
    	sum := f(1,2)
    	fmt.Println(sum)
    }
    

    可以看出函数也是一种类型,就像上面的add函数,用变量名保存起来之后,打印它的类型,func(int, int) int,函数变量名加()也可以调用。

    执行结果:

    类型:func(int, int) int
    3
    
    函数类型

    既然函数也是一种类型,那么也可以声明一个函数类型。

    package main
    
    import "fmt"
    
    // 声明一个cacl函数类型
    type cacl func(int, int) int
    
    func add(a, b int) int {
    	return a + b
    }
    
    func main() {
    	var c cacl
    	c = add
    	fmt.Printf("类型:%T
    ", c)
    	sum := c(1, 2)
    	fmt.Println(sum)
    }
    
    

    执行结果:

    类型:main.cacl
    3
    

    高阶函数

    满足其中一个条件为高阶函数

    • 函数作为参数传入
    • 函数作为返回值返回

    函数作为参数

    package main
    
    import "fmt"
    
    func add(a, b int) int {
    	return a + b
    }
    
    func cacl(a, b int, f func(int, int) int) int {
    	sum := f(a, b)
    	return sum
    }
    
    func main() {
    	res := cacl(10, 10, add)
    	fmt.Println(res)
    }
    
    

    函数作为返回值

    package main
    
    import (
    	"errors"
    	"fmt"
    )
    
    func sum(a, b int) int {
    	return a + b
    }
    
    func sub(a, b int) int {
    	return a - b
    }
    
    // 定义一个计算的函数,根据传入的操作字符串执行对应的加减
    func cacl(do string) (func(int, int) int, error) {
    	switch do {
    	case "加":
    		return sum, nil
    	case "减":
    		return sub, nil
    	default:
    		err := errors.New("不支持该操作")
    		return nil, err
    	}
    }
    
    func main() {
    	do1, _ := cacl("加")
    	sum := do1(1, 20)
    	fmt.Println(sum)
    
    	do2, _ := cacl("减")
    	sub := do2(10, 8)
    	fmt.Println(sub)
    
    	do2, err := cacl("乘")
    	fmt.Println(err)
    }
    
    

    执行结果:

    21
    2
    不支持该操作
    

    匿名函数

    在其他语言中,函数内部还可以定义函数,即函数嵌套。但是go语言函数内部不能再像以前那样定义函数了,只能定义匿名函数。顾名思义就是没有函数名字的函数,常用于回调函数和闭包。

    格式:

    func (参数) (返回值) {
    				函数体
    			}
    
    // 可以当做没有函数名字的普通函数定义
    

    因为没有函数名了,不能和普通函数那样直接调用。匿名函数需要保存到某个变量或者立即作为函数执行。

    保存为变量

    定义一个匿名函数之后,保存到变量中。

    package main
    
    import "fmt"
    
    func main() {
    	add := func(a, b int) int {
    		sum := a + b
    		return sum
    	}
    	sum := add(1, 2)
    	fmt.Println(sum)
    }
    
    
    立即执行

    定义一个匿名函数之后,后面直接加(),立即执行。

    package main
    
    import "fmt"
    
    func main() {
    	sum := func(a, b int) int {
    		sum := a + b
    		return sum
    	}(1, 2)
    	fmt.Println(sum)
    }
    
    

    闭包

    函数是编译期静态的概念,闭包是运行期动态的概念。

    闭包 = 函数 + 引用环境

    闭包示例1
    package main
    
    import "fmt"
    
    func adder() func(int) int {
    	var x int
    	return func(y int) int {
    		x += y
    		return x
    	}
    }
    func main() {
        // f是一个函数,应用了其外部作用域的x变量,此时f就是一个闭包,在f的声明周期内,x变量一直有效
    	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
    }
    
    
    闭包示例2
    package main
    
    import "fmt"
    
    func main() {
    	// 定义一个字符串变量
    	str1 := "hello world"
    	fmt.Printf("修改之前---%s
    ", str1)
    
    	// 定义一个匿名函数
    	func() {
    		str1 = "hello go"
    	}()
    	fmt.Printf("修改后---%s
    ", str1)
    
    }
    

    匿名函数中并没有定义str1,也不是通过参数传递的方式。就算通过传递参数话的方式。由于字符串不可变。不会对原有字符串发生改变。但是却对str1进行了修改。

    str1 的定义在匿名函数之前,此时,str1就被引用到匿名函数中形成了闭包。

    闭包示例3
    package main
    
    import "fmt"
    
    // 定义一个函数,返回值为一个匿名函数,匿名函数与原函数形成闭包,把name 、hp 返回
    func Gen(name string) func() (string, int) {
    	hp := 150
    	return func() (string, int) {
    		return name, hp
    	}
    }
    
    func main() {
    	// 调用函数
    	gen := Gen("天使彦")
    	// 通过匿名函数和闭包,返回天使彦的名字和血量
    	name, hp := gen()
    	fmt.Println(name, hp)
    }
    
    

    可以看出闭包还具有一定的封装性,血量是无法从外部修改的,与面向对象的封装类似,限制外部内内部的访问权限。

    闭包很灵活,记住一句话,闭包 = 函数 + 引用环境

    defer语句

    这是go语言独有的特性,延时执行语句。

    延时语句会在所在函数结束时进行。函数结束可以是正常返回时,也可以是发生宕机时。

    由于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
    

    宕机(panic)和宕机恢复(recover)

    • panic:终止程序运行
    • recover:防止程序崩溃

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

    可以手动触发宕机。让程序崩溃。开发者能及时的发现错误,同时减少可能的损失。

    示例1:手动触发宕机

    package main
    
    import "fmt"
    
    func testPanic()  {
    	fmt.Println("start")
    	panic("宕机")
    	fmt.Println("end")
    }
    
    func main() {
    	testPanic()
    }
    
    

    执行结果:

    start
    panic: 宕机
    
    goroutine 1 [running]:
    main.testPanic()
    	D:/Study/Go_Study/src/golang/study/day04、函数/test.go:7 +0x9d
    main.main()
    	D:/Study/Go_Study/src/golang/study/day04、函数/test.go:12 +0x27
    
    Process finished with exit code 2
    

    示例2:宕机后执行defer语句

    package main
    
    import "fmt"
    
    func testPanic()  {
    	fmt.Println("start")
    	// 在宕机时触发延迟执行语句
    	defer fmt.Println("宕机要做的第二件事")
    	defer fmt.Println("宕机要做的第一件事")
    	panic("宕机")
    }
    
    func main() {
    	testPanic()
    }
    
    

    执行结果:

    start
    宕机要做的第一件事
    宕机要做的第二件事
    panic: 宕机
    
    goroutine 1 [running]:
    main.testPanic()
    	D:/Study/Go_Study/src/golang/study/day04、函数/test.go:10 +0x151
    main.main()
    	D:/Study/Go_Study/src/golang/study/day04、函数/test.go:14 +0x27
    
    Process finished with exit code 2
    

    示例3:宕机恢复

    无论是代码运行错误,抛出的宕机错误,还是主动触发的宕机错误,都可以配合defer和recover实现错误捕捉和恢复,让代码崩溃后继续运行。

    package main
    
    import "fmt"
    
    func testRecover() {
    
    	// 延时语句捕捉宕机
    	defer func() {
    		fmt.Println("开始捕捉")
    		err := recover()
    		fmt.Println("捕捉成功,错误为:", err)
    	}()
    
    	panic("发生宕机了,之后执行defer语句,defer语句执行匿名函数,函数内部recover()捕捉错误,继续执行")
    
    }
    
    func main() {
    	testRecover()
    }
    
    

    执行结果:

    开始捕捉
    捕捉成功,错误为: 发生宕机了,之后执行defer语句,defer语句执行匿名函数,函数内部recover()捕捉错误,继续执行
    

    注意:

    • recover()必须搭配defer使用。
    • defer一定要在可能引发panic的语句之前定义。
  • 相关阅读:
    Hdu2222——Keywords Search(AC自动机模板题)
    20180804的Test
    Poj3764---The xor-longest Path
    Bzoj4567---背单词
    Bzoj1590——Secret Message(Trie)
    Bzoj 1212----L语言(Trie)
    Poj1056---IMMEDIATE DECODABILITY(Trie)
    The Xor Largest Pair(Trie)
    Bzoj 4260——Codechef REBXOR(Trie)
    [接上一篇]spring boot启动成功之后,测试用例中需要使用的注入对象均为null
  • 原文地址:https://www.cnblogs.com/xjmlove/p/11200676.html
Copyright © 2020-2023  润新知