zoukankan      html  css  js  c++  java
  • Go-函数

    函数定义

    func funcName(形参列表) (返回值列表) {
        // 函数体
        return 
    }
    

    函数名称首字母大写时,该函数对其它包可见;小写时,只有包内可见。

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

    函数特点

    • 函数可以没有输入参数,也可以没有返回值(默认返回0)

      func A() {
          // do something
      }
      
      func A() int{
          // do something 
          return 1
      }
      
    • 多个相邻的同类型参数可以简写

      func add(a, b int) int {
          return a + b
      }
      
    • 支持命名返回值变量,参数名就是函数体内最外层的局部变量。

      • 命名返回值变量初始化为类型的零值

      • 最后的return 语句可以省略参数名

        func add(a, b int) (sum int) {
            sum = a + b // 
            return      // 相当于 return sum
        }
        
        func add2(a, b int) (sum int){
            sum := a + b // 新声明一个sum 局部变量
            return sum // 必须显示调用返回变量
        }
        
    • 不支持默认值参数

    • 不支持函数重载

    • 不支持命名函数嵌套定义,支持匿名函数嵌套

      func add(a, b int) (sum int){
          anonymous := func(x,y int) int{
              return x + y
          }
          return anonymous(a, b)
      }
      

    函数分类

    • 命名函数
    • 匿名函数

    函数类型

    一个函数的类型就是函数定义首行去掉函数名,参数名和{。可以使用fmt.Printf("%T")打印的类型

    package main
    import "fmt"
    func add(a,b int) int{
        return a+b
    }
    
    func main() {
        fmt.Printf("%T
    ", add) // func(int,int) int
    }
    

    判断2函数类型相同的条件

    • 相同的形参列表和返回值列表。即列表元素的次序,个数和数量都相同,形参名可以不同

      func add(a,b int) int { 
          return a + b 
      }
      
      func sub(x int, y int) (c int) {
          c = x - y
          return c
      }
      
      // 上面2个函数的函数类型相同 
      

    使用 type 关键字定义函数类型

    函数类型变量可以作为函数的参数或返回值

    package main
    import "fmt"
    
    func add(a, b int) int {
        return a + b
    }
    
    func sub(a, b int) int {
        return a - b
    }
    
    type Op func(int,int) int // 定义一个函数类型,输入的是两个 int 类型,返回值是int
    
    func do(f Op, a, b int) int { // 定义一个函数,第一个参数是函数类型 Op
        return f(a,b)  // 函数类型变量可以直接用来进行函数调用返回
    }
    
    func main() {
        a := do(add, 1, 2)
        fmt.Printf("a=%v", a)
        
        s := do(sub, 1, 2)
        fmt.Printf("b=%v", b)
    }
    
    • 函数类型变量是一种引用类型, 未初始化的函数类型的变量默认值为 nil
    • 命名函数的函数名可以使用函数名直接调用,也可以赋值给函数类型变量

    匿名函数

    使用场景

    匿名函数需要保存到某个变量或者作为立即执行函数。

    使用示例

    • 匿名函数被直接赋值给函数变量

      • 匿名函数没有函数名,无法通过函数名调用,因此,匿名函数需要保存到某个变量或者作为立即执行函数
      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 wrap(op string) func(int, int) int {
          switch op {
          case "add":
              return func(a, b int) int {
                  return a + b
              }
          case "sub":
              return func(a, b int) int {
                  return a - b
              }
          default:
              return nil
          }
      }
      
    • 匿名函数作为实参

      package main
      import "fmt"
      
      // 匿名函数被直接赋值函数变量
      var sum = func(a, b int) int {
          return a + b
      }
      
      // 匿名函数作为形参
      func doinput(f func(int, int) int, a, b int) int {
          return f(a, b)
      }
      
      // 匿名函数作为返回值
      func wrap(op string) func(int, int) int {
          switch op {
          case "add":
              return func(a, b int) int {
                  return a + b
              }
          case "sub":
              return func(a, b int) int {
                  return a - b
              }
          default:
              return nil
          }
      }
      
      func main() {
          // 直接调用匿名函数
          defer func() {
              if err := recover(); err != nil {
                  fmt.Println(err)
              }
          }()
          
          sum(1,2)
          
          // 匿名函数作为实参
          doinput(func(x,y int) int {
              return x + y  
          }, 1, 2)   // return 1 + 2
          
          opFunc := wrap("add")
          re := opFunc(2,3)
          
          fmt.Printf("%d
      ", re) // 5
      }
      

    函数参数

    传递方式

    • 值类型参数默认就是值传递

    • 引用类型参数默认就是引用传递

    不管是值传递还是引用传递,传递给函数的都是变量的副本,值传递的是值的拷贝,引用传递的时地址的拷贝。通常地址拷贝方式效率高(数据量小),而值拷贝由变量的数据量大小决定其效率。

    实参到形参的传递

    函数实参到形参的传递永远是值拷贝

    package main
    import "fmt"
    
    func chvalue(a int) int{
        a = a + 1
        return a
    }
    
    func chpointer(a *int) {
        *a = *a + 1
        return 
    }
    
    func main() {
        a := 10
        chvalue(a)     // 实参传递给形参是值拷贝
        fmt.Println(a) // 10 
        
        chpointer(&a)  // 实参传递给形参仍然是值拷贝,只不过复制的是 a 的地址值
        fmt.Println(a) // 11
    }
    

    函数作为参数

    package main
    import "fmt"
    
    func add(x, y int) int {
    	return x + y
    }
    func calc(x, y int, op func(int, int) int) int {
    	return op(x, y)
    }
    func main() {
    	ret2 := calc(10, 20, add)
    	fmt.Println(ret2) //30
    }
    

    函数作为返回值

    func do(s string) (func(int, int) int, error) {
    	switch s {
    	case "+":
    		return add, nil
    	case "-":
    		return sub, nil
    	default:
    		err := errors.New("无法识别的操作符")
    		return nil, err
    	}
    }
    

    error作为返回值类型时,必须作为最后一个

    不定参数

    函数支持不定数目的形式参数,不定参数声明使用 param ...type 的语法格式

    package main
    
    import "fmt"
    
    func add(args ...int) (sum int) {
    	for _, v := range args {
    		sum += v
    	}
    	return sum
    }
    
    func main() {
    	fmt.Printf("sum=%v", add(10, 1, 2))
    }
    
    
    • 不定参数类型必须相同

    • 不定参数必须是函数最后一个参数

    • 不定参数名在函数体内是相当于切片

    • 切片作为参数传递给不定参数,切片名后要加上...

      package main
      
      import "fmt"
      
      func add(args ...int) (sum int) {
      	for _, v := range args {
      		sum += v
      	}
      	return sum
      }
      
      func main() {
          s1 := []int{1,2,3,4,5}
          add(s1...)
      }
      
      

    变量作用域

    全局变量

    定义在函数外部的变量,它在整个运行周期内都有效。

    package main
    import "fmt"
    
    // 全局变量
    var num int = 1
    
    func f1() {
        fmt.Printf("num=%d
    ", num) // 函数中可以访问全局变量num
    }
    
    func main() {
    	f1() //num=1
    }
    
    

    局部变量

    函数内定义的变量无法在该函数外使用

    如果局部变量和全局变量重名,优先访问局部变量。

    package main
    
    import "fmt"
    
    //定义全局变量num
    var num int64 = 10
    
    func testNum() {
    	num := 100
    	fmt.Printf("num=%d
    ", num) // 函数中优先使用局部变量
    }
    func main() {
    	testNum() // num=100
    }
    

    语句块内定义的变量

    if条件判断、for循环、switch语句上定义的变量

    for i := 0; i < 10; i++ {
    		fmt.Println(i) //变量i只在当前for语句块中生效
    }
    

    闭包

    闭包指的是一个函数和与其相关的引用环境组合而成的实体。简单来说,闭包=函数+引用环境

    示例

    package main
    import "fmt"
    
    func addUpper() func(int) int{
        var n int = 10
        return func(x int) int{
            n = n + x
            return n
        }
    }
    
    func main() {
        f := addUpper()
        fmt.Println(f(1)) // 11 ,n 初始化为10
        fmt.Println(f(2)) // 13 ,此时n为11
        fmt.Println(f(3)) // 16 ,此时n为13
    }
    
    • addUpper 是一个函数,返回的数据类型 func(int) int

    • 闭包

      image-20210307090350261

      • 返回是一个匿名函数,它引用外部变量n,因此,这个匿名函数和变量n就形成一个整体,构成闭包
    • 反复调用f 函数时,变量 n 初始化一次,后面每次调用进行累积

    实践

    编程题目要求

    1. 编写一个函数 makeSuffix(suffix string) ,可以接收一个文件后缀名(如 .jpg),并返回一个闭包
    2. 调用闭包,可以传入一个文件名,若该文件名没有指定的后缀(如 .jpg),则返回 "文件名.jpg",如果已经有后缀.jpg,则返回原文件名
    3. 使用闭包方式完成
    4. 提示:strings.HasSuffix 函数用于判断某个字符串是否有指定后缀

    代码

    package main
    
    import (
    	"fmt"
        "strings"
    )
    
    func makeSuffix(suffix string) func(string) string {
        
        return func(name string) string {
            // 判断后缀
            if !strings.HasSuffix(name, suffix) {
                return name + suffix
            }
            return name
        }
    }
    
    func main() {
        f := makeSuffix(".jpg")
        fmt.Println("文件名称: ", f("biao")) // 
        fmt.Println("文件名称: ", f("write.jpg")) //
    }
    
    • 返回的匿名函数 和 makeSuffix(suffix string) 的变量suffix 构成一个闭包。返回的函数引用外部变量 suffix

    • 该案例中,闭包引用的变量可以反复使用

    • 使用传统函数方法实现,每次都需要传入后缀名

      package main
      
      import (
      	"fmt"
          "strings"
      )
      
      func makeSuffix2(name, suffix string) string {
          if !strings.HasSuffix(name, suffix) {
              return name + suffix
          }
          return name
      }
      
      func main() {
          fmt.Println("文件名称: ", makeSuffix2("biao", ".jpg")) // 
          fmt.Println("文件名称: ", makeSuffix2("write.jpg", ".jpg")) //
      }
      

    defer (延迟调用)

    defer 关键字用于注册延迟调用,它们以FILO(先进后出)的顺序在函数返回前被执行

    defer 常用于确保资源(如:文件句柄、数据库连接、锁 ...)最终能被释放和回收。

    • defer 后面必须是函数或方法的调用,不能是语句

    • defer 函数的实参在注册时通过值拷贝传递进去

      package main
      import "fmt"
      
      func f() int {
          a := 0
          // go 执行到 defer 时,不会立即执行defer后的语句,而是将defer后面的语句压入到一个独立的栈,然后执行函数的下一个语句
          defer func(i int) {
              fmt.Println("defer i= ", i)  // defer 将语句入栈的同时,也会将相关的值拷贝入栈
          }(a)
          
          a++
          // 当函数执行完毕时,再从栈顶中依次语句执行
          return a
      }
      
      func main() {
          f() // defer i=  0
      }
      
    • 若defer 位于 return 之后,则不会被执行

    • 主动调用 os.Exit(int) 退出进程时,defer 无论如何都不会被执行

    • defer 注册要延迟执行的函数时,该函数的所有参数都需要确定其值

      package main
      import "fmt"
      
      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))
      	x = 10
      	defer calc("BB", x, calc("B", x, y))
      	y = 20
      }
      
      /* 函数调用执行顺序
      1. calc("A", x, y)   // A 1 2 3
      2. calc("B", x, y)   // B 10 2 12
      3. calc("BB", x, 12) // BB 10 12 22
      4. calc("AA", x, 3)  // AA 1 3 4
      */
      

      执行结果:

      image-20210305170300242

    defer执行时机

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

    defer执行时机

    注意事项

    • defer 语句的位置不当,有可能导致 panic ,一般 defer 语句放在错误检查语句之后。
    • defer 也有明显的副作用:defer 会推迟资源的释放,defer 尽量不要放到循环语句里面,将大函数内部的 defer 语句单独拆分成一个小函数是一种很好的实践方式。
    • defer 相对于普通的函数调用需要间接的数据结构的支持,相对于普通函数调用有一定的性能损耗 。
    • defer 中最好不要对有名返回值参数进行操作 ,否则会引发匪夷所思的结果
  • 相关阅读:
    一个漂亮的PHP验证码
    一个漂亮的php验证码类(分享)
    PHP中exit()与die()的区别
    自定义PHP页面跳转函数redirect($url, $time = 0, $msg = '')
    MySQL时间字段究竟使用INT还是DateTime
    mysql中为int设置长度究竟是什么意思
    Array数组对象
    Math对象
    对象篇学习-字符串对象
    事件的学习
  • 原文地址:https://www.cnblogs.com/binliubiao/p/14493610.html
Copyright © 2011-2022 走看看