您的位置:首页 > 编程语言 > Go语言

Go语言基础入门--函数,错误处理

2015-04-22 14:39 696 查看

函数func

函数声明
函数的基本组成为:关键字func、函数名、参数列表、返回值、函数体和返回语句。

一般的: func 函数名 (传入参数) (返回参数) {函数体}

如:

package mymath
import "errors"
func Add(a int, b int) (ret int, err error) {
if a < 0 || b < 0 { // 假设这个函数只支持两个非负数字的加法
err= errors.New("Should be non-negative numbers!")
return
}
return a + b, nil // 支持多重返回值
}
如果参数列表中若干个相邻的参数类型相同,可以省略前面变量的类型声明,如:

func Add(a, b int</span>)(ret int, err error) {
// ...
}

如果返回值列表中多个返回值的类型相同,也可以用同样的方式合并。

如果函数只有一个返回值,也可以这么写:

func Add(a, b int) int {
// ...
}
函数调用
1、导入该函数所在包

2、如下调用

import "mymath"// 假设Add被放在一个叫 mymath的包中
// ...
c := mymath.Add(1, 2)
值得注意的是:如果想在外部调用该包函数,所调用的函数名必须大写。

Go语言有着这样的规定:

小写字母开头的函数只在本包内可见,大写字母开头的函数才能被其他包使用。

这个规则也适用于类型和变量的可见性。

不定参数

不定参数是指函数传入的参数个数为不定数量。为了做到这点,首先需要将函数定义为接受不定参数类型:

func myfunc(args ... int) {//参数名 ...type
for _, arg := range args {
fmt.Println(arg)
}
}
该函数可接收不定数量参数,如:
myfunc(2, 3, 4)
myfunc(1, 3, 7, 13)
从内部实现机理上来说,类型...type本质上是一个数组切片,也就是[]type,这也是为什么上面的参数args可以用for循环来获得每个传入的参数。

假如没有...type这样的语法糖,开发者将不得不这么写:

func myfunc2(args [] int) {
for _, arg := range args {
fmt.Println(arg)
}
}
从函数的实现角度来看,这没有任何影响,该怎么写就怎么写。但从调用方来说,情形则完全不同:

myfunc2([] int{1, 3, 7, 13})
你会发现,我们不得不加上[]int{} 来构造一个数组切片实例。

不定参数的传递

func myfunc(args ... int) {
// 按原样传递
myfunc3(args...)
// 传递片段,实际上任意的int slice都可以传进去
myfunc3(args[1:]...)
}
任意类型的不定参数

之前的例子中将不定参数类型约束为 int ,如果你希望传任意类型,可以指定类型为interface{} 。

下面是Go语言中fmt,Printf()函数原型:

func Printf(format string, args ... interface{}) {
// ...
}
下面代码可以让我们清晰的知道传入参数的类型:

package main
import "fmt"
func MyPrintf(args ... interface{}) {
for _, arg := range args {
switch arg. (type)  {
case int:
fmt.Println(arg, "is an int value.")
case string:
fmt.Println(arg, "is a string value.")
case int64:
fmt.Println(arg, "is an int64 value.")
default:
fmt.Println(arg, "is an unknown type.")
}
}
}
func main() {
var v1 int = 1
var v2 int64 = 234
var v3 string = "hello"
var v4 float32 = 1.234
MyPrintf(v1, v2, v3, v4)
}
该程序的输出结果为:
1 is an int value.
234 is an int64 value.
hello is a string value.
1.234 is an unknown type.
匿名函数和闭包

在Go里面,函数可以像普通变量一样被传递或使用,这与C语言的回调函数比较类似。不同

的是, Go语言支持随时在代码里定义匿名函数。

匿名函数由一个不带函数名的函数声明和函数体组成,如下所示:

func(a, b int, z float64) bool {
return a*b <int(z)
}
匿名函数可以直接赋值给一个变量或者直接执行:
f := func(x, y int) int {
return x + y
}
func(ch chan int) {
ch <- ACK
} (reply_chan) // 花括号后直接跟参数列表表示函数调用
闭包

闭包是可以包含自由(未绑定到特定对象)变量的代码块,这些变量不在这个代码块内或者

任何全局上下文中定义,而是在定义代码块的环境中定义。要执行的代码块(由于自由变量包含

在代码块中,所以这些自由变量以及它们引用的对象没有被释放)为自由变量提供绑定的计算环

境(作用域)。

这是官方的说法,我的理解是:

简单说就一个封闭的func,它可以访问它外部声明的变量,但外部访问不了它内部声明的变量,除非它愿意提供一个句柄

Go语言中的闭包同样也会引用到函数外的变量。闭包的实现确保只要闭包还被使用,那么

被闭包引用的变量会一直存在, 如:

