golang 面試題(十三)interface內部結構和nil詳解

1.以下代碼打印出來什麼內容,說出爲什麼。

package main

import (
	"fmt"
)

type People interface {
	Show()
}

type Student struct{}

func (stu *Student) Show() {

}

func live() People {
	var stu *Student
	return stu
}

func main() {
	if live() == nil {
		fmt.Println("AAAAAAA")
	} else {
		fmt.Println("BBBBBBB")
	}
}

考點:interface內部結構
解答:
很經典的題! 這個考點是很多人忽略的interface內部結構。 go中的接口分爲兩種一種是空的接口類似這樣:

var in interface{}

另一種如題目:

type People interface {
    Show()
}

他們的底層結構如下:

type eface struct {      //空接口
    _type *_type         //類型信息
    data  unsafe.Pointer //指向數據的指針(go語言中特殊的指針類型unsafe.Pointer類似於c語言中的void*)
}
type iface struct {      //帶有方法的接口
    tab  *itab           //存儲type信息還有結構實現方法的集合
    data unsafe.Pointer  //指向數據的指針(go語言中特殊的指針類型unsafe.Pointer類似於c語言中的void*)
}
type _type struct {
    size       uintptr  //類型大小
    ptrdata    uintptr  //前綴持有所有指針的內存大小
    hash       uint32   //數據hash值
    tflag      tflag
    align      uint8    //對齊
    fieldalign uint8    //嵌入結構體時的對齊
    kind       uint8    //kind 有些枚舉值kind等於0是無效的
    alg        *typeAlg //函數指針數組,類型實現的所有方法
    gcdata    *byte
    str       nameOff
    ptrToThis typeOff
}
type itab struct {
    inter  *interfacetype  //接口類型
    _type  *_type          //結構類型
    link   *itab
    bad    int32
    inhash int32
    fun    [1]uintptr      //可變大小 方法集合
}

可以看出iface比eface 中間多了一層itab結構。 itab 存儲_type信息和[]fun方法集,從上面的結構我們就可得出,因爲data指向了nil 並不代表interface 是nil, 所以返回值並不爲空,這裏的fun(方法集)定義了接口的接收規則,在編譯的過程中需要驗證是否實現接口 結果:

BBBBBBB

@轉載,https://my.oschina.net/qiangmzsx/blog/1478739?p=1

 

2.Golang 關於 nil 的認識。

1. 什麼是 nil ?

大家都清楚,當你聲明瞭一個變量 但卻還並木優賦值時,golang中會自動給你的變量類型給一個對應的默認零值。這是每種類型對應的零值:

bool      -> false                              
numbers -> 0                                 
string    -> ""      

pointers -> nil
slices -> nil
maps -> nil
channels -> nil
functions -> nil
interfaces -> nil

再來一個strcut :

type Person struct {
  Age int
  Name string
  Friends []Person
}

var p Person // Person{0, "", nil}

變量p只聲明但沒有賦值,所以p的所有字段都有對應的零值。

1. Go的文檔中說到,nil是預定義的標識符,代表指針、通道、函數、接口、映射或切片的零值,並不是GO 的關鍵字之一

2. nil只能賦值給指針、channel、func、interface、map或slice類型的變量 (非基礎類型) 否則會引發 panic

 

2. 那麼 nil 有何用?

pointers

var p *int
p == nil    // true
*p          // panic: invalid memory address or nil pointer dereference

指針表示指向內存的地址,如果對爲nil的指針進行解引用的話就會導致panic。

interface 與 nil (重點講解)

nil只能賦值給指針、channel、func、interface、map或slice類型的變量。如果未遵循這個規則,則會引發panic。

在底層,interface作爲兩個成員來實現,一個類型和一個值

看這裏有官方明確說明:http://golang.org/doc/go_faq.html#nil_error

 

在底層,interface作爲兩個成員實現:一個類型和一個值。該值被稱爲接口的動態值, 它是一個任意的具體值,而該接口的類型則爲該值的類型。對於 int 值3, 一個接口值示意性地包含(int, 3)。

只有在內部值和類型都未設置時(nil, nil),一個接口的值才爲 nil。特別是,一個 nil 接口將總是擁有一個 nil 類型。若我們在一個接口值中存儲一個 *int 類型的指針,則內部類型將爲 int,無論該指針的值是什麼:(int, nil)。 因此,這樣的接口值會是非 nil 的,即使在該指針的內部爲 nil。

來看看interface倒底是什麼。會用到反射,關於反射的文章你可以自己下來學習,也可參考這篇文章

反射文章講解

package main

import (
    "fmt"
    "reflect"
)

func main() {
    var val interface{} = int64(58)
    fmt.Println(reflect.TypeOf(val)) // int64
    val = 50
    fmt.Println(reflect.TypeOf(val)) // int
}

 

