• 函数


    定义

    函数是结构化编程的最小模块单元,将复杂的算法过程分解为若干较小任务,代码复用和测试的基本单元;关键字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
    ", 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") //永不执行
    }
    

      

  • 相关阅读:
    数据库(一)数据库优点,常用数据库,创建数据库,设计创建数据表,数据类型,数据表操作,完整性约束(非空,主键,唯一,默认,外键)
    mybatis 中mapper文件 if判断 <if test="validCoupon == '1'">类似问题
    如何解决JavaScript中UUID作为方法参数在方法中无法传递而数字却正常传递的问题
    js 将子页面得到的数据返回并赋值给父页面
    在IE下面报错"缺少函数",函数明明是有的,其他浏览器下正常
    javaPOI把excel转换成html 中去掉序号列
    linux 常用命令(个人用)
    mysql 统计 group
    es 复制索引
    elasticsearch 判断某个字段是否含有大写字母
  • 原文地址:https://www.cnblogs.com/hongpeng0209/p/9200785.html
Copyright © 2020-2023  润新知