package main
import (
"fmt"
)
func main() {
var j int = 5
a := func()( func()) {
var i int = 10
return func() {
fmt.Printf("i, j: %d, %d\n", i, j)
}
}()
a()
j *= 2
a()
}
上述例子的执行结果是:

i, j: 10, 5
i, j: 10, 10
在上面的例子中,变量a指向的闭包函数引用了局部变量i和j , i的值被隔离,在闭包外不

能被修改,改变j 的值以后,再次调用a,发现结果是修改过的值。

在变量a指向的闭包函数中,只有内部的匿名函数才能访问变量i,而无法通过其他途径访问

到,因此保证了i的安全性。

错误处理

Go语言的错误处理非常的便捷,引入了一个错误处理的标准模式error接口:
type error interface {
Error() string
一般的,函数返回错误可将其设置为返回值中的最后一个:

func Foo(param int)(n int, err error) {
// ...
}
我们调用时通过判断err是否nil,用来处理错误信息:

n, err := Foo(0)
if err != nil {
// 错误处理
} else {
// 使用返回值n
}
那我们该如何实现自己的错误信息类型呢:

type PathError struct {//参数名大写,结构体外可查看
Op string
Path string
Err error
}
我们再为该结构体实现一个Error()方法,(为什么Error大写)

func (e *PathError) Error() string {
retu
4000
rn e.Op + " " + e.Path + ": " + e.Err.Error()
}
下面我们就可以自己包装错误信息了

func Stat(name string) (fi FileInfo, err error) {
var stat syscall.Stat_t
err = syscall.Stat(name, &stat)
if err != nil {//Stat返回的错误信息通过PathError返回
return nil, &PathError{"stat", name, err}
}
return fileInfoFromStat(&stat, name), nil
}
如果在处理错误时获取详细信息,而不仅仅满足于打印一句错误信息,那就需要用到类型转

换知识了:

fi, err := os.Stat("a.txt")
if err != nil {
if e, ok := err.(*os.PathError); ok && e.Err != nil {
// 获取PathError类型变量e中的其他信息并处理
}
}
defer

defer关键字:延迟函数。它可以再函数return后,函数结束前执行

较c++明显区别是:比如打开一文件输入流,需要及时关闭。任何一个程序员都不能做到百分百,而defer可以很好的实现该功能

func CopyFile(dst, src string) (w int64, err error) {
srcFile, err := os.Open(src)
if err != nil {
return
}
defer srcFile.Close()
dstFile, err := os.Create(dstName)
if err != nil {
return
}
defer dstFile.Close()
return io.Copy(dstFile, srcFile)
}
即使其中的Copy() 函数抛出异常, Go仍然会保证dstFile和srcFile会被正常关闭。

如果觉得一句话干不完清理的工作,也可以使用在defer后加一个匿名函数的做法:

defer func() {
// 做你复杂的清理工作
} ()
另外,一个函数中可以存在多个defer语句,因此需要注意的是, defer语句的调用是遵照

先进后出的原则,即最后一个defer语句将最先被执行。只不过,当你需要为defer语句到底哪

个先执行这种细节而烦恼的时候,说明你的代码架构可能需要调整一下了。

panic() 和recover()
Go语言引入了两个内置函数panic() 和recover() 以报告和处理运行时错误和程序中的错

误场景:

func panic(interface{})
func recover() interface{}
当在一个函数执行过程中调用panic() 函数时,正常的函数执行流程将立即终止,但函数中

之前使用defer关键字延迟执行的语句将正常展开执行,之后该函数将返回到调用函数,并导致

逐层向上执行panic流程,直至所属的goroutine中所有正在执行的函数被终止。错误信息将被报

告,包括在调用panic() 函数时传入的参数,这个过程称为错误处理流程。

从panic() 的参数类型interface{} 我们可以得知,该函数接收任意类型的数据,比如整

型、字符串、对象等。调用方法很简单,下面为几个例子:

panic(404)
panic("network broken")
panic(Error("file not exists"))
recover() 函数用于终止错误处理流程。一般情况下, recover() 应该在一个使用defer

关键字的函数中执行以有效截取错误处理流程。如果没有在发生异常的goroutine中明确调用恢复

过程(使用recover关键字),会导致该goroutine所属的进程打印异常信息后直接退出。

我们对于foo() 函数的执行要么心里没底感觉可能会触发错误处理,或者自己在其中明确加

入了按特定条件触发错误处理的语句,那么可以用如下方式在调用代码中截取recover() :
defer func() {
if r := recover(); r != nil {
log.Printf("Runtime error caught: %v", r)
}
}()
foo()
无论foo() 中是否触发了错误处理流程,该匿名defer函数都将在函数退出时得到执行。假

如foo() 中触发了错误处理流程, recover() 函数执行将使得该错误处理过程终止。如果错误处

理流程被触发时,程序传给panic函数的参数不为nil,则该函数还会打印详细的错误信息。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: