go 語法快速入門

Joey_Coder發表於2019-12-04

第一個程式HelloWorld

按照國際慣例,我們來實現helloworld

package main

import "fmt"

func main() {
    fmt.Println("Hello,World")
}

package main標註程式位置

import "fmt"匯入fmt包,類似python,包名需要使用""圈起,如果引入的包太多,考慮使用import()

import (
    "fmt"
    "time"
    "os"
)

func main定義函式名為main的函式,接著在函式里面使用fmt.Println函式輸出“hello,world”

變數和常量

在golang中,定義一個變數可以使用var語法

var name type = value

如果是在函式體內,你還可以使用更加簡潔的命名方式

varName := value

變數的命名採用駱駝法,首字母小寫,往後每個新單詞的首字母大寫。如myString。如果想要定義的內容被其他外部包使用,則首字母也要大寫

package main

import "fmt"

func main() {

    const c = 45.2 //常量
    var myInt int = 23

    myString := "hello"

    var myBool1,myBool2 bool
    myBool1 = true
    myBool2 = false

    fmt.Println(myInt,c)
    fmt.Printf("my string: %s, type: %T",myString,myString)

    fmt.Printf("my bool: %v,type %T",myBool1,myBool2)

    myBool1 = false //賦值語句,myBool1之前已經使用:=宣告過了,所以直接使用=

    fmt.Println(myBool1)

}

if,switch和for

if語句可以如下定義:

if condition {

}

if-else:

if condition {

} else {

}

if-else if-else

if condition {

} else if condition2 {

} else {

}

在使用if-else時,必須保證else與if的}在同一行,否則else無效

package main

import "fmt"

func main() {
    var first int = 10
    var cond int

    if first <= 0 {

        fmt.Printf("first is less than or equal to 0\n")
    } else if first > 0 && first < 5 {

        fmt.Printf("first is between 0 and 5\n")
    } else {

        fmt.Printf("first is 5 or greater\n")
    }
    if cond = 5; cond > 10 { 
        //此處cond = 5為賦值語句,等同於cond=5 if cond > 10 {}
        fmt.Printf("cond is greater than 10\n")
    } else {

        fmt.Printf("cond is not greater than 10\n")
    }
}

switch語句定義如下

switch var {
    case condition1:
        .....
    case condition2:
        .....
    case condition3:
        fallthrough
    default:
        ......
}

上述的var,default,fallthrough,都是非必須選項,default代表switch最後預設執行,fallthrough代表在執行case後繼續執行後續的case


package main

import "fmt"

func main() {
    num := 10
    switch num {
    case 1:
        fmt.Println("num is 1")
    case 10:
        fmt.Println("num is 10")
    default:
        fmt.Println("default")

    }
}

試著修改上面的第二case為12,看看輸出結果是否不同?
當然我們在比較數值時,會把num放到switch裡面

package main

import "fmt"

func main() {
    var num int = 100

    switch {
    case num == 1:
        fmt.Println("Num is 1")
    case num > 1 && num < 60:
        fmt.Println("1 < Num < 60")
    case num >= 60 && num < 100:
        fmt.Println("60 <= num < 100")
    case num == 100:
        fmt.Println("Num is 100")
        fallthrough
    default:
        fmt.Println("default")

    }
}

上述程式碼加入了fallthrough,使得原本不表達的default也被執行了
記得if的賦值語句,你也可以把上面的程式碼這樣寫

package main

import "fmt"

func main() {

    switch num := 100; {
    case num == 1:
        fmt.Println("Num is 1")
    case num > 1 && num < 60:
        fmt.Println("1 < Num < 60")
    case num >= 60 && num < 100:
        fmt.Println("60 <= num < 100")
    case num == 100:
        fmt.Println("Num is 100")
        fallthrough
    default:
        fmt.Println("default")
    }
}

for 語句的語法如下:

for 初始化語句;條件語句;修飾語句{};
package main

import "fmt"

func main() {
    for i := 0; i < 10; i++ {
        fmt.Printf("i is %d\n", i)
    }
}

