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")
}
}