日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 人文社科 > 生活经验 >内容正文

生活经验

Go 学习笔记(14)— 结构体定义、实例化、初始化、匿名结构体、结构体访问、结构体作为形参、结构体指针

發布時間:2023/11/27 生活经验 45 豆豆
生活随笔 收集整理的這篇文章主要介紹了 Go 学习笔记(14)— 结构体定义、实例化、初始化、匿名结构体、结构体访问、结构体作为形参、结构体指针 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

Go 語言中沒有 “類” 的概念,也不支持 “類” 的繼承等面向對象的概念。Go 語言不僅認為結構體能擁有方法,且每種自定義類型也可以擁有自己的方法。

1. 結構體的定義

結構體是由一系列具有相同或者不同類型數據組成的集合。結構體的定義需要使用 typestruct 關鍵字。含義如下:

  • type 關鍵字定義了結構體的名稱;

  • struct 關鍵字定義新的數據類型,結構體中可以有一個或者多個成員;

結構體成員是由一系列的成員變量構成,這些成員變量也被稱為“字段”。字段有以下特性:

  • 字段擁有自己的類型和值。
  • 字段名必須唯一。
  • 字段的類型也可以是結構體,甚至是字段所在結構體的類型。

type 是自定義類型的關鍵字,不僅支持 struct 類型的創建,還支持任意其它子定義類型(整型、字符串、布爾等)的創建。

結構體的標準格式如下:

type structName struct {member type;member type;...member type;
}

2. 結構體實例化

結構體的定義只是一種內存布局的描述,只有當結構體實例化時,才會真正地分配內存,因此必須在定義結構體并實例化后才能使用結構體的字段。

package mainimport ("fmt"
)type Student struct {Id intName stringAge intMobile intMajor string
}func main() {// 創建Student指針類型變量s1 := new(Student)// 創建Student指針類型變量s2 := &Student{}// 創建Student類型變量s3 := Student{}// 創建Student指針類型變量,并根據字段名附初始值s4 := &Student{Id:     20210901,Name:   "wohu",Age:    20,Mobile: 123456,Major:  "move brick",}// 創建Student類型變量,并根據字段在結構體中的順序附初始值s5 := Student{20170901,"wohu",20,123456,"move brick",}fmt.Println("s1:", s1)fmt.Println("s2:", s2)fmt.Println("s3:", s3)fmt.Println("s4:", *s4)fmt.Println("s5:", s5)
}

輸出結果:

s1: &{0  0 0 }
s2: &{0  0 0 }
s3: {0  0 0 }
s4: {20210901 wohu 20 123456 move brick}
s5: {20170901 wohu 20 123456 move brick}

通過在結構體后邊加大括號 {} 的方式創建變量與使用 new 關鍵字創建對象不同之處在于 new 創建出來的是指針類型,而結構體名加大括號 {} 創建出來的不是指針類型。

如果在使用結構體名加大括號 {} 創建變量時,在結構體名前邊加上地址操作符 & ,那么這樣創建出來的也是指針類型變量,效果就與 new 關鍵字一樣了,如上邊示例代碼中,s1 與 s2 都是指針類型變量。

在程序中,可以通過結構體類型變量名加上點號 . ,再加上字段名的方式訪問結構體中的字段。如訪問學生姓名的操作是:

s1.Name = "hi hzwy23"
s3.Name = "hello hzwy23"
fmt.Println(s3.Name)
fmt.Println(s1.Name)

不管變量是指針類型,還是非指針類型,操作結構體字段,都是一樣的方法,如上邊示例中 s1 是指針類型,s3 是非指針類型,在訪問字段名為 Name 的字段時,都是采用點號 . 加上字段名的方式訪問。這一點與 C/C++ 語言不同。

2.1 普通實例化

結構體本身是一種類型,可以像整型、字符串等類型一樣,以 var 的方式聲明結構體即可完成實例化。這種用法是為了更明確地表示一個變量被設置為零值。