上面是一個簡單的for語句,有趣的是,for語句中的初始化,條件,修飾都不是必須選項,你可以這樣修改


package main

import "fmt"

func main() {
    i := 0
    for i < 10 {
        fmt.Printf("i is %d\n", i)
        i++
    }
}

只保留條件語句,甚至你可以連條件語句也去掉:

package main

import "fmt"

func main() {
    i := 0
    for {
        fmt.Printf("i is %d\n", i)
        if i == 10 {
            break
        }
        i++
    }
}

for{}的功能其實等同於while,go語言沒有while的語法
接下來我們來看另一個for語句

package main

import "fmt"

func main() {

    str := "Hello,World"

    for i := 0; i < len(str); i++ {
        fmt.Printf("Character on position %d is : %c\n", i, str[i])
    }
}

獲取字串或陣列的下標和對應值是比較常見的,go提供了for-range結構可以優雅的實現,go-reange語法:

for pos, char := range str {}

所以上面的程式碼可以for-range優雅的表述為

package main

import "fmt"

func main() {
    str := "Hello,World"

    for pos, cha := range str {
        fmt.Printf("Character on position %d is : %c\n", pos, cha)
    }
}

陣列,切片,map

我們可以這樣定義一個陣列:

var name [len]type //e.g. var myArr [10]int

類似於c語言,還可以有如下定義:

var name = new([len]int) //e.g. var myArr = new([10]int)

上述兩種定義方式產生的效果不同:
第一種定義方式產生的是[len]int e.g. [10]int
第二種定義方式產生的是*[len]int e.g. *[10]int
我們可以透過下面兩個程式來體會兩者的不同

package main

import "fmt"

func main() {
    var arr1 [5]int
    test(arr1)
    fmt.Println("The orignal array is:", arr1)

}

func test(a [5]int) {
    for i, v := range a {
        fmt.Printf("Old value is:%d ----> ", v)
        a[i] = i
        fmt.Printf("New value is:%d\n", a[i])
    }
    fmt.Println("The array is: ", a)
}

上面的程式可以看到,當傳遞到test函式的值為[len]int時,函式並未作用到原來的arr1,只是複製了一個新陣列,然後在test函式內對新陣列進行賦值

package main

import "fmt"

func main() {
    //var arr1 [5]int
    var arr1 = new([5]int)
    test(arr1)
    fmt.Println("The orignal array is:", arr1)

}

func test(a *[5]int) {
    for i, v := range a {
        fmt.Printf("Old value is:%d ----> ", v)
        a[i] = i
        fmt.Printf("New value is:%d\n", a[i])
    }
    fmt.Println("The array is: ", a)
}

但是如果傳入的值為new[5]int時,test函式就可以直接作用到main中的arr陣列
類似於一維陣列,多維陣列的定義如下

var name [len1][len2]int // e.g. var myArray [2][3]int

多維陣列的賦值,讀取操作:

package main

import (
    "fmt"
    "strconv" //使用strconv.Itoa將int轉為str
)

func main() {
    var myArr [3][4]string
    for pos, val := range myArr {
        fmt.Printf("pos is %d,type of val is %T\n", pos, val)
        for posC, valC := range val {
            valC = "row:" + strconv.Itoa(pos) + ",col:" + strconv.Itoa(posC)
            fmt.Printf("the value is %s\n", valC)
        }
    }
}

如果你想定義一個常量陣列,你可以這樣

var myArr = [5]int{1,2,3,4,5}
// or
var myArr = [...]int{1,2,3,4,5}

切片可以理解為長度可以變化的陣列,是陣列的一小段,我們可以如下定義:

var slicename []type = arr[start:end] 
//arr為同型別的陣列,start為陣列的起始標,end為陣列的終止下標

或者也可以這樣定義:

slicename := arr[start:end]

package main

import (
    "fmt"
)

