Golang函数

作者: TZX_0710 | 来源:发表于2021-07-30 17:52 被阅读0次

    函数

    Golang函数特点

    • 无需声明原型
    • 支持多返回值
    • 不定参数传参 也就是函数的参数个数不是固定的 但是后面的类型是固定的
    • 支持命名返回参数
    • 支持匿名函数和闭包
    • 函数也是一种类型 可以赋值给一个变量
    • 不支持 嵌套 一个包不能有2个名字一样的函数
    • 不支持重载
    • 不支持默认参数

    函数声明

    函数声明包含一个函数名,参数列表,返回值列表和函数体,如果函数没有返回值,则返回列表可以省略。函数从第一条语句开始执行,直到执行return语句或者执行函数的最后一条语句。

    func test(str,x string,y int)(int,string){
     //相同的2个类型 可以省略type合并称一个
     //(int,string)返回参数
    }
    //函数是第一类对象 可以作为参数传递 
    
    • 参数

      函数定义时指出,函数定义时有参数,该变量可被称为函数的形参,形参就像定义在函数的局部变量。但是当调用函数的时候,传递过来的变量就是函数的实参,函数可以通过2种方式传递参数

      1. 值传递:在 在调用函数的时候将实际参数复制一份传递到函数中,这样在函数中如果对参数修改,不会影响到实际参数
      func swap(x ,y int)int{
      
      }
      
      1. 引用传递: 引用传递是指在调用函数时把实际参数的地址传递到函数中,那么函数中对参数进行修改就是对实际参数修改,会影响到实际参数。

        func swap(x,y *int){//传入指针
         var temp int //创建一个临时变量
            temp =*x  //x的地址的值赋值给temp临时变量
            *x=*y //y的地址的值给x 
            *y=temp   //temp的地址给到y
        }
        

        在默认情况下Go语言使用的是值传递,也就是在调用过程中不会影响到实际参数

        无论是值传递,还是引用传递,传递给函数的都是变量的副本,不过,值传递是值得拷贝。引用传递是地址得拷贝,一般来说,地址拷贝更加高效。而值拷贝取决于对象得大小,对象越大 性能越低

        map\slice\array\chan\指针\interface 默认都是以引用的方式传递

      Golang可变参数本质上就是slice。只能有一个,且必须是最后一个。在参数赋值的时候不用一个个的赋值。可以直接传递一个数组或者切片,注意最后一个参数要加上args...int

    • 返回值

      "_"标识符,用来忽略函数的某个返回值。Go的返回值可以被命名,并且像在函数开头声明的变量那样使用。返回值的名称应当具有一定的意义,可以作为文档使用。没有参数的return语句返回各个变量的当前值。这种用法被称作"裸返回"。直接返回语句仅应用在像下面这样的短函数中,在长的函数中它们会影响代码的可读性。

      //直接返回sum
      func add(x, y int) (sum int) {
      sum = x + y
       return
      }
      func main() {
       sum := add(10, 11)
       fmt.Println(sum)//21
      }
      //命名返回参数允许defer延迟调用通过闭包读取和修改
      //直接返回sum
      
      //直接返回sum
      func add(x, y int) (sum int) {
       defer func() {
           fmt.Println("加100")
           sum += 100
       }()
       sum = x + y
       return
      }
      func main() {
       sum := add(10, 11)
      fmt.Println(sum) //加100 121
      //显示return返回前,会先修改命名返回参数
      }
      
    
    

    匿名函数

    匿名函数是指不需要定义函数名的一种函数实现方式。在GO里面,函数可以像普通变量一样被传递或使用,Go语言支持随时在代码里定义匿名函数。匿名函数由一个不带函数声明和函数体组成。匿名函数的优越性可以直接使用函数内的变量不必声明。

    func main() {
      var sum = func() int {
          return 100 * 100
      >   }
      fmt.Println(sum())//10000
    }
    //Golang匿名函数可赋值给变量,作为结构字段,或者在channel里传送
    
    func main() {
      //创建一个函数变量
      fun := func() {
          fmt.Println("111")
      }
      fun() //111
      //fun 数组 创建多个函数 函数的格式为接受一个参数返回一个int类型
      funs := [](func(x int) int){
          func(x int) int {
              return x + 1
          },
          func(x int) int {
              return x + 2
          },
      }
      fmt.Println(funs[0](100)) //101
    
      //作为结构体的一个field
      d := struct {
          fn func(x int) int
      }{ //创建一个函数变量
          fn: func(x int) int { //函数变量赋值
              return 1
          },
      }
      fmt.Println(d.fn(1)) //1
    
      //channel
      c := make(chan func() string, 2)
      c <- func() string {
          return "hello world"
      }
      fmt.Println((<-c)())//hello world
    
    }
    

    闭包

    闭包可以理解为一种保存函数状态的方法,当我们调用一个函数,或者执行操作,或者返回结果,函数运行结束之后,随即消亡,因为函数一般都是在堆上,当系统检测当前内存空间没有被引用就会回收

    闭包的作用就是保存函数的运行状态 避免函数被回收。

    • 访问所在的作用域
    • 函数嵌套
    • 在所在作用域外被调用
    func bb() func(i int) int {
     var s int = 0
       fmt.Println("ccccc")
     fmt.Println("ccccc")
     b := func(i int) int {
         s = s + 1
         fmt.Printf("地址%#v", &s)
         return s
     >   }
     >   return b
     > }
     > func main() {
     >   a := bb()
     >   b := bb()
     >   fmt.Println(a(1))
     >   fmt.Println(a(1))
     >   fmt.Println(b(1))
     >   fmt.Println(b(1))
     > }
     > //ccccc
     > //ccccc
     > //ccccc
     > //ccccc
     > //地址(*int)(0xc00000a0a8)1
     > //地址(*int)(0xc00000a0a8)2
     > //地址(*int)(0xc00000a0c0)1
     > //地址(*int)(0xc00000a0c0)2
     > //可以看到a和b变量的调用 s被存在了不同的内存当中
     > //当采用a调用的时候 s的变量被保存到了0xc00000a0a8当中
     > //当采用b调用的时候 s变量被保存到了0xc00000a0c0 中 因为这是在调用bb()方法的时候 都申请了一块内存
     > 
     > //把s定义成全局变量
     > var s int = 0
     > func bb() func(i int) int {
     >   b := func(i int) int {
     >       s = s + 1
     >       fmt.Printf("地址%#v", &s)
     >       return s
     >   }
     >   return b
     > }
     > func main() {
     >   a := bb()
     >   b := bb()
     >   fmt.Println(a(1))
     >   fmt.Println(a(1))
     >   fmt.Println(b(1))
     >   fmt.Println(b(1))
     > }
     > //ccccc
     > //ccccc
     > //ccccc
     > //ccccc
     > //地址(*int)(0x85dc68)1
     > //地址(*int)(0x85dc68)2
     > //地址(*int)(0x85dc68)3
     > //地址(*int)(0x85dc68)4
     > //可以看到当s提升为全局变量 申请的一块地址 用于a和b调用的时候 是共享的内存变量
     > ```
     >
     > 通过以上案例我们可以得出在a:=bb()的时候执行了bb函数,实际上这里的指向是直接指向的bb()函数的内部子函数。
     >
     > 当我们调用a()的时候 直接执行的也是内部的子函数。
     >
     > 通过局部变量和全局变量可以发现,申请的内存方式是不同的
    
    

    递归函数

    递归就是在运行的过程中调用自己。一个函数调用自身叫做递归函数

    构成递归函数得条件:

    子问题必须与原始问题为同样的事,且更为简单

    不能无限制调用必须有个出口,化简为分递归状态处理

    func factorial(i int) int {
      if i <= 1 {
          return 1
      }
      return i * factorial(i-1)
    }
    func main() {
      fmt.Println(factorial(7))//5040
    }
    
    

    延迟调用(defer)

    defer特性:
    1.关键字defer用于注册延迟调用
    2. 这些调用直到return前才会被执行。因此可以用来做资源清理
    3.多个defer,按照先进后出的方式执行
    4.defer语句中的变量,在defer声明时就决定了
    defer用途:
    1.关闭文件句柄
    2.锁资源释放
    3.数据库连接释放
    
    func main() {
    
     for i := 0; i < 10; i++ {
         //defer是先进后出的   defer 调用的函数参数的值 defer 被定义时就确定了.
         //defer fmt.Print(strconv.Itoa(i) + "\t") //9   8   7   6   5   4   3   2   1   0
         //defer 碰上闭包 
         defer func() {
             fmt.Println(&i) //defer func内部所使用的变量的值需要在这个函数运行时才确定
             //也就是讲这在闭包用到的时候这个变量已经变成了10,所以输出全都是10
         }()
     }
    }
    
    
    

    defer 与return

    //defer 与return
    func foo() (i int) {
    
     i = 0
     defer func() {
         fmt.Println(i)
     }()
     //在具名返回函数中,执行return 2的时候已经将i的值赋值为2了。所以输出的时候结果为2不是0
     return 2
    }
    
    func main() {
     foo()
    }
    

    defer nil报错

    //defer nil函数报错
    
    func test() {
     //声明的时候未被调用
     var run func() = nil
     //被调用 报错
     defer run()
     fmt.Println("runs")
    }
    
    func main() {
     //捕捉异常
     defer func() {
         if err := recover(); err != nil {
             fmt.Println("错误日志")
             fmt.Println(err)
         }
     }()
     //调用 runtime error: invalid memory address or nil pointer dereference
     //main从上到下开始执行。defer 延迟执行
     //执行test方法。test方法调用的时候,出test的时候进行报错。 当test调用完成的时候 defer run才会被调用
     test()
    }
    
    

    在错误的地方使用defer

    func do() error {
     res, err := http.Get("http://www.google.com")
     defer res.Body.Close()
     if err != nil {
         return err
     }
     return nil
    }
    
    func main() {
     do()
     //panic: runtime error: invalid memory address or nil pointer dereference
     //因为google无法访问因为网络原因 defer 直接使用了res变量 res为nill未判断
    //可以在defer 的时候加上一层判断解决该错误
    if res!=nil{
       defer res.Body.Close()
    }
    }
    
    

    不检查错误

    //对于f.Close()可能会返回一个错误,但是这个错误会被我们忽略掉
    func do() error {
     f, err := os.Open("book.txt")
     if err != nil {
         return err
     }
    
     if f != nil {
         defer f.Close()
     }
    
    
     return nil
    }
    
    func main() {
     do()
    }
    // 改进
    if f!=nill{
     defer func(){
         if err:=f.Close();err!=nill{
             //code
         }
     }
    }
    
    

    释放相同的资源

    func do() error {
    f, err := os.Open("book.txt")
    if err != nil {
       return err
    }
    if f != nil {
       defer func() {
           if err := f.Close(); err != nil {
               fmt.Printf("defer close book.txt err %v\n", err)
           }
       }()
    }
    
    f, err = os.Open("another-book.txt")
    if err != nil {
       return err
    }
    if f != nil {
       defer func() {
           if err := f.Close(); err != nil {
               fmt.Printf("defer close another-book.txt err %v\n", err)
           }
       }()
    }
    
    return nil
    }
    
    func main() {
    do()//输出结果: defer close book.txt err close ./another-book.txt: file already closed
    //当延迟函数执行时候,只有最后一个变量会被用到,因此f会成为最后那个资源。而且2个资源都将这一个资源作为关闭对象
    }
    //解决方案
    //可以把f当作参数传递进去
    defer func(f io.Closer){
    
    }(f)
    

    异常处理

    Golang没有结构化异常,使用panic抛出异常,recover捕获错误。

    异常的使用场景很简单:Go可以抛出一个panic异常,然后在defer中通过recover捕获这个异常。然后正常处理

    panic:
      1.内置函数
      2.加入函数F中书写了panic语句 ,会终止其后要执行得代码,在panic所在函数F内如果存在要执行得defer函数列表。按照defer先进后出执行
      3.返回函数F得调用者G,在G中,调用函数F语句之后得代码不会执行,加入函数G在存在要执行得defer先进后厨执行
      4.直到goroutine整个退出,并报告错误
    recover:
      1.内置函数
      2.用来控制一个goroutine得panicking行为,捕获panic,从而影响应用得行为
      3.一般的调用建议 
          1.在defer函数中,通过recever来终止一个goroutine的panicking过程,从而恢复正常代码的执行
          2.可以获取通过panic传递的error
    注:
      1.利用recover处理panic指令,defer必须放在panic之前定义,另外recover只有在defer调用的函数中才有效,否则当panic,receover无法捕捉到panic,无法防止panic扩散
      2.recover处理异常后,逻辑并不会恢复到panic那个点去,函数跑到defer之后的那个点
      3.多个defer会形成defer栈,后定义的defer语句最先被调用。
    

    延迟调用

    //延迟调用中引发错误,可被后续异常调用捕获,但仅最后一个错误可被捕获
    func test() {
      defer func() {
          fmt.Println("最后调用")
          fmt.Println(recover())
      }()
    
      defer func() {
          fmt.Println("第一次调用")
          panic("defer panic")
      }()
    
      panic("test panic")
    }
    
    func main() {
      test()
    }
    //捕获函数recover 只有在延迟调用内直接调用才会终止错误,否则总是返回nill。任何未捕获的错误都会沿调用堆栈向外传递
    package main
    
    
    
    import (
    "fmt"
    )
    
    
    func test() {
      defer func() {
          fmt.Println(recover()) //有效
      }()
      defer recover()              //无效!
      defer fmt.Println(recover()) //无效!
      defer func() {
          func() {
              println("defer inner")
              recover() //无效!
          }()
      }()
    
      panic("test panic")
    }
    
    func main() {
      test()
    }
    
    
    

    Go实现类似try catch的异常处理

    func Try(fn func(), handler func(interface{})) {
      defer func() {
          if err := recover(); err != nil {
              handler(err)
          }
      }()
      fn()
    }
    func main() {
      Try(func() {
          fmt.Println("测试")
      }, func(err interface{}) {
    
      })
    }
    

    相关文章

      网友评论

        本文标题:Golang函数

        本文链接:https://www.haomeiwen.com/subject/iqjcvltx.html