函数

定义

函数是结构化编程的最小模块单元,将复杂的算法过程分解为若干较小任务,代码复用和测试的基本单元;关键字func用于定义函数

参数

go不支持有默认值的可选参数,不支持命名实参,调用时,必须按签名顺序传递指定类型和数量的实参

参数列表中,相邻的同类型参数可合并

参数可视为函数局部变量,不能在相同层次定义同名变量

形参是指函数定义中的参数,实参则是函数调用时传递的参数;形参类似函数的局部变量,而实参则是函数外部对象

不管是指针,引用类型,还是其他类型参数,都是值拷贝传递;在函数调用前,会为形参和返回值分配内存空间,并将实参拷贝到形参内存

如果函数的参数过多,建议将其重构为一个复杂结构类型,也算是变相实现可选参数和命名实参功能

package main

import (
	"fmt"
	"log"
	"time"
)

type serverOption struct {
	address  string
	port     int
	path     string
	timeount time.Duration
	log      *log.Logger
}

func newOption() *serverOption {
	return &serverOption{
		address:  "0.0.0.0",
		port:     8080,
		path:     "/var/test",
		timeount: time.Second * 5,
		log:      nil,
	}
}

func server(option *serverOption) {
	fmt.Println(option)
}

func main() {
	opt := newOption()
	opt.port = 8085
	server(opt)
}

  

变参

变参本质上就是一个切片,只能接收一到多个同类型参数,且必须放在列表尾部

package main

import (
	"fmt"
)

func test(s string, a ...int) {
	fmt.Printf("%T,%v\n", a, a)
}

func main() {
	test("abc", 1, 2, 3, 4)
}

  

输出

[]int,[1 2 3 4]

  

将切片作为变参时,需进行展开操作,如果是数组,先将其转换为切片

func test(a ...int) {
	fmt.Println(a)
}

func main() {
	a := [3]int{10, 20, 30}
	test(a[:]...)
}

  

返回值

有返回值的函数,必须有明确的return终止语句

多返回值列表必须括号

可以对返回值命名

func div(x, y int) (int, error) {
	if y == 0 {
		err := errors.New("division by zero")
		return -1, err
	}
	return x / y, nil
}

func main() {
	result, err := div(4, 0)
	if err != nil {
		fmt.Println(err)
	}
	fmt.Println(result)
}

  

匿名函数

匿名函数是指没有定义名字符号的函数

在函数内部定义匿名函数,匿名函数可直接调用,保存到变量,作为参数或返回值

直接执行

func main() {
	func(s string) {
		fmt.Println(s)
	}("hello world")
}

  

赋值给变量

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

  

作为参数

func test(f func()) {
	f()
}

func main() {
	test(func() {
		fmt.Println("hello")
	})
}

  

闭包

example

func test(x int) func() {
	return func() {
		fmt.Println(x)
	}
}

func main() {
	f := test(123)
	f()
}

  

test返回的匿名函数会引用上下文环境变量x,在main中执行时,依然可正确读取x的值,这种现象称为闭包

正因为闭包通过指针引用环境变量,导致其生命周期延长,甚至被分配到堆内存

输出

0xc04204e080 2
0xc04204e080 2

  

main执行这两函数时,读取的环境变量i=2

怎么解决?

每次用不同的环境变量或传参赋值,让各自闭包环境不同

func test() []func() {
	var s []func()
	for i := 0; i < 2; i++ {
		x := i
		s = append(s, func() {
			fmt.Println(&x, x)
		})
	}
	return s
}

func main() {
	for _, f := range test() {
		f()
	}
}

  

延迟调用

defer向当前函数注册 稍后执行的函数调用,这些调用称为延迟调用,因为它们直到当前函数执行结束前才被执行,常用于资源释放,解除锁定,错误处理等

func main() {
	x, y := 1, 2
	defer func(x int) {
		fmt.Println(x, y) //y为闭包引用
	}(x)             //注册时复制调用参数
	x += 100          //对x的修改不用影响延时调用
	y += 100
	fmt.Println(x, y)
}

  

输出

101 102
1 102

  

多个延迟注册按FILO次序执行

编译器通过插入额外指令来实现延迟调用执行,return和panic会终止当前函数流程,引发延迟调用

package main

import (
	"fmt"
)

func test() (z int) {
	defer func() {
		fmt.Println("defer:", z)
		z += 100
	}()
	return 1 //实际执行顺序:z=1,call defer,ret
}

func main() {
	fmt.Println("test:", test())
}

  

输出

defer: 1
test: 101

  

误用

延迟调用在函数结束时才被执行

案例:循环处理多个日志文件,不恰当的defer导致文件关闭时间延长

func main() {
	for i := 0; i < 1000; i++ {
		path := fmt.Sprintf("./log/%d.txt", i)

		f, err := os.Open(path)
		if err != nil {
			log.Println(err)
			continue
		}

		defer f.Close()
	}
}

  

这个关闭操作在main函数执行结束才会执行,不会在循环中执行,应该重构为函数,将循环和处理算法分离

func main() {
	do := func(n int) {
		path := fmt.Sprintf("./log/%d.txt", n)

		f, err := os.Open(path)
		if err != nil {
			log.Println(err)
			return
		}

		defer f.Close()
	}

	for i := 0; i < 1000; i++ {
		do(i)
	}
}

  

错误处理

error

标准库将error定义为接口类型,以便实现自定义错误类型

type errpr interface {
	Error() string
}

  

自定义错误类型,根据错误类型判断

package main

import (
	"fmt"
)

type DivError struct {
	x, y int
}

func (DivError) Error() string {
	return "division by zero"
}

func div(x, y int) (int, error) {
	if y == 0 {
		return 0, DivError{x, y}
	}
	return x / y, nil
}

func main() {
	_, err := div(5, 0)
	if err != nil {
		switch e := err.(type) {
		case DivError:
			fmt.Println(e, e.x, e.y)
		default:
			fmt.Println(e)
		}
	}
}

  

panic,recover

panic会立即中断当前函数流程,执行延迟调用;在延迟调用中,recover能捕获并返回panic提交的错误对象

package main

import (
	"fmt"
)

func main() {
	defer func() {
		if err := recover(); err != nil {
			fmt.Println(err)
		}
	}()

	panic("dead")
	fmt.Println("end") //永不执行
}

  

 

posted @ 2018-07-26 20:29  hongpeng0209  阅读(183)  评论(0编辑  收藏  举报