第一个程序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就可以看到测试结果了
后记
文章主要给想要在短时间内了解go语法的开发人员提供帮助,对go的讨论较为浅显。后期会逐步撰写深入go语法和类型的内部实现文章,文章会在本文的各个类型章节中以跳转链接的形式给出。
另,如发现本文中有任何表述错误的地方,欢迎联系指出
有疑问加站长微信联系(非本文作者)