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。任何未捕获的错误都会沿调用堆栈向外传递
注意:
- recover如果想起作用的话, 必须在defered函数中使用
- 在正常函数执行过程中,调用recover没有任何作用, 他会返回nil。如这样:fmt.Println(recover())
- 如果当前的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)
}
}