go-結構體記憶體佈局
初始化方式
方式一:通過 var 宣告結構體
在 Go 語言中當一個變數被宣告的時候,系統會自動初始化它的預設值,比如 int 被初始化為 0,指標為 nil。
var 宣告同樣也會為結構體型別的資料分配記憶體,所以我們才能像上一段程式碼中那樣,在聲明瞭 var s T
之後就能直接給他的欄位進行賦值
方式二:使用 new
使用 new 函式給一個新的結構體變數分配記憶體,它返回指向已分配記憶體的指標:var t *T = new(T)。
type struct1 struct { i1 int f1 float32 str string } func main() { ms := new(struct1) ms.i1 = 10 ms.f1 = 15.5 ms.str= "Chris" fmt.Printf("The int is: %d\n", ms.i1) fmt.Printf("The float is: %f\n", ms.f1) fmt.Printf("The string is: %s\n", ms.str) fmt.Println(ms) }
與面嚮物件語言相同,使用點操作符可以給欄位賦值:structname.fieldname = value
。
同樣的,使用點操作符可以獲取結構體欄位的值:structname.fieldname
。
方式三:使用字面量
type Person struct { name string age int address string } func main() { var p1 Person p1 = Person{"lisi", 30, "shanghai"} //方式A p2 := Person{address:"beijing", age:25, name:"wangwu"} //方式B p3 := Person{address:"NewYork"} //方式C }
在方式A中,值必須以欄位在結構體定義時的順序給出。
方式B是在值前面加上了欄位名和冒號,這種方式下值的順序不必一致,並且某些欄位還可以被忽略掉,就像方式C那樣。
除了上面這三種方式外,還有一種初始化結構體實體更簡短和常用的方式,如下:
ms := &Person{"name", 20, "bj"}
ms2 := &Person{name:"zhangsan"}
&Person{a, b, c}
是一種簡寫,底層仍會呼叫 new()
,這裡值的順序必須按照欄位順序來寫,同樣它也可以使用在值前面加上欄位名和冒號的寫法(見上文的方式B,C)。
表示式 new(Type) 和 &Type{} 是等價的。
幾種初始化方式之間的區別
到目前為止,我們已經瞭解了三種初始化結構體的方式:
//第一種,在Go語言中,可以直接以 var 的方式宣告結構體即可完成例項化(var 宣告同樣也會為結構體型別的資料分配記憶體 )
var t T
t.a = 1
t.b = 2
//第二種,使用 new() 例項化
t := new(T)
//第三種,使用字面量初始化
t := T{a, b} //分配記憶體,並初始化
t := &T{} //簡寫方式,等效於 new(T),其實就是上面那種寫法加上一個取地址,這樣 t 就是指標型別了
使用 var t T
會給 t 分配記憶體,並零值化記憶體,但是這個時候的 t 的型別是 T;使用 new 關鍵字時 t := new(T)
,變數 t 則是一個指向 T 的指標。
從記憶體佈局上來看,我們就能看出這三種初始化方式的區別:
使用 var 宣告:直接為該變數分配記憶體。
使用 new 初始化:為型別分配記憶體後,返回該型別的指標。
使用結構體字面量初始化:相當於上面兩種方式的簡寫。
下面來看一個具體的例子
package main
import "fmt"
type Person struct {
name string
age int
}
func main() {
var p1 Person // 分配記憶體,並初始化為 0 值
p1.name = "zhangsan"
p1.age = 18
fmt.Printf("This is %s, %d years old\n", p1.name, p1.age)
p2 := new(Person) //分配記憶體,返回指標
p2.name = "lisi"
p2.age = 20
(*p2).age = 23 //這種寫法也是合法的
fmt.Printf("This is %s, %d years old\n", p2.name, p2.age)
p3 := Person{"wangwu", 25} //分配記憶體,並初始化為字面值
fmt.Printf("This is %s, %d years old\n", p3.name, p3.age)
}
輸出:
This is zhangsan, 18 years old
This is lisi, 23 years old
This is wangwu, 25 years old
上面例子的第二種情況,雖然 p2 是指標型別,但我們仍然可以像 p2.age = 23
這樣賦值,不需要像 C++ 中那樣使用 ->
操作符,Go 會自動進行轉換。
注意也可以先通過 *
操作符來獲取指標所指向的內容,再進行賦值:(*p2).age = 23
。
疑點解答
- 為什麼new 出來的指標變數,也可以像 var 宣告的變數一樣來使用結構體欄位?
雖然 p2 是指標型別,但我們仍然可以像 p2.age = 23
這樣賦值,不需要像 C++ 中那樣使用 ->
操作符,Go 會自動進行轉換。
結構體的記憶體佈局
Go 語言中,結構體和它所包含的資料在記憶體中是以連續塊的形式存在的。
即使結構體中巢狀有其他的結構體,這在效能上帶來了很大的優勢。
不像 Java 中的引用型別,一個物件和它裡面包含的物件可能會在不同的記憶體空間中,這點和 Go 語言中的指標很像。
下面的例子清晰地說明了這些情況:
type Rect1 struct {Min, Max Point }
type Rect2 struct {Min, Max *Point }