概述 - Overview 在我初學 C++ 時,static、inline、extern 可能是最令我迷惑的 C++ 說明符,原因是它們在不同的語境下會發揮不同的作用,而且某些說明符的含義已經和以前不同,這加劇了我在查詢資料時的困擾。所以今天決定好好總結一下。 首先要介紹 C++ 的兩個概念:存儲 ...
空結構體
func main() {
a := struct{}{}
fmt.Println(unsafe.Sizeof(a))
fmt.Printf("%p\n", &a)
}
列印
0
0x117f4e0
有經驗的開發人員都知道,所有的空結構體是指向一個 zerobase的地址,而且大小為0
一般用來作結合map作為set 或者 在channel中 傳遞信號。
type void struct{}
type void1 struct {
a void
}
type void2 struct {
a void
b int
}
func main() {
a0 := void{}
a1 := void1{}
a2 := void2{}
fmt.Println(unsafe.Sizeof(a0))
fmt.Println(unsafe.Sizeof(a1))
fmt.Println(unsafe.Sizeof(a2))
fmt.Printf("void: %p\n", &a0)
fmt.Printf("void1:%p\n", &a1)
fmt.Printf("void2: %p\n", &a2)
}
列印:
0
0
8
void: 0x11804e0 zerobase的地址,不是固定,每次運行都會有偏移量
void1:0x11804e0
void2: 0xc00010c008
能看到當一個空結構體中,包含了其他類型的變數,就不指向 zerobase。
runtime的malloc.go中
// base address for all 0-byte allocations
var zerobase uintptr
介面
go中的介面都是隱式的,增加的封裝的靈活性,也為閱讀源碼增加了一些難度。
正常使用: 情況1
type Person interface {
eat()
}
type Man struct {
name string
}
func (m Man) eat() {
fmt.Println(" man eat")
}
func main() {
var p Person = Man{}
p.eat()
}
情況2:
func main() {
// 變成指針 也是正常的
var p Person = &Man{}
p.eat()
}
情況3:
// 指針實現
func (m *Man) eat() {
fmt.Println(" man eat")
}
func main() {
var p Person = &Man{}
p.eat()
}
也正常
情況4:
// 指針
func (m *Man) eat() {
fmt.Println(" man eat")
}
func main() {
// 未加指針
var p Person = Man{}
p.eat()
}
報錯: cannot use Man{} (value of type Man) as Person value in variable declaration: Man does not implement Person (method eat has pointer receiver) (typecheck)
Man結構未實現,person的方法 eat這個。
網上很多人有講過這個,這裡換個角度歸納下:
只有一種情況下是失敗的:當實現介面方法時候,採用指針,用的使用 未採用指針。
原理:在使用 func (m Man) eat()
實現介面時候,編譯器會自動加上 帶指針的實現 func (m *Man) eat()
,反之,不會。所以才會導致情況4失敗。
介面的定義
這個數據結構,就是上面例子中變數 p 底層結構。
介面的內部實現:
type iface struct {
tab *itab
data unsafe.Pointer // 具體實現介面的對象, 就是例子中的 Man結構體的實例
}
type itab struct {
inter *interfacetype // 介面自身定義的類型信息,用於定位到具體interface類型
_type *_type
hash uint32 // _type.hash的拷貝,用於快速查詢和判斷目標類型和介面中類型是一致
_ [4]byte
fun [1]uintptr // 實現那些介面方法
}
整理下:介面值的底層表示
介面數據使用 runtime.iface 表示
iface記錄了數據的地址
iface 中記錄了介面類型信息和實現的方法 , 在介面斷言時候,用到這些信息。
空介面
空介面的底層實現:
type eface struct {
_type *_type // 只記錄數據類型,因為沒有方法,所以不用像iface一樣,記錄介面方法信息
data unsafe.Pointer // 指向數據本身
}
空介面常用來作為 任意類型的形參 使用。
例如:
type any = interface{}
func Println(a ...any) (n int, err error) {
return Fprintln(os.Stdout, a...)
}
為什麼 空介面可以作為任意類型使用?
基於它的底層實現定義:任意的類型,都可以表示為 數據類型 和 數據本身,例如
int 5
,類型int
,數據5
例如在我們使用 fmt.Println(5)
時候,會先將 5 進行組裝:
偽代碼:
a := eface{type : int ,data : 5}
fmt.Println(a)
整理:空介面的用途
空介面的最大用途是作為任意類型的函數入參
函數調用時,會新生成一個空介面,再傳參
nil
定義:
var nil Type // Type must be a pointer, channel, func, interface, map, or slice type
也就是說nil只能表示 指針、channel、func、interface、map 、slice 這六種類型的空值。
註意這裡沒有 struct
空結構體是zerobase的空值,不是nil
var a *int
var b map[string]string
var c struct{}
fmt.Println(a == nil) // true
fmt.Println(b == nil) // true
fmt.Println(c == nil) // mismatched types struct{} and untyped nil
fmt.Println(a == b) // mismatched types *int and map[string]string
都是nil,a和b的值也不同。
有了上面空介面的基礎就好理解了, 一個數據,包含了數據類型和數據本身的值。這裡a和b都是nil,只是值為nil,但是它們的類型並不一樣,所以不等
小結:
nil 是空,並不一定是“空指針”
nil是6種類型的 “零值〞
每種類型的nil是不同的,無法比較
再一個例子:
var a *int
var b interface{}
fmt.Println(a == nil) // true
fmt.Println(b == nil) // true
b = a
fmt.Println(b == nil) // false
回憶下上面 nil的定義,可以表示 interface
的空值,但是,通過上面的瞭解,interface
底層實際上是一個結構體eface
。
nil能作為eface的值,有嚴格的要求,要求type 和 data 都為空
當把 b = a
時候,這時候 type 已經有值,data還為空,但是這個時候 eface
已經是一個結構體了。nil 不能表示 結構體的值,而且這個結構體中成員還不為空。
總結:
- nil是多個類型的零值,或者空值
- 空結構體的指針和值都不是nil。 指針是zerobase
3.空介面零值是nil,-旦有了類型信息就不是nil