菜单 学习猿地 - LMONKEY

VIP

开通学习猿地VIP

尊享10项VIP特权 持续新增

知识通关挑战

打卡带练!告别无效练习

接私单赚外块

VIP优先接,累计金额超百万

学习猿地私房课免费学

大厂实战课仅对VIP开放

你的一对一导师

每月可免费咨询大牛30次

领取更多软件工程师实用特权

入驻
99
0

go 语法快速入门

原创
05/13 14:22
阅读数 496

第一个程序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就可以看到测试结果了

相关热门文章

发表评论

0/200
99 点赞
0 评论
收藏
为你推荐 换一批