goalng nil interface浅析
0.遇到一个问题
代码
func GetMap (i interface{})(map[string]interface{}){
if i == nil { //false ???
i = make(map[string]interface)
fmt.Println("xxxxx")
}
}
var testMap map[string]interface{}
getMap := GetMap(testMap)
getMap["add"] = "add" //panic
问题:比较困惑的是对于一个传进来的testMap是nil,但是在GetMap 里面if却是false。实践看来函数内部形参确实已经不是nil。那么interface{}判断nil的具体是什么过程?
找答案: 看了这个视频understanding nil 整理了一下
总结答案 :
- 空interface实际数据结构是包含type和value两个字段。
- 判断interface==nil的时候需要type和value都是null,才等于nil。
- testMap赋值给i之后,接口包含了赋值对象的类型细信息。的type字段不再是null了,因此代码if的结果就是false。
持续学习
通过遇到的问题引申学习主要解决两个问题
- nil到底是什么?
第一部分标题1-3整理了一些nil在go中的使用,对于不同类型对nil比较的实际操作并举了例子。 - interface的存储了什么?
第二部分标题4对interface实际内存中结构进行了探索。包括带方法的interface和空interface
1.nil是什么
个人理解: 学习之前类比为c的null空指针。学习之后知道有点以偏概全
官方定义:
// nil is a predeclared identifier representing the zero value for a
// pointer, channel, func, interface, map, or slice type.
var nil Type
// Type must be a pointer, channel, func, interface, map, or slice type
- nil 并不是关键字,只是预定义的标识符。
- nil代表数据类型的0值,并不仅仅是空指针。
- nil 可以和 pointer, channel, func, interface, map, or slice 进行比较
不同类型对应的0值如下:
类型 | 零值 |
---|---|
numbers | 0 |
string | "" |
bool | false |
pointer | nil |
slices | nil |
maps | nil |
channels | nil |
functions | nil |
interfaces | nil |
结构体的0值
对于结构体来说,每个字段都是nil值,整个结构体才是nil
type Person struct {
Age int
Name string
Friends []Person
}
var p Person // Person{0, "", nil}
2.nil类型
nil 没有类型 不是关键字 可被修改
var nil = errors.New("***")
不同类型对应nil实际判断标准
类型 | 实际存储 | nil判断 |
---|---|---|
pointers | 类c | 不指向任何内存, 内存安全 垃圾回收 |
slices | [ptr(*elem)\ | len()|cap()] |
maps,channels,functions | ptr | 没有初始化 |
interface | (type,data) | (nil,nil) |
- 特别的 -- interface 的 nil
interface 包含type 和 data
nil interface指的是 type是nil,且 value 是nil
var s fmt.Stringer // Stringer(nil,nil)
fmt.Println(s == nil) //true
//(nil, nil) == nil
var p *Person //nil of type *Person
var s fmt.Stringer = p //Stringer(*Person nil)
fmt.Println(s == nil) // false
//(*Person, nil) != nil
- nil interface 不等于 nil??
错误例子:这里和最开始的问题类似,函数返回error的时候,定义了一个空的error并返回,在函数碗面判断error!=nil的时候并不是true。所以在实际开发的时候,对于没有error的情况,要直接返回nil。
type error interface {
Error() string
}
func do() error { // 实际 error(*doError, nil)
var err *doError
return err //类型是 *doError 的nil
}
func main(){
err := do() //error (*doError , nil)
fmt.Println(err == nil) //false
}
正确方式:
不定义error类型,直接返回nil
func do() *doError { //nil of type *doError
return nil
}
func main(){
err := do() //nil of type *doError
fmt.Println(err == nil) //true
}
对于多层函数调用
里层函数定义了返回值虽然是nil,但是包含了type类型。所以避免这种写法
func do() *doError{ //nil of type *doError
return nil
}
func wrapDo error { //error(*doError , nil)
return do() //nil of type *doError
}
func main(){
err := wrapDo() //error(*doError,nil)
fmt.Println(err == nil)//false
}
综上:不要声明具体error类型,以为的nil interface实际上已经不是nil了。以上和文章问题的初衷是一致的。因为某种类型的nil赋值给nil interface之后 interface!=nil了。 赋值后的interface虽然没有值,但是已经有类型信息了
nil 不仅仅是 null
3.不同type nil的操作
pointers 取值panic
var p *int
p == nil //true
*p //panic
nil receiver
slices 可遍历 可append 不可取值panic
var s []slice
len(s) // 0
cap(s) // 0
for range s // zero times
s[i] // panic:index out of range
append // ok 自动扩容 1024以内2倍扩容 以上1.25倍
maps 可以遍历 可取值 可赋值
var m map[t]u
len(m) //0
for range m { // zero times
v,ok := m[i] // zero(u), false
m[i] = x
}
channels 读写阻塞 close会panic
//nil channel
var c chan t
<-c //blocks forever
c<-x // blocks forever
close(c) // panic: close of nil channel
//closed channel
v, ok <- c //zero(t),false
c <- x //panic: send on closed channel
close(c) //panic: close of nil channel
interfaces
type Summer interface{
func Sum() int
}
//pointer
var t *tree
var s Summer = t
fmt.Println(t == nil, s.Sum() ) //true, 0
//slice
type ints []int
func (i ints)Sum() int{
s:=0
for _, v := range i {
s += v
}
return s
}
var i ints
var s Summer = i
fmt.Println( i == nil, s.Sum()) //true , 0
// nil value can satisfy nil interface
4.interface
gopher 讲的 interface使用Tomas Senart - Embrace the Interface
Google Understanding Go Interfaces
- writing generic algorithm
- hiding implementation detail
- providing interception points
用于声明方法集合,可嵌套,不包含方法实现。不定义字段。
优势:隐藏一些具体的实现,泛型编程,不用声明实现哪些func运行时确定
4.1 interface数据结构
有方法的接口
iface
iface 是 runtime 中对 interface 进行表示的根类型 (src/runtime/runtime2.go)
type iface struct{
tab *itab //类型信息
data unsafe.Pointer //实际对象指针
}
type itab struct {
inter *interfacetype //接口类型
_type *type //实际对象类型
fun [1]uintptr //实际对象方法地址
...
}
iface 的 itab字段存储接口定义的方法相关信息,method 的具体实现存放在 itab.fun变量里。描述interface类型和其指向的数据类型的数据结构可以看下面gdb调试过程结构的打印.
data存储interface持有的具体的值信息,不可被修改。当赋值的a被修改并不会影响interface里面的data
itab
_type 这个类型是 runtime 对任意 Go 语言类型的内部表示。_type 类型描述了一个“类型”的每一个方面: 类型名字,特性(e.g. 大小,对齐方式...),某种程度上类型的行为(e.g. 比较,哈希...) 也包含在内了。。(src/runtime/type.go)
interfacetype 的指针,这只是一个包装了 _type 和额外的与 interface 相关的信息的字段。描述了interface本身的类型。(src/runtime/type.go)
func 数组持有组成该interface虚表的的函数的指针。
空接口
- 空接口可以被任何类型赋值,默认值是nil。
- 没有方法
- 存储结构也和有方法的interface不同。如下
eface
(src/runtime/runtime2.go)
type eface struct {
_type *_type //对象类型信息
data unsafe.Pointer //对象指针
}
type _type struct {
size uintptr // type size
ptrdata uintptr // size of memory prefix holding all pointers
hash uint32 // hash of type; avoids computation in hash tables
tflag tflag // extra type information flags
align uint8 // alignment of variable with this type
fieldalign uint8 // alignment of struct field with this type
kind uint8 // enumeration for C
alg *typeAlg // algorithm table
gcdata *byte // garbage collection data
str nameOff // string form
ptrToThis typeOff // type for pointer to this type, may be zero
}
- eface没有方法声明,存储*_type包含类型信息。可以看到一个空接口也存了两个字段,这里根本解释了最开始提到的问题,对于判断interface{}==nil的时候,需要保证接口两个字段都是null才是true。下面4.2debug的例子中调试29行,34行和35行对比ei的时候可以看到,虽然一个nil struct赋值给了interface{}后,空接口的_type,data字段都已经不是null了。
- interface被赋值之后也支持比较。(如果赋值对象支持比较)
func main(){
var t1,t2 interface{}
fmt.Println(t1==nil) //true
fmt.Println(t1==t2) //true
t1,t2=100,100
fmt.Println(t1==t2) // true
t1,t2=map[string]int{},map[string]int{}
fmt.Println(t1==t2)} //panic runtime error:comparing uncomparable type map[string]int
}
断言 .(asert)
interface{}可作为函数参数,实现泛型编程。
asert 用于判断变量类型 并且 可以判断变量是否实现了某个接口
type data int
func(d data)String()string{
return fmt.Sprintf("data:%d",d)
}
func main(){
var d data=15
var x interface{}=d
if n,ok:=x.(fmt.Stringer);ok{ //转换为更具体的接口类型
fmt.Println(n)
}
if d2,ok:=x.(data);ok{ //转换回原始类型
fmt.Println(d2)
}
e:=x.(error) //错误:main.data is not error
fmt.Println(e)
}
- 使用ok-idiom模式不用担心转换失败时候panic
- 利用switch可以在多种类型条件下进行匹配 ps type switch不支持fallthrought
func main(){
var x interface{}=func(x int)string{return fmt.Sprintf("d:%d",x)}
switchv:=x.(type){ //局部变量v是类型转换后的结果
case nil :
fmt.Println("nil")
case*int:
fmt.Println(*v)
case func(int)string:
fmt.Println( v(100) )
case fmt.Stringer:
fmt.Println(v)
default:
fmt.Println("unknown")
}
}
output:100
4.2 debug一下
code
1 package main
2 import(
3 "fmt"
4 )
5
6 type A struct {
7
8 }
9 type Aer interface {
10 AerGet()
11 AerSet()
12 }
13 func (a A)AerGet(){
14 fmt.Println("AerGet")
15 }
16
17 func (a *A)AerSet(){
18 fmt.Println("AerSet")
19 }
20 func main(){
21 var a A
22 var aer Aer
23 aer = &a
24 aer.AerGet()
25 aer.AerSet()
26
27 var ei interface{}
28 if ei == nil {
29 fmt.Println("ei is nil")
30 }else {
31 fmt.Println("ei not nil")
32 }
33 var aa A
34 ei = aa
35 if ei == nil {
36 fmt.Println("ei is nil")
37 }else {
38 fmt.Println("ei not nil")
39 }
40
41 }
debug
mac版本10.14.2 gdb版本8.2.1。mac系统更当前新版本之后gdb并不能使用了,尝试创建证书授权给gdb,但是并没有成功。教程gdb wiki
因此使用了lldb。主要打印了eface和iface内存,利用用nil struct对interface{}赋值之后interface{}的内存变化。
* thread #1, stop reason = breakpoint 1.1
frame #0: 0x000000000109376b test`main.main at interfacei.go:23
20 func main(){
21 var a A
22 var aer Aer
-> 23 aer = &a
24 aer.AerGet()
25 aer.AerSet()
26
Target 0: (test) stopped.
(lldb) p aer //interface iface struct
(main.Aer) aer = {
tab = 0x0000000000000000
data = 0x0000000000000000
}
(lldb) n
21 var a A
22 var aer Aer
23 aer = &a
-> 24 aer.AerGet()
25 aer.AerSet()
26
27 var ei interface{}
Target 0: (test) stopped.
(lldb) p aer
(main.Aer) aer = {
tab = 0x000000000112c580
data = 0x000000000115b860
}
(lldb) p &aer
(*main.Aer) = 0x000000000112c580
(lldb) p *aer.tab // itab struct
(runtime.itab) *tab = {
inter = 0x00000000010acc60
_type = 0x00000000010aba80
link = 0x0000000000000000
hash = 474031097
bad = false
inhash = true
unused = ([0] = 0, [1] = 0)
fun = ([0] = 0x0000000001093a10)
}
(lldb) p *aer.tab._type
(runtime._type) *_type = {
size = 0x0000000000000008
ptrdata = 0x0000000000000008
hash = 474031097
tflag = 1
align = 8
fieldalign = 8
kind = 54
alg = 0x000000000113bd50
gcdata = 0x00000000010d4ae4
str = 6450
ptrToThis = 0
}
(lldb) p *aer.tab.inter //inter struct
(runtime.interfacetype) *inter = {
typ = {
size = 0x0000000000000010
ptrdata = 0x0000000000000010
hash = 2400961726
tflag = 7
align = 8
fieldalign = 8
kind = 20
alg = 0x000000000113bd80
gcdata = 0x00000000010d4ae6
str = 10139
ptrToThis = 45184
}
pkgpath = {
bytes = 0x0000000001093e78
}
mhdr = (len 2, cap 2) {
[0] = (name = 5032, ityp = 61664)
[1] = (name = 5041, ityp = 61664)
}
}
(lldb) n
ei is nil
27 var ei interface{}
28 if ei == nil {
29 fmt.Println("ei is nil")
30 }else {
31 fmt.Println("ei not nil")
32 }
33 var aa A //aa == nil
-> 34 ei = aa
35 if ei == nil {
36 fmt.Println("ei is nil")
37 }else {
Target 0: (test) stopped.
(lldb) p ei //interface{} == ni
(interface {}) ei = { //eface struct
_type = 0x0000000000000000
data = 0x0000000000000000
}
(lldb) n
32 }
33 var aa A
34 ei = aa
-> 35 if ei == nil {
36 fmt.Println("ei is nil")
37 }else {
38 fmt.Println("ei not nil")
(lldb) p ei
(interface {}) ei = { //interface{} != nil
_type = 0x00000000010acbe0
data = 0x000000000115b860
}
(lldb) p *ei._type // _type struct
(runtime._type) *_type = {
size = 0x0000000000000000
ptrdata = 0x0000000000000000
hash = 875453117
tflag = 7
align = 1
fieldalign = 1
kind = 153
alg = 0x000000000113bd10
gcdata = 0x00000000010d4ae4
str = 6450
ptrToThis = 98304
}
汇编
汇编代码看不大懂,放在这里督促学习。
go build -gcflags '-l' -o interfacei interfacei.go
go tool objdump -s "main\.main" interfacei
TEXT main.main(SB) /Users/didi/go/src/test/interface/interfacei.go
interfacei.go:20 0x10936b0 65488b0c25a0080000 MOVQ GS:0x8a0, CX
interfacei.go:20 0x10936b9 483b6110 CMPQ 0x10(CX), SP
interfacei.go:20 0x10936bd 0f8635010000 JBE 0x10937f8
interfacei.go:20 0x10936c3 4883ec70 SUBQ $0x70, SP
interfacei.go:20 0x10936c7 48896c2468 MOVQ BP, 0x68(SP)
interfacei.go:20 0x10936cc 488d6c2468 LEAQ 0x68(SP), BP
interfacei.go:20 0x10936d1 488d05e8920100 LEAQ 0x192e8(IP), AX
interfacei.go:21 0x10936d8 48890424 MOVQ AX, 0(SP)
interfacei.go:21 0x10936dc e8afb7f7ff CALL runtime.newobject(SB)
interfacei.go:21 0x10936e1 488b442408 MOVQ 0x8(SP), AX
interfacei.go:21 0x10936e6 4889442430 MOVQ AX, 0x30(SP)
//24 aer.AerGet()
interfacei.go:24 0x10936eb 48890424 MOVQ AX, 0(SP)
interfacei.go:24 0x10936ef e87c010000 CALL main.(*A).AerGet(SB)
interfacei.go:24 0x10936f4 488b442430 MOVQ 0x30(SP), AX
//25 aer.AerSet()
interfacei.go:25 0x10936f9 48890424 MOVQ AX, 0(SP)
interfacei.go:25 0x10936fd e82effffff CALL main.(*A).AerSet(SB)
interfacei.go:29 0x1093702 48c744244800000000 MOVQ $0x0, 0x48(SP)
interfacei.go:29 0x109370b 48c744245000000000 MOVQ $0x0, 0x50(SP)
interfacei.go:29 0x1093714 488d0505030100 LEAQ 0x10305(IP), AX
interfacei.go:29 0x109371b 4889442448 MOVQ AX, 0x48(SP)
interfacei.go:29 0x1093720 488d0dd9240400 LEAQ 0x424d9(IP), CX
interfacei.go:29 0x1093727 48894c2450 MOVQ CX, 0x50(SP)
interfacei.go:29 0x109372c 488d4c2448 LEAQ 0x48(SP), CX
interfacei.go:29 0x1093731 48890c24 MOVQ CX, 0(SP)
interfacei.go:29 0x1093735 48c744240801000000 MOVQ $0x1, 0x8(SP)
interfacei.go:29 0x109373e 48c744241001000000 MOVQ $0x1, 0x10(SP)
interfacei.go:29 0x1093747 e8b48dffff CALL fmt.Println(SB)
interfacei.go:29 0x109374c 488d056d920100 LEAQ 0x1926d(IP), AX
// 35 if ei == nil {
interfacei.go:35 0x1093753 4885c0 TESTQ AX, AX
interfacei.go:35 0x1093756 7454 JE 0x10937ac
//38 fmt.Println("ei not nil")
interfacei.go:38 0x1093758 48c744245800000000 MOVQ $0x0, 0x58(SP)
interfacei.go:38 0x1093761 48c744246000000000 MOVQ $0x0, 0x60(SP)
interfacei.go:38 0x109376a 488d05af020100 LEAQ 0x102af(IP), AX
interfacei.go:38 0x1093771 4889442458 MOVQ AX, 0x58(SP)
interfacei.go:38 0x1093776 488d05a3240400 LEAQ 0x424a3(IP), AX
interfacei.go:38 0x109377d 4889442460 MOVQ AX, 0x60(SP)
interfacei.go:38 0x1093782 488d442458 LEAQ 0x58(SP), AX
interfacei.go:38 0x1093787 48890424 MOVQ AX, 0(SP)
interfacei.go:38 0x109378b 48c744240801000000 MOVQ $0x1, 0x8(SP)
interfacei.go:38 0x1093794 48c744241001000000 MOVQ $0x1, 0x10(SP)
interfacei.go:38 0x109379d e85e8dffff CALL fmt.Println(SB)
interfacei.go:41 0x10937a2 488b6c2468 MOVQ 0x68(SP), BP
interfacei.go:41 0x10937a7 4883c470 ADDQ $0x70, SP
interfacei.go:41 0x10937ab c3 RET
interfacei.go:36 0x10937ac 48c744243800000000 MOVQ $0x0, 0x38(SP)
interfacei.go:36 0x10937b5 48c744244000000000 MOVQ $0x0, 0x40(SP)
interfacei.go:36 0x10937be 488d055b020100 LEAQ 0x1025b(IP), AX
interfacei.go:36 0x10937c5 4889442438 MOVQ AX, 0x38(SP)
interfacei.go:36 0x10937ca 488d053f240400 LEAQ 0x4243f(IP), AX
interfacei.go:36 0x10937d1 4889442440 MOVQ AX, 0x40(SP)
interfacei.go:36 0x10937d6 488d442438 LEAQ 0x38(SP), AX
interfacei.go:36 0x10937db 48890424 MOVQ AX, 0(SP)
interfacei.go:36 0x10937df 48c744240801000000 MOVQ $0x1, 0x8(SP)
interfacei.go:36 0x10937e8 48c744241001000000 MOVQ $0x1, 0x10(SP)
interfacei.go:36 0x10937f1 e80a8dffff CALL fmt.Println(SB)
interfacei.go:35 0x10937f6 ebaa JMP 0x10937a2
interfacei.go:20 0x10937f8 e883aafbff CALL runtime.morestack_noctxt(SB)
interfacei.go:20 0x10937fd e9aefeffff JMP main.main(SB)
参考
Go Interface 源码剖析
Tomas Senart - Embrace the Interface
Google Understanding Go Interfaces
understanding nil
goalng nil interface浅析的更多相关文章
- Go语言第一深坑:interface 与 nil 的比较
interface简介 Go 语言以简单易上手而著称,它的语法非常简单,熟悉 C++,Java 的开发者只需要很短的时间就可以掌握 Go 语言的基本用法. interface 是 Go 语言里所提供的 ...
- 【荐】详解 golang 中的 interface 和 nil
golang 的 nil 在概念上和其它语言的 null.None.nil.NULL一样,都指代零值或空值.nil 是预先说明的标识符,也即通常意义上的关键字.在 golang 中,nil 只能赋值给 ...
- SQLite浅析
对于iOS工程师有一道常考的面试题,即iOS数据存储的方式 标答如下: Plist(NSArray\NSDictionary) Preference (偏好设置\NSUserDefaults) NSC ...
- 理解Go Interface
理解Go Interface 1 概述 Go语言中的接口很特别,而且提供了难以置信的一系列灵活性和抽象性.接口是一个自定义类型,它是一组方法的集合,要有方法为接口类型就被认为是该接口.从定义上来看,接 ...
- Go“一个包含nil指针的接口不是nil接口”踩坑
最近在项目中踩了一个深坑--"Golang中一个包含nil指针的接口不是nil接口",总结下分享出来,如果你不是很理解这句话,那推荐认真看下下面的示例代码,避免以后写代码时踩坑. ...
- 重学Golang系列(一): 深入理解 interface和reflect
前言 interface(即接口),是Go语言中一个重要的概念和知识点,而功能强大的reflect正是基于interface.本文即是对Go语言中的interface和reflect基础概念和用法的一 ...
- 如何理解golang中的nil
nil的奇怪行为 刚接触golang时,发现nil在不同的上下文,行为表现是不同的,并且和其他语言中的表现,也不大相同 实例1:输入true, true, false,不符合传递性 func main ...
- Golang | 既是接口又是类型,interface是什么神仙用法?
本文始发于个人公众号:TechFlow,原创不易,求个关注 今天是golang专题的第12篇文章,我们来继续聊聊interface的使用. 在上一篇文章当中我们介绍了面向对象的一些基本概念,以及gol ...
- UI数据库
一.数据库 SQL: SQL是Structured Query Language(结构化查询语言)的缩写.SQL是专为数据库而建立的操作命令集, 是一种功能齐全的数据库语言. 二.数据库管理系统 数据 ...
随机推荐
- 在Windows Server 2012中打开传统的磁盘管理界面
在“运行”中输入diskmgmt.msc即可
- 关于p标签
说p标签是不能嵌套div和p的,嵌套会被浏览器解析分离.但如果你使用了document.createElement创建div,再appendChild的话反而可以了.看来浏览器并不支持动态解析
- springCloud微服务入门
目录 前言 Eureka 注册中心server 新建 配置 服务提供者service 新建 配置 服务消费者controller 新建 配置 使用 Feign负载均衡 前言 springCloud是一 ...
- Oracle EBS AR 删除应收发票
DECLARE -- Non-scalar parameters require additional processing p_errors arp_trx_validate.mess ...
- 使用ModelForm表单验证
1.定义model.py model中定义的字段类型,只有在通过form进行验证的时候才有效,数据库中的字段类型与其并不完全一致,如数据库中并没有ipaddress类型.如果不通过form对字段进行验 ...
- 铁乐学Python_day12_装饰器
[函数的有用信息] 例: def login(user, pwd): ''' 功能:登录调用 参数:分别有user和pwd,作用分别是用户和密码: return: 返回值是登录成功与否(True,Fa ...
- 服务器安装LNMP及构建个人站点
服务器安装LNMP(centos6.6+nginx1.7.12+mysql5.6.24+php5.6.7) 本次安装 centos6.6+nginx1.7.12+mysql5.6.24+php5.6 ...
- C# 算法题系列(二) 各位相加、整数反转、回文数、罗马数字转整数
各位相加 给定一个非负整数 num,反复将各个位上的数字相加,直到结果为一位数. 示例: 输入: 输出: 解释: 各位相加的过程为: + = , + = . 由于 是一位数,所以返回 . 进阶:你可以 ...
- randint(1,100) s.add(n) 集合的去重复性
- PHP设计模式系列 - 数据访问对象模式
数据访问对象模式 数据访问对象模式描述了如何创建透明访问数据源的对象. 场景设计 设计一个BaseDao基类,实现数据库操作基本的一些query,insert,update方法 在实际使用的过程中,继 ...