函数
Go语言的函数的基本组成包括:关键字func,函数名,参数列表,返回值,函数体和返回语句。
函数定义
这里举个简单的例子来说明一下Go语言中函数的定义问题:
func SumFunc(a int, b int) int{
return a + b
}
当形参的类型一样的时候可以简化为:
func SumFunc(a, b int) int {
return a + b
}
对于函数的定义,除了多了个关键字func之外,其它的和其它语言也没什么大的区别。这里需要注意一下一个概念函数的标识符。函数的类型被称为函数的标识符。如果两个函数形式参数列表和返回值列表中的变量类型一一对应,那么这两个函数被认为有相同的类型和标识符。你可以使用以下代码打印上面函数的类型
fmt.Printf("%T\n", SumFunc) //func(int, int) int
你可能会偶尔遇到没有函数体的函数声明,这表示该函数不是以Go实现的。这样的声明定义了函数标识符。
package math func Sin(x float64) float //implemented in assembly language
多返回值
在Go语言中,可以一次性返回多个函数值,前面的例子已经讲过,比如看下面的一个从网上下载图片的代码:
func GetPicUrl(movi_name string) (*picUrl, error) {
resp, err := http.Get(URL + movi_name + KEY)
if err != nil {
return nil, err
}
if resp.StatusCode != http.StatusOK {
resp.Body.Close()
return nil, fmt.Errorf("search query failed: %s", resp.Status)
}
var result picUrl
if err := json.NewDecoder(resp.Body).Decode(&result); err != nil {
resp.Body.Close()
return nil, err
}
resp.Body.Close()
fmt.Println(result.PICTUREURL, movi_name)
SaveImage(result.PICTUREURL, movi_name)
return &result, nil
}
这里返回的是一个picUrl的指针和错误,当代码正常运行完之后错误就是nil。
如果一个函数将所有的返回值都显示的变量名,那么该函数的return语句可以省略操作数。这称之为bare return。像上面的代码就可以写成:
func GetPicUrl1(movi_name string) (picurl *picUrl, err error) {
resp, err := http.Get(URL + movi_name + KEY)
if err != nil {
return
}
if resp.StatusCode != http.StatusOK {
resp.Body.Close()
return
}
if err = json.NewDecoder(resp.Body).Decode(&picurl); err != nil {
resp.Body.Close()
return
}
resp.Body.Close()
fmt.Println(picurl.PICTUREURL, movi_name)
SaveImage(picurl.PICTUREURL, movi_name)
return
}
bare return的好处就是很多返回值的时候,写起来方便。但代码的可读性就变差了,所以尽量不要乱用。
错误处理
Go语言引入了3个关键字用于标准的错误处理流程,这3个关键字分别为defer、panic和recover:
type error interface {
Error() string
}
所以对于可能返回错误的函数,大致可以设置如下模式,error一般作为最后一个返回值:
func Func(para int)(i int,err error){
}
_,err := Func(4)
if err != nil{
//处理错误
}
else{
//正常代码处理
}
函数值
Go语言提供了函数值的功能,可以将函数作为值使用:
func FuncValue(a, b int) int {
return a + b
}
funcvalue := FuncValue
funcvalue2 := func(a, b int) int {
return a * b
}
fmt.Println(funcvalue(1, 2))
fmt.Println(funcvalue2(3, 4))
匿名函数
在Go里面,匿名函数与C语言的回调函数比较类似。但GO语言可以在函数内部随时定义匿名函数。这种方式定义的函数可以访问完整的词法环境(lexical environment),这意味着在函数中定义的内部函数可以引用该函数的变量.
//直接定义匿名函数
funcvalue2 := func(a, b int) int {
return a * b
}
funcvalue2(1,2)
//在函数内部返回匿名函数
func AnonymousFunc() func() {
var sum int
childFunc := func() {
sum++
fmt.Println("The sum is", sum)
}
return childFunc
}
如果上述的匿名函数采用下面的调用方式:
func1 := AnonymousFunc()
func1()
func1()
func1()
结果将会是 func1 := AnonymousFunc()
The sum is 1
The sum is 2
The sum is 3
这里说明sum变量和匿名函数是同时存在的,当你执行匿名函数func1的时候,sum的值是一直保存的。这里牵扯到另外一个概念闭包。闭包个人理解就是内层函数可以访问外层的变量,但外层的没法访问内层的变量。
可变参数
参数可变的函数称为可变参数函数,像我们的fmt的Println这种包就是典型的可变参数函数。
//可变参数
func UnstableParam(vals ...int) {
for i, val := range vals {
fmt.Println(i, val)
}
}
UnstableParam(1, 2, 3, 4, 5)
UnstableParam(1, 2)
如果用slice作为参数的话,可以使用下面代码:
myslice := []int{10, 11, 12, 13, 14}
UnstableParam(myslice...)
这个和append函数使用一样,原理应该是将myslice打散成一个个的元素,然后传递给函数。
Deferred函数
defer是GO语言里面很有用的一个特性,先来看下面的一个例子:
func GetPicUrl(movi_name string) (*picUrl, error) {
resp, err := http.Get(URL + movi_name + KEY)
if err != nil {
return nil, err
}
if resp.StatusCode != http.StatusOK {
resp.Body.Close()
return nil, fmt.Errorf("search query failed: %s", resp.Status)
}
var result picUrl
if err := json.NewDecoder(resp.Body).Decode(&result); err != nil {
resp.Body.Close()
return nil, err
}
resp.Body.Close()
fmt.Println(result.PICTUREURL, movi_name)
SaveImage(result.PICTUREURL, movi_name)
return &result, nil
}
这是前面写的获取json文件内容并且下载图片的函数,可以看到resp.Body.Close()这个函数调用了很多次。这时候我们可以直接写成下面的代码:
func GetPicUrl(movi_name string) (*picUrl, error) {
resp, err := http.Get(URL + movi_name + KEY)
if err != nil {
return nil, err
}
if resp.StatusCode != http.StatusOK {
return nil, fmt.Errorf("search query failed: %s", resp.Status)
}
var result picUrl
if err := json.NewDecoder(resp.Body).Decode(&result); err != nil {
return nil, err
}
defer resp.Body.Close()
fmt.Println(result.PICTUREURL, movi_name)
SaveImage(result.PICTUREURL, movi_name)
return &result, nil
}
defer的作用就是在程序退出后执行后面的操作,不管是什么形式退出。所以defer对于文件处理和锁等操作有很大的作用。
来看defer的一个例子:
func TteratePara() {
for i := 0; i < 3; i++ {
defer func() {
fmt.Println(i)
}()
}
}
func TteratePara() {
for i := 0; i < 3; i++ {
a := i
defer func() {
fmt.Println(a)
}()
}
}
TteratePara()
上面两个函数的唯一不同点在于下面的函数单独定义了一个局部的变量a,把a传送给了defer后面的函数。两个函数的输出结果:333和210。
这是因为函数记录的是i的内存地址,而不是某一个时刻的值。所以i的值最后是3.
Panic()和recover()
Go语言引入了这两个函数用来处理程序运行过程中的错误。
Go的类型系统会在编译时捕获很多错误,但有些错误只能在运行时检查,如数组访问越界、空指针引用等。这些运行时错误会引起painc异常。当然也可以通过panic函数直接触发panic错误。一般而言,当panic异常发生时,程序会中断运行,并立即执行在该goroutine(可以先理解成线程,在第8章会详细介绍)中被延迟的函数(defer 机制)
看看例子:
func ErrProcess() {
panic("wwx")
}
func main(){
ErrProcess()
fmt.println("hello")
}
这样hello是不会输出的,因为panic会把所在的goroutine(可以理解为线程先)终止掉。
但有时候有些异常我们不想直接把整个线程给终止掉。这时候就需要使用recover函数了。recover函数可以直接捕获掉这个异常,不再上报给上一层。什么意思那?
假设函数funa调用了函数funb,funb又调用函数func,如果func里面产生了panic异常,如果没有recover函数,那么该异常会上报给funb,如果funb不存在recover,将上报给funa,一直到整个goroutine的开始。所以如果想打印出下面的hello,那么需要在ErrProcess里面用recover函数来捕获这个异常:
func ErrProcess() {
defer func() {
if p := recover(); p != nil {
fmt.Println("internal error: %v", p)
}
}()
panic("wwx")
}