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语言函数语法下篇的主要内容,如果未能解决你的问题,请参考以下文章

Go语言函数语法下篇

go语言语法(基础语法篇)

001:go语言的一些语法基础

Go语言函数语法上篇

Go语言函数语法上篇

Go语言学习记录2——基础语法包变量函数