2018-07-19 09:27:42

goweb-033-golang-函数

函数声明

  • 函数声明包括函数名、形式参数列表、返回值列表(可省略)以及函数体。
  • 形式参数列表描述了函数的参数名以及参数类型。
  • 返回值列表描述了函数返回值的变量名以及类型。
  • 若函数返回一个无名变量或者没有返回值,返回值列表的括号是可以省略的。
  • 若函数声明不包括返回值列表,那么函数体执行完毕后,不会返回任何值。
func name(parameter-list) (result-list) {
    body
}

func add(a int ,b int) int{
    return a+b
}
// 命名返回值
func min(a, b int) (min int) {
    if a > b {
        min = b
    } else {
        min = a
    }
    return
}

一组形参或返回值有相同的类型,不必为每个形参都写出参数类型。

func f(i, j, k int, s, t string)                 { /* ... */ }
func f(i int, j int, k int,  s string, t string) { /* ... */ }
  • 函数的类型被称为函数的签名☝️如果两个函数形式参数列表返回值列表中的变量类型一一对应,那么这两个函数被认为有相同的类型或签名。
  • 函数调用都必须按照声明顺序为所有参数提供实参。
  • 实参通过值的方式传递,对形参进行修改不会影响实参。
  • 实参包括引用类型,如指针,slicemap等类型,实参可能会由于函数的间接引用被修改。

没有函数体的函数声明,这表示该函数不是以Go实现的,只有函数签名。

package math

func Sin(x float64) float //implemented in assembly language

递归

函数可以是递归的,函数可以直接或间接的调用自身。

func fib(n int) int {
    if n <= 1 {
        return 1
    }
    return fib(n-1) + fib(n-2)
}
func main() {
    for i := 0; i < 10; i++ {
        fmt.Printf("\t%d", fib(i))
    }
}

多返回值

  • Go语言中,一个函数可以返回多个值。
  • 标准库中的许多函数返回2个值,一个是期望得到的返回值,另一个是函数出错时的错误信息。
func f(a, b int) (max, min int) {
    if a > b {
        max = a
        min = b
    } else {
        max = b
        min = a
    }
    return
}

func main() {
    max, min := f(10, 100)
    fmt.Println(max, min)
}

http请求

package main

import (
    "fmt"
    "net/http"
)

func main() {
    // resp 服务器响应内容
    // err http请求出错时候的信息
    resp, err := http.Get(`https://blog.zxysilent.com`)
    if err != nil { // 不等于nil 表示有错误
        panic(err) //抛出异常
    }
    // 准备容器
    buf := make([]byte, 1024*10) //1kb*10
    // 读取响应内容到 数据容器中
    l, err := resp.Body.Read(buf) //读取长度和错误
    fmt.Println(l, err)
    // 字节转换为字符串
    fmt.Println(string(buf[:l]))
}

函数值

在Go语言中,函数被看作第一类值(first-class values):函数像其他值一样,拥有类型,可以被赋值给其他变量,传递给函数,从函数返回。

func add(a, b int) int {
    return a + b
}
func fn() func(int, int) int {
        // 产生了一个不知名的函数
        // 匿名函数
    return func(a, b int) int {
        return a + b
    }
}
func main() {
    f := add
    fmt.Println(f(10, 20))
        //插播内容
        func() {
        fmt.Println("匿名函数自执行")
    }()
    fmt.Println(fn()(100, 200))
}

函数类型的零值是nil,调用值为nil的函数值会引起panic

    var fn func(int) int
    fn(10) // 此处f的值为nil, 会引起panic错误

函数值可以与nil比较。

    var fn func(int) int
    if fn != nil {
        fn(10)
    }

✍️函数值之间是不可比较的,也不能用函数值作为mapkey

可变参数

  • 参数数量可变的函数称为可变参数函数。 fmt.Printf首先接收一个必备的参数,之后接收任意个数的后续参数。
  • 在声明可变参数函数时,需要在参数列表的最后一个参数类型之前加上省略符号..,这表示该函数会接收任意数量的该类型参数。