func main() {
    var myArr [7]int
    var mySlice []int = myArr[2:4] //定義切片

    for i := 0; i < len(myArr); i++ {
        myArr[i] = i
    }

    for i := 0; i < len(mySlice); i++ {
        fmt.Printf("Slice at %d is %d\n", i, mySlice[i])
    }

    fmt.Printf("The length of myArr is %d\n", len(myArr))
    fmt.Printf("The length of mySlice is %d\n", len(mySlice))
    fmt.Printf("The cap of mySlice is %d\n", cap(mySlice)) //cap方法
    fmt.Println("------------------切片擴容--------------------->")

    //切片擴容
    mySlice = mySlice[:3]
    for i := 0; i < len(mySlice); i++ {
        fmt.Printf("mySlice at %d is %d\n", i, mySlice[i])
    }
    fmt.Printf("The length of mySlice is %d\n", len(mySlice))
    fmt.Printf("The cap of mySlice is %d\n", cap(mySlice))

    //切片,陣列共享資料
    mySlice[0] = 100

    for pos, val := range myArr {
        fmt.Printf("The myArr at %d is %d\n", pos, val)
    }

    for pos, val := range mySlice {
        fmt.Printf("The mySlice at %d is %d\n", pos, val)
    }
}
  • 切片myArr[2:4]獲取陣列myArr的的myArr[2],myArr[3]但不包含myArr[4],即[2,3]
  • go提供了len來獲取切片的長度為2
  • cap(mySlice)獲取切片的容量,容量即切片的最大長度,mySlice的cap為3,即從mySlice[2]開始算起,整個myArr的長度
  • 切片和陣列共享陣列,修改切片中的數值,陣列的相應值也會發變化

將切片傳遞給函式可參考陣列傳遞

package main

import (
    "fmt"
)

func sum(a []int) int {
    s := 0
    for _, val := range a {
        s += val
    }
    return s
}

func main() {
    var myArr = [5]int{0, 1, 2, 3, 4}
    fmt.Println("The sum of myArr is ", sum(myArr[:]))
}

上面切片的定義總是依賴陣列,但是如果想單獨定義一個切片的話可以這樣:

var mySlice = make([]type,len,cap) //cap為可選引數,e.g. var mySlice = make([]int,10,20)
//or
var mySlice = new([len]type)[start:end] //e.g. var mySlice = new([20]int)[0:10]

go中的字串是無法修改的,類似於陣列,我們可以字串進行切片,len等常規操作

package main

import (
    "fmt"
)

func main() {
    var myStr string = "Hello,World"

    //len操作
    for i := 0; i < len(myStr); i++ {
        fmt.Printf("Pos is %d,Val is %c\n", i, myStr[i])
    }

    //切片操作
    mySlice := myStr[:5]
    fmt.Printf("mySlice is %s", mySlice)

}

如果我們想對string型別進行更多的操作,如更換其中某個位置的值的話,可以考慮引入一個位元組陣列[]byte

package main

import "fmt"

func main() {
    var myStr string = "Hello,World"

    //引入一個byte陣列,修改str中的某個位置的值
    myByteArr := []byte(myStr)
    myByteArr[0] = 'P'
    myStr = string(myByteArr)
    fmt.Printf("myStr is %s\n", myStr)

    //copy操作
    myByteArr1 := make([]byte, len(myByteArr))
    copy(myByteArr1, myByteArr)
    fmt.Printf("myByteArr1 is %s\n", myByteArr1)

    //appand操作
    myByteArr = append(myByteArr, "..."...)
    fmt.Printf("myByteArr is %s\n", myByteArr)

}

在使用append時,當我們插入的是同型別的陣列時,需要在陣列後面加入...,如果插入的是當個值,如append([]int,int)時,插入的int不需要新增...


map類似於python中的dict結構,我們可以這樣定義map

var mapname map[keytype]valuetype // e.g. var myMap map[string]int
//or
var mapname = make(map[keytype]valuetype,cap) // cap可選,e.g. var myMap = make(map[string]int)