在上面的例子中,第一條打印語句輸出的是:int64。這是因爲已經顯示的將類型爲int64的數據58賦值給了interface類型的變量val,所以val的底層結構應該是:(int64, 58)。

我們暫且用這種二元組的方式來描述,二元組的第一個成員爲type,第二個成員爲data。第二條打印語句輸出的是:int。這是因爲字面量的整數在golang中默認的類型是int,所以這個時候val的底層結構就變成了:(int, 50)。

請看下面的代碼:

package main

import (
    "fmt"
    "reflect"
)

type MyError struct {
    Name string
}

func (e *MyError) Error() string {
    return "a"
}

func main() {

    // nil只能賦值給指針、channel、func、interface、map或slice類型的變量 (非基礎類型) 否則會引發 panic
    var a *MyError                          // 這裏不是 interface 類型  => 而是 一個值爲 nil 的指針變量 a
    fmt.Printf("%+v\n", reflect.TypeOf(a))  // *main.MyError
    fmt.Printf("%#v\n", reflect.ValueOf(a)) // a => (*main.MyError)(nil)
    fmt.Printf("%p %#v\n", &a, a)           // 0xc42000c028 (*main.MyError)(nil)
    i := reflect.ValueOf(a)
    fmt.Println(i.IsNil()) // true

    if a == nil {
        fmt.Println("a == nil") //  a == nil
    } else {
        fmt.Println("a != nil")
    }

    fmt.Println("a Error():", a.Error()) //a 爲什麼 a 爲 nil 也能調用方法?(指針類型的值爲nil 也可以調用方法!但不能進行賦值操作!如下:)
    // a.Name = "1"           // panic: runtime error: invalid memory address or nil pointer dereference

    var b error = a

    // 爲什麼 a 爲 nil 給了 b 而 b != nil ??? => error 是 interface 類型,type = *a.MyError  data = nil
    fmt.Printf("%+v\n", reflect.TypeOf(b))  // type => *main.MyError
    fmt.Printf("%+v\n", reflect.ValueOf(b)) // data => a == nil
    if b == nil {
        fmt.Println("b == nil")
    } else {
        fmt.Println("b != nil")
    }
    fmt.Println("b Error():", b.Error()) // a

}

1. 首先 a 是個變量指針,(注意這裏 a 並不是interface)該變量指針只是聲明,但並沒有指向任何地址,所以 a == nil

2. 指針類型的值爲 nil ,也能調用方法,但不能進行賦值操作,否則就會引起 panic

3. var b error = a, 這時這裏的b 是一個interface, 即應該要滿足 上面提到的 interface 與 nil 的關係,即 只有當它的 type 和 data 都爲 nil 時才 = nil! (b 是有類型的 爲 *main.MyError) 所以最後會有 b != nil

 

3. 來看一個 error 的例子吧

有時候您想自定義一個返回錯誤的函數來做這個事,可能會寫出以下代碼:

package main  
   
import (  
    "fmt"  
)  
   
type data struct{}  
   
func (this *data) Error() string { return "" }  
   
func test() error {  
    var p *data = nil  
    return p  
}  
   
func main() {  
    var e error = test()  
    if e == nil {  
        fmt.Println("e is nil")  
    } else {  
        fmt.Println("e is not nil")   //  e is not nil
    }  
}  

分析:

error是一個接口類型,test方法中返回的指針p雖然數據是nil,但是由於它被返回成包裝的error類型,也即它是有類型的。所以它的底層結構應該是(*data, nil),很明顯它是非nil的。可以打印觀察下底層結構數據:

package main

import (
    "fmt"
    "unsafe"
)

type data struct{}

func (*data) Error() string { return "" }

func test() error {
    var p *data = nil // (*data, nil)
    return p
}

type aa struct {
    itab uintptr
    data uintptr
}

func main() {
    var e error = test()
    d := (*aa)(unsafe.Pointer(&e))
    dd := (*struct {
        itab uintptr
        data uintptr
    })(unsafe.Pointer(&e))

    fmt.Println(d)          // &{17636960 0}
    fmt.Printf("%+v\n", d)  // &{itab:17644608 data:0}
    fmt.Printf("%#v\n", d)  // &main.aa{itab:0x10d3e00, data:0x0}
    fmt.Printf("%#v\n", dd) // &struct { itab uintptr; data uintptr }{itab:0x10d3ca0, data:0x0}
}

正確的做法應該是:

package main  
   
import (  
    "fmt"  
)  
   
type data struct{}  
   
func (this *data) Error() string { return "" }  
   
func bad() bool {  
    return true  
}  
   
func test() error {  
    var p *data = nil  
    if bad() {  
        return p  
    }  
    return nil   // 直接拋 nil
}  
   
func main() {  
    var e error = test()  
    if e == nil {  
        fmt.Println("e is nil")  
    } else {  
        fmt.Println("e is not nil")  
    }  
}  

@轉載,https://www.jianshu.com/p/a6987f84a5cf

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章