记录一些go的知识点(比较零碎,主要是冷启动项目代码里涉及到的)
interfaceinterface是一种类型
interface(接口)是一种类型,其中只能包含空方法,不能包含变量。
type I interface {
Get() int
}
如果一个类型(一般是struct)实现了一个 interface 中所有方法,我们说该类型实现了这个 interface。注意这个类型不需要显示地表明他要实现哪一个interface,只要他刚好实现了某一个interface中的所有方法,那么go编译器会自动识别出他实现了该interface。
interface变量存储的是实现者的值
我们知道,interface类型里只有空方法,没有变量,但是许多代码使用interface时,却能够从中取出数据,那么interface变量是怎么存储数据的呢?
例子如下,声明一个interface类型I,然后还有一个结构体S,可见结构体中有一个变量Age,S还实现了I的两个方法,就是说S实现了interface I。
//1
type I interface {
Get() int
Set(int)
}
//2
type S struct {
Age int
}
func(s S) Get()int {
return s.Age
}
func(s *S) Set(age int) {
s.Age = age
}
//3
func f(i I){
i.Set(10)
fmt.Println(i.Get())
}
func main() {
s := S{}
f(&s) //4
}
在以上代码中,interface的主要用处在于,函数f的输入参数为interface类型,函数体中也只是对interface变量i进行操作。而调用函数f时,可以传入任何实现了I的结构体。当一个实现了I的结构体作为参数传递给函数 f 时,go会将其转换成interface类型,并且得到了他的变量值,此时interface变量存储的便是该实现者的变量值以及该实现者实现的方法,但是该interface变量实际上是不知道他自己拥有什么变量的,所以他只能调用自己的方法来操作这些变量值。也就是说,interface完成的是一种泛用的能力。
如何判断interface变量存储的是哪种类型
一个interface可以被各种类型实现,那么如何确定一个interface变量,存储的是哪种类型的值呢?例子如下:
//方法1
if t, ok := i.(*S); ok {
fmt.Println("s implements I", t)
}
//方法2
switch t := i.(type) {
case *S:
fmt.Println("i store *S", t)
case *R:
fmt.Println("i store *R", t)
}
空interface
interface{}interface{}interface{}
func doSomething(v interface{}){
}
任何类型传参进来后,都会被转换成interface类型,并且得到他的值。
defer panic recoverGolang的异常捕获和回复机制:panic和recover,其中recover需要搭配defer使用。
defer
defer与匿名函数搭配,在包含它的函数运行到结尾时调用。当一个函数中存在多个defer函数时,defer将这些函数按出现次序入栈,到包含它们的函数运行到结尾时出栈调用。
defer的作用既然是延迟到函数结尾处运行,那为什么不直接把代码写到函数结尾处呢?这是因为defer函数内容一般是做一些清理工作,比如关闭文件、释放资源等,如果直接写到函数结尾,那么当函数中间出现异常时,会导致后面部分得不到运行,无法完成清理工作。而defer函数能够保证,即使函数中间出现异常要退出,也能保证在函数退出前运行defer函数中的内容,确保完成其中的清理工作。(当然要在出现异常前已经定义过defer函数,所以一般defer函数写在函数内部的最前面)
值得注意的一个点是,在定义defer函数时,就已经把当前参数的值传递给defer并且缓存起来了,即使这个变量后面改变了,也不会影响到defer的运行结果,如果defer中又改变了这个变量的值,而且函数返回值是这个变量,则函数返回值变成改变后的结果
panic
当函数执行panic函数时,会抛出异常并立刻停止执行当前函数(函数返回前会执行defer函数的内容),然后返回上一层调用处继续抛出异常,层层崩溃。
recover
recover只有在defer的函数里面才能发挥真正的作用。如果是正常的情况(没有发生panic),调用recover将会返回nil并且没有任何影响。如果当前的goroutine panic了,recover的调用将会捕获到panic的值,并且恢复正常执行。
可变参数可变参数是指一个函数的参数数量可变,在类型前加上省略号…即可定义可变参数:
func main() {
print("1","2","3")
}
func print (a ...interface{}){
for _,v:=range a{
fmt.Print(v)
}
fmt.Println()
}
我们可以往函数print中传递任意多个参数。
方法接受者在其他语言里,方法和函数是等同的。而在go语言里,函数和方法是有区别的
- 函数是指不属于任何结构体、类型的方法,也就是说,函数是没有接收者的;
- 方法是有接收者的(一般是一个结构体),只能使用这个接收者类型的变量调用该方法。
直观的看,函数是func关键字后面直接跟着函数名,而方法则是func关键字与方法名之间增加了一个参数,这个参数就是接收者,它又分为了值接收者和引用接收者两种:
type user struct {
name string
email string
}
// 值接收者
func (u user) notify() {
log.Printf("sending User Email to %s<%s>\n", u.name, u.email)
}
// 引用接收者
func (u *user) notifyPointer() {
log.Printf("sending User Email to %s<%s>\n", u.name, u.email)
}
- 值接收者声明的方法,调用时会使用这个值的一个副本去执行,所以对该值的任何操作,不会影响原来的类型变量。
- 指针接收者声明的方法,调用时会共享接收者所指向的值,即可以修改指向的值。
内建函数 make 用来为 slice,map 或 chan 类型分配内存和初始化一个对象(注意:只能用在这三种类型上),make 返回类型的本身而不是指针。
之所以只有这三种类型需要用make来创建,是因为他们都需要分配内存
type slice struct {
array unsafe.Pointer
len int
cap int
}
// A header for a Go map.
type hmap struct {
count int
flags uint8
B uint8
noverflow uint16
hash0 uint32
buckets unsafe.Pointer
oldbuckets unsafe.Pointer
nevacuate uintptr
extra *mapextra
}
type hchan struct {
qcount uint
dataqsiz uint
buf unsafe.Pointer
elemsize uint16
closed uint32
elemtype *_type
sendx uint
recvx uint
recvq waitq
sendq waitq
lock mutex
}
可见三种都有一个unsafe.Pointer类型的变量,是分配内存后返回的指针,因此需要使用make来为他们在堆上分配内存。
select channel参考select
selectselectselect
select与case搭配的形式与switch很相似,但是select的case里条件语句只能是channel的收发操作,当这个channel收发操作成功时,才能执行它对应的操作语句。
func fibonacci(c, quit chan int) {
x, y := 0, 1
for {
select {
case c <- x:
x, y = y, x+y
case <-quit:
fmt.Println("quit")
return
}
}
}
c <- x<-quit
- 任一case触发都会立刻执行case中的代码。
- 当两个case同时被触发时,就会随机选择一个case执行。
- 两个case都没有被触发时,会阻塞goroutine进行等待,直到其中一个case触发。(如果select中有default语句,则不会阻塞,而是直接执行default中的代码)