go语言函数(参数、匿名、defer、异常处理、单元测试、压力测试)

本文详细介绍了Go语言中函数的特点,包括可返回多个值、可变参数、匿名函数和闭包的使用。还讲解了defer的执行顺序和用途,以及异常处理机制panic和recover的运用。此外,还涵盖了单元测试的基本规则和注意事项。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

1. go中函数有以下几个特点:

(1)可返回多个值,但需用括号将返回值类型括起来

(2)参数个数可变,可以理解为切片,使用...表示

(3)支持匿名函数和闭包

(4)函数本身就是一种类型,可以赋值给变量

(5)返回值可以被命名,当返回值都被命名时,return语句之后可以没有参数,比如:

func calc(a, b int) (sum int, avg int) {
    sum = a + b
    avg = (a + b) / 2

    return
}

但当代码较长时,该写法会影响代码可读性,故只建议在上面这种代码比较简短的情况时使用

明明返回参数允许defer延迟调用通过闭包读取和修改:

package main

func add(x, y int) (z int) {
	defer func() {
		z += 100
	}()

	z = x + y
	return
}
func main() {
	println(add(1,2))
}

运行结果:

package main

func add(x, y int) (z int) {
	defer func() {
		println(z)
		z += 100
	}()

	z = x + y
	return z + 200
}
func main() {
	println(add(1,2))
}

运行结果:

由输出结果可以看出,defer会将函数的return延迟,待defer内的函数执行完之后才会返回,所以才会输出两个不一样的数字,由此可以看出上例add函数中的执行顺序为 z=z+200 ——> call defer ——>return

2. 关于go中函数可变参数的一个实例如下:

package main

import (
	"fmt"
)

func test(s string, n ...int) string {
	var x int
	for _, i := range n {
		x += i
	}
	return fmt.Sprintf(s,x)
}

func main() {
	println(test("sum: %d",1, 2, 3))
	s := []int{1, 2, 3}
	res := test("sum: %d", s...) //使用slice对象做变参时,必须要使用...展开
	println(res)
}

3. 多返回值可直接作为其他函数的实参来调用

package main

func test() (int, int) {
	return 1, 2
}

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

func sum(n ...int) int {
	var x int
	for _, i := range n {
		x += i
	}
	return x
}
func main() {
	println(add(test()))
	println(sum(test()))
}

4. 匿名函数

匿名函数由一个不带函数名和函数声明和函数体构成,其优越性在于可以直接使用函数体内的变量,不必声明

几种用法的实例如下:

package main

import (
	"fmt"
	"math"
)
func main() {
	//1.赋值给变量
	getSqrt := func(a float64) float64 {
		return math.Sqrt(a)
	}
	fmt.Println(getSqrt(4))

	fn := func() {println("Hello World!")}
	fn()

	//2.函数集合
	fns := [](func (x int) int) {
		func(x int) int {return x + 1},
		func(x int) int {return x + 2},
	}
	println(fns[0](100))

	//3.可赋值给变量,作为结构字段
	d := struct {
		fn func() string
	}{
		fn: func() string {return "Hello, World!"},
	}
	println(d.fn())

	//channel of function
	fc := make(chan func() string, 2)
	fc <- func() string {return "Hello, World!"}
	println((<-fc)())
}

运行结果:

 

5. 延迟调用(defer)

defer特性:

1. 关键字defer用于注册延迟调用

2. 这些调用直到return前才被执行。因此,可以用来做资源清理

3. 多个defer语句,按先进后出的方式执行

4. defer语句中的变量,在defer声明时就决定了

defer用途:

1. 关闭文件句柄

2. 锁资源释放

3. 数据库连接释放

(1)defer先进后出,因为后面的语句会依赖前面的资源,因此如果前面的资源先释放了,后面的语句就没法执行了

一个实例如下:

package main

import (
	"fmt"
)

func main() {
	var whatever [5]struct{}

	for i := range whatever {
		defer fmt.Println(i)
	}
}

 运行结果:

一个很容易犯错的地方(看下面这两个例子):

package main

import "fmt"

type Test struct {
	name string
}

func (t *Test) Close() {
	fmt.Println(t.name, " closed")
}

func main() {
	ts := []Test{{"a"}, {"b"}, {"c"}}
	for _, t := range ts {
		defer t.Close()
	}
}

package main

import "fmt"

type Test struct {
	name string
}

func (t *Test) Close() {
	fmt.Println(t.name, " closed")
}

func main() {
	ts := []Test{{"a"}, {"b"}, {"c"}}
	for _, t := range ts {
		t2 := t
		defer t2.Close()
	}
}

上面两个例子,只差了一个看似多此一举的声明t2,结果却完全不同,第一个例子是我们需要注意的,不要这样的错误

至于为什么会有这种差异,由官方文档的解释来看,有如下结论:

defer后面的语句在执行的时候,函数调用的参数会被保存起来,但是不执行。也就是复制了一份,但是并没有说struct这里的this指针如何处理,通过上述例子可以看出go语言并没有把这个明确写出来的this指针当作参数来看待。

defer用于http.Get失败时抛出异常

package main

import (
	"net/http"
)

func do() error {
	res, err := http.Get("http://www.google.com")
	if res != nil {  //注意这里一定要判别res是否为nil,这是http.Get的一个警告,否则会将错误返回而造成panic
		defer res.Body.Close()
	}
	if err != nil {
		return err
	}
	return nil
}
func main() {
	do()
}

6. 异常处理

Go没有结构化一场,使用panic抛出错误,recover捕获错误。

异常的使用场景简单描述:Go中可以抛出一个panic的异常,然后在defer中通过recover捕获这个异常,然后正常处理

panic(引发中断性错误):

(1) 内置函数

(2) 若函数中写了panic语句,则会终止其后要执行的代码

(3) 直到整个goroutine退出,并报告错误

recover:

(1) 内置函数

(2) 用来控制一个goroutine的panicking行为,捕获panic,从而影响应用的行为

(3) 一般的调用建议

         a)在defer函数中,通过recover来终止一个goroutine的panicking过程,从而恢复正常代码的执行

         b)  可以获取通过panic传递的error

注意:

(1) 利用recover处理panic指令,defer必须放在panic之前定义,并且recover必须放在defer的函数中,否则不会影响panic的扩散

(2) recover处理异常后,函数会恢复到defer之后的那个点

error(表示函数调用状态):使用标准库errors.New和fmt.Errorf函数用于创建实现error接口的错误对象

一个实例如下:

package main

import (
	"errors"
	"fmt"
)

var ErrDivByZero = errors.New("division by zero")

func div(x, y int) (int, error) {
	if y == 0 {
		return 0, ErrDivByZero
	}
	return x / y, nil
}
func main() {
	defer func() {
		fmt.Println(recover())
	}()
	switch z, err := div(10, 0); err{
	case nil:
		println(z)
	case ErrDivByZero:
		panic(err)
	}

}

运行结果:

如何区别使用panic和error两种方式?

一般来说,导致关键流程出现不可修复性错误的使用panic,其他使用error

 

7. 单元测试

Go语言中的测试依赖go test命令,在包目录内,所有以_test.go为后缀名的源代码文件都是go test测试的一部分,不会被go build编译到最终的可执行文件中。

go test命令会遍历所有的*_test.go文件中符合上述命名规则的函数,然后生成一个临时的main包用于调用相应的测试函数,然后构建并运行、报告测试结果,最后清理测试中生成的临时文件

Golang单元测试对文件名和方法名,参数都有很严格的要求:

(1) 文件名必须以*_test.go命名

(2) 方法必须是Test[^a-z]开头

(3) 方法参数必须时 t *testing.T

(4) 使用go test进行单元测试

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值