几个初步的 Go 语言特点:
- 类型安全
- 简洁的面向对象
- 语言级并发
- 垃圾回收
一共 25 个关键字:
|
|
包引入
|
|
- 支持相对路径和绝对路径
- 绝对路径
import "xxx/yyy"
会引入$GPATH/src/xxx/yyy
- 点导入:
import . "fmt"
类似于 using namespace fmt; - 别名导入:
import f "fmt"
- 下划线导入:
import _ "xxx/xxx/xxx"
只调用包中init
函数
变量
|
|
常量
|
|
基础内建类型
|
|
- Go 的底层数据结构:https://research.swtch.com/godata
iota 枚举
|
|
- 大写字母开头的变量是可导出的,也就是其它包可以读取的,是公有变量;
- 小写字母开头的就是不可导出的,是私有变量;
- 大写字母开头的函数相当于
class
中的带public
关键词的公有函数; - 小写字母开头的函数相当于
class
中的带private
关键词的私有函数。
array
|
|
- 当把一个数组作为参数传入函数的时候,传入的其实是该数组的拷贝,而不是它的指针。如果要使用指针,需要
slice
类型。
slice
|
|
map
|
|
make, new 操作
make
用于内建类型(map
、slice
和channel
)的内存分配,返回一个有初始值 (非零) 的T
类型,而不是*T
。导致这三个类型有所不同的原因是指向数据结构的引用在使用前必须被初始化。make
返回初始化后的(非零)值。new
用于各种类型的内存分配,new(T)
返回了一个指针,指向新分配的类型T
的零值
零值
|
|
流程控制
if
- 条件不需要括号
- 条件允许声明变量,并用
;
做判断
|
|
godo
- 不要使用
for
|
|
- 支持
break
或continue
- 配合
range
可以用于读取slice
和map
的数据
|
|
switch
|
|
函数
|
|
channel
,slice
,map
这三种类型的实现机制类似指针,所以可以直接传递,而不用取地址后传递指针。(若函数需改变slice
的长度,则仍需要取地址传递指针
defer 延时语句
- 当函数执行到最后时,defer 语句会按照逆序执行,最后该函数返回。
- 类似于析构函数
|
|
- 注意逆序输出:
|
|
函数类型与传值
- 类似于函数指针
|
|
panic 与 recover
- panic 与 recover 机制替代了异常
- panic() 为内建函数,可以中断原有的控制流程,进入一个令人恐慌的流程中。当函数
F
调用panic
,函数F的执行被中断,但是F
中的延迟函数会正常执行,然后F返回到调用它的地方。在调用的地方,F
的行为就像调用了panic
。这一过程继续向上,直到发生panic
的goroutine
中所有调用的函数返回,此时程序退出。恐慌可以直接调用panic
产生。也可以由运行时错误产生,例如访问越界的数组。 - recover() 为内建的函数,可以让进入令人恐慌的流程中的
goroutine
恢复过来。recover
仅在延迟函数中有效。在正常的执行过程中,调用recover
会返回nil
,并且没有其它任何效果。如果当前的goroutine
陷入恐慌,调用recover
可以捕获到panic
的输入值,并且恢复正常的执行。
|
|
main 与 init 函数
- main (只能应用于
package main
)与 init 函数(能够应用于所有的package
)均为保留函数 - 定义时不能有任何的参数和返回值
- 程序会自动调用
init()
和main()
- 每个
package
中的init
函数都是可选的,但package main
就必须包含一个main
函数。 - 执行机制:
struct 类型
|
|
面向对象
方法
-
对象方法与函数声明语法几乎一致,只在 func 后增加一个 receiver
1 2 3 4
// function func funcName(input1 type1, input2 type2) (output1 type1, output2 type2) // method func (r ReceiverType) funcName(parameters) (results)
-
方法里可以访问接收者的字段
-
调用方法通过
.
访问,就像struct里面访问字段一样 -
方法可以定义在任何自定义类型、内置类型、struct 等上
|
|
再看一个例子:
|
|
- Go 中的面向对象没有私有共有关键字,通过名称的大小写来实现
接口
- 接口定义了一组方法,如果对象实现了某个接口的所有方法,则对象就实现了接口。
|
|
- 接口可以被任意对象实现,上面的 Men 接口被 Human, Student 和 Employee 实现
- 同理,一个对象可以实现多个 interface,上面的 Student 实现了 Men 和 YoungChap 两个接口
- 任意类型均实现了空接口
interface{}
接口值
- 定义了的接口变量,可以保存实现接口的任意类型对象。
- 上面的例子中定义的 Men 接口类型的比变量 m 可以存储 Human, Student 或 Employee 值
|
|
- 接口知识一组抽象方法的集合,必须由其他非接口类型实现,不能自我实现
- 由于任意类型都实现了空接口,因此空接口可以存储任何类型数值(类似于void *)
|
|
接口函数参数
- 接口的变量可以持有任意实现该接口的对象,因此我们可以通过定义接口参数使函数接受各种类型参数
|
|
- 实现了 error 接口的对象(即 Error() string 对象),fmt 输出时会调用
Error()
方法,无需定义 String() 方法
接口变量的存储类型
-
既然可以存储任意类型,通常有两种方法来反向检查变量的类型
-
Comma-ok 断言
- 判断类型时通过
value, ok = element.(T)
,value 为变量值,ok 是 bool 类型,element 是接口变量,T 是断言类型 - 如果存储了 T 类型,ok 为 true
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
list := make(List, 3) list[0] = 1 // an int list[1] = "Hello" // a string list[2] = Person{"Dennis", 70} for index, element := range list { if value, ok := element.(int); ok { fmt.Printf("list[%d] is an int and its value is %d\n", index, value) } else if value, ok := element.(string); ok { fmt.Printf("list[%d] is a string and its value is %s\n", index, value) } else if value, ok := element.(Person); ok { fmt.Printf("list[%d] is a Person and its value is %s\n", index, value) } else { fmt.Printf("list[%d] is of a different type\n", index) } }
- 判断类型时通过
-
Switch 测试
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
list := make(List, 3) list[0] = 1 //an int list[1] = "Hello" //a string list[2] = Person{"Dennis", 70} for index, element := range list{ switch value := element.(type) { case int: fmt.Printf("list[%d] is an int and its value is %d\n", index, value) case string: fmt.Printf("list[%d] is a string and its value is %s\n", index, value) case Person: fmt.Printf("list[%d] is a Person and its value is %s\n", index, value) default: fmt.Println("list[%d] is of a different type", index) } }
嵌入接口
- 接口也有匿名字段,也叫做嵌入字段
|
|
反射
-
运行时状态检查使用 reflect 包
-
使用 reflect 分三步:
- 将要反射的值(这些值都实现了空接口)转化为 reflect 对象(reflect.Type 或者 reflect.Value)
1 2
t := reflect.TypeOf(i) //得到类型的元数据,通过t我们能获取类型定义里面的所有元素 v := reflect.ValueOf(i) //得到实际的值,通过v我们获取存储在里面的值,还可以去改变值
- 将 reflect 对象转化成相应的值
1 2
tag := t.Elem().Field(0).Tag //获取定义在struct里面的标签 name := v.Elem().Field(0).String() //获取存储在第一个字段里面的值
- 获取反射值返回对应的类型和数值
1 2 3 4 5
var x float64 = 3.4 v := reflect.ValueOf(x) fmt.Println("type:", v.Type()) fmt.Println("kind is float64:", v.Kind() == reflect.Float64) fmt.Println("value:", v.Float())
-
如果需要反射的值可修改,则
|
|
并发
runtime 包中包含几个处理 goroutine 的函数:
- Goexit: 退出当前执行的 goroutine,但继续调用 defer
- Gosched: 让出当前 goroutine 执行权限,调度器安排其他等待的任务运行,在下次某个时候从该位置恢复执行
- NumCPU: CPU 核数
- GOMAXPROCS: 设置并行计算的 CPU 核最大值,并返回之前的值
goroutine
- Go 在语言层面支持并发,只需要极少栈内存,支持伸缩
- goroutine 的本质为协程,并且在语言层实现了 goroutine 之间的内存共享
- 通过 runtime 管理的一个线程管理器,使用
go
关键字
|
|
channels
- goroutine 运行在相同的地址空间,因此内存共享会出现一致性问题。
- channel 提供了类似于双向管道的机制,可以通过它发送或接受值,并且只能为 channel 类型
- 定义 channel 也需要定义发送到 channel 的值得类型,且必须使用 make 创建
|
|
- channel 接受和发送数据均阻塞,除非另一端已经准备好,因此不需要显式的 lock,即
value := <-ch
会阻塞到有数据为止 - 任何发送也会被阻塞,直到数据被取出
buffered channels
- 可以指定 channel 缓冲大小,即可以存储的元素
ch:= make(chan bool, 4)
创建了可以存储四个元素的 bool 类型 channel- 这时前四个元素无阻塞写入,后续元素写入需要等到有元素从 channel 中取出后才能写入,类似于池化技术
ch := make(chan type, value)
当 value 为 0 时, channel 无缓冲阻塞读写,当 value > 0 时候, channel 有缓冲、非阻塞,直到 value 个元素写满。
range 和 close
|
|
select
- 当存在多个 channel 时,可以使用 select 监听 channel 上的数据流动
- select 默认阻塞,只有当监听的 channel 中由发送或接受可以进行时才会运行
- 当多个 channel 准备好时,select 随机选择一个执行
- 类似于 switch
|
|