Golang学习之函数

函数

函数声明

函数声明包括函数名、形式参数列表、返回值列表(可省略)以及函数体。

func function_name( [parameter list] ) [return_types] {
   函数体
}

函数定义解析:

  • func:函数由 func 开始声明
  • function_name:函数名称,函数名和参数列表一起构成了函数签名。
  • parameter list:参数列表,参数就像一个占位符,当函数被调用时,你可以将值传递给参数,这个值被称为实际参数。参数列表指定的是参数类型、顺序、及参数个数。参数是可选的,也就是说函数也可以不包含参数。
  • return_types:返回类型,函数返回一列值。return_types 是该列值的数据类型。有些功能不需要返回值,这种情况下 return_types 不是必须的。
  • 函数体:函数定义的代码集合。

如果一个函数声明不包括返回值列表,那么函数体执行完毕后,不会返回任何值。 在hypot函数中:

func hypot(x, y float64) float64 {
    return math.Sqrt(x*x + y*y)
}
fmt.Println(hypot(3,4)) // "5"

x和y是形参名,3和4是调用时的传入的实数,函数返回了一个float64类型的值。 返回值也可以像形式参数一样被命名。在这种情况下,每个返回值被声明成一个局部变量,并根据该返回值的类型,将其初始化为0。 如果一个函数在声明时,包含返回值列表,该函数必须以 return语句结尾,除非函数明显无法运行到结尾处。例如函数在结尾时调用了panic异常或函数中存在无限循环。

下面,我们给出4种方法声明拥有2个int型参数和1个int型返回值的函数.blank identifier(即下文的_符号)可以强调某个参数未被使用。

func add(x int, y int) int   {return x + y}
func sub(x, y int) (z int)   { z = x - y; return}
func first(x int, _ int) int { return x }
func zero(int, int) int      { return 0 }

fmt.Printf("%T\n", add)   // "func(int, int) int"
fmt.Printf("%T\n", sub)   // "func(int, int) int"
fmt.Printf("%T\n", first) // "func(int, int) int"
fmt.Printf("%T\n", zero)  // "func(int, int) int"

函数的类型被称为函数的标识符。如果两个函数形式参数列表和返回值列表中的变量类型一一对应,那么这两个函数被认为有相同的类型和标识符。形参和返回值的变量名不影响函数标识符也不影响它们是否可以以省略参数类型的形式表示。

以下实例为 max() 函数的代码,该函数传入两个整型参数 num1 和 num2,并返回这两个参数的最大值:

/* 函数返回两个数的最大值 */
func max(num1, num2 int) int {
   /* 声明局部变量 */
   var result int

   if (num1 > num2) {
      result = num1
   } else {
      result = num2
   }
   return result 
}

你可能会偶尔遇到没有函数体的函数声明,这表示该函数不是以Go实现的。这样的声明定义了函数标识符。

package math

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

函数调用

当创建函数时,你定义了函数需要做什么,通过调用该函数来执行指定任务。
调用函数,向函数传递参数,并返回值,例如:

package main

import "fmt"

func main() {
   /* 定义局部变量 */
   var a int = 100
   var b int = 200
   var ret int

   /* 调用函数并返回最大值 */
   ret = max(a, b)

   fmt.Printf( "最大值是 : %d\n", ret )
}

/* 函数返回两个数的最大值 */
func max(num1, num2 int) int {
   /* 定义局部变量 */
   var result int

   if (num1 > num2) {
      result = num1
   } else {
      result = num2
   }
   return result 
}

以上实例在 main() 函数中调用 max()函数,执行结果为:

最大值是 : 200

多返回值

在Go中,一个函数可以返回多个值。

package main

import "fmt"

func change(a ,b int) (int,int){
	return b,a
}
func main(){
	var d int
	var e int
	d,e = change(3,6)
	fmt.Println(d,e)
}

错误

Go 语言通过内置的错误接口提供了非常简单的错误处理机制。
error类型是一个接口类型,这是它的定义:

type error interface {
    Error() string
}

error类型可能是nil或者non-nil。nil意味着函数运行成功,non-nil表示失败。对于non-nil的error类型,我们可以通过调用error的Error函数或者输出函数获得字符串类型的错误信息。

fmt.Println(err)
fmt.Printf("%v", err)

当一次函数调用返回错误时,调用者有应该选择何时的方式处理错误。根据情况的不同,有很多处理方式,让我们来看看常用的五种方式。

首先,也是最常用的方式是传播错误。这意味着函数中某个子程序的失败,会变成该函数的失败。如果对http.Get的调用失败,会直接将这个HTTP错误返回给调用者:

resp, err := http.Get(url)
if err != nil{
    return nil, err
}

