Go語言學(xué)習(xí)教程之結(jié)構(gòu)體的示例詳解
前言
結(jié)構(gòu)體是一個(gè)序列,包含一些被命名的元素,這些被命名的元素稱為字段(field),每個(gè)字段有一個(gè)名字和一個(gè)類型。
結(jié)構(gòu)體用得比較多的地方是聲明與數(shù)據(jù)庫交互時(shí)需要用到的Model類型,以及與JSON數(shù)據(jù)進(jìn)行相互轉(zhuǎn)換。(當(dāng)然,項(xiàng)目中任何需要多種數(shù)據(jù)結(jié)構(gòu)組合在一起使用的地方,都可以選擇用結(jié)構(gòu)體)
代碼段1:聲明一個(gè)待辦事項(xiàng)的Model類型:
type Todo struct { ID uint `gorm:"primarykey"` CreatedAt time.Time UpdatedAt time.Time DeletedAt gorm.DeletedAt `gorm:"index"` Title string Detail string `gorm:"column:todo_detail;comment:待辦詳情"` Done bool `gorm:"default:false"` }
結(jié)構(gòu)體類型Todo包含unit類型的字段ID,time.Time類型的字段CreatedAt,string類型的字段Title...,當(dāng)執(zhí)行db.AutoMigrate(&Todo{})
自動(dòng)遷移model到數(shù)據(jù)庫中時(shí),對應(yīng)的創(chuàng)建表的SQL為:
CREATE TABLE `todos` ( `id` bigint unsigned NOT NULL AUTO_INCREMENT, `created_at` datetime(3) DEFAULT NULL, `updated_at` datetime(3) DEFAULT NULL, `deleted_at` datetime(3) DEFAULT NULL, `title` longtext, `todo_detail` longtext COMMENT '待辦詳情', `done` tinyint(1) DEFAULT '0', PRIMARY KEY (`id`), KEY `idx_todos_deleted_at` (`deleted_at`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci
(關(guān)于gorm的約定,本文不會(huì)描述,重點(diǎn)是放在結(jié)構(gòu)體本身)
代碼段2:整理所需的數(shù)據(jù)并轉(zhuǎn)換為JSON格式:
type Novel struct { ID uint Title string Chapters []string } novel := Novel{ ID: 1, Title: "我與掘金的二三事", Chapters: []string{ "注冊了賬號", "寫了一篇文", "又寫了一篇文", "升級了,開森", }, } a, err := json.Marshal(novel) if err != nil { fmt.Println(err) } os.Stdout.Write(a)
執(zhí)行以上代碼打印的內(nèi)容如下:
{"ID":1,"Title":"我與掘金的二三事","Chapters":["注冊了賬號","寫了一篇文","又寫了一篇文","升級了,開森"]}%
可導(dǎo)出的標(biāo)識符
允許從另一個(gè)包中訪問的標(biāo)識符是可導(dǎo)出的。只有同時(shí)滿足以下兩個(gè)條件的標(biāo)識符為可導(dǎo)出的:
- 標(biāo)識符的名稱的第一個(gè)字符是一個(gè)Unicode大寫字母。
- 標(biāo)識符在包塊中聲明或者它是一個(gè)字段名或方法名。
所有其他的標(biāo)識符都是不可導(dǎo)出的。
以上是可導(dǎo)出標(biāo)識符的定義,所以對于結(jié)構(gòu)體來說,結(jié)構(gòu)體中的一個(gè)字段可導(dǎo)出,字段的名稱需要首字母大寫。
只在當(dāng)前項(xiàng)目中使用的字段可以使用小寫字母開頭,但是對于需要導(dǎo)出到別的包中進(jìn)行使用的結(jié)構(gòu)體字段,必須首字母大寫,否則其他包無法訪問該字段。
代碼段3:
type Novel struct { ID uint Title string Chapters []string inited bool } novel := Novel{ inited: true, ID: 2, Title: "小步慢跑", Chapters: []string{ "?( ? )?", }, } a, err := json.Marshal(novel) if err != nil { fmt.Println(err) } os.Stdout.Write(a)
打印的內(nèi)容如下:
{"ID":2,"Title":"小步慢跑","Chapters":["?( ? )?"]}%
可以看到字段inited
只是在代碼中賦值為true
了,打印出的內(nèi)容中并沒有inited
字段。在json.Marshal
轉(zhuǎn)換時(shí),不會(huì)對字段inited
進(jìn)行處理,因?yàn)橥獠康陌荒茉L問不可導(dǎo)出字段。
嵌入字段
一個(gè)聲明了類型但是沒有顯式字段名稱的字段稱為嵌入字段。一個(gè)嵌入字段必須聲明一個(gè)類型名稱T,或者一個(gè)指向非接口類型的指針*T
,并且T本身不是一個(gè)指針類型。
代碼段4:只有標(biāo)識符列表:
type Novel struct { ID uint Deleted bool CreatedTime time.Time Title string Chapters []string } novel := Novel{ ID: 2, Deleted: false, CreatedTime: time.Now(), Title: "小步慢跑", Chapters: []string{ "?( ? )?", }, } a, err := json.Marshal(novel) if err != nil { fmt.Println(err) } os.Stdout.Write(a)
代碼段5: 使用了嵌入字段:
type Common struct { ID uint Deleted bool CreatedTime time.Time } type Novel struct { Common Title string Chapters []string } common := Common{ ID: 2, Deleted: false, CreatedTime: time.Now(), } novel := Novel{ Common: common, Title: "小步慢跑", Chapters: []string{ "?( ? )?", }, } a, err := json.Marshal(novel) if err != nil { fmt.Println(err) } os.Stdout.Write(a)
以上兩段代碼打印出的內(nèi)容是一樣的。包含嵌入字段的類型聲明中,Common
就是嵌入字段,類型的名稱就是嵌入字段的名稱。
type Novel struct { Common Title string Chapters []string }
提升
結(jié)構(gòu)體x中的嵌入字段的一個(gè)字段或者方法被稱為提升(promoted)。(首先x.f
需要為一個(gè)表示字段或者方法f
的合法的選擇器)
1.提升字段和結(jié)構(gòu)體的原始字段行為很像,除了它們不能在結(jié)構(gòu)體的復(fù)合字面量中作為字段名使用。
如代碼段4中所示,原始字段直接使用結(jié)構(gòu)體字面量進(jìn)行賦值,但是在使用了嵌入字段的代碼段5中,不能直接使用Novel{ID: 2, ...}
,因?yàn)镮D是嵌入字段的字段,不能在結(jié)構(gòu)體字面量中使用,否則會(huì)報(bào)編譯錯(cuò)誤:
unknown field ID in struct literal
但是代碼段4和代碼段5都可以使用以下方式對字段賦值:
novel.ID = 2
2.給到一個(gè)結(jié)構(gòu)體類型S
以及一個(gè)定義類型T
,提升方法被包括在結(jié)構(gòu)體的方法集合中:
- 如果
S
包含一個(gè)嵌入字段T
,S
和*S
的方法集都包含接收器T的提升方法。*S
的方法集還包括接收器*T
的提升方法。 - 如果
S
包含一個(gè)嵌入字段*T
,S
和*S
的方法集都包含接收器T或者*T
的提升方法。
代碼段6:
func promotedA() { type Todo struct { T1 *T2 Title string } todo := Todo{ Title: "寫一篇小文文", } todo1 := &Todo{ Title: "跑步10分鐘", } PrintMethodSet(todo) PrintMethodSet(todo1) } type T1 struct{} func (T1) M1() { fmt.Println("m1") } func (*T1) M2() { fmt.Println("m2") } type T2 struct{} func (T2) M3() { fmt.Println("m3") } func (*T2) M4() { fmt.Println("m4") } // 打印類型的方法集 func PrintMethodSet(x interface{}) { v := reflect.ValueOf(x) t := v.Type() fmt.Printf("類型%s的方法集:\n", t) for i := 0; i < v.NumMethod(); i++ { // NumMethod()返回值的方法集中,可導(dǎo)出方法的數(shù)量 fmt.Println( t.Method(i).Name, // 獲取第i個(gè)方法的名稱 ) } }
打印的內(nèi)容為:
類型main.Todo的方法集:
M1
M3
M4
類型*main.Todo的方法集:
M1
M2
M3
M4
通過反射可以拿到類型的方法集。關(guān)于反射的說明,可以查看我之前寫的Go語言中的反射。
標(biāo)簽
一個(gè)字段聲明可能會(huì)跟著一個(gè)可選的字符串字面量標(biāo)簽(tag),會(huì)成為對應(yīng)的字段聲明中的所有字段的一個(gè)屬性。一個(gè)空的標(biāo)簽被認(rèn)為就是沒有標(biāo)簽。標(biāo)簽通過反射接口可見,并參與結(jié)構(gòu)類型標(biāo)識,但在其他方面被忽略。
按照慣例,標(biāo)簽字符串是可選的空格分隔符分隔的并列的key:"value"
對。每個(gè)鍵都是一個(gè)非空的字符串,由除了空格(U+0020 ' '),引號(U+0022 '"')和冒號(U+003A ':')外的非控制字符組成。每個(gè)值使用引號引起來并使用字符串字面量語法。
代碼段7:
type Todo struct { Title string `gorm:""` Detail string `gorm:"column:todo_detail;comment:待辦詳情" json:"detail"` Done bool `gorm:"default:false" json:"done"` } todo := Todo{} t := reflect.TypeOf(todo) for i := 0; i < t.NumField(); i++ { // NumField返回結(jié)構(gòu)體的字段的數(shù)量 field := t.Field(i) fmt.Println( fmt.Sprintf("字段 %s 對應(yīng)的標(biāo)簽值: ", field.Name), field.Tag.Get("gorm"), field.Tag.Get("json")) // 獲取標(biāo)簽中的鍵"gorm"和"json"對應(yīng)的值 }
打印的內(nèi)容如下:
字段 Title 對應(yīng)的標(biāo)簽值:
字段 Detail 對應(yīng)的標(biāo)簽值: column:todo_detail;comment:待辦詳情 detail
字段 Done 對應(yīng)的標(biāo)簽值: default:false done
結(jié)構(gòu)體與JSON相互轉(zhuǎn)換
結(jié)構(gòu)體轉(zhuǎn)JSON
在代碼段2中就將結(jié)構(gòu)體轉(zhuǎn)換為了JSON,使用了encoding/json包的json.Marshal
方法。
type Novel struct { ID uint Title string Chapters []string } novel := Novel{ ID: 1, Title: "我與掘金的二三事", Chapters: []string{ "注冊了賬號", "寫了一篇文", "又寫了一篇文", "升級了,開森", }, } a, err := json.Marshal(novel) if err != nil { fmt.Println(err) } os.Stdout.Write(a)
json.Marshal
可以用于將Go對象轉(zhuǎn)換為JSON編碼的數(shù)據(jù),不只是結(jié)構(gòu)體,還包括布爾值、數(shù)組等,這里只說結(jié)構(gòu)體相關(guān)的部分。
每一個(gè)結(jié)構(gòu)體字段的編碼方式可以由存儲(chǔ)在結(jié)構(gòu)體標(biāo)簽中的"json"
鍵下的格式化字符串決定。格式化字符串給出字段的名字,可能還會(huì)跟著一個(gè)由逗號分隔的選項(xiàng)。名稱可以為空,以方便在不覆蓋默認(rèn)字段名稱的情況下指定選項(xiàng)。
"omitempty"
選項(xiàng)指明字段是一個(gè)空值的時(shí)候,需要在編碼時(shí)被省略。空值為false、0、空指針、以及任何空的數(shù)組、切片、映射或字符串。
如果字段的標(biāo)簽是"-"
,字段永遠(yuǎn)會(huì)被省略。注意一個(gè)名字為"-"
的字段可以使用標(biāo)簽"-,"
進(jìn)行生成。
在代碼段2的基礎(chǔ)上修改結(jié)構(gòu)體的類型聲明,觀察得到的結(jié)果:
代碼段8:
type Novel struct{ ID uint `json:"novel_id"` Title string Chapters []string }
打印的內(nèi)容:
{"novel_id":1,"Title":"我與掘金的二三事","Chapters":["注冊了賬號","寫了一篇文","又寫了一篇文","升級了,開森"]}%
使用"json"
的值novel_id
作為ID字段轉(zhuǎn)換為JSON后的字段名。
代碼段9:
type Novel struct { ID uint `json:"novel_id"` Title string Chapters []string `json:"novel_chapters,omitempty"` } novel := Novel{ ID: 1, Title: "我與掘金的二三事", Chapters: []string{}, } a, err := json.Marshal(novel) if err != nil { fmt.Println(err) } os.Stdout.Write(a)
打印的內(nèi)容:
{"novel_id":1,"Title":"我與掘金的二三事"}%
因?yàn)?code>[]string{} 是一個(gè)空數(shù)組,是一個(gè)空值,因?yàn)槭褂昧?code>omitempty,所以該字段在轉(zhuǎn)換時(shí)被忽略了。
代碼段10:
type Novel struct { ID uint `json:",omitempty"` Title string `json:"-"` Chapters []string `json:"-,"` } novel := Novel{ ID: 1, Title: "我與掘金的二三事", Chapters: []string{}, } a, err := json.Marshal(novel) if err != nil { fmt.Println(err) } os.Stdout.Write(a)
打印的內(nèi)容:
{"ID":1,"-":[]}%
字段ID
省略了指定名字的部分,所以轉(zhuǎn)化的JSON字段與結(jié)構(gòu)體的字段名一樣;字段Title
由于使用了-
標(biāo)簽值,會(huì)在轉(zhuǎn)換的時(shí)候永遠(yuǎn)被忽略;字段Chapters
轉(zhuǎn)換的JSON數(shù)據(jù)使用的字段名為-
。
JSON轉(zhuǎn)結(jié)構(gòu)體
func Unmarshal(data []byte, v any) error
Unmarshal
解析JSON編碼的數(shù)據(jù)并將其存儲(chǔ)到v
指向的值中。
還是在代碼段2的基礎(chǔ)上進(jìn)行修改,把轉(zhuǎn)換為JSON的數(shù)據(jù)又轉(zhuǎn)換回來:
代碼段11:
type Novel struct { ID uint Title string Chapters []string } novel := Novel{ ID: 1, Title: "我與掘金的二三事", Chapters: []string{ "注冊了賬號", }, } a, err := json.Marshal(novel) if err != nil { fmt.Println(err) } os.Stdout.Write(a) fmt.Println("") err = json.Unmarshal(a, &novel) if err != nil { fmt.Println(err) } fmt.Println(novel) novel1JSON := []byte(`{"ID":2,"Title":"小步慢跑","Chapters":["?( ? )?"]}`) var novel1 Novel err = json.Unmarshal(novel1JSON, &novel1) if err != nil { fmt.Println(err) } fmt.Println(novel1)
打印的內(nèi)容為:
{"ID":1,"Title":"我與掘金的二三事","Chapters":["注冊了賬號"]}
{1 我與掘金的二三事 [注冊了賬號]}
{2 小步慢跑 [?( ? )?]}
練習(xí)代碼步驟
1.創(chuàng)建一個(gè)文件夾并cd到文件夾目錄下:
$ mkdir practice $ cd practice
2.使用go mod init 初始化模塊,模塊名稱為practice
,這會(huì)創(chuàng)建一個(gè)go.mod
文件,這個(gè)文件可以用于對代碼進(jìn)行依賴追蹤。
$ go mod init practice
此時(shí)go.mod
文件的內(nèi)容為:
module practice go 1.18
3.創(chuàng)建一個(gè)屬于main
包的文件main.go
,當(dāng)運(yùn)行main
包的時(shí)候,會(huì)默認(rèn)執(zhí)行其中的main
函數(shù)。
package main import "fmt" func main() { fmt.Println("小步慢跑(? ˙o˙)?") }
在main
包所在的文件目錄下執(zhí)行go run .
,表示編譯和運(yùn)行當(dāng)前目錄下的main
包:
$ go run .
4.導(dǎo)入gorm庫中的包:gorm核心程序,gorm mysql數(shù)據(jù)庫驅(qū)動(dòng)程序:
package main import ( "fmt" "gorm.io/driver/mysql" "gorm.io/gorm" ) //...(此處為省略的使用gorm的代碼)
導(dǎo)入新的包時(shí),執(zhí)行go mod tidy
下載新增的包并更新go.mod
文件,此時(shí)go.mod
文件內(nèi)容為:
module todo go 1.18 require ( gorm.io/driver/mysql v1.3.5 gorm.io/gorm v1.23.8 ) require ( github.com/go-sql-driver/mysql v1.6.0 // indirect github.com/jinzhu/inflection v1.0.0 // indirect github.com/jinzhu/now v1.1.5 // indirect )
執(zhí)行go mod vendor
在main
模塊的根目錄下創(chuàng)建一個(gè)名為vendor
的目錄,其中包含主模塊所需的包的副本。
5.完整的代碼地址:https://github.com/renmo/myBlog/tree/master/2022-07-31-struct
以上就是Go語言學(xué)習(xí)教程之結(jié)構(gòu)體的示例詳解的詳細(xì)內(nèi)容,更多關(guān)于Go語言 結(jié)構(gòu)體的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
Go語言類型內(nèi)嵌和結(jié)構(gòu)體內(nèi)嵌的具體使用
本文主要介紹了Go語言類型內(nèi)嵌和結(jié)構(gòu)體內(nèi)嵌的具體使用,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2023-04-04基于Golang設(shè)計(jì)一套可控的定時(shí)任務(wù)系統(tǒng)
這篇文章主要為大家學(xué)習(xí)介紹了如何基于Golang設(shè)計(jì)一套可控的定時(shí)任務(wù)系統(tǒng),文中的示例代碼講解詳細(xì),感興趣的小伙伴可以跟隨小編一起學(xué)習(xí)一下2023-07-07Golang高性能持久化解決方案BoltDB數(shù)據(jù)庫介紹
這篇文章主要為大家介紹了Golang高性能持久化解決方案BoltDB數(shù)據(jù)庫介紹,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步2021-11-11Golang分布式注冊中心實(shí)現(xiàn)流程講解
這篇文章主要介紹了Golang分布式注冊中心實(shí)現(xiàn)流程,注冊中心可以用于服務(wù)發(fā)現(xiàn),服務(wù)注冊,配置管理等方面,在分布式系統(tǒng)中,服務(wù)的發(fā)現(xiàn)和注冊是非常重要的組成部分,需要的朋友可以參考下2023-05-05Go基礎(chǔ)教程系列之WaitGroup用法實(shí)例詳解
這篇文章主要介紹了Go基礎(chǔ)教程系列之WaitGroup用法實(shí)例詳解,需要的朋友可以參考下2022-04-04Gin 框架快速創(chuàng)建靜態(tài)文件下載Web服務(wù)
本文主要介紹了Gin 框架快速創(chuàng)建靜態(tài)文件下載Web服務(wù),文中通過示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2021-12-12Go語言中利用http發(fā)起Get和Post請求的方法示例
這篇文章主要給大家介紹了關(guān)于Go語言中利用http發(fā)起Get和Post請求的相關(guān)資料,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧。2017-11-11