文章目录
go语言函数
1.1 函数声明
func name(parameter-list) (result-list) {
body
}
实参通过值的方式传递,因此函数的形参是实参的拷贝。对形参进行修改不会影响实参。引用类型除外,如指针,slice(切片)、map、function、channel等类型,实参可能会由于函数的引用被修改。
1.2 多返回值
- 调用多返回值函数时,返回给调用者的时一组值,调用者必须
显示
的将这写值分配给变量:
return-value1, return-value2 := func(parameter-list)
- 如果某个值不被使用,可以将其分配给blank identifier:
return-value1, _ := func(parameter-list)
1.3 错误 error
一个良好的程序永远不应该发生panic异常。
通常使用多返回值返回一个bool值,通常被命名为ok
,常用的代码组织方式:
value, ok := cache.Lookup(key)
if !ok {
// ...cache[key] dose not exist...
}
若要了解更多的错误信息,则需要返回error类型
,当函数返回nil
意味者函数运行成功,non-nil
表示失败。
Go使用控制流机制(如if和return)出理异常,这使得编码人员能够更多的关注错误出理
1.3.1 错误出理策略
-
传播错误(
常用
)函数中某个子程序的失败,会变成该函数的失败。例如:
resp, err := http.Get(url) if err != nil{ return nill, err }
如果原函数对http.Get的调用失败, 原函数会直接将这个HTTP错误返回给调用者。
-
重新尝试失败的操作
如果错误的发生时偶然性的,或由不可预测的问题导致的,一个明智的选择是重新尝试失败的操作,但在重试时需要限制时间间隔或重试的次数,防止无限制的重试。例如:
//WaitForServer attempts to contact the server of a URL. //It tries for one minute using exponential back-off. //It reports an error if all attempts fail. func WaitForServer(url string) error { const timeout = 1 * time.Minute deadline := time.Now().Add(timeout) for tries := 0; time.Now().Before(deadline); tries++ { _, err := http.Head(url) if err == nil { return nil //success } log.Printf("server not responding (%s); retrying...", err) time.Sleep(time.Second << uint(tries)) // exponential back-off } return fmt.Errorf("server %s failed to respond after %s", url, timeout) }
-
输出错误信息并结束程序
需要注意的是,这种策略只应该在main中执行。对库函数而言,应仅向上传播错误,除非该错误意味者程序内部包含不一致性,即遇到了bug,才能在库函数中结束程序。
//(In function main.) if err := WaitForServer(url); err != nil { fmt.Fprintf(os.Stderr, "Site is down: %v\n", err) os.Exit(1) }
调用log.Fatalf可以更简洁的代码达到与上下文相同的效果。log中所有的函数,都默认会在错误信息之前输出事件信息。
if err := WaitForServer(url); err != nil { log.Fatalf("Site is down: %v\n", err) }
我们可以设置log的前缀信息屏蔽时间信息,一般而言,前缀信息会被设置成命令名。
log.SetPrefix("wait:") log.SetFlags(0)
-
只输出错误信息,不需要中断程序的运行。我们可以通过使用log包提供的函数:
if err := Ping(); err != nil { log.Printf("ping failed: %v; networking disable", err) }
或者使用标准错误流输出错误信息。
if err := Ping(); err != nil { fmt.Fprintf(os.Stderr, "ping failed: %v; networking disable\n", 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中,错误出理有一套独特的编码风格。检查某个子函数是否失败后,我们通常将处理失败的逻辑代码放在处理成功的代码之前(如策略一所示)。如果某个错误会导致函数返回,那么成功时的逻辑代码不应放在else语句块中,` 而应直接放在函数体中`。Go中大部分函数的代码结构几乎相同:
- 首先是一系列的初始检查,防止错误发生
- 函数的实际逻辑
1.3.2 文件结尾错误(EOF)
-
io.EOF 定义
package io import "errors" //EOF is the error returned by Read when no more input is available var EOF = errors.New("EOF")
in := bufio.NewReader(os.Stdin) for { r, _, err := in.ReadRune() if err == io.EOF { break // finished reading } if err != nil { return fmt.Errorf("read failed:%v", err) } // ...use r… }
1.4 函数值
在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错误
函数值可以与nil比较:注:只可与nil比较
var f func(int) int
if f != nil {
f(3)
}
1.5 匿名函数
没看太懂 ? 留着下次看
1.5.1. 警告:捕获迭代变量
1.6 可变参数
在声明可变参数函数时,需要在参数列表的最后一个参数类型之前加上省略符号“…”,这表示
该函数会接收任意数量的该类型参数。
func sum(vals...int) int {
total := 0
for _, val := range vals {
total += val
}
return total
}
sum函数返回任意个int型参数的和。在函数体中,vals被看作是类型为[]int的切片。sum可以接收任意数量的int型参数。
1.7 Deferred函数
在调用普通函数或方法前加上关键字defer
,当defer语句被执行时, 跟在defer后面的函数会被延迟执行。直到包含defer语句的函数执行完毕时,defer后的函数才会被执行,不论包含defer语句的函数是通过return正常结束,还是由于panic导致的异常结束。defer执行的顺序与声明顺序相反(栈)。defer语句经常被用于处理成对的操作,如打开、关闭、连接、断开连接、加锁、释放锁。
类似与java中的finally语句块
-
案例一:对文件的操作
package ioutil func ReadFile(filename string)([]byte, err) { f, err := os.Open(filename) if err != nil { return nil, err } defer f.Close() return ReadAll(f) }
-
案例二:处理互斥锁
var mu sync.Mutex var m = make(map[string]int){ mu.Lock() defer mu.Unlock() return m[key] }
-
案例三:defer机制也用于记录何时进入和退出函数。
func bigSlowOperation() { defer trace("bigSlowOperation")() //don't forget the() //进入调用trace,退出时调用trace返回的函数 extra parentheses //...lots of work time.Sleep(10 * time.Second) operation by sleeping } func trace(msg string) func() { start := time.Now() log.Printf("enter %s", msg) return func() { log.Printf("exit %s (%s)", msg, time.Since(start)) } }
-
案例四:对匿名函数采用defer机制,可以使其观察函数的返回值。
因为defer语句中的函数会在return语句更新返回值变量后再执行,又因为在函数中定义的匿名函数可以访问该函数包括返回值变量在内的所有变量。
func double(x int) (result int) { defer func() { fmt.Printf("double(%d)\n", x,result)}() return x+x } _=double(4) // Output: //double(4) = 8
1.7 Panic异常
当panic异常发生时,程序会中断运行,并立即执行在该goroutine中被延迟的函数(defer 机制)
1.8 Recover捕获异常
如果在defer函数中调用了内置函数recover,并且定义该defer语句的函数发生了panic异
常,recover会使程序从panic中恢复,并返回panic value。导致panic异常的函数不会继续运
行,但能正常返回。在未发生panic时调用recover,recover会返回nil。
func Parse(input string)(s *Syntax, err error) {
defer func() {
if p := recover(); p != nil {
err = fmt.Errorf("internal error: %v", p)
}()
}
}
注意:
为了标识某个panic是否应该被恢复,我们可以将panic value设置成特殊类型。在recover时对panic value进行检查,如果发现panic value是特殊类型,就将这个panic作为error处理,如果不是,则按照正常的panic进行处理。