08-Go语言之函数

内容目录

  • 函数基础
    • 定义
    • 调用
    • 参数
    • 返回值
  • 高阶函数
    • 作用域
    • 函数作为变量
    • 函数作为参数
    • 函数作为返回值
    • 匿名函数
    • 闭包
  • defer语句

内容详细

函数基础:定义

  • Go语言中支持函数、匿名函数和闭包,并且函数在Go语言中属于“一等公民”。

  • Go语言中定义函数使用func关键字,具体格式如下:

    func 函数名(参数)(返回值){
        函数体
    }
    

    其中:

    • 函数名:由字母、数字、下划线组成。但函数名的第一个字母不能是数字。在同一个包内,函数名称不能重名(包的概念详见后文)。
    • 参数:参数由参数变量和参数变量的类型组成,多个参数之间使用,分隔。
    • 返回值:返回值由返回值变量和其变量类型组成,也可以只写返回值的类型,多个返回值必须用()包裹,并用,分隔。
    • 函数体:实现指定功能的代码块。

函数基础:调用

  • 定义了函数之后,我们可以通过函数名()的方式调用函数。

  • 注意,调用有返回值的函数时,可以不接收其返回值。

    func intSum(x int, y int) int {
    	return x + y
    }
    
    func main() {
    	ret := intSum(10, 20)
    	fmt.Println(ret)
    }
    
    

函数基础:参数

  • GO语言中没有默认参数!!

类型简写

  • 如果定义函数时,把返回值也声明了,那么在函数体中无需再次声明

    func intSum(x int, y int) (ret int) {	// 也可以直接定义返回值名称和类型
    	ret = x + y		// 此时则不需要声明,应为定义函数时已经声明好返回值了
    	return 			// 返回值也可以省略,函数会自动寻找定义好的返回值名称
    }
    
    func main() {
    	ret := intSum(10, 20)
    	fmt.Println(ret)
    }
    

可变参数

  • 可变参数是指函数的参数数量不固定。Go语言中的可变参数通过在参数名后加...来标识。

  • 本质上,函数的可变参数是通过切片来实现的。

  • 注意:可变参数通常作为函数的最后一个参数出现。

    // 函数接收可变参数,在参数名后面加... 表示可变参数
    // 可变参数在函数体中是切片类型
    func intSum(a ...int) int {
    	ret := 0
    	for _, arg := range a {
    		ret += arg
    	}
    	return	ret
    }
    func main() {
    	ret1 := intSum()
    	ret2 := intSum(10)
    	ret3 := intSum(10,20)
    	fmt.Println(ret1,ret2,ret3)
    }
    // 0 10 30
    
  • 固定参数和可变参数搭配使用

  • 多个参数如果类型一致,则只需要在参数末尾写类型即可,参数之间用逗号隔开

    // 固定参数和可变参数同时出现时,可变参数要放在最后
    func intSum(a, b ...int) int {
    	ret := a
    	for _, arg := range b {
    		ret += arg
    	}
    	return	ret
    }
    
    func main() {
    	ret1 := intSum(100)
    	ret2 := intSum(100,200,300)
    	fmt.Println(ret1,ret2)
    }
    // 100 600
    

函数基础:返回值

多返回值

  • 定义多个返回值,函数如果有多个返回值时必须用()将所有返回值包裹起来。

    // 定义具有多个返回值的函数
    // 多返回值也支持类型简写
    func calc(a, b int) (sum, sub int) {
    	sum = a + b
    	sub = a - b
    	return
    }
    
    func main() {
    	// 函数调用
    	x, y := calc(200, 100)
    	fmt.Println(x,y)
    }
    

返回值补充

  • 当我们的一个函数返回值类型为slice时,nil可以看做是一个有效的slice,没必要显示返回一个长度为0的切片。

    func someFunc(x string) []int {
    	if x == "" {
    		return nil // 没必要返回[]int{}
    	}
    	...
    }
    

函数高阶:作用域

全局变量

  • 全局变量是定义在函数外部的变量,它在程序整个运行周期内都有效。 在函数中可以访问到全局变量。

局部变量

  • 函数内定义的变量无法在函数外使用
  • 如果局部变量和全局变量重名,优先访问局部变量。
  • 语句块定义的变量:在if条件判断、for循环、switch语句上定义的变量,只能在其中使用

项目作用域

  • 在一个项目中不同的包下,使用首字母大写的变量、函数、slice、map等都在该项目的其他文件中调用

    // tt包中创建:
    package tt
    import "fmt"
    
    // 私有的全局变量
    var a int = 100
    // 项目中所有都可以调用的全局变量
    var A int = 18
    
    func f1(){
    	fmt.Println("f1")
    }
    
    func F1()  {
    	fmt.Println("F1")
    }
    
    // main包中使用:
    package main
    
    import (
    	"fmt"
    	"github.com/liyn/day02/tt"
    )
    
    func main() {
    	tt.A = 200	// 调用tt包中的A变量
    	tt.F1()		// 调用tt包中的F1函数
    	fmt.Println(tt.A)
    }
    // F1
    // 200
    

函数高阶:函数作为变量

  • 函数可以作为变量的值

    func add(x,y int)int{
    	return x+y
    }
    func main(){
    	ret := add
    	fmt.Println(ret(5,6))
    }
    // 11
    

函数高阶:函数作为参数

  • 函数可以作为参数

    func add(x,y int)int{
    	return x+y
    }
    
    func sub(x,y int)int{
    	return x - y
    }
    
    func calc(x,y int,op func(a,b int)int)int {
    	return op(x,y)
    }
    
    func main() {
    	r1 := calc(200,100,add)
    	r2 := calc(200,100,sub)
    	fmt.Println(r1,r2)
    }
    

