不少熟悉Go的程序员们都会说到Go是一门很简单的语言,话虽如此,但实际上Go的简单是基于复杂底层的极简包装。程序员
Go在不少地方均作了“隐式”的转换,这也就致使了不少迷惑点,本文总结了Go开发中几个使人迷惑的地方,若有不当之处请指正。golang
nil
到底是什么首先明确一点:nil是值而非类型。nil值只能赋值给slice、map、chan、interface和指针。函数
在Go中,任何类型都会有一个初始值。数值类型的初始值为0,slice、map、chan、interface和指针类型的初始值为nil,对于nil值的变量,咱们能够简化理解为初始状态变量。ui
但nil在实际使用过程当中,仍有很多使人迷惑的地方。this
var err error e := &err if e != nil { fmt.Printf("&err is not nil:%p\n", e) } // 输出:&err is not nil:0xc0000301f0
err是一个接口类型的变量,其初始值为nil
,而后对err进行取址操做会发现能成功取到地址,这就是Go和C++最大的不一样之一。有C++基础的人在刚接触Go的时候,天然而然的会认为nil是个空指针类型值,上面的代码力证在Go中,nil只是一个表示初始状态的值。指针
对于slice
、map
、chan
、interface
,当值为nil
时,不具有可写性。code
// 1 var s []int fmt.Printf("%v\n", s[0]) // 输出panic // 2 var c chan int val := <-c fmt.Printf("%v\n", val) // 输出panic // 3 var m map[int]int m[1] = 123 // 输出panic
上面3段代码均会出现panic,对于slice
、map
、chan
类型的nil
值变量,能够理解为可读不可写,只有经过make
(new
)建立的对象实例知足可写性。对象
Go官方文档中表示:interface
自己是引用类型,即接口类型自己是指针类型。接口
type Animal interface { Barking() } type Cat struct { } func (c *Cat) Barking() { fmt.Printf("Meow~~\n") } type Dog struct{} func (d Dog) Barking() { fmt.Printf("W~W~W~\n") }
Cat和Dog类型都实现了Barking
接口,须要注意的是,Cat
是以指针接收器方式实现Barking
接口,Dog
是以值传递方式实现Barking
接口。在Go中,当调用接口方法时,会自动对指针进行解引用。下面的代码能够证实这一点:ci
d := &Dog{} d.Barking() c := Cat{} c.Barking() /* 输出: W~W~W~ Meow~~ */
接口的做为函数参数如何传递?
func AnimalBarking(a Animal) { a.Barking() }
根据上面这段代码,如何调用AnimalBarking
方法呢?
首先明确Animal
是引用类型(指针),因为接口会自动对传递的指针进行解引用,因此当接口类型做为函数参数传递时,有如下规则:
AnimalBarking
的参数必须为对象指针。AnimalBarking
的参数既能够是对象指针(指针会自动解引用),也能够是对象实例。下面的代码合法:
d1 := &Dog{} AnimalBarking(d1) d2 := Dog{} AnimalBarking(d2)
指向接口的指针是无心义的。
接口自己是类型,接口类型在runtime中大概是这样:
type eface struct { _type *_type // 8bytes data unsafe.Pointer // 8bytes }
其中_type是实现者(即实现了接口方法的struct),data是指向实现者的指针。那么,指向接口的指针是什么?
type Handler interface { Func() } type Server struct{} func (s *Server) Func() { fmt.Printf("*Server.Func\n") } func Func(handler *Handler) { handler.Func() }
上面的代码在Go1.13下没法经过编译:handler.Func undefined (type *Handler is pointer to interface, not interface)
。
这里要清楚,指向结构的指针和指向接口的指针是两回事,接口直接存放告终构的类型信息以及结构指针。在Go中,没法为实现了接口方法的struct生成指向接口的指针并调用接口方法。
关于接口的延申阅读:Go interface
在Go中提供defer
这样优雅的函数退出后“收尾”操做,但不少人会忽略defer
机制中的一点:defer
在声明时引用到的变量就已被实时编译。下面的代码:
var ErrNotFound error = errors.New("Not found") func TestDefer1() error { var err error defer fmt.Printf("TestDefer1 err: %v\n", err) // ... err = ErrNotFound return err } /* 输出: TestDefer1 err: <nil> */
当defer声明func时,状况不同了:
func TestDefer2() error { var err error defer func() { fmt.Printf("TestDefer2 err: %v\n", err) }() // ... err = ErrNotFound return err } /* 输出: TestDefer2 err: Not found */
因此:当defer
在声明语句时引用到的变量就已被实时编译。
chan
是否应该加锁先说答案:不须要。具体缘由能够从runtime/chan.go
中知道。chan
的原始struct
以下:
type hchan struct { qcount uint // total data in the queue dataqsiz uint // size of the circular queue buf unsafe.Pointer // points to an array of dataqsiz elements elemsize uint16 closed uint32 elemtype *_type // element type sendx uint // send index recvx uint // receive index recvq waitq // list of recv waiters sendq waitq // list of send waiters // lock protects all fields in hchan, as well as several // fields in sudogs blocked on this channel. // // Do not change another G's status while holding this lock // (in particular, do not ready a G), as this can deadlock // with stack shrinking. lock mutex }
从chan
的struct
定义上来看,有lock
字段,再来看看chan
的读写实现(简化代码):
func chanrecv(c *hchan, ep unsafe.Pointer, block bool) (selected, received bool) { // ... lock(&c.lock) // ... unlock(&c.lock) // ... } func chansend(c *hchan, ep unsafe.Pointer, block bool, callerpc uintptr) bool { // ... lock(&c.lock) // ... unlock(&c.lock) // ... }
从chan
的实现源代码看到,其读写内部均加了锁,实际上在关闭chan
时内部也是加锁了,因此实际应用中,多个coroutine同时读写chan
时不须要加锁。