map相當於強化版的陣列,普通陣列的下標pos只能是int,而map的下標key可以是string,int,float等型別。
我們在介紹陣列時提到陣列可以使用new定義,但是map中一定別用new定義,因為這樣你只會得到一個空應用的指標

package main

import "fmt"

func main() {
    var myMap = map[string]int{"one": 1, "two": 2}
    var myMap1 = make(map[int]string) //宣告時不加入cap引數,map的容量動態增長

    myMap1[1] = "one" //下標不必從零開始
    myMap1[2] = "two"

    for key, val := range myMap {
        fmt.Printf("myMap key is %s,val is %d\n", key, val)
    }

    //使用if val,isPresent := myMap[keyname]判斷keyname是否存在,
    //如果存在,則ifPresentweitrue,反之則反
    if val,isPresent := myMap["two"];isPresent{
        fmt.Println(`The valueof "two" in myMap is `,val)
    } else {
        fmt.Println(`myMap does not contain "two"`)
    }

    //使用delete(mapname,keyname)刪除
    delete(myMap,"two")
    fmt.Println(myMap)
}

map的val可以為任意型別,例如函式,空介面型別等

package main

import "fmt"

func main() {
    var myMap = map[string]func(int) int{
        "func": func(a int) int { return a },
    }
    //定義一個string:func的map,func接收一個int,並將其返回
    fmt.Println(myMap["func"](23))
}

結構體

go中的結構體struct相當於其他語言中類的概念,我們可以這樣定義一個結構體struct

type namestruct struct {
    fieldname1 type
    fieldname2 type
    ...
}
var ms = new(namestruct) //使用new返回一個指向結構體的指標*structname

我們可以使用.符來獲取struct中的field, e.g. structname.fieldname
使用fmt.Printf("%v",structname)可以很好的輸出結構體的內容

package main

import "fmt"

type person struct {
    name string
    age  int
}

func main() {
    var joey = new(person)
    joey.name = "Joey"
    joey.age = 23
    //或者也可以這樣賦值
    // joey := person{name:"Joey",age:23}

    fmt.Printf("The name is: %s\n", joey.name)
    fmt.Printf("The age is: %d\n", joey.age)
    fmt.Printf("The struct is %v\n", joey)
    fmt.Println(joey)
}

結構體的繼承的使用內嵌結構體實現

type structname1 struct {
    fielName structname //
}

結構體的方法使用結構器實現:

func (recv receiverType) methodName(parameterList) (returnValueList){
    ...
}
//e.g. func (p *structname) add() int {
    return p.x + p.y
}
package main

import "fmt"

//定義一個包含x,y的結構體
type myStruct struct {
    x int
    y int
}

//定義myStruct1繼承myStruct,再新增一個field
type myStruct1 struct {
    myStruct
    z int
}

//定義myStruct的一個加法方法
func (p *myStruct) Add() int {
    return p.x + p.y
}

//定義myStruct1的一個加法方法
func (p *myStruct1) Add() int {
    return p.myStruct.Add() + p.z
}

func main() {
    s1 := myStruct{x: 1, y: 2}
    fmt.Printf("The add method in myStruct is %d\n", s1.Add())

    var s2 = myStruct1{s1, 3}
    fmt.Printf("The add method in myStruct1 is %d\n", s2.Add())
}

介面

介面是方法method的集合,我們可以這樣定義一個介面

type Namer interface {
    Method1(paramList) returnType
    Method2(parmList) returnType
}
//介面裡面只能包含方法 

介面的命名採用er結尾。一般來說,一個介面通常包括0到3個方法

package main

import (
    "fmt"
    "math"
)

//定義一個圓的結構體
type circle struct {
    radius float32
}

//定義一個矩形結構體
type rectangle struct {
    length, width float32
}

//圓的面積計算方法
func (c circle) Area() float32 {
    return math.Pi * c.radius * c.radius
}

//矩形的面積計算方法
func (r *rectangle) Area() float32 {
    return r.length * r.width
}

//定義一個介面,裡面包含Area方法
type shaper interface {
    Area() float32
}