var phone Phone
// 其中,Phone 為結構體類型,phone 為結構體的實例

用結構體表示的點結構(Point)的實例化過程請參見下面的代碼:

type Point struct {X intY int
}var p Point
p.X = 10
p.Y = 20

在例子中,使用.來訪問結構體的成員變量,如p.Xp.Y等,結構體成員變量的賦值方法與普通變量一致。

與數組類型相同,結構體類型屬于值類型,因此結構體類型的零值不是 nil,上述 Point 的零值就是 Point{}

2.2 new 實例化

Go 語言中,還可以使用 new 關鍵字對類型(包括結構體、整型、浮點數、字符串等)進行實例化,結構體在實例化后會形成指針類型的結構體。

使用 new 的格式如下:

ins := new(T)

其中:

  • T 為類型,可以是結構體、整型、字符串等。
  • insT 類型被實例化后保存到 ins 變量中, ins 的類型為 *T ,屬于指針。

Go 語言讓我們可以像訪問普通結構體一樣使用.來訪問結構體指針的成員。

type Player struct{Name stringHealthPoint intMagicPoint int
}tank := new(Player)
tank.Name = "Canon"
tank.HealthPoint = 300

經過 new 實例化的結構體實例在成員賦值上與普通實例化的寫法一致。

在 C/C++ 語言中,使用 new 實例化類型后,訪問其成員變量時必須使用 -> 操作符。

Go 語言中,訪問結構體指針的成員交量時可以繼續使用 . 。 這是因為 Go 語言為了方便開發者訪問結構體指針的成員交量,使用了語法糖技術,將 ins.Name 形式轉換為 (*ins).Name

2.3 取地址實例化

Go 語言中,對結構體進行 &取地址操作時,視為對該類型進行一次 new 的實例化操作,取地址格式如下:

ins := &T{}

其中:

  • T : 表示結構體類型。
  • ins :為結構體的實例,類型為 *T ,是指針類型。

下面使用結構體定義一個命令行指令(Command),指令中包含名稱、變量關聯和注釋等,對 Command 進行指針地址的實例化,并完成賦值過程,代碼如下:

type Command struct {Name    string    // 指令名稱Var     *int      // 指令綁定的變量Comment string    // 指令的注釋
}var version int = 1cmd := &Command{}
cmd.Name = "version"
cmd.Var = &version
cmd.Comment = "show version"

取地址實例化是最廣泛的一種結構體實例化方式,可以使用函數封裝上面的初始化過程,代碼如下:

func newCommand(name string, varref *int, comment string) *Command {return &Command{Name:    name,Var:     varref,Comment: comment,}
}cmd = newCommand("version",&version,"show version",
)

3. 結構體初始化

結構體在實例化時可以直接對成員變量進行初始化,初始化有兩種形式分別是以字段“鍵值對”形式和多個值的列表形式,鍵值對形式的初始化適合選擇性填充字段較多的結構體,多個值的列表形式適合填充字段較少的結構體

3.1 鍵值對初始化

結構體可以使用“鍵值對”(Key value pair)初始化字段,每個“鍵”(Key)對應結構體中的一個字段,鍵的“值”(Value)對應字段需要初始化的值。

鍵值對的填充是可選的,不需要初始化的字段可以不填入初始化列表中。

結構體實例化后字段的默認值是字段類型的默認值,例如 ,數值為 0 、字符串為 “”(空字符串)、布爾為 false 、指針為 nil 等。

鍵值對初始化的格式如下:

varName := structName{key1: value1, key2: value2..., keyn: valuen}
// 鍵值之間以:分隔,鍵值對之間以,分隔。

下面示例中描述了家里的人物關聯,正如兒歌里唱的:“爸爸的爸爸是爺爺”,人物之間可以使用多級的 child 來描述和建立關聯,使用鍵值對形式填充結構體的代碼如下:

type People struct {name  stringchild *People
}relation := &People{name: "爺爺",child: &People{name: "爸爸",child: &People{name: "我",},},
}

