Golang的一些高级语法(interface, defer, 方法接受者等)

记录一些go的知识点(比较零碎,主要是冷启动项目代码里涉及到的)

interface

参考理解 Go interface 的 5 个关键点

interface是一种类型

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 没有方法,所以可以认为所有的类型都实现了 interface{}。如果定义一个函数参数是 interface{} 类型,这个函数应该可以接受任何类型作为它的参数。

func doSomething(v interface{}){    
}

任何类型传参进来后,都会被转换成interface类型,并且得到他的值。

defer panic recover

Golang的异常捕获和回复机制:panic和recover,其中recover需要搭配defer使用。

参考Golang 高效实践之defer、panic、recover实践

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语言实战笔记(八)| Go 函数方法 Go 中的方法集合指针接收者和值接收者

在其他语言里,方法和函数是等同的。而在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()

参考【go】golang中置new()函数和make()函数的区别

内建函数 make 用来为 slicemapchan 类型分配内存和初始化一个对象(注意:只能用在这三种类型上),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

C 语言中的 select 关键字可以同时监听多个文件描述符的可读或者可写的状态,Go 语言中的 select 关键字也能够让 Goroutine 同时等待多个 Channel 的可读或者可写,在多个文件或者 Channel 发生状态改变之前,select 会一直阻塞当前线程或者 Goroutine。

Golang-Select-Channels

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中的代码)
  • 1
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值