func main() {

    c := circle{3}
    r := &rectangle{2, 3}
    //定義一個介面型別的陣列
    s := []shaper{c, r}
    for pos, _ := range s {
        fmt.Printf("shape details: %T\n", s[pos])
        fmt.Println("Area of this shape is: ", s[pos].Area())
    }
}

使用介面可以使得程式更加可讀和靈活,接下來我們用介面來實現整數陣列的排序

package main

import "fmt"

//定義一個整形陣列
type intArray []int

//定義整形陣列的Len方法,該方法返回陣列的長度
func (p intArray) Len() int {
    return len(p)
}

//定義陣列的比較方法
func (p intArray) Less(i, j int) bool {
    return p[i] < p[j]
}

//定義陣列的交換方法,該方法交換陣列裡面的兩個數
func (p intArray) Swap(i, j int) {
    p[i], p[j] = p[j], p[i]
}

//定義介面,該介面包含intArray的所有方法
type Sorter interface {
    Len() int
    Less(i, j int) bool
    Swap(i, j int)
}

//定義排序函式,該函式呼叫介面的方法實現
func Sort(data Sorter) {
    for pass := 1; pass < data.Len(); pass++ {
        for i := 0; i < data.Len()-pass; i++ {
            if data.Less(i+1, i) {
                data.Swap(i, i+1)
            }
        }
    }
}

func main() {
    data := []int{2, 4, 1, 8, 5, 3, 7, 6}
    a := intArray(data)
    var t Sorter = a
    Sort(t)
    fmt.Println(data)
}

空介面是不包含任何方法,但是可以被賦予任意型別的值,在go語言中應用廣泛。我們可以這樣定義一個空介面

type Any interface {}
package main

import "fmt"

type person struct {
    name string
    age  int
}

//定義空介面
type Any interface{}

func main() {

    var val Any
    //賦予int
    val = 5
    fmt.Printf("The type of val is %T,val of Any is %v\n", val, val)
    //賦予string
    val = "Apple"
    fmt.Printf("The type of val is %T,val of Any is %v\n", val, val)
    //賦予person
    val = person{"Joey", 23}
    fmt.Printf("The type of val is %T,val of Any is %v\n", val, val)
    //val.(type)獲取val型別
    switch t := val.(type) {
    case int:
        fmt.Printf("Type int %T\n", t)
    case string:
        fmt.Printf("Type string %T\n", t)
    case bool:
        fmt.Printf("Type boolean %T\n", t)
    case person:
        fmt.Printf("Type person %T\n", t)
    default:
        fmt.Printf("Unexpected type %T\n", t)
    }
}

上面的例子中,我們使用val.(type)方法獲取field的型別。除此之外我們還可以使用reflect反射達到同樣的效果,reflect反射是go的一個內建包,使用時需要先import

package main

import (
    "fmt"
    "reflect"
)

type rectangle struct {
    length int
    width  int
}

func (p rectangle) Area() int {
    return p.length * p.width
}

var secret interface{} = rectangle{2, 3}

func main() {
    //使用ValueOf方法獲取空介面的值
    val := reflect.ValueOf(secret)

    //使用TypeOf方法獲取空介面的型別
    typ := reflect.TypeOf(secret)

    //對獲取的值使用Kind方法進一步獲取其基本型別
    k := val.Kind()

    fmt.Println("the val is ", val)
    fmt.Println("the type is ", typ)
    fmt.Println("The kind is ", k)

    //NumField獲取val的field數量,Field(int)可獲取對應下標的val的值
    for i := 0; i < val.NumField(); i++ {
        fmt.Printf("Field %d: %v\n", i, val.Field(i))
    }
}

協程與通道

在go語言中,為了能夠並行的執行程式,我們可以使用go關鍵字,e.g.

go func(){}() //開啟一個協程執行匿名函式
//OR
go funcName(paramList) // 開啟一個協程執行函式

觀看以下程式

package main

import (
    "fmt"
    "time"
)

