使用Go已经快有半年了,为了适应新公司的开发需要,半道入行,写了很多基于Go的应用(当然大部分是二次开发),也算是Golang的小用户了。一直没有太多的时间很系统地去看一些Golang的书籍,平时写代码也是凭借着对Java/C++的经验去猜测写Golang的代码,对常用的一些系统库也是在工作中学习。今天也是想总结回顾一下之前在使用过程中学习了一些东西,也借此巩固分享一下。
先说说总体对Golang相对于其它面向对象语言的初步体会:
- 简单清晰,变量定义更简洁,吸收了javascript的语法,支持“动态类型”,没有java那么繁冗
- 突破常规
为什么这么说呢,一般一说高级语言从语法上有比较大的相似性,比如C++, Java, 但Go感觉还是有比较大的反常规的语法:
- 变量声明,包括参数,先声明变量名,再是变量类型
- 方法签名支持返回参数命名
- 类与接口类型
变量
声明与定义
Golang支持两种变量的声明方式:
- 先声明,再定义: var var_name var_type
var name string
name = "John"
- 用:=将声明跟定义放在一起
name := "John"
内置变量类型
Golang支持许多其它高级语言的build-in类型,比如int, long, float32, float64, 集合类型:map, array, slice
变量类型 | Example |
---|---|
map | scores := make(map[string]int) |
array | courses := []string {"math", "english"} |
slice | slice1 := make([]int, length, capacity) or slice2 := array[i:len(array)] |
关于slice与array在Golang里面的区别,推荐这一篇文章:Arrays vs Slices
简单总结它们的特点:
- array是fix-size,slice是可变长度的
- slice表示的是array某个**连续区间[start:end)**的值的列表
- slice结构不存储实际的变量值,实际的值都是存储在array上,可以把slice想像成一个sliceheader的结构,如下:
type sliceHeader struct {
Length int,
Capacity int,
ZerothElement: *T
}
slice的capacity是其所指向array的长度,所以我们可以动态地增加slice的长度(slice2 := slice1[0:len(slice1)+1]
),但这个长度必须小于它的capacity, 否则程序会报错。
自定义变量类型
Golang用struct来表示其它高级语言里面的类, 定义struct的语法:
type <struct_name> struct {
Field1 field_type,
...
FieldN field_type
}
struct类型也可以实现field的继承,比如:
type TypeA struct {
Field1 string
}
type TypeB struct {
TypeA
Field2 string
}
t := TypeB { TypeA{"field1_value"}, "field2_value"}
fmt.Println(t.Field1) // "field1_value"
fmt.Print(t.Field2) // "field2_value"
这样TypeB就有两个field:从TypeA继承过来的Field1以及自身定义的Field2
需要指出的是,Capitalize字母开头的field表示它是export的,这样在其它的包中也可以引用这个field, 如t.Field1,比如如果你struct需要marshal/unmarshar成json,就需要capitalize field name. 更多参考这里
指针类型
Go里面保留C/C++中的指针,但对指针类型变量与普通类型的变量,在使用上没有差异,比如:
type Student struct {
Name string,
class int,
id string
}
var studentA = Student{"John", 1, "20180004"}
fmt.Println(studentA.Name)
var pointerA *Student
pointA = &studentA
fmt.Println(pointA.Name)
当function的receiver参数的类型为指针时,意味着在function里面改变这个指针所指向的对象的值,则在调用方也可以看到这个变化,这个跟C++没有差别。
指针的引用也使得参数在模块,函数间的传递更具效率(跟传值比较)
空接口interface{}
interface{}在Go里面表示空接口类型,它可以表示任意具体的类型,这个有点像是Java里面的Object. 任何类型都实现了空接口。
比如fmt.Println(a …interface{})
常用的 function
len(): 获取array, slice或者map的长度
append(slice, item): 创建一个新的slice, 并将item追加到新slice的末尾,返回新的slice, 注意,只有返回的slice包含了新的item, 参数里面的slice并未追加。append也可以merge两个slice:
slice1 := []int{1, 2, 3}
slice2 := append(slice1, 4) // {1, 2, 3, 4}
// merge slice
slice3 := []int{4, 5, 6}
slice4 := append(slice1, slice3...) // [1, 2, 3, 4, 5, 6]
遍历集合
遍历数组可以有下面两种方式
slice := []int{1,2,3}
// #1 iterate both index and value
for i, v := range slice {
fmt.Println(slice[i], "-", v)
}
// #2 iterate only index
for i := range slice {
fmt.Println(slice[i])
}
map的遍历同样有下面类似的方式:
myMap := make(map[string]string)
myMap["key1"] = "v1"
myMap["key2"] = "v2"
for k := range myMap {
fmt.Println(k)
}
for k, v := range myMap {
fmt.Println(k + ":" + v)
}
三点运算符
…在Golang里面可以有几个意义
- 在function的参数类型里面表示可变参数(variadic),而且只能是最后一个参数
// 在这里,names其实就是个slice, 等同于[]string
func Greetings(prefix string, names ...string) {
for _, name := range names {
fmt.Println(prefix, name)
}
}
// hello, John
// hello, Jackie
// hello, May
- 在调用包含可变参数里,…跟在slice后面表示展开(unpacking)这个slice
names := []string{"John", "Jackie", "May"}
Greetings("hello," names...)
- 定义数组时,表示数组的长度由初始化的值的列表长度决定
var names = [...]string{"John", "Jackie", "May"}
fmt.Printf("%T\n", names) // [3]string