当对html.Parse的调用失败时,不会直接返回html.Parse的错误,因为缺少两条重要信息:1、错误发生在解析器;2、url已经被解析。这些信息有助于错误的处理,会构造新的错误信息返回给调用者:

doc, err := html.Parse(resp.Body)
resp.Body.Close()
if err != nil {
    return nil, fmt.Errorf("parsing %s as HTML: %v", url,err)
}

fmt.Errorf函数使用fmt.Sprintf格式化错误信息并返回。

如果错误发生后,程序无法继续运行,我们就可以采用第三种策略:输出错误信息并结束程序。需要注意的是,这种策略只应在main中执行。对库函数而言,应仅向上传播错误,除非该错误意味着程序内部包含不一致性,即遇到了bug,才能在库函数中结束程序。

if err := WaitForServer(url); err != nil {
    log.Fatalf("Site is down: %v\n", err)
}

log中的所有函数,都默认会在错误信息之前输出时间信息。

第四种策略:有时,我们只需要输出错误信息就足够了,不需要中断程序的运行。我们可以通过log包提供函数

if err := Ping(); err != nil {
    log.Printf("ping failed: %v; networking disabled",err)
}

第五种,也是最后一种策略:我们可以直接忽略掉错误。

dir, err := ioutil.TempDir("", "scratch")
if err != nil {
    return fmt.Errorf("failed to create temp dir: %v",err)
}
// ...use temp dir…
os.RemoveAll(dir) // ignore errors; $TMPDIR is cleaned periodically

函数值

在Go中,函数被看作第一类值(first-class values):函数像其他值一样,拥有类型,可以被赋值给其他变量,传递给函数,从函数返回。对函数值(function value)的调用类似函数调用。例子如下:

func square(n int) int { return n * n }
func negative(n int) int { return -n }
func product(m, n int) int { return m * n }

f := square
fmt.Println(f(3)) // "9"

f = negative
fmt.Println(f(3))     // "-3"
fmt.Printf("%T\n", f) // "func(int) int"

f = product // compile error: can't assign func(int, int) int to func(int) int

函数类型的零值是nil。调用值为nil的函数值会引起panic错误:

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

匿名函数

拥有函数名的函数只能在包级语法块中被声明,通过函数字面量(function literal),我们可绕过这一限制,在任何表达式中表示一个函数值。函数字面量的语法和函数声明相似,区别在于func关键字后没有函数名。函数值字面量是一种表达式,它的值被成为匿名函数(anonymous function)。

 func(参数列表) 返回值列表 {

       函数体...

 }

比如:

func (a,b int ,c float64) bool{
	return a*b <int(c)
}

匿名函数可以直接赋值给一个变量或者直接执行:

f :=func(x,y int) int{
	return x+y
}
func(ch chan int){
	ch <- ACK
}(reply_chan)//花括号后直接跟参数列表表示函数调用

可变参数

参数数量可变的函数称为为可变参数函数。典型的例子就是fmt.Printf和类似函数。Printf首先接收一个的必备参数,之后接收任意个数的后续参数。

在声明可变参数函数时,需要在参数列表的最后一个参数类型之前加上省略符号“…”,这表示该函数会接收任意数量的该类型参数。

func sum(vals...int) int {
    total := 0
    for _, val := range vals {
        total += val
    }
    return total
}

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

fmt.Println(sum())           // "0"
fmt.Println(sum(3))          // "3"
fmt.Println(sum(1, 2, 3, 4)) // "10"

在上面的代码中,调用者隐式的创建一个数组,并将原始参数复制到数组中,再把数组的一个切片作为参数传给被调函数。如果原始参数已经是切片类型,我们该如何传递给sum?只需在最后一个参数后加上省略符。下面的代码功能与上个例子中最后一条语句相同。

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

defer函数

当defer语句被执行时,跟在defer后面的函数会被延迟执行。直到包含该defer语句的函数执行完毕时,defer后的函数才会被执行,不论包含defer语句的函数是通过return正常结束,还是由于panic导致的异常结束。你可以在一个函数中执行多条defer语句,它们的执行顺序与声明顺序相反。

defer语句经常被用于处理成对的操作,如打开、关闭、连接、断开连接、加锁、释放锁。通过defer机制,不论函数逻辑多复杂,都能保证在任何执行路径下,资源被释放。释放资源的defer应该直接跟在请求资源的语句后。

延时调用函数的语法如下:

defer func_name(param-list)

当一个函数调用前有关键字 defer 时, 那么这个函数的执行会推迟到包含这个 defer 语句的函数即将返回前才执行. 例如:

package main
 
import (
    "fmt"
)
 