注意:結構體成員中只能包含結構體的指針類型,包含非結構體指針類型會引起編譯錯誤。

3.2 多值列表初始化

Go 語言可以在“鍵值對”初始化的基礎上忽略“鍵”,也就是說,可以使用多個值的列表初始化結構體的字段。多個值使用逗號分隔初始化結構體,例如:

varName := structName{value1, value2...valuen}

使用這種格式初始化時,需要注意:

  • 必須初始化結構體的所有字段
  • 每一個初始值的填充順序必須與字段在結構體中的聲明順序一致
  • 鍵值對與值列表的初始化形式不能混用
type Address struct {Province    stringCity        stringZipCode     intPhoneNumber string
}addr := Address{"陜西","西安",710000,"12345",
}fmt.Println(addr)	// {陜西 西安 710000 12345}

3.3 初始化注意事項

初始化復合對象,必須使用類型標簽,且左大括號必須在類型尾部。

package mainfunc main() {var a struct { x int } = { 100 }  // syntax error,  syntax error: unexpected {, expecting expressionvar b []int = { 1, 2, 3 }  // syntax errorc := struct {x int; y string}  // syntax error: unexpected semicolon or newline{}var a = struct{ x int }{100}var b = []int{1, 2, 3}}

初始化值以 “,” 分隔。可以分多行,但最后一行必須以 “,” 或 “}” 結尾。

a := []int{1,2 // Error: need trailing comma before newline in composite literal
}a := []int{1,2, // ok
}b := []int{1,2 } // ok

3.4 空結構體

type Empty struct{} // Empty是一個不包含任何字段的空結構體類型

空結構體類型有什么用呢?

var s Empty
println(unsafe.Sizeof(s)) // 0

我們看到,輸出的空結構體類型變量的大小為 0,也就是說,空結構體類型變量的內存占用為 0。基于空結構體類型內存零開銷這樣的特性,我們在日常 Go 開發中會經常使用空結構體類型元素,作為一種“事件”信息進行 Goroutine 之間的通信,就像下面示例代碼這樣:

var c = make(chan Empty) // 聲明一個元素類型為Empty的channel
c<-Empty{}               // 向channel寫入一個“事件”

這種以空結構體為元素類建立的 channel,是目前能實現的、內存占用最小的 Goroutine 間通信方式。

4. 匿名結構體

匿名結構體沒有類型名稱,無須通過 type 關鍵字定義就可以直接使用。

4.1 匿名結構體定義和初始化

匿名結構體的初始化寫法由結構體定義和鍵值對初始化兩部分組成,結構體定義時沒有結構體類型名,只有字段和類型定義,鍵值對初始化部分由可選的多個鍵值對組成,如下格式所示:

