第3章 函数

3.1函数的定义

不支持 嵌套 (nested)、重载 (overload) 和 默认参数 (default parameter)
• 无需声明原型。
• 支持不定长变参。
• 支持多返回值。
• 支持命名返回参数。
• 支持匿名函数和闭包。
使用关键字 func 定义函数,左大括号依旧不能另起一行

func test(x, y int, s string) (int, string) { // 类型相同的相邻参数可合并。
	n := x + y // 多返回值必须⽤括号。
	return n, fmt.Sprintf(s, n)
}

函数是第⼀类对象,可作为参数传递。建议将复杂签名定义为函数类型,以便于阅读
有返回值的函数,必须有明确的终⽌语句,否则会引发编译错误

func test(fn func() int) int {
	return fn()
}

type FormatFunc func(s string, x, y int) string // 定义函数类型。
func format(fn FormatFunc, s string, x, y int) string {
	return fn(s, x, y)
}

func main() {
	s1 := test(func() int { return 100 }) // 直接将匿名函数当参数。
	s2 := format(func(s string, x, y int) string {
		return fmt.Sprintf(s, x, y)
	}, "%d, %d", 10, 20)
	fmt.Println(s1, s2)
}
/*
	100 10, 20
*/

3.2变参

变参本质上就是 slice。只能有⼀个,且必须是最后⼀个

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

func main() {
	fmt.Println(test("sum: %d", 1, 2, 3))
}

使⽤ slice 对象做变参时,必须展开

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

func main() {
	s := []int{1, 2, 3}
	fmt.Println(test("sum: %d", s...))
}

3.3 返回值

不能用容器对象接收多返回值。只能用多个变量,或 “_” 忽略

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

func main() {
	// s := make([]int, 2)
	// s = test() // Error: multiple-value test() in single-value context
	x, _ := test()
	fmt.Println(x)
}

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

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() {
	fmt.Println(add(test()))
	fmt.Println(sum(test()))
}

命名返回参数可看做与形参类似的局部变量,最后由 return 隐式返回

func add(x, y int) (z int) {
	z = x + y
	return
}
func main() {
	fmt.Println(add(1, 2))
}

命名返回参数可被同名局部变量遮蔽,此时需要显式返回

func add(x, y int) (z int) {
	{ // 不能在⼀个级别,引发 "z redeclared in this block" 错误。
		var z = x + y
		// return // Error: z is shadowed during return
		return z // 必须显式返回。
	}
}

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

func add(x, y int) (z int) {
	defer func() {
		z += 100
	}()
	z = x + y
	return
}
func main() {
	fmt.Println(add(1, 2)) // 输出: 103
}

显式 return 返回前,会先修改命名返回参数

func add(x, y int) (z int) {
	defer func() {
		println(z) // 输出: 203
	}()
	z = x + y
	return z + 200 // 执⾏顺序: (z = z + 200) -> (call defer) -> (ret)
}

func main() {
	println(add(1, 2)) // 输出: 203
}

3.4 匿名函数

匿名函数可赋值给变量,做为结构字段,或者在 channel 里传送

// --- function variable ---
fn := func() { fmt.Println("Hello, World!") }
fn()
// --- function collection ---
fns := [](func(x int) int){
	func(x int) int { return x + 1 },
	func(x int) int { return x + 2 },
}
fmt.Println(fns[0](100))
// --- function as field ---
d := struct {
	fn func() string
}{
	fn: func() string { return "Hello, World!" },
}
fmt.Println(d.fn())
// --- channel of function ---
fc := make(chan func() string, 2)
fc <- func() string { return "Hello, World!" }
fmt.Println((<-fc)())

闭包复制的是原对象指针,这就很容易解释延迟引用现象

func test() func() {
	x := 100
	fmt.Printf("x (%p) = %d\n", &x, x)
	return func() {
		fmt.Printf("x (%p) = %d\n", &x, x)
	}
}

func main() {
	f := test()
	f()
	/*
		x (0xc0000aa058) = 100
		x (0xc0000aa058) = 100
	*/
}

3.5 延迟调用

关键字 defer 用于注册延迟调用。这些调用直到 ret 前才被执行,通常用于释放资源或错误处理

f, err := os.Create("test.txt")
if err != nil {
	fmt.Printf("error=%v", err)
}
defer f.Close() // 注册调⽤,⽽不是注册函数。必须提供参数,哪怕为空。
f.WriteString("Hello, World!")

多个 defer 注册,按 FILO 次序执行。哪怕函数或某个延迟调用发生错误,这些调用依旧会被执行
tips:后进先出(LIFO)== 先进后出(FILO)

func test(x int) {
	defer println("a")
	defer println("b")
	defer func() {
		println(100 / x) // div0 异常未被捕获,逐步往外传递,最终终⽌进程。
	}()
	defer println("c")
}

