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

    本文参考:https://www.liwenzhou.com/posts/Go/09_function/

    函数

    函数概述

    函数是一段能够重复使用的代码的封装。函数参数定义了外界给函数输入的数据。返回值定义了函数给外界输出的数据。Go语言函数支持不定长参数和多个返回值。

    函数定义

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

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

    函数名:由字母、数字、下划线组成。但函数名的第一个字母不能是数字。函数名也不能重复命名。

    参数:参数由参数变量和参数变量的类型组成,多个参数之间使用,分割

    返回值:返回值由返回值变量和其变量类型组成,也可以只写返回值的类型,多个返回值必须用()包裹,并用,分割

    函数体:实现指定功能的代码块

    // 实现求两个数之和的函数
    func intSum(x int, y int) int{
    	return x + y
    }
    

    当然,函数的参数和返回值都是可选的,例如我们可以实现一个既不需要参数也没有返回值的函数

    func sayHello(){
    	fmt.Println("fuck off")
    }
    

    函数的调用

    定义了函数之后,可以通过函数名()的方式调用函数。注意:调用有返回值的函数时,可以不接收其返回值。

    func main(){
    	sayHello()
    	ret := intSum(1,2)
    	fmt.Println(ret)
    }
    

    参数

    类型简写

    函数的参数中如果相邻的变量的类型相同,则可以省略类型。

    // 函数接收两个参数,这两个参数的类型均为int,因此可以省略x的类型,y后面有类型说明
    func intSum(x,y int)int{
    	return x + y
    }
    

    可变参数(不定长参数)

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

    注意:可变参数通常要座位函数的最后一个参数

    func intSum(x ...int)int{
    	fmt.Println(x)  // x是一个切片
    	sum := 0
    	for _,v := range x{
    		sum += v
    	}
    	return sum
    }
    
    func main(){
        fmt.Println(intSum())  // 0
        fmt.Println(intSum(10))  // 10
        fmt.Println(intSum(10,20)) // 30
        fmt.Println(intSum(10,20,30)) // 60
    }
    

    如果固定参数和可变参数混合使用,可变参数必须是在固定参数的后面

    func intSum(x int, y ...int) int {
        fmt.Println(x, y)
        sum := x
        for _,v := range y{
            sum += v
        }
        return sum
    }
    
    func main(){
        fmt.Println(intSum(10))  // 10
        fmt.Println(intSum(10,20))  // 30
        fmt.Println(intSum(10,20,30)) // 60
    }
    

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

    返回值

    通过return 关键字向外输出返回值。

    多返回值

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

    func calc(x,y int)(int, int){
    	sum := x + y
    	sub := x - y
        return sum,sub
    }
    

    返回值命名

    函数定义时可以给返回值命名,并在函数体中直接使用这些变量,最后通过return关键字返回。

    func calc(x,y int)(sum,sub int){
    	sum = x + y
    	sum = x - y
    	return 
    }
    

    返回值补充

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

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

    函数进阶

    变量作用域

    全局变量

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

    package main
    
    import "fmt"
    
    // 定义全局变量
    var num int64 = 10   // 不能使用 num := 10
    
    func globalVar(){
        fmt.Println(num)  // 函数中可以访问到全局变量num
    }
    
    func main(){
        globalVar()   // num=10
    }
    

    局部变量

    局部变量又分为以下几种:

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

      func localVar(){
      	// 定义一个函数局部变量x,仅在该函数内生效
          x := 100
          fmt.Println(x)
      }
      
      func main(){
          localVar()
          fmt.Println(x)   // 此时无法使用变量x
      }
      
    • 如果局部变量和全局变量重名,优先访问局部变量

      package main
      
      import "fmt"
      
      // 定义全局变量num 
      var num int64 = 100
      
      func testNum(){
          num := 10
          fmt.Println(num)   // 10 函数优先使用局部变量
      }
      
      fumc main(){
          testNum()  // 10
          fmt.Println(num) // 100
      }
      
    • iffor,switch等语句块中定义的变量,也只能在语句块中访问

      func localVar(x, y int){
      	fmt.Println(x,y) // 函数的参数也只能在本函数中生效
          if x > 0{
              z := 100 // 变量z只能在if语句块生效
              fmt.Println(z)
          }
          // fmt.Println(z)   // 此处无法使用变量z
      }
      
    • for循环语句中定义的变量,也是只能在for语句中生效

      func localVar(){
          for i := 0; i < 10; i++ {
              fmt.Println(i)  // 变量i只能在当前for语句块中生效
          }
          // fmt.Println(i)  // 此处无法使用变量i
      }
      

    函数类型与变量

    定义函数类型

    我们可以通过type关键字来定义一个函数类型

    type calc func(int, int) int
    

    上面的语句定义了一个calc函数类型,这种函数类型接收两个int类型的参数并且返回一个int类型的返回值。

    简单说,凡是满足这个条件的函数都是calc类型的函数。例如下面的sum和sub都是calc类型

    func sum(x, y int)int{
    	return x + y 
    }
    
    func sum(x, y int)int{
        return x - y
    }
    

    sum和sub都能赋值给calc类型的变量

    var c calc
    c = add
    

    函数类型变量

    我们可以声明函数类型的变量并未该变量赋值

    func main(){
    	var c calc  //声明一个calc类型的变量c
        c = sum    // 把sum赋值给c
        fmt.Printf("type of c:%T
    ",c)  //type of c:main.calc
        fmt.Println(c(1,2))  // 像调用sum一样调用c
        
        d := sub  // 把函数sub赋值给变量d
        fmt.Printf("type if d:%T
    ", d)  // type of d:func(int,int)
        fmt.Println(d(20,10))  // 像调用sub一样调用d
    }
    

    高阶函数

    函数作为参数

    func sum(x,y int)int{
        return x + y
    }
    
    func calc(x,y int, op func(int,int)int)int{
        return op(x,y)
    }
    
    func main(){
        ret := calc(10,20,sum)
        fmt.Println(ret) // 30
    }
    

    函数作为返回值

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

    匿名函数

    匿名函数主要作用是封装一段一次性执行的代码,它无所谓复用,所以无需起名,之所以进行封装的意义在于使一段代码成为 一个整体。

    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 adder()func(int)int{
    	var x int   // 初始为0
    	return func(y int)int{
    		x += y
    		return x
    	}
    }
    
    func main(){
        var a = adder()     // x = 0
        fmt.Println(a(10))  // adder()(10)   x=0, y=10  x=10
        fmt.Println(a(20))  // adder()(20)   x=10,y=20  x=30
        fmt.Println(a(30))  // adder()(30)   x=30,y=30  x=60
        
        b := adder()        // x = 0
        fmt.Println(b(40))  // adder()(40)   x=0,y=40  x=40
        fmt.Println(b(50))  // adder()(50)   x=40,y=50 x=90
    }
    

    变量ab是一个函数并且它们引用了其外部作用域中x变量。此时ab就是闭包。在ab的生命周期内,变量x一直有效。

    闭包进阶示例1:

    func adder(x int) func(int) int {
    	return func(y int) int {
    		x += y
    		return x
    	}
    }
    
    func main(){
        var a = adder(10)   // x=10
        fmt.Println(a(10))  // adder(10)(10)  x=10,y=10,x=20
        fmt.Println(a(20))  // adder(20)(20)  x=20,y=20,x=40
        fmt.Println(a(30))  // adder(40)(30)  x=40,y=30,x=70
        
        b := adder(20)  // x=20
        fmt.Println(b(40))  // adder(20)(40)   x=20,y=40,x=60
        fmt.Println(b(50))  // adder(60)(50)   x=60,y=50,x=110
    }
    

    闭包进阶示例2:

    func makeSuffixFunc(suffix string) func(string) string {
        return func(name string) string {
            if !strings.HasSuffix(name, suffix){
                return name + suffix
            }
            return name
        }
    }
    
    func main(){
        jpgFunc := makeSuffixFunc(".jpg")
        txtFunc := makeSuffixFunc(".txt")
        fmt.Println(jpgFunc("test"))  // test.jpg
        fmt.Println(txtFunc("test"))  // test.txt
    }
    

    闭包进阶示例3:

    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(){
        a,b := calc(10)   // base = 10
        fmt.Println(a(1),b(2))  // a(1)->add(10)(1)->base=10,i=1,base=11;
        						// b(2)->sub(11)(2)->base=11,i=2,base=9
        fmt.Println(a(3),b(4))  // a(3)->add(9)(3)->base=12, b(4)->8
        
    }
    

    闭包的好处:内层函数的状态被保存在闭包中,不使用闭包就要开辟多个全局变量来保存函数以外的数据。如果这个函数被多方调用,大家都需要各自保存各自的数据 ,此时就需要开辟多个全局变量,具体使用哪个全局变量,还要在函数内做判断,增大了重复代码。令代码开起来比较垃圾。

    使用多个全局变量保存多套副本的索引
    // 全局变量
    var heros = [...]string{"关胜","林冲","秦明","呼延灼","武松","鲁达"}
    // 宋江的索引
    var index1 = 0
    // 吴用的索引
    var index2 = 0
    // 脑补卢员外的索引,柴进的索引...
    
    func useNormal(){
        for i:=0; i<10;i++{
            fmt.Println(giveMeOne("宋江"))
        }
        for i:=0;i<10;i++{
            fmt.Println(giveMeOne("吴用"))
        }
    }
    
    func giveMeOne(who string) string{
        var theOne = ""
        // 差不多的东西写两遍,很垃圾
        // 万一卢俊义也来带队,那就需要三个全局变量,三个if分支。。。
        if who == "宋江"{
            theOne = heros[index1]
            index1++
            if index1 > len(heros) -1{
                index1 = 0
            }
        } else {
            theOne = heros[index2]
            index2 ++
            if index2 >len(heros)-1{
                index2 = 0
            }
        }
        return theOne
    }
    
    使用闭包函数
    // 全局变量
    var heros = [...]string{"关胜","林冲","秦明","呼延灼","武松","鲁达"}
    // 使用函数闭包的案例
    func useClosure(){
        // 得到返回的闭包内函数
        songjiang := giveHimOne(0)
        wuyong := giveHimOne(4)
        for i:=0;i<10;i++{
            fmt.Println("宋江线", songjiang("黑子"))
        }
        for i:=0;i<10;i++{
            fmt.Println("吴用线", wuyong("大坏比"))
        }
    }
    
    // 闭包函数:返回函数的函数
    func giveHimOne(start int)func(name string)string{
        // 保存闭包系统内的状态
        var index int = start
        // 内层函数
        return func(name string) string{
            theOne := heros[index]
            // 状态被保存在外层的闭包中
            index++
            if index > len(heros)-1{
                index = 0
            }
            return name + ":" + theOne        
        }
    }
    

    defer语句

    Go语言中的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语句延迟调用的特性,所以defer语句能非常方便的处理资源释放问题,比如:资源清理,文件关闭,解锁及记录时间等。

    defer执行时机

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

    func f1() int {
        x := 5
        defer func(){
            x++    // 改变的是x,不是返回值
        }()
        return x
    }
    
    func f2() (x int){
        defer func(){
            x++
        }()
        return 5    // 最终返回x,先给x赋值,x=5,执行defer,x++,此时x=6, 最后返回x
    }
    
    func f3() (y int){
        x := 5
        defer func(){
            x++    // 改变的是y
        }()
        return x   // 返回x   5
    }
    
    func f4() (x int){
        defer func(x int){
            x++     // 改变x的副本
        }(x)
        return 5    // 5
    }
    
    func f5()(x int){
        defer func(x int) int {
    	x ++
    	return x   // 改变x的副本
        }(x)
        return 5     // 5
    }
    
    // 传一个x的指针
    func f6()(x int){
        defer func(x *int){
    	*x ++
        }(&x)
        return 5  // 1.返回值=x=5; 2.defer x=6, 3.返回x
    }
    
    func main(){
        fmt.Println(f1())    // 5
        fmt.Println(f2())    // 6
        fmt.Println(f3())    // 5
        fmt.Println(f4())    // 5
        fmt.Println(f5())    // 5
        fmt.Println(f6())    // 6
    }
    

    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))
    	x = 10
    	defer calc("BB",x,calc("B",x,y))
    	y = 20
    }
    

    输出结果:

    // 分析
    //1: a = 1, b = 2
    //2: calc("A",1,2)->3
    //3: defer calc("AA",x,3)->defer calc("AA",1,3)
    //4: x = 10
    //5: calc("B",10,2)->12
    //6: defer calc("BB",10,12)
    //7: y = 20
    //8: 执行6 
    //9:执行3
    

    递归函数

    递归:函数自己调用自己

    // 计算n的阶乘
    func f(n uint64)uint64{
        if n<= 1{
    	return 1
        }
        return n * f(n-1)
    }
    
    // 上台阶面试题
    // n个台阶,一次走一步,也可以走两步,有多少种走法
    func taijie(n uint64) uint64{
        if n == 1{
    	return 1
        }
        if n == 2{
    	return 2
        }
        return taijie(n-1) + taijie(n-2)
    }
    

    内置函数介绍

    内置函数 介绍
    close 主要用来关闭channel
    len 用来求长度,比如string、array、slice、map、channel
    new 用来分配内存,主要用来分配值类型,比如int、struct。返回的是指针
    make 用来分配内存,主要用来分配引用类型,比如chan、map、slice
    append 用来追加元素到数组、slice中
    panic和recover 用来做错误处理

    panic/recover

    使用panic/recover模式来处理错误。panic可以再任何地方引发,但recover只有在defer调用的函数中有效。

    func f1(){
    	fmt.Println("func f1")
    }
    
    func f2(){
    	panic("panic in f2")
    }
    
    func f3(){
    	fmt.Println("func f3")
    }
    
    func main(){
    	f1()
    	f2()
    	f3()
    }
    

    输出:

    func f1
    panic:panic in f2
    
    goroutine 1 [running]:
    main.f2(...)
            .../code/func/main.go:12
    main.main()
            .../code/func/main.go:20 +0x98
    

    程序运行期间f2中引发panic导致程序崩溃,异常退出了,这时候我们就可以通过recover将程序恢复回来,继续往后执行。

    func funcA(){
        fmt.Println("func A")
    }
    
    func funcB(){
        defer func(){
            err := recover()
            // 如果程序出现了panic错误,可以通过recover恢复过来
            if err != nil{
                fmt.Println("recover in B")
            }
        }()
        panic("panic in B")
    }
    
    func funcC(){
        fmt.Println("func C")
    }
    
    func main(){
        funcA()
        funcB()
        funcC()
    }
    

    注意:

    recover()必须搭配defer使用

    defer一定要在可能引发panic的语句之前定义

    练习题

    你有50枚金币,需要分配给一下几个人:Mathew,Sarah,Augustus,Heidi,Emilie,Peter,Giana,Adriano,Aaron,Elizabeth.
    分配规则如下:
    a.名字中每包含1个'e'或'E'分1枚金币
    b.名字中没包含1个'i'或'I'分2枚金币
    c.名字中没包含1个'o'或'I'分O枚金币
    b.名字中没包含1个'u'或'I'分U枚金币

    写一个程序,计算每个用户分到多少金币,以及最后剩余多少金币?
    程序结构如下,请实现'dispatchCoin'函数

    var (
        coins = 50
        users = [] string{"Matthew","Sarah","Augustus","Heidi","Emilie","Peter","Giana","Adriano","Aaron","Elizabeth",}
        distribution = make(map[string]int, len(users))
    )
    
    func main() {
        left := dispatchCoin()
        fmt.Println("剩下:",left)
        fmt.Println(distribution)
    }
    
    func dispatchCoin()(left int){
        for _,name:=range users{
    	for _,c:=range name{
    	    switch  c {
    	    case 'e','E':
    	    // 满足这个条件,分一枚金币
    	        distribution[name] ++
    		coins --
    	    case 'i','I':
    		distribution[name] += 2
    		coins -= 2
    	    case 'o','O':
    		distribution[name] +=3
    		coins -= 3
    	    case 'u', 'U':
    		distribution[name] += 4
    		coins -= 4
    	    }
            }
        }
        return coins
    }
    

    本文参考:https://www.liwenzhou.com/posts/Go/09_function/

  • 相关阅读:
    AccessControlAllowOrigin 跨域设置多域名
    C#基于LibUsbDotNet实现USB通信(一)
    移动端设置行高等于高度的时候文本不居中???
    阿里云OSS设置跨域访问还是不行。
    没错,就是AccessControlAllowOrigin,跨域
    移动端 lineheight 文字不居中问题解决方案
    Chromium.org team 改进vs编译速度的一些建议
    isapi x86 在win7x64下的安装问题
    Entity Framwork one to one problem
    Google Talk使用技巧
  • 原文地址:https://www.cnblogs.com/huiyichanmian/p/12768750.html
Copyright © 2011-2022 走看看