func sum(vals...int) int {
    total := 0
    for _, val := range vals {
        total += val
    }
    return total
}

sum函数返回任意个int型参数的和。在函数体中,vals被看作是类型为[] int的切片。

fmt.Println(sum())       
fmt.Println(sum(3))  
fmt.Println(sum(1, 2, 3, 4)) 

原始参数已经是切片类型,我们该如何传递给sum❓。

values := []int{1, 2, 3}
fmt.Println(sum(values...)) 

...int 型参数的行为看起来很像切片类型,但实可变参数函数和以切片作为参数的函数是不同的。

func f(...int) {}
func g([]int) {}
fmt.Printf("%T\n", f) // "func(...int)"
fmt.Printf("%T\n", g) // "func([]int)"

defer

函数返回前执行的函数⛲️

  • 在调用普通函数或方法前加上关键字defer,就完成了defer所需要的语法。
  • 可以在一个函数中执行多条defer语句,它们的执行顺序与声明顺序相反。
  • 无论包含defer语句的函数是通过return正常结束,还是由于panic导致的异常结束defer后的函数都会被执行。
  • 函数体内某个变量作为defer时匿名函数的参数,则在定义defer时即已经获得了拷贝,否则则是引用某个变量的地址。
package main
import (
    "fmt"
)
func main() {
    //函数返回前执行
    defer fmt.Println("defer1")
    defer fmt.Println("defer2")
    fmt.Println("exit")
}

alt

import (
    "fmt"
)
func fn() {
    defer fmt.Println("异常也会执行")
    fmt.Println("exit")
    panic("手动异常")
}
func main() {
    fn()
}

拷贝还是引用

package main
import "fmt"
func main() {
    defer fmt.Println("hello defer")
    // 拷贝还是引用
    for i := 0; i < 5; i++ {
        //普通匿名函数
        func() {
            fmt.Println(i)
        }()
        // defer 引用
        defer func() {
            fmt.Println("defer:", i)
        }()
        // defer 拷贝
        defer func(x int) {
            fmt.Println("defer-:", x)
        }(i)
    }
}

函数返回前⌛️

package main

import "fmt"

func main() {
    res := test(10)
    fmt.Println(res) //100

    res1 := test1(10)
    fmt.Println(res1) //101
}

func test(i int) int {
    defer func() {
        i++
    }()
    return i * 10
}
func test1(i int) (r int) {
    defer func() {
        r++
    }()
    r = i * 10
    return
}

panic异常

Go的类型系统会在编译时捕获很多错误,但有些错误只能在运行时检查,如数组访问越界、空指针引用等。这些运行时错误会引起painc异常。

  • 当panic异常发生时,程序会中断运行,并立即执行在该goroutine中被延迟的函数(defer)。
  • 随后程序崩溃并输出日志信息。日志信息包括panic value和函数调用的堆栈跟踪信息。 -直接调用内置的panic函数也会引发panic异常。panic函数接受任何值interface{}作为参数。

recover捕获异常

当异常发生的时候程序会停止运行。当一个web服务(教学管理系统)某一个模块出现问题(登陆)但其他模块应该可以正常提供服务(抢课)。

  • defer中调用了内置函数recoverrecover会使程序从panic中恢复,并返回panic value
  • 导致panic异常的函数不会继续运行,但能正常返回。
  • 在未发生panic时调用recoverrecover会返回nil
package main

import "fmt"

func main() {
    defer func() {
        if err := recover(); err != nil {
            fmt.Println("defer:", err)
        }
    }()
    panic("提前终止程序")
}

不影响其他功能

package main
import "fmt"
func fn1() {
    // defer func() {
    //     if err := recover(); err != nil {
    //         fmt.Println("defer:", err)
    //     }
    // }()
    panic("panic")
}
func fn2() {
    defer func() {
        if err := recover(); err != nil {
            fmt.Println("defer:", err)
        }
    }()
    fmt.Println("zxysilent")
}
func main() {
    for i := 0; i < 3; i++ {
        fn1()
        fn2()
    }
}

本文链接:https://blog.zxysilent.com/post/goweb-03-3.html

-- EOF --

Comments