ins := struct {// 匿名結構體字段定義字段1 字段類型1字段2 字段類型2}{// 字段值初始化初始化字段1: 字段1的值,初始化字段2: 字段2的值,}

下面是對各個部分的說明:

  • 字段1、字段2……:結構體定義的字段名。
  • 初始化字段1、初始化字段2……:結構體初始化時的字段名,可選擇性地對字段初始化。
  • 字段類型1、字段類型2……:結構體定義字段的類型。
  • 字段1的值、字段2的值……:結構體初始化字段的初始值。

鍵值對初始化部分是可選的,不初始化成員時,匿名結構體的格式變為:

ins := struct {字段1 字段類型1字段2 字段類型2}

4.2 匿名結構體示例

在本示例中,使用匿名結構體的方式定義和初始化一個消息結構,這個消息結構具有消息標示部分(ID)和數據部分(data),打印消息內容的 printMsg() 函數在接收匿名結構體時需要在參數上重新定義匿名結構體,代碼如下:

package mainimport ("fmt"
)// 定義 printMsgType() 函數,參數為 msg,類型為 *struct{id int data string},
// 因為類型沒有使用 type 定義,所以需要在每次用到的地方進行定義。
func printMsgType(msg *struct {id   intdata string
}) {// 使用動詞%T打印msg的類型fmt.Printf("%T\n", msg)	// *struct { id int; data string }
}func main() {// 實例化一個匿名結構體msg := &struct {  // 定義部分id   intdata string}{  // 值初始化部分1024,"hello",}printMsgType(msg)
}

5. 結構體訪問

在訪問結構體變量成員時,使用點號 . 操作符,格式為:

結構體.成員名

結構體類型變量使用 struct 關鍵字定義,示例如下:

package mainimport "fmt"type Phone struct {model stringcolor stringprice int
}func main() {var phone1 Phonevar phone2 Phonevar phone4 Phonefmt.Printf("phone1 init is %v\n", phone1)// 創建一個新的結構體phone1 = Phone{"華為", "black", 1000}fmt.Printf("phone1 is %v\n", phone1)// 也可以使用 key => value 格式phone2 = Phone{model: "華為", color: "black", price: 1000}fmt.Printf("phone2 is %v\n", phone2)// 忽略的字段為 0 或 空phone3 := Phone{model: "華為", price: 1000}fmt.Printf("phone3 is %v\n", phone3)// 訪問結構體成員phone4.price = 8000phone4.color = "blue"phone4.model = "蘋果"fmt.Printf("phone4 is %v\n", phone4)}

輸出:

phone1 init is {  0}
phone1 is {華為 black 1000}
phone2 is {華為 black 1000}
phone3 is {華為  1000}
phone4 is {蘋果 blue 8000}

6. 結構體作為形參

可以將結構體類型作為參數傳遞給函數。

package mainimport "fmt"type Phone struct {model stringcolor stringprice int
}func main() {var phone Phone// 訪問結構體成員phone.price = 8000phone.color = "blue"phone.model = "蘋果"printPhone(phone)}func printPhone(phone Phone) {fmt.Printf("phone price : %d\n", phone.price)fmt.Printf("phone color : %s\n", phone.color)fmt.Printf("phone model : %s\n", phone.model)
}

輸出:

phone price : 8000
phone color : blue
phone model : 蘋果

指針結構體作為值傳遞。

package mainimport "fmt"type InnerData struct {a int
}type Data struct {complax  []intinstance InnerDataptr      *InnerData
}func passByValue(inFunc Data) Data {fmt.Printf("inFunc value: %+v\n", inFunc)fmt.Printf("inFunc ptr: %p\n", &inFunc)return inFunc
}func main() {in := Data{complax: []int{1, 2, 3},instance: InnerData{5,},ptr: &InnerData{1},}fmt.Printf("in value: %+v\n", in)fmt.Printf("in ptr: %p\n", &in)out := passByValue(in)fmt.Printf("out value: %+v\n", out)fmt.Printf("out ptr: %p\n", &out)
}

輸出結果:

in value: {complax:[1 2 3] instance:{a:5} ptr:0xc0000160e8}
in ptr: 0xc000082150
inFunc value: {complax:[1 2 3] instance:{a:5} ptr:0xc0000160e8}
inFunc ptr: 0xc0000821e0
out value: {complax:[1 2 3] instance:{a:5} ptr:0xc0000160e8}
out ptr: 0xc0000821b0

7. 結構體指針

指向結構體的指針類似于其它指針變量,格式如下:

var structPointer *Phone

上面定義的指針變量可以存儲結構體變量的地址。如果需要查看結構體變量在內存中的地址,可以將 & 符號放置于結構體變量前:

structPointer = &phone1

使用結構體指針訪問結構體成員,使用 . 操作符:

structPointer.title

使用示例

package mainimport "fmt"type Phone struct {model stringcolor stringprice int
}func main() {var p *Phonevar phone Phonep = &phone// 訪問結構體成員p.price = 8000p.color = "blue"p.model = "蘋果"fmt.Printf("p is : %v\n", p)fmt.Printf("*p is : %v\n", *p)fmt.Printf("&p is : %v\n", &p)fmt.Printf("phone is : %v\n", phone)printPhone(p)}func printPhone(phone *Phone) {fmt.Printf("phone price : %d\n", phone.price)fmt.Printf("phone color : %s\n", phone.color)fmt.Printf("phone model : %s\n", phone.model)
}

輸出結果:

p is : &{蘋果 blue 8000}
*p is : {蘋果 blue 8000}
&p is : 0xc00000e028
phone is : {蘋果 blue 8000}
phone price : 8000
phone color : blue
phone model : 蘋果
var p *Phone     // 就是說 p 這個指針是 Phone 類型的
p = &phone     //	phone 是 Phone 的一個實例化的結構,&phone 就是把這個結構體的內存地址賦給了 p,
*p         //	那么在使用的時候,只要在 p 的前面加個*號,就可以把 p 這個內存地址對應的值給取出來了
&p        // 就是取了 p 這個指針的內存地址,也就是 p 這個指針是放在內存空間的什么地方的。
phone       // 就是 phone 這個結構體,打印出來就是它自己。也就是指針 p 前面帶了 * 號的效果。

8. 結構體擁有自身類型的指針類型、以自身類型為元素類型的切片類型

type T struct {t T  	// error... ...
}

Go 語言不支持這種在結構體類型定義中,遞歸地放入其自身類型字段的定義方式。面對上面的示例代碼,編譯器就會給出 invalid recursive type T 的錯誤信息。

但是下面的是可以的

type T struct {t  *T           // okst []T          // okm  map[string]T // ok
}     

一個類型,它所占用的大小是固定的,因此一個結構體定義好的時候,其大小是固定的。但是,如果結構體里面套結構體,那么在計算該結構體占用大小的時候,就會成死循環。

但如果是指針、切片、map 等類型,其本質都是一個 int 大小(指針,4字節或者8字節,與操作系統有關),即因為指針、map、切片的變量元數據的內存占用大小是固定的,因此該結構體的大小是固定的,類型就能決定內存占用的大小。

因此,結構體是可以接受自身類型的指針類型、以自身類型為元素類型的切片類型,以及以自身類型作為 value 類型的 map 類型的字段,而自己本身不行。

9. 零值初始化

零值初始化說的是使用結構體的零值作為它的初始值。對于 Go 原生類型來說,這個默認值也稱為零值。Go 結構體類型由若干個字段組成,當這個結構體類型變量的各個字段的值都是零值時,我們就說這個結構體類型變量處于零值狀態。

var book Book // book為零值結構體變量

Go 語言標準庫和運行時的代碼中,有很多踐行“零值可用”理念的好例子,最典型的莫過于 sync 包的 Mutex 類型了。

運用“零值可用”類型,給 Go 語言中的線程互斥鎖帶來了什么好處呢?我們橫向對比一下 C 語言中的做法你就知道了。如果我們要在 C 語言中使用線程互斥鎖,我們通常需要這么做:

pthread_mutex_t mutex; 
pthread_mutex_init(&mutex, NULL);pthread_mutex_lock(&mutex); 
... ...
pthread_mutex_unlock(&mutex); 

我們可以看到,在 C 中使用互斥鎖,我們需要首先聲明一個 mutex 變量。但這個時候,我們不能直接使用聲明過的變量,因為它的零值狀態是不可用的,我們必須使用 pthread_mutex_init 函數對其進行專門的初始化操作后,它才能處于可用狀態。再之后,我們才能進行 lockunlock 操作。

但是在 Go 語言中,我們只需要這幾行代碼就可以了:

var mu sync.Mutex
mu.Lock()
mu.Unlock()

Go 標準庫的設計者很貼心地將 sync.Mutex 結構體的零值狀態,設計為可用狀態,這樣開發者便可直接基于零值狀態下的 Mutex 進行 lockunlock 操作,而且不需要額外顯式地對它進行初始化操作了。

總結

以上是生活随笔為你收集整理的Go 学习笔记(14)— 结构体定义、实例化、初始化、匿名结构体、结构体访问、结构体作为形参、结构体指针的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。