函数高阶:函数作为返回值

  • 函数也可以作为返回值

    // 定义一个函数它的返回值是一个函数
    func testDemo() func(){
    	return func() {
    		fmt.Println("沙河小王子")
    	}
    }
    func main(){
    	ret := testDemo()
    	ret()	// 相当于执行了testDemo函数内部的匿名函数
    }
    

函数高阶:匿名函数

  • 匿名函数因为没有函数名,所以没办法像普通函数那样调用,所以匿名函数需要保存到某个变量或者作为立即执行函数:

    • 方式一:将匿名函数保存到变量中,执行变量()就可以执行匿名函数了
    • 方式二:匿名函数末尾加参数,可直接执行匿名函数
    // 格式:
    func(参数)(返回值){
        函数体
    }
    
    // 示例:
    func main() {
    	// 将匿名函数保存到变量
    	add := func(x, y int) {
    		fmt.Println(x + y)
    	}
    	add(10, 20) // 通过变量调用匿名函数
    
    	//自执行函数:匿名函数定义完加()直接执行
    	func(x, y int) {
    		fmt.Println(x + y)
    	}(10, 20)
    }
    

函数高阶:闭包

  • 闭包内部的变量不会回收

  • 无参数的闭包

    // 定义一个函数它的返回值是一个函数
    func testDemo() func(){
    	name := "沙河娜扎"
    	return func() {
    		fmt.Println("hello", name)
    	}
    }
    func main(){
    	// 闭包 = 函数 + 外层变量的引用
    	ret := testDemo()	// ret此时就是一个闭包
    	ret()	// 相当于执行了testDemo函数内部的匿名函数
    }
    
  • 闭包示例一:

    // 定义一个函数它的返回值是一个函数
    func testDemo(name string) func(){
    	return func() {
    		fmt.Println("hello", name)
    	}
    }
    
    func main(){
    	ret := testDemo("沙河娜扎")
    	ret()
    }
    
  • 闭包示例二:

    // 判断一个字符串是否以指定字段结尾:
    
    // 定义一个函数它的返回值是一个函数
    func makeSuffixFunc(suffix string) func(string)string{
    	return func(name string) string {
    		if !strings.HasSuffix(name,suffix){ // 检测是否以第二个参数为末尾的值
    			return name + suffix
    		}else {
    			return name
    		}
    	}
    }
    func main() {
        // 闭包 = 函数 + 外层变量的引用
    	r := makeSuffixFunc(".txt")
    	ret := r("沙河娜扎")
    	fmt.Println(ret)
    }
    // 沙河娜扎.txt
    
  • 闭包示例三:

    // 定义一个函数它的返回值是一个函数
    func calc(base int) (func(int) int, func(int) int) {
    	add := func(i int) int {
    		base += i
    		return base
    	}
    
    	sub := func(i int) int {
    		base -= i
    		return base
    	}
    	return add, sub
    }
    func main() {
        // base一直没有消
    	x,y := calc(100)
    	ret1 := x(200)
    	fmt.Println(ret1) // 此时base=100,add函数参数i=200,结果为300
    	ret2 := y(300)
    	fmt.Println(ret2) // 此时base=300,sub函数参数i=300,结果为0
    }
    

defer语句

  • defer语句会将其后面跟随的语句进行延迟处理。在defer归属的函数即将返回时,将延迟处理的语句按defer定义的逆序进行执行,也就是说,先被defer的语句最后被执行,最后被defer的语句,最先被执行。

  • 由于defer语句延迟调用的特性,所以defer语句能非常方便的处理资源释放问题。比如:资源清理、文件关闭、加锁解锁及记录时间等。

      func main() {
      	fmt.Println("start")
      	defer fmt.Println(1)
      	defer fmt.Println(2)
      	defer fmt.Println(3)
      	fmt.Println("end")
      }
      // start
      // end
      // 3
      // 2
      // 1
    
  • defer不管出不出异常,肯定执行,为了关闭资源

    func chuF(a,b int)int{
    	return a / b
    }
    func main() {
    	defer fmt.Println("数据库关闭")
    	defer chuF(3,0)
    	defer fmt.Println("数据库开启")
    }
    // 数据库开启
    // 数据库关闭
    // panic: runtime error: integer divide by zero
    
  • 如果defer中有函数,就先执行函数

    func calc(index string, a, b int) int {
    	ret := a + b
    	fmt.Println(index, a, b, ret)
    	return ret
    }
    
    func main() {
    	x := 1
    	y := 2
    	defer calc("AA", x, calc("A", x, y))	 // 先执行calc中的函数
    	x = 10
    	defer calc("BB", x, calc("B", x, y))
    	y = 20
    }
    /*
    	A 1 2 3
    	B 10 2 12
    	BB 10 12 22
    	AA 1 3 4
    */
    

defer执行时机

  • 在Go语言的函数中return语句在底层并不是原子操作,它分为给返回值赋值和RET指令两步。而defer语句执行的时机就在返回值赋值操作后,RET指令执行前。

内置函数介绍

内置函数 介绍
close 主要用来关闭channel
len 用来求长度,比如string、array、slice、map、channel
new 用来分配内存,主要用来分配值类型,比如int、struct。返回的是指针
make 用来分配内存,主要用来分配引用类型,比如chan、map、slice
append 用来追加元素到数组、slice中
panic和recover 用来做错误处理
posted @ 2020-01-21 09:50  薛定谔的猫儿  阅读(69)  评论(0编辑  收藏  举报