
Go 语言支持普通函数、匿名函数和闭包,从设计上对函数进行了优化和改进,让函数使用起来更加方便。
Go 语言的函数属于“一等公民”(first-class),也就是说:
函数定义:
- func function_name( [parameter list] ) [return_types] {
- 函数体
- }
返回值可以为多个:
- func test(x, y int, s string) (int, string) {
- // 类型相同的相邻参数,参数类型可合并。 多返回值必须用括号。
- n := x + y
- return n, fmt.Sprintf(s, n)
- }
函数做为一等公民,可以做为参数传递。
- func fn() int {
- return 300
- }
- func test(fn func() int) int {
- return fn()
- }
- func main() {
- //这是直接使用匿名函数
- s := test(func() int { return 200 })
- fmt.Println(s) //200
- //传入一个函数
- s2 := test(fn)
- fmt.Println(s2) //300
- }
在将函数做为参数的时候,我们可以使用类型定义,将函数定义为类型,这样便于阅读
- type myFunc func(s string, x, y int) string
-
- func format(fn myFunc, s string, x, y int) string {
- return fn(s, x, y)
- }
-
- func formatFunc(s string, x, y int) string {
- return fmt.Sprintf(s, x, y)
- }
- func main() {
- s2 := format(formatFunc, "%d %d", 10, 20)
-
- fmt.Println(s2)
- }
函数返回值可以有多个,同时Go支持对返回值命名
- func main() {
- var a, b = 10, 20
- fmt.Println(sum(a, b))
- }
- //多个返回值 用括号扩起来
- func sum(a, b int) (int, int) {
- return a, b
- }
函数定义时指出,函数定义时有参数,该变量可称为函数的形参。
形参就像定义在函数体内的局部变量。
但当调用函数,传递过来的变量就是函数的实参,函数可以通过两种方式来传递参数:
- func swap(x, y int) int {
- ... ...
- }
- package main
-
- import (
- "fmt"
- )
-
- /* 定义相互交换值的函数 */
- func swap(x, y *int) {
- *x,*y = *y,*x
- }
-
- func main() {
- var a, b int = 1, 2
- /*
- 调用 swap() 函数
- &a 指向 a 指针,a 变量的地址
- &b 指向 b 指针,b 变量的地址
- */
- swap(&a, &b)
-
- fmt.Println(a, b)
- }
map、slice、chan、指针、interface默认以引用的方式传递。
不定参数传值
不定参数传值 就是函数的参数不是固定的,后面的类型是固定的。(可变参数)
Golang 可变参数本质上就是 slice。只能有一个,且必须是最后一个。
在参数赋值时可以不用用一个一个的赋值,可以直接传递一个数组或者切片。
格式:
- func test1(args ...int) { //0个或多个参数
-
- }
-
- func test2(a int, args ...int) int { //1个或多个参数
-
- }
-
- func test3(a int, b int, args ...int) int { //2个或多个参数
-
- }
注意:其中args是一个slice,我们可以通过arg[index]依次访问所有参数,通过len(arg)来判断传递参数的个数.
- func main() {
- a := []int{10, 20, 10}
- fmt.Println(test("sum: %d", a...)) // slice... 展开slice
- }
-
- func test(s string, n ...int) string {
- var x = 0
- for _, v := range n {
- x += v
- }
- return fmt.Sprintf(s, x)
- }
匿名函数是指不需要定义函数名的一种函数实现方式。
在Go里面,函数可以像普通变量一样被传递或使用,Go语言支持随时在代码里定义匿名函数。
匿名函数由一个不带函数名的函数声明和函数体组成。匿名函数的优越性在于可以直接使用函数内的变量,不必声明。
匿名函数的定义格式如下:
- func(参数列表)(返回参数列表){
- 函数体
- }
- func main() {
- //这里将一个函数当做一个变量一样的操作。
- getSqrt := func(a float64) float64 {
- return math.Sqrt(a)
- }
-
- fmt.Println(getSqrt(4.123))
- }
在定义时调用匿名函数
匿名函数可以在声明后调用,例如:
- func main() {
- func(a int) {
- fmt.Println(a)
- }(100) //(100),表示对匿名函数进行调用,传递参数为 100。
- }
返回多个匿名函数
- package main
-
- import "fmt"
-
- func FGen(x, y int) (func() int, func(int) int) {
-
- //求和的匿名函
- sum := func() int {
- return x + y
- }
-
- // (x+y) *z 的匿名函数
- avg := func(z int) int {
- return (x + y) * z
- }
- return sum, avg
- }
-
- func main() {
-
- f1, f2 := FGen(1, 2)
- fmt.Println(f1())
- fmt.Println(f2(3))
- }
所谓“闭包”,指的是一个拥有许多变量和绑定了这些变量的环境的表达式(通常是一个函数),因而这些变量也是该表达式的一部分。
闭包=函数+引用环境
- package main
-
- import "fmt"
-
- func main() {
- // 创建一个玩家生成器
- generator := playGen("码神")
- // 返回玩家的名字和血量
- name, hp := generator()
- // 打印值
- fmt.Println(name, hp)
-
- generator1 := playGen2()
- name1, hp1 := generator1("码神")
- // 打印值
- fmt.Println(name1, hp1)
- }
-
- // 创建一个玩家生成器, 输入名称, 输出生成器
- func playGen(s string) func() (string, int) {
- // 血量一直为150
- hp := 150
- // 返回创建的闭包
- return func() (string, int) {
- // 将变量引用到闭包中
- return s, hp
- }
- }
-
- // 创建一个玩家生成器, 输入名称, 输出生成器
- func playGen2() func(name string) (string, int) {
- hp := 150
- return func(name string) (string, int) {
- return name, hp
- }
- }
Go语言的 defer 语句会将其后面跟随的语句进行延迟处理
defer特性:
defer的用途:
go 语言的defer功能强大,对于资源管理非常方便,但是如果没用好,也会有陷阱。
- package main
-
- import (
- "log"
- "time"
- )
-
- func main() {
- start := time.Now()
- log.Printf("开始时间为:%v", start)
- defer log.Printf("时间差:%v", time.Since(start)) // Now()此时已经copy进去了
- //不受这3秒睡眠的影响
- time.Sleep(3 * time.Second)
-
- log.Printf("函数结束")
- }
如何解决上述问题:使用defer func()
- func main() {
- start := time.Now()
-
- defer func() {
- log.Printf("开始调用defer")
- log.Printf("时间差:%v", time.Since(start))
- log.Printf("结束调用defer")
- }()
- time.Sleep(3 * time.Second)
-
- log.Printf("函数结束")
- }
因为拷贝的是函数指针,函数属于引用传递
另一个问题:
- func main() {
- var whatever = [5]int{1, 2, 3, 4, 5}
- for i, _ := range whatever {
- //函数正常执行,由于闭包用到的变量 i 在执行的时候已经变成4,所以输出全都是4.
- defer func() { fmt.Println(i) }()
- }
- }
解决:
- func main() {
- var whatever = [5]int{1, 2, 3, 4, 5}
- for i, _ := range whatever {
- i := i
- defer func() { fmt.Println(i) }()
- }
- }
Go语言中使用 panic 抛出错误,recover 捕获错误。
异常的使用场景简单描述:Go中可以抛出一个panic的异常,然后在defer中通过recover捕获这个异常,然后正常处理。
panic:
recover:
golang 的错误处理流程:当一个函数在执行过程中出现了异常或遇到 panic(),正常语句就会立即终止,然后执行 defer 语句,再报告异常信息,最后退出 goroutine。如果在 defer 中使用了 recover() 函数,则会捕获错误信息,使该错误信息终止报告。
注意:
- func main() {
- test()
- }
-
- func test() {
- defer func() {
- if err := recover(); err != nil {
- log.Println(err) //2024/02/28 23:03:38 print out panic
-
- }
- }()
-
- panic("print out panic")
-
- }
由于 panic、recover 参数类型为 interface{},因此可抛出任何类型对象。
- func panic(v interface{})
- func recover() interface{}
延迟调用中引发的错误,可被后续延迟调用捕获,但仅最后一个错误可被捕获:
- func test() {
- defer func() {
- // defer panic 会打印
- fmt.Println(recover()) //defer panic
- }()
-
- defer func() {
- panic("defer panic")
- }()
-
- panic("test panic")
- }
-
- func main() {
- test()
- }
除用 panic 引发中断性错误外,还可返回 error 类型错误对象来表示函数调用状态:
- type error interface {
- Error() string
- }
标准库 errors.New 和 fmt.Errorf 函数用于创建实现 error 接口的错误对象。通过判断错误对象实例来确定具体错误类型。
- package main
-
- import (
- "errors"
- "fmt"
- "log"
- )
-
- var divError = errors.New("div error:分母不能为0")
-
- func div(x, y int) (int, error) {
- if y == 0 {
- return 0, divError
- }
- return x / y, nil
- }
-
- func main() {
- defer func() {
- if err := recover(); err != nil {
- log.Fatalln(err) //2024/02/28 23:17:14 div error:分母不能为0
-
- }
- }()
-
- res, err := div(8, 0)
-
- switch err {
- case nil:
- fmt.Println(res)
- case divError:
- panic(err)
- }
-
- }