func main() {
    defer fmt.Println("我是最后执行的")
        fmt.Println("我是第一个")
        fmt.Println("我是第二个")
}

运行结果是:

我是第一个
我是第二个
我是最后执行的

小结:defer 调用的函数参数的值 defer 被定义时就确定了

再看下面一个案例:

package main
 
import "fmt"
 
func main() {
    i := 1
    defer fmt.Println("Deferred print:",i)
    i++
    fmt.Println("Normal print:", i)
}

运行结果如下:

Normal print: 2
Deferred print: 1

在 “defer fmt.Println(“Deferred print:”, i)” 调用时, i 的值已经确定了, 因此相当于 defer fmt.Println(“Deferred print:”, 1)了

小结:需要强调的时, defer 调用的函数参数的值在 defer 定义时就确定了, 而 defer 函数内部所使用的变量的值需要在这个函数运行时才确定

看这个案例:

package main
 
import "fmt"
 
func f1() (r int) {
    r = 1
    defer func() {
        r++
        fmt.Println(" r value = ",r)
    }()
    r = 2
    return
}
 
func main() {
    f1()
}

运行结果是:

 r value =  3

小结:上面的例子中, 最终打印的内容是 “3”, 这是因为在 “r = 2” 赋值之后, 执行了 defer 函数, 因此在这个函数内, r 的值是2了, 自增后变为3

如果有多个defer 调用, 则调用的顺序是先进后出的顺序, 类似于入栈出栈一样(后进先出/先进后出)
看下案例:

package main
 
import "fmt"
 
func f3() (r int) {
    defer func() {
        r++
    }()
    return 0
}
 
func main() {
    fmt.Println(f3())
}

运行结果是:

1

上面 fmt.Println(f3()) 打印的是什么呢? 很多朋友可能会认为打印的是0, 但是正确答案是 1. 这是为什么呢?
要弄明白这个问题, 我们需要牢记两点:

  • defer 函数调用的执行时机是外层函数设置返回值之后, 并且在即将返回之前
  • return XXX 操作并不是原子的

再看下面案例:

package main
 
import "fmt"
 
func main() {
    fmt.Println("a return:", a()) // 打印结果为 a return: 0
}
 
func a() int {
    var i int
 
    defer func() {
        i++
        fmt.Println("a defer2:", i) // 打印结果为 a defer2: 2
    }()
 
    defer func() {
        i++
        fmt.Println("a defer1:", i) // 打印结果为 a defer1: 1
    }()
 
    return i
}

运行结果下:

a defer1: 1
a defer2: 2
a return: 0

panic异常

golang中提供panic用于错误处理。
panic 意思是抛出一个异常, 和python的raise用法类似
当调用panic()时,正常的执行流程将停止。defer定义的操作还是会执行,然后一层一层向上返回,直到整个进程终止。

package main


import (
        "fmt"
)


func main() {
        foo()
}

func foo(){

        defer func (){
                fmt.Println("defer end...")
        }()

        panic("error test.....")

        fmt.Println("END")
}

运行结果是:

defer end...
panic: error test.....

goroutine 1 [running]:
main.foo()
	/home/huangyongpeng/go/src/first/w1.go:155 +0x55
main.main()
	/home/huangyongpeng/go/src/first/w1.go:145 +0x20

可以看到调用panic后,除了执行defer操作外,函数调用栈被逆向依次打印出来。

recover捕获异常

recover是捕获异常,和python的except用法类似
如果在deferred函数中调用了内置函数recover,并且定义该defer语句的函数发生了panic异常,recover会使程序从panic中恢复,并返回panic value。导致panic异常的函数不会继续运行,但能正常返回。在未发生panic时调用recover,recover会返回nil。

golang 的错误处理流程:当一个函数在执行过程中出现了异常或遇到panic(),正常语句就会立即终止,然后执行 defer 语句,再报告异常信息,最后退出 goroutine。如果在 defer 中使用了recover() 函数,则会捕获错误信息,使该错误信息终止报告。

看下面的一个例子:

package main

import (
    "fmt"
)

func main() {
    f()
    fmt.Println("Returned normally from f.")
}

func f() {
    defer func() {
        if r := recover(); r != nil {
            fmt.Println("Recovered in f", r)
            fmt.Println("Recovered in f", r)
        }
    }()
    fmt.Println("Calling g.")
    g()
    fmt.Println("Returned normally from g.")
}

func g() {
    panic("ERROR")
}

运行结果是:

Calling g.
Recovered in f ERROR
Recovered in f ERROR
Returned normally from f.

猜你喜欢

转载自blog.csdn.net/huang_yong_peng/article/details/82948914