func printInt() {
    for i := 1; i < 6; i++ {
        fmt.Println("printInt ---> ", i)
    }
    time.Sleep(1e9)
}

func printStr() {
    s := []byte{'a', 'b', 'c', 'd', 'e'}
    for _, i := range s {
        fmt.Println("printStr ---> ", string(i))

    }
    time.Sleep(1e9)
}

func main() {
    t1 := time.Now()
    go printInt()
    go printStr()
    time.Sleep(1e9)
    fmt.Println(time.Since(t1))
}

上面的例子中,如果main函式中的printInt,printStr函式的呼叫去掉go,那麼整個程式的執行時間為3s。但是由於使用了協程,main,printInt,printStr同時執行,所以程式的執行時間為1s。(實際測試為1s稍多一點)
上面的例子其實存在一個缺陷,如果你嘗試把main函式中的time.Sleep去掉,那麼整個程式將無法按照預期執行。
因為main函式一旦退出,系統並不會等待printInt和printStr執行結束,整個程式馬上結束了。
為了防止這種情況的發生,我們在main中讓程式等待1s,但是這種方式看起來很笨拙,接下來,我們來介紹一種優雅的方式(通道)協調協程和主函式的執行
通道的宣告

var channelName chan type // chan為通道的關鍵字,type為通道可接收的型別,可以是int,string,struct等

當然我們還可以使用make語句定義通道

var channelName = make(chan type,int) //make裡的第一個引數是chan的型別,第二個引數是該通道的容量,e.g. var ch = make(chan string,10)
//如果make的第二個引數int省略的話,預設為1

現在我們知道定義通道方法了,但是要做到協調,我們還需要知道如何進行通道的通訊

var ch = make(chan int)
ch <- 3 //往通道內傳送3
s := <-ch //將通道內的3傳送給s

通道的通訊使用<-符表示,箭頭的方向代表通道的傳送和接收,需要注意的是,一個通道最大能接收的資料量為通道的容量,而且只有當通道內有資料時才能被讀取

package main

import (
    "fmt"
    "time"
)

func printInt(ch chan bool) {
    for i := 1; i < 6; i++ {
        fmt.Println("printInt ---> ", i)
    }
    time.Sleep(1e9)
    ch <- true
}

func printStr(ch chan bool) {
    s := []byte{'a', 'b', 'c', 'd', 'e'}
    for _, i := range s {
        fmt.Println("printStr ---> ", string(i))

    }
    time.Sleep(1e9)
    ch <- true
}

func main() {
    t1 := time.Now()
    chInt := make(chan bool)
    chStr := make(chan bool)
    go printInt(chInt)
    go printStr(chStr)
    <-chInt //如果printInt函式中chInt沒有數傳送,則會一直阻塞
    <-chStr
    fmt.Println(time.Since(t1))
}

使用通道協調main和協程的本質在於通道的阻塞機制。上面的程式main函式中的<-chInt 依賴於協程printInt的ch<-true,否則會一直阻塞。這就相當於main函式需要等待printInt執行結束。

當我們在寫程式中需要使用較多數量的通道時,select語句就變得很有用。select語句類似於switch語句

select {
    case u := <- ch1:
        ...
    case v := <- ch2:
        ...
        ...
    default:
        ...
}

我們試著將上一個程式使用select語句編寫

package main

import (
    "fmt"
    "time"
)

func printInt(ch, cs chan bool) {
    for i := 1; i < 6; i++ {
        fmt.Println("printInt ---> ", i)
    }
    time.Sleep(1e9)
    ch <- true
    cs <- true
}

func printStr(ch, cs chan bool) {
    s := []byte{'a', 'b', 'c', 'd', 'e'}
    for _, i := range s {
        fmt.Println("printStr ---> ", string(i))

    }
    time.Sleep(1e9)
    ch <- true
    cs <- true
}

