实习了一段时间深感java已经不太行了,转go才是出路
变量,结构与函数 变量 与java不同,go可以不显示声明变量数据类型。一般来说有:
整数型int,int8,int16,int32,int64,无符号整型uint,uint8,uint16,uint32,uint64
浮点型:float32,float64,相当于单精度浮点型float和双精度浮点型double
布尔:true和false,值得一提的是bool赋初始值的时候默认false
字符串:string直接就是一个数据类型了,还有一个rune,后续再去了解
很重要的一点是go语言的变量声明了就必须得用,而且最后不用加分号。下面是声明数据类型的例子,有:=可以代替var进行类型推断,可以同时推断多个类型(但我觉得还是显示声明类型比较好,否则一个函数返回来怎么判断类型)
1 2 3 4 5 6 7 8 9 10 11 12 13 var age_1 uint8 = 31 var age_2 = 32 age_3 := 33 fmt.Println(age_1, age_2, age_3) var age_4, age_5, age_6 int = 31 , 32 , 33 fmt.Println(age_4, age_5, age_6) var name_1, age_7 = "Tom" , 30 fmt.Println(name_1, age_7) name_2, is_boy, height := "Jay" , true , 180.66 fmt.Println(name_2, is_boy, height)
常量也是类似,可以进行类型推断,但是必须赋初始值,且一旦定义了就不能改变了,类似java中的private static final int = 1;这种
函数与判断结构 go的函数与主流编程语言类似,但是估计不分static和非静态,也是给出参数列表和返回值。但是这里可以返回多个变量,这一点应该会比java好
1 2 3 4 5 6 7 8 9 10 11 12 func main () { var numerator int = 11 var denominator int = 2 var result, remainder int = intDivision(numerator, denominator) fmt.Println(result, remainder) } func intDivision (num1, num2 int ) (int , int ) { var result int = num1 / num2 var remainder int = num1 % num2 return result, remainder }
注意这里的异常处理,与java中try-catch的思想不同,函数在返回的时候也会给一个error返回值,外部调用通过error是否为nil来判断函数执行是否出错。这是一个广泛使用的设计思想,后续可能需要遵守。
例如这里的除数为0的例子,当除数为0相当于要抛出异常,用errors包下的一个函数throw new RunTimeException(),再在主函数去判断这个error是否为空,为空说明没有抛出异常。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 package mainimport ( "errors" "fmt" ) func main () { var numerator int = 11 var denominator int = 0 result, remainder, err := intDivision(numerator, denominator) if err == nil { fmt.Println(result, remainder) } else { fmt.Println(err) } } func intDivision (num1, num2 int ) (int , int , error ) { var err error if num2 == 0 { err = errors.New("num1 is zero" ) return 0 , 0 , err } var result int = num1 / num2 var remainder int = num1 % num2 return result, remainder, err }
最后提一下go的判断结构,if后面的括号必须贴着同一行,else的哪一行必须写成”} else {“,否则编译器会报错,此外switch语句不需要写break了
数组,切片和哈希表 数组 go中的数组跟java的数组很像,但是go的数组可以操作指针。数组的大小在声明的时候就已经固定,如果想用跟ArrayList那样的动态数组,请使用slice切片。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 func main () { var intArr [10 ]int32 for i := 0 ; i < len (intArr); i++ { intArr[i] = int32 (i + 1 ) } fmt.Println(intArr[4 :7 ]) for i := 0 ; i < len (intArr); i++ { fmt.Println(&intArr[i]) } }
值得注意的是这里数组如果传入的是形式变量,需要传地址,跟c语言一样,否则就只会改变形参
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 func main () { var arr = [5 ]int {1 , 2 , 3 , 4 , 5 } withAddress(&arr) fmt.Println(arr) noAddress(arr) fmt.Println(arr) } func withAddress (a *[5]int ) { a[1 ] = 20 } func noAddress (a [5]int ) { a[3 ] = 20 fmt.Println(a) }
切片slice 基本上跟ArrayList的机制一样,长度和容量,如果到了设定阈值就会动态扩容。如果能够预估业务数据量,在构造slice的时候直接指定容量可以免去动态扩容的开销。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 package mainimport "fmt" func main () { sliceDynamic() } func sliceStatic () { var slice []int32 = []int32 {1 , 2 , 3 } fmt.Printf("length is %v, with capacity is %v\n" , len (slice), cap (slice)) slice = append (slice, 4 ) fmt.Printf("length is %v, with capacity is %v\n" , len (slice), cap (slice)) fmt.Println(slice) } func sliceDynamic () { var intSlice []int32 = make ([]int32 , 3 , 20 ) fmt.Printf("length is %v, with capacity is %v\n" , len (intSlice), cap (intSlice)) for i := 0 ; i < len (intSlice); i++ { fmt.Println(intSlice[i]) } } func slicePartition () { sli := []int {1 , 2 , 3 , 4 , 5 , 6 } fmt.Printf("len=%d cap=%d slice=%v\n" , len (sli), cap (sli), sli) fmt.Println("sli[1] ==" , sli[1 ]) fmt.Println("sli[:] ==" , sli[:]) fmt.Println("sli[1:] ==" , sli[1 :]) fmt.Println("sli[:4] ==" , sli[:4 ]) fmt.Println("sli[0:3] ==" , sli[0 :3 ]) fmt.Printf("len=%d cap=%d slice=%v\n" , len (sli[0 :3 ]), cap (sli[0 :3 ]), sli[0 :3 ]) }
循环 go语言中没有while循环(反正我也没经常用),对于slice和map需要特别注意,range关键字会在遍历这两个数据结构的时候进行处理。例如slice通过range关键字的时候会有index和value两个值,不需要index则直接”_”,跟python相似;同理map会遍历出key和value
这里就顺带把map提一下,map[key]value,这样的结构,查询一个元素直接括号里面找,注意找不到也会返回0这个默认值,所以以后用到的时候可能需要判断
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 func sliceWithClass () { var teachers []Teacher = make ([]Teacher, 0 ) teachers = append (teachers, Teacher{"yangyifan" , 12 }) fmt.Println(teachers) var teacherMap map [string ]Teacher = make (map [string ]Teacher) teacherMap["yangyifan" ] = Teacher{"yangyifan" , 12 } fmt.Println(teacherMap["yangyifan" ]) teacherMap["xuxuanyan" ] = Teacher{"xuxuanyan" , 12 } fmt.Println(teacherMap["xuxuanyan" ]) delete (teacherMap, "xuxuanyan" ) fmt.Println(teacherMap["xuxuanyan" ]) for _, teacher := range teachers { fmt.Println(teacher) } for _, teacher := range teacherMap { fmt.Println(teacher) } }
string与rune 在go中string的底层是一个字节数组,采用utf8编码,由于utf8是不固定长度的,一般来说汉字都会占3B。所以直接去用len一个string数组长度返回的是字节数量,有两种遍历方式,一种是直接遍历len,这样会返回每一个未解码的utf8字节,比如一个汉字“大”,占三位,用普通遍历就会返回这三个字节的初始值;但是如果用range关键字,他会帮我们做一些处理,把这三个字节解码拼成一块,就会返回真实的字符,但这样前面的index仍然不准确。
如果采用rune就是我们直觉上的遍历字符数组了,例子如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 package mainimport "fmt" import "strings" func main () { runeSlice() stringBuilder() } func stringByte () { var str1 = "大连理工大学" char := str1[0 ] fmt.Println(char) fmt.Printf("%v has %v character\n" , str1, len (str1)) for i := 0 ; i < len (str1); i++ { fmt.Printf("index: %d, char:%c\n" , i, str1[i]) } for index, v := range str1 { fmt.Printf("index: %d, char:%c\n" , index, v) } } func runeSlice () { var runeSlice = []rune ("大连理工大学" ) for i := 0 ; i < len (runeSlice); i++ { fmt.Printf("index: %d, char:%c\n" , i, runeSlice[i]) } }
此外一些string操作都在strings这个包下面,例如stringBuilder和其他的一些字符串操作,需要的时候导入。
结构体与接口 go应该是一个面向过程的语言,这里采用的还是结构体,但是类似的也有接口和类方法,在实现go中的接口时,不需要有java那种implements,编译器搜索所有有该方法签名的结构体自动绑定;在实现类方法时,需要在方法名前绑定结构体。例子如下所示,有一个engine接口,两个实现类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 package mainimport "fmt" type Engine interface { milesLeft() uint8 } type GasEngine struct { mpg uint8 gallons uint8 } func (ge GasEngine) milesLeft() uint8 { return ge.mpg * ge.gallons } type ElectricEngine struct { mpkwh uint8 kwh uint8 } func (ee ElectricEngine) milesLeft() uint8 { return ee.mpkwh * ee.kwh } func canMakeIt (engine Engine, remainMiles uint8 ) bool { if engine.milesLeft() < remainMiles { return false } else { return true } } func main () { var milesLeft uint8 = 60 var ge GasEngine = GasEngine{10 , 20 } fmt.Println(canMakeIt(ge, milesLeft)) var ee ElectricEngine = ElectricEngine{10 , 5 } fmt.Println(canMakeIt(ee, milesLeft)) }