Go语言函数语法下篇
Posted ych9527
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Go语言函数语法下篇相关的知识,希望对你有一定的参考价值。
文章目录
一、延迟执行语句defer
1.1defer概念
- Go语言的 defer 语句会将其后面跟随的语句进行延迟处理,在 defer 归属的函数即将返回时,将延迟处理的语句按 defer 的逆序进行执行,也就是说,先被 defer 的语句最后被执行,最后被 defer 的语句,最先被执行(类似栈先进后出)
- 一般用于释放某些已分配的资源,典型的例子就是对一个互斥解锁,或者关闭一个文件
1.2 使用演示
-
多个延迟执行语句的处理顺序
- 下面代码之中可以看到第三个语句是最先执行的
func main() defer fmt.Printf("%d ",1) defer fmt.Printf("%d ",2) defer fmt.Printf("%d ",3) //3 2 1
-
使用延迟语句在函数退出的时候释放资源
-
使用延迟并发解锁
-
在下面的例子中会在函数中并发使用 map,为防止竞态问题,使用 sync.Mutex 进行加锁
//常规写法 var ( testMap = make(map[string]string) testMutex sync.Mutex ) func Read(key string) string testMutex.Lock() //进行加锁 v := testMap[key] testMutex.Unlock() //进行解锁 return v //延迟写法 func ReadLater(key string) string testMutex.Lock() defer testMutex.Unlock() //延迟语句,在退出函数的时候执行该语句 return testMap[key]
-
使用延迟释放文件句柄
-
文件的操作需要经过打开文件、获取和操作文件资源、关闭资源几个过程,如果在操作完毕后不关闭文件资源,进程将一直无法释放文件资源,在下面的例子中将实现根据文件名获取文件大小的函数,函数中需要打开文件、获取文件大小和关闭文件等操作,由于每一步系统操作都需要进行错误处理,而每一步处理都会造成一次可能的退出,因此就需要在退出时释放资源,而我们需要密切关注在函数退出处正确地释放文件资源
//常规写法 func fiileTest(filename string) int64 //打开文件 f, err := os.Open(filename) if err != nil return 0 //获取文件状态信息 info, err := f.Stat() if err != nil f.Close() return 0 //获取文件大小 size := info.Size() //关闭文件 f.Close() return size //延迟写法 func fileTestLater(filename string) int64 f, err := os.Open(filename) if err != nil return 0 //延迟语句 defer f.Close() info, err := f.Stat() if err != nil return 0 size := info.Size() return size
-
-
-
二、递归函数
-
Go语言和C++一样,也支持函数之间的递归
//斐波那契 func fibFunc(num int) int if num <= 2 return 1 else return fibFunc(num-1) + fibFunc(num-2)
三、Go语言处理运行时错误
3.1错误处理思想
- 一个可能造成错误的函数,需要返回值中返回一个错误接口(error),如果调用是成功的,错误接口将返回 nil,否则返回错误
- 在函数调用后需要检查错误,如果发生错误,则进行必要的错误处理
3.2错误接口的定义
-
定义格式
-
error 是 Go 系统声明的接口类型
(调用器接口) type error interface Error() string
-
返回错误前,需要定义会产生哪些可能的错误,在Go语言中,使用 errors 包进行错误的定义
var err = errors.New("this is an error")
-
errors包中的实现
//错误内置接口类型是的常规接口 //表示错误条件,nil值表示无错误。 //--这是一个调用器接口 type error interface Error() string //定义一个结构体 //包含一个描述错误信息的成员 type errorString struct s string //传入信息字符串构建且返回一个结构体对象 func New(text string) error return &errorString(text) //结构体对象的实现 实现错误接口 func (e *errorString) Error() string return e.s
-
-
使用演示
package main import ( "errors" "fmt" ) //定义一个被除数为0的错误 var divError = errors.New("div by zero") func div(child, mather int) (int, error) if mather == 0 return 0, divError //此时返回的是一个结构体对象,后面的函数接收对象之后进行Error()方法的调用即可 return child / mather, nil func main() _, err := div(10, 0) if err != nil fmt.Println(err.Error()) //div by zero //div by zero //更加简洁的写法 func div(child, mather int) (int, error) if mather == 0 return 0, errors.New("div by zero") //在使用的时候进行定义即可 return child / mather, nil func main() _, err := div(10, 0) if err != nil fmt.Println(err.Error()) //div by zero
四、Go语言宕机
-
手动触发宕机
-
Go语言可以在程序中手动触发宕机,让程序崩溃,这样开发者可以及时地发现错误,同时减少可能的损失
-
Go语言程序在宕机时,会将堆栈和 goroutine 信息输出到控制台,所以宕机也可以方便地知晓发生错误的位置,那么我们要如何触发宕机呢,示例代码如下所示
package main func main() panic("crash") //输出信息 panic: crash goroutine 1 [running]: main.main() /Users/didi/test/test.go:94 +0x27 exit status 2
-
-
在宕机时触发延迟执行语句
-
当 panic() 触发的宕机发生时,panic() 后面的代码将不会被运行,但是在 panic() 函数前面已经运行过的 defer 语句依然会在宕机发生时发生作用
-
宕机前,defer 语句会被优先执行,这个特性可以用来在宕机发生前进行宕机信息处理
package main import "fmt" func main() defer fmt.Println("宕机后要做的事情1") defer fmt.Println("宕机后要做的事情2") panic("宕机") //控制台输出内容 -> defer语句在宕机前执行了 宕机后要做的事情2 宕机后要做的事情1 panic: 宕机 goroutine 1 [running]: main.main() /Users/didi/test/test.go:96 +0xac exit status 2
-
五、Go语言宕机恢复recover
5.1基础概念
- 在其它语言之中,宕机通常是以异常的形式存在,上层逻辑通过try/carch机制捕获异常,没有被捕获的严重异常会导致宕机,捕获的异常可以被忽略,让代码继续运行
- Go语言没有异常系统,其使用 panic 触发宕机类似于其他语言的抛出异常,recover 的宕机恢复机制就对应其他语言中的 try/catch 机制
- Recover 是一个Go语言的内建函数,可以让进入宕机流程中的 goroutine 恢复过来,recover 仅在延迟函数 defer 中有效,在正常的执行过程中,调用 recover 会返回 nil 并且没有其他任何效果,如果当前的 goroutine 陷入恐慌,调用 recover 可以捕获到 panic 的输入值,并且恢复正常的执行
- 通常来说,不应该对进入 panic 宕机的程序做任何处理,但有时,需要我们可以从宕机中恢复,至少我们可以在程序崩溃前,做一些操作,举个例子,当 web 服务器遇到不可预料的严重问题时,在崩溃前应该将所有的连接关闭,如果不做任何处理,会使得客户端一直处于等待状态,如果 web 服务器还在开发阶段,服务器甚至可以将异常信息反馈到客户端,帮助调试
5.2让程序在崩溃时继续执行
package main
import (
"fmt"
"runtime"
)
func Downtime(testFunc func())
defer func()
err := recover() //panic时,获得panic传递的信息,并且不宕机
switch err.(type)
case runtime.Error: //运行时错误
fmt.Println("runtime_error:", err)
default: //非运行时错误
fmt.Println("not runtime Error:", err)
()
testFunc() //执行传进来的函数
fmt.Println("判断退出点1") //这一句没有打印,说明是从宕机点退出当前函数的
func main()
Downtime(func()
fmt.Println("手动宕机前")
panic("我手动宕机了")
)
Downtime(func()
fmt.Println("给空指针赋值前")
var ptr *int
*ptr = 10 //给空指针赋值
fmt.Println("给控制住赋值后") //这句不能打印,因为从宕机点退出
)
fmt.Println("判断退出点2") //这里演示使用了recover后可以执行到这里
//执行结果
手动宕机前
not runtime Error: 我手动宕机了
给空指针赋值前
runtime_error: runtime error: invalid memory address or nil pointer dereference
判断退出点2
5.3panic和recover的关系
- 有 panic 没 recover,程序宕机
- 有 panic 也有 recover,程序不会宕机,执行完对应的 defer 后,从宕机点退出当前函数后继续执行
- 虽然 panic/recover 能模拟其他语言的异常机制,但并不建议在编写普通函数时也经常性使用这种特性
- 在 panic 触发的 defer 函数内,可以继续调用 panic,进一步将错误外抛,直到程序整体崩溃
六、计算函数执行时间
- 在Go语言中我们可以使用 time 包中的 Since() 函数来获取函数的运行时间
package main
import (
"fmt"
"time"
)
func timeTest()
start := time.Now() //获取当前时间
for i := 0; i < 10000; i++
cost := time.Since(start) //等价于time.Now().Sub(start)
fmt.Println("执行一万次循环用了:", cost) //执行一万次循环用了: 2.822µs
func main() snice
timeTest()
七、Go语言哈希函数
-
Go提供了MD5、SHA-1等几个哈希函数
package main import ( "crypto/md5" "crypto/sha1" "fmt" ) func main() TestString := "abcd" hashMd5 := md5.New() //获取一个哈希对象 hashMd5.Write([]byte(TestString)) //写入需要转化的内容 ret := hashMd5.Sum([]byte("")) //进行转换 fmt.Printf("%x\\n", ret) //e2fc714c4727ee9395f324cd2e7f331f hashSha := sha1.New() //获取一个哈希对象 hashSha.Write([]byte(TestString)) //写入需要转化的内容 ret2 := hashSha.Sum([]byte("")) //进行转换 fmt.Printf("%x\\n", ret2) //81fe8bfe87576c3ecb22426f8e57847382917acf
八、test功能测试函数
8.1测试规则
-
要开始一个单元测试,需要准备一个 go 源码文件,在命名文件时文件名必须以
_test.go
结尾,单元测试源码文件可以由多个测试用例(可以理解为函数)组成,每个测试用例的名称需要以 Test 为前缀func TestXxx( t *testing.T ) //......
-
测试用例文件不会参与正常源码的编译,不会被包含到可执行文件中
-
测试用例的文件名必须以_test.go结尾
-
需要使用包testing包
-
测试函数的名称要以Test或Benchmark开头,后面可以跟任意字母组成的字符串,但第一个字母必须大写,例如 TestAbc(),一个测试用例文件中可以包含多个测试函数
-
单元测试则以
(t *testing.T)
作为参数,性能测试以(t *testing.B)
做为参数 -
测试用例文件使用
go test
命令来执行,源码中不需要 main() 函数作为入口,所有以_test.go
结尾的源码文件内以Test
开头的函数都会自动执行
8.2提供的功能
-
Go语言的 testing 包提供了三种测试方式,分别是单元(功能)测试、性能(压力)测试和覆盖率测试
-
单元测试需要在同一文件下创建两个文件
package main func Add(num1,num2 int)int return num1+num2 func main() Add(10,20) //-------------- package main import "testing" func TestAdd(t *testing.T) ret := Add(100,200) if ret > 200 t.Error("当前的值大于200") //执行命令 go test main_test.go main.go //输出值 --- FAIL: TestAdd (0.00s) main_test.go:9: 当前的值大于200 FAIL FAIL command-line-arguments 0.283s FAIL
-
以上是关于Go语言函数语法下篇的主要内容,如果未能解决你的问题,请参考以下文章