Go语言程序设计-第5章--函数

Go语言程序设计-第5章–函数

5.1 函数声明

每个函数声明都包含一个名字、一个形参列表、一个可选的返回列表以及函数体:

func name(parameter-list) (result-list) {
    
    
	body
}
func add(x int, y int) int {
    
     return x + y}
func sub(x, y int) (z int) {
    
    z = x - y; return}
func first(x int, _ int) int {
    
     return x }
func zero(int, int) int {
    
    return 0}

fmt.Printf("%T\n", add) // "func(int, int) int"
fmt.Printf("%T\n", sub) // "func(int, int) int"
fmt.Printf("%T\n", add) // "func(int, int) int"
fmt.Printf("%T\n", zero) // "func(int, int) int"

函数的类型称为函数签名

实参是按值传递的。如果提供的实参包含引用类型,比如指针、slice、map、函数或者通道,那么当函数使用形参变量时就有可能间接地修改实参变量。

有些函数的声明没有函数体,说明这个函数使用除了 Go 以外的语言实现。

package math

func Sin(x float64) float64 // 使用汇编语言实现

5.2 递归

5.3 多返回值

函数可以有多个返回值。一个函数如果有命名的返回值,可以省略 return 语句的操作数,称为裸返回

5.4 错误

Go 程序使用通常的控制流机制(比如if 和 return语句)应对错误。

5.4.1 错误处理策略

5.4.2 文件结束标识

EOF 定义:

package io
import "errors"

var EOF = errors.New("EOF")

使用示例:

in := bufio.NewReader(os.Stdin)
for {
    
    
	r, _, err := in.ReadRune()
	if err == io.EOF {
    
    
		break // 结束读取
	}
	if err != nil {
    
    
		return fmt.Errorf("read failed: %v", err)
	}
}

5.5 函数变量

函数变量也有类型,可以赋给变量或者传递给其他函数,或者从其他函数中返回。

func square(n int) int {
    
     return n * n }
func negative(n int) int {
    
     return  -n }
func product(m, n int) int {
    
    return m * n}

f = square
mt.Println(f(3)) // "9"

函数类型的零值是nil(空值),调用一个空的函数变量导致宕机。

5.6 匿名函数

strings.Map(func(r rune) rune) {
    
    return r + 1}, "HAL-9000")

函数里可以使用外层函数的变量。这些隐藏的变量引用就是我们把函数归类为引用类型而且函数变量无法进行比较的原因。函数变量类似于使用闭包方法实现的变量,Go 程序员通常把函数变量成为闭包。

func squares() func() int {
    
    
	var x int
	return func() int {
    
    
		x++
		return x * x
	}
}
func main() {
    
    
	f := squares()
	fmt.Println(f())
	fmt.Println(f())
	fmt.Println(f())
	fmt.Println(f())
}

输出:

1
4
9
16

警告:捕获迭代变量

var rmdirs []func()
for _, d := range tempDirs() {
    
    
	dir := d // 注意,这一行是必须的
	os.MkdirAll(dirkk, 0755)
	rmdirs = append(rmdirs, func() {
    
    
		os.RemoveAll(dir)
		})
}

for _, rmdir := range rmdirs {
    
    
	rmdir() // 清理
}

为什么在循环体内将循环变量赋给一个新的局部变量 dir,而不是下面的版本。

var rmdirs []func()
for _, d := range tempDirs() {
    
    
	os.MkdirAll(dirkk, 0755)
	rmdirs = append(rmdirs, func() {
    
    
		os.RemoveAll(dir)
		})
}

原因是循环变量的作用域的规则限制。在上面的程序中,dir 在 for 循环引进的一个块作用域内进行声明。在循环里创建的所有函数变量共享相同的变量 – 一个可以访问的存储位置,而不是固定的值。dir 变量的值在不断地迭代中更新,因为当调用清理函数时,dir 变量是最后一次迭代时的值。我们用内部变量解决这个问题。

for _, dir := range tempDirs() {
    
    
	dir := dir // 声明内部 dir,并以外表 dir 初始化
}

5.7 变长函数(有可变的参数个数)

func sum(vals ...int) int {
    
    
	total := 0
	for _, val := range vals {
    
    
		total += val
	}
	return total
}

变长函数的类型和一个带有普通 slice 参数的函数类型不相同。

5.8 延迟函数调用

package ioutil

func ReadFile(filename string)([]byte, error) {
    
    
	f, err := os.Open(filename)
	if err != nil {
    
    
		return nil, err
	}
	defer f.Close
	return ReadAll(f)
}

defer 语句,无论在正常的情况下,执行 return 语句或者函数执行完毕,还是在不正常的情况下,比如发生宕机,实际的调用推迟到包含 defer 语句的函数结束后才放行。

func bigSlowOperation() {
    
    
	defer traxce("bigSlowOperation")() // 别忘记这对圆括号
	time.Sleep(10 * time.Second)
}

func trace(msg string) func() {
    
    
	start := time.Now()
	log.Printf("enter %s", msg)
	return func() {
    
     log.Printf("exit %s (%s)", msg, time.Since(start))}
}

延迟的匿名函数能够改变外层函数返给调用者的结果。

func triple(x int) (result int) {
    
    
    defer func() {
    
     result += x }()
    return x + x
}

func main() {
    
    
    fmt.Println(triple(4))
}

5.9 宕机(Panic)

有些错误(比如数组越界访问或者解引用空指针)都需要在运行时进行检查。当 Go 语言运行时检测到这些错误,就会发生宕机。

一个典型的宕机发生时,正常的程序执行会终止, goroutine 中所有的延迟函数会执行,然后程序会异常退出并打印一条日志。日志消息包括宕机的值,这往往代表某种错误消息,每一个 goroutine 都会在宕机的时候显示一个函数调用的栈跟踪消息。

宕机发生时,defer 函数会以倒序执行。

func main() {
    
    
	defer printStack()
}

func printStack() {
    
    
	var buf [4096]byte
	n := runtime.Stack(buf[:], false)
	os.Stdout.Write(buf[:n])
}

5.10 恢复

recover 会终止当前的宕机状态,并且返回宕机的值。函数不会从之前宕机的地方继续运行而是正常返回。如果 recover 在没有宕机的情况下调用,没有任何结果,并且返回 nil。

猜你喜欢

转载自blog.csdn.net/houzhizhen/article/details/135287364