func main() {
    t1 := time.Now()
    chInt := make(chan bool)
    chStr := make(chan bool)
    chStop := make(chan bool, 2)
    go printInt(chInt, chStop)
    go printStr(chStr, chStop)
    for {
        select {
        case <-chInt:
            fmt.Println("func printInt done!")
        case <-chStr:
            fmt.Println("func printStr done!")
        default:

            if len(chStop) == 2 {
                fmt.Println(time.Since(t1))
                return
            }
        }
    }
}

錯誤處理和測試

go語言沒有try/catch的錯誤處理機制,對於一些普通的錯誤,在設計程式時就將程式的正確執行與否作為返回值,如果返回的值為nil,則證明函式正常執行

通常我們可以使用if語句來判斷

if value,err := func(paramList);nil != nil {
    processError()
}
package main

import (
    "errors"
    "fmt"
)

func divison(dividend float64, divisor float64) (float64, error) {
    if divisor == 0 {
        return 0, errors.New("The divisor is zero...")
    }
    return dividend / divisor, nil
}

func main() {
    if val, err := divison(3, 0); err != nil {
        fmt.Printf("error:%v\n", err)
    } else {
        fmt.Println("The value is ", val)
    }
}

上面的例子中我們用errors.New方法定義了一個error,用以鑑別除數為零的錯誤情況
雖然出現了錯誤,但是上面的程式還是能夠完整的執行,只不過是返回的結果不同。如果我們在執行程式中,碰到了一些嚴重的錯誤,需要讓整個程式停止下來時,可以使用panic

package main

import (
    "fmt"
)

func divison(dividend float64, divisor float64) (float64, error) {
    if divisor == 0 {
        //return 0, errors.New("The divisor is zero...")
        panic("The divisor is zero...")
    }
    return dividend / divisor, nil
}

func main() {
    if val, err := divison(3, 0); err != nil {
        fmt.Printf("error:%v\n", err)
    } else {
        fmt.Println("The value is ", val)
    }
}

程式一旦觸發panic,會先列印panic方法內的字串,然後標明觸發的位置和資訊
如果需要從panic中恢復,使得程式繼續執行,可以使用recover語句。

package main

import (
    "fmt"
)

func divison(dividend float64, divisor float64) (float64, error) {
    if divisor == 0 {
        //return 0, errors.New("The divisor is zero...")
        panic("The divisor is zero...")
    }
    return dividend / divisor, nil
}

func test() {
    defer func() {
        if e := recover(); e != nil {
            fmt.Printf("Panic %s\r\n", e)
        }
    }()
    divison(3, 0)
    fmt.Println("Test function...")
}

func main() {
    fmt.Println("Start main...")
    test()
    fmt.Println("End main...")
}

recover語句只能和defer搭配使用,defer語句通常和匿名函式配合使用,可以說recover,defer,匿名函式是一套組合
類似於python,在go語言中,我們也可以使用go test工具對程式進行測試。使用go test需要遵循以下幾個規則:

  • 引入testing包
  • 測試的程式名需要以_test結尾
  • _test.go中的測試函式名需要以Test開頭
    接下來我們按照上述規則來寫一個例項,新建一個資料夾even,資料夾中新建一個檔案even.go
package even

//判斷一個數為奇偶

func Even(i int) bool {
    return i%2 == 0
}

func Odd(i int) bool {
    return i%2 != 0
}

接下來在even中再建立一個測試檔案even_test.go

package even

import "testing"

func TestEven(t *testing.T) {
    if Even(10) {
        t.Log("10 must be even!")
    }
    if Even(7) {
        t.Log("7 is not even")
        t.Fail()
    }
}

func TestOdd(t *testing.T) {
    if !Odd(11) {
        t.Log("11 must be odd!")
    }
    if Odd(10) {
        t.Log("10 is not odd!")
        t.Fail()
    }
}

even_test.go中的函式分別對Even和Odd函式進行測試,Log方法列印錯誤資訊,Fail標記函式失敗,然後繼續接下來的測試

在資料夾所在路徑下,終端執行go test就可以看到測試結果了

本作品採用《CC 協議》,轉載必須註明作者和本文連結

相關文章