func main() {
	test(0)
	/*
		c
		b
		a
		panic: runtime error: integer divide by zero
	*/
}

延迟调用参数在注册时求值或复制,可用指针或闭包 “延迟” 读取

x, y := 10, 20
defer func(i int) {
	fmt.Println("defer:", i, y) // y 闭包引⽤
}(x) // x 被复制
x += 10
y += 100
fmt.Println("x =", x, "y =", y)
/*
	x = 20 y = 120
	defer: 10 120
*/

滥用defer 可能会导致性能问题,尤其是在⼀个 “大循环” 里
go test是所有在以_test结尾的源码内以Test开头的函数会自动被执行

//go test -v -bench="." E:\GoGithub\src\Golang-100-Days\Day01-15(Go语言基础)\code\day01\helloworld_test.go
var lock sync.Mutex

func test() {
	lock.Lock()
	lock.Unlock()
}

func testdefer() {
	lock.Lock()
	defer lock.Unlock()
}

func BenchmarkTest(b *testing.B) {
	for i := 0; i < b.N; i++ {
		test()
	}
}

func BenchmarkTestDefer(b *testing.B) {
	for i := 0; i < b.N; i++ {
		testdefer()
	}
}

/*
	goos: windows
	goarch: amd64
	cpu: AMD Ryzen 7 4800HS with Radeon Graphics
	BenchmarkTest
	BenchmarkTest-16                140230344                8.548 ns/op
	BenchmarkTestDefer
	BenchmarkTestDefer-16           100000000               12.42 ns/op
	PASS
	ok      command-line-arguments  3.933s
*/

3.6 错误处理

没有结构化异常,使用panic 抛出错误,recover 捕获错误
注意:
由于 panic、recover 参数类型为 interface{},因此可抛出任何类型对象

func panic(v interface{})
func recover() interface{}

func test() {
	defer func() {
		if err := recover(); err != nil {
			println(err.(string)) // 将 interface{} 转型为具体类型。
		}
	}()
	panic("panic error!")
}

func main() {
	test()
}

延迟调用中引发的错误,可被后续延迟调用捕获,但仅最后⼀个错误可被捕获

func test() {
	defer func() {
		fmt.Println(recover())
	}()
	defer func() {
		panic("defer panic")
	}()
	panic("test panic")
}

func main() {
	test()
}

/*
	defer panic
*/

捕获函数 recover 只有在延迟调用内直接调用才会终止错误,否则总是返回 nil。任何未捕获的错误都会沿调用堆栈向外传递
注意:

  1. recover如果想起作用的话, 必须在defered函数中使用
  2. 在正常函数执行过程中,调用recover没有任何作用, 他会返回nil。如这样:fmt.Println(recover())
  3. 如果当前的goroutine panic了,那么recover将会捕获这个panic的值,并且让程序正常执行下去。不会让程序crash。
func except() {
	if err := recover(); err != nil {
		fmt.Println(err) // 这里的err其实就是panic传入的内容
	}
}

func test() {
	// defer recover() // ⽆效!

	// defer fmt.Println(recover()) // ⽆效!

	// defer func() {
	// 	func() {
	// 		println("defer inner")
	// 		recover() // ⽆效!
	// 	}()
	// }()

	defer except() //有效捕获!

	// defer func() {
	// 	println("defer inner2")
	// 	recover() // 有效捕获!
	// }()
	panic("test panic")
}

func main() {
	test()
}

如果需要保护代码片段,可将代码块重构成匿名函数,如此可确保后续代码被执行

//方法1:
func test(x, y int) {
	var z int
	func() {
		defer func() {
			if err := recover(); err != nil {
				fmt.Println(err)
				z = 100000
			}
		}()
		z = x / y
		return
	}()
	fmt.Println("x / y =", z)
}
func main() {
	test(2, 0)
	/*
		runtime error: integer divide by zero
		x / y = 100000
	*/
}

//方法2:
func test(x, y int) {
	var z int
	defer func() {
		if err := recover(); err != nil {
			fmt.Println(err)
			z = 100000
			fmt.Println("x / y =", z)
		}
	}()
	z = x / y
}

func main() {
	test(2, 0)
	/*
		runtime error: integer divide by zero
		x / y = 100000
	*/
}

除用panic 引发中断性错误外,还可返回 error 类型错误对象来表示函数调用状态

type error interface {
	Error() string
}

标准库 error.New 和 fmt.Errorf 函数用于创建实现 error 接口的错误对象。通过判断错误对象实例来确定具体错误类型
如何区别使用 panic 和 error 两种方式?惯例是:在包内部使用 panic,对外 API 使用error 返回值

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() {
		if error := recover(); error != nil {
			fmt.Println(error)
		}
	}()
	switch z, err := div(10, 0); err {
	case nil:
		fmt.Println(z)
	case ErrDivByZero:
		panic(err)
	}
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值