内容简介:很多熟悉Go的程序员们都会说到Go是一门很简单的语言,话虽如此,但实际上Go的简单是基于复杂底层的极简包装。Go在很多地方均做了“隐式”的转换,这也就导致了很多迷惑点,本文总结了Go开发中几个令人迷惑的地方,如有不当之处请指正。首先明确一点:nil是值而非类型。nil值只能赋值给slice、map、chan、interface和指针。
很多熟悉 Go 的 程序员 们都会说到Go是一门很简单的语言,话虽如此,但实际上Go的简单是基于复杂底层的极简包装。
Go在很多地方均做了“隐式”的转换,这也就导致了很多迷惑点,本文总结了Go开发中几个令人迷惑的地方,如有不当之处请指正。
nil
究竟是什么
首先明确一点:nil是值而非类型。nil值只能赋值给slice、map、chan、interface和指针。
在Go中,任何类型都会有一个初始值。数值类型的初始值为0,slice、map、chan、interface和指针类型的初始值为nil,对于nil值的变量,我们可以简化理解为初始状态变量。
但nil在实际使用过程中,仍有不少令人迷惑的地方。
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
时,不具备可写性。
// 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中, 当调用接口方法时,会自动对指针进行解引用
。下面的代码可以证明这一点:
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
defer机制
在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
时不需要加锁。
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网
猜你喜欢:- 当我们谈论 DevOps 时,我们在谈论什么?
- 当我们在谈论单元测试时我们在谈论什么
- 当我们在谈论单测时我们在谈论什么
- 当我们在谈论synchronized的时候,我们在谈论什么?
- 当我们谈论锁,我们谈什么
- 当我们谈论Monad的时候(二)
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
App研发录:架构设计、Crash分析和竞品技术分析
包建强 / 机械工业出版社 / 2015-10-21 / CNY 59.00
本书是作者多年App开发的经验总结,从App架构的角度,重点总结了Android应用开发中常见的实用技巧和疑难问题解决方法,为打造高质量App提供有价值的实践指导,迅速提升应用开发能力和解决疑难问题的能力。本书涉及的问题有:Android基础建设、网络底层框架设计、缓存、网络流量优化、制定编程规范、模块化拆分、Crash异常的捕获与分析、持续集成、代码混淆、App竞品技术分析、项目管理和团队建设等......一起来看看 《App研发录:架构设计、Crash分析和竞品技术分析》 这本书的介绍吧!