Go語言學習教程之結構體的示例詳解
前言
結構體是一個序列,包含一些被命名的元素,這些被命名的元素稱為字段(field),每個字段有一個名字和一個類型。
結構體用得比較多的地方是聲明與數(shù)據(jù)庫交互時需要用到的Model類型,以及與JSON數(shù)據(jù)進行相互轉換。(當然,項目中任何需要多種數(shù)據(jù)結構組合在一起使用的地方,都可以選擇用結構體)
代碼段1:聲明一個待辦事項的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"`
}結構體類型Todo包含unit類型的字段ID,time.Time類型的字段CreatedAt,string類型的字段Title...,當執(zhí)行db.AutoMigrate(&Todo{})自動遷移model到數(shù)據(jù)庫中時,對應的創(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
(關于gorm的約定,本文不會描述,重點是放在結構體本身)
代碼段2:整理所需的數(shù)據(jù)并轉換為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í)行以上代碼打印的內容如下:
{"ID":1,"Title":"我與掘金的二三事","Chapters":["注冊了賬號","寫了一篇文","又寫了一篇文","升級了,開森"]}%
可導出的標識符
允許從另一個包中訪問的標識符是可導出的。只有同時滿足以下兩個條件的標識符為可導出的:
- 標識符的名稱的第一個字符是一個Unicode大寫字母。
- 標識符在包塊中聲明或者它是一個字段名或方法名。
所有其他的標識符都是不可導出的。
以上是可導出標識符的定義,所以對于結構體來說,結構體中的一個字段可導出,字段的名稱需要首字母大寫。
只在當前項目中使用的字段可以使用小寫字母開頭,但是對于需要導出到別的包中進行使用的結構體字段,必須首字母大寫,否則其他包無法訪問該字段。
代碼段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)
打印的內容如下:
{"ID":2,"Title":"小步慢跑","Chapters":["?( ? )?"]}%
可以看到字段inited只是在代碼中賦值為true了,打印出的內容中并沒有inited字段。在json.Marshal轉換時,不會對字段inited進行處理,因為外部的包不能訪問不可導出字段。
嵌入字段
一個聲明了類型但是沒有顯式字段名稱的字段稱為嵌入字段。一個嵌入字段必須聲明一個類型名稱T,或者一個指向非接口類型的指針*T,并且T本身不是一個指針類型。
代碼段4:只有標識符列表:
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)
以上兩段代碼打印出的內容是一樣的。包含嵌入字段的類型聲明中,Common就是嵌入字段,類型的名稱就是嵌入字段的名稱。
type Novel struct {
Common
Title string
Chapters []string
}提升
結構體x中的嵌入字段的一個字段或者方法被稱為提升(promoted)。(首先x.f需要為一個表示字段或者方法f的合法的選擇器)
1.提升字段和結構體的原始字段行為很像,除了它們不能在結構體的復合字面量中作為字段名使用。
如代碼段4中所示,原始字段直接使用結構體字面量進行賦值,但是在使用了嵌入字段的代碼段5中,不能直接使用Novel{ID: 2, ...},因為ID是嵌入字段的字段,不能在結構體字面量中使用,否則會報編譯錯誤:
unknown field ID in struct literal
但是代碼段4和代碼段5都可以使用以下方式對字段賦值:
novel.ID = 2
2.給到一個結構體類型S以及一個定義類型T,提升方法被包括在結構體的方法集合中:
- 如果
S包含一個嵌入字段T,S和*S的方法集都包含接收器T的提升方法。*S的方法集還包括接收器*T的提升方法。 - 如果
S包含一個嵌入字段*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()返回值的方法集中,可導出方法的數(shù)量
fmt.Println(
t.Method(i).Name, // 獲取第i個方法的名稱
)
}
}打印的內容為:
類型main.Todo的方法集:
M1
M3
M4
類型*main.Todo的方法集:
M1
M2
M3
M4
通過反射可以拿到類型的方法集。關于反射的說明,可以查看我之前寫的Go語言中的反射。
標簽
一個字段聲明可能會跟著一個可選的字符串字面量標簽(tag),會成為對應的字段聲明中的所有字段的一個屬性。一個空的標簽被認為就是沒有標簽。標簽通過反射接口可見,并參與結構類型標識,但在其他方面被忽略。
按照慣例,標簽字符串是可選的空格分隔符分隔的并列的key:"value"對。每個鍵都是一個非空的字符串,由除了空格(U+0020 ' '),引號(U+0022 '"')和冒號(U+003A ':')外的非控制字符組成。每個值使用引號引起來并使用字符串字面量語法。
代碼段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返回結構體的字段的數(shù)量
field := t.Field(i)
fmt.Println(
fmt.Sprintf("字段 %s 對應的標簽值: ", field.Name),
field.Tag.Get("gorm"),
field.Tag.Get("json")) // 獲取標簽中的鍵"gorm"和"json"對應的值
}打印的內容如下:
字段 Title 對應的標簽值:
字段 Detail 對應的標簽值: column:todo_detail;comment:待辦詳情 detail
字段 Done 對應的標簽值: default:false done
結構體與JSON相互轉換
結構體轉JSON
在代碼段2中就將結構體轉換為了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對象轉換為JSON編碼的數(shù)據(jù),不只是結構體,還包括布爾值、數(shù)組等,這里只說結構體相關的部分。
每一個結構體字段的編碼方式可以由存儲在結構體標簽中的"json"鍵下的格式化字符串決定。格式化字符串給出字段的名字,可能還會跟著一個由逗號分隔的選項。名稱可以為空,以方便在不覆蓋默認字段名稱的情況下指定選項。
"omitempty"選項指明字段是一個空值的時候,需要在編碼時被省略??罩禐閒alse、0、空指針、以及任何空的數(shù)組、切片、映射或字符串。
如果字段的標簽是"-",字段永遠會被省略。注意一個名字為"-"的字段可以使用標簽"-,"進行生成。
在代碼段2的基礎上修改結構體的類型聲明,觀察得到的結果:
代碼段8:
type Novel struct{
ID uint `json:"novel_id"`
Title string
Chapters []string
}打印的內容:
{"novel_id":1,"Title":"我與掘金的二三事","Chapters":["注冊了賬號","寫了一篇文","又寫了一篇文","升級了,開森"]}%
使用"json"的值novel_id作為ID字段轉換為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)
打印的內容:
{"novel_id":1,"Title":"我與掘金的二三事"}%
因為[]string{} 是一個空數(shù)組,是一個空值,因為使用了omitempty,所以該字段在轉換時被忽略了。
代碼段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)
打印的內容:
{"ID":1,"-":[]}%
字段ID省略了指定名字的部分,所以轉化的JSON字段與結構體的字段名一樣;字段Title由于使用了-標簽值,會在轉換的時候永遠被忽略;字段Chapters轉換的JSON數(shù)據(jù)使用的字段名為-。
JSON轉結構體
func Unmarshal(data []byte, v any) error
Unmarshal解析JSON編碼的數(shù)據(jù)并將其存儲到v指向的值中。
還是在代碼段2的基礎上進行修改,把轉換為JSON的數(shù)據(jù)又轉換回來:
代碼段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)
打印的內容為:
{"ID":1,"Title":"我與掘金的二三事","Chapters":["注冊了賬號"]}
{1 我與掘金的二三事 [注冊了賬號]}
{2 小步慢跑 [?( ? )?]}
練習代碼步驟
1.創(chuàng)建一個文件夾并cd到文件夾目錄下:
$ mkdir practice $ cd practice
2.使用go mod init 初始化模塊,模塊名稱為practice,這會創(chuàng)建一個go.mod文件,這個文件可以用于對代碼進行依賴追蹤。
$ go mod init practice
此時go.mod文件的內容為:
module practice go 1.18
3.創(chuàng)建一個屬于main包的文件main.go,當運行main包的時候,會默認執(zhí)行其中的main函數(shù)。
package main
import "fmt"
func main() {
fmt.Println("小步慢跑(? ˙o˙)?")
}在main包所在的文件目錄下執(zhí)行go run .,表示編譯和運行當前目錄下的main包:
$ go run .
4.導入gorm庫中的包:gorm核心程序,gorm mysql數(shù)據(jù)庫驅動程序:
package main
import (
"fmt"
"gorm.io/driver/mysql"
"gorm.io/gorm"
)
//...(此處為省略的使用gorm的代碼)
導入新的包時,執(zhí)行go mod tidy下載新增的包并更新go.mod文件,此時go.mod文件內容為:
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)建一個名為vendor的目錄,其中包含主模塊所需的包的副本。
5.完整的代碼地址:https://github.com/renmo/myBlog/tree/master/2022-07-31-struct
以上就是Go語言學習教程之結構體的示例詳解的詳細內容,更多關于Go語言 結構體的資料請關注腳本之家其它相關文章!
相關文章
Golang高性能持久化解決方案BoltDB數(shù)據(jù)庫介紹
這篇文章主要為大家介紹了Golang高性能持久化解決方案BoltDB數(shù)據(jù)庫介紹,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步2021-11-11
Gin 框架快速創(chuàng)建靜態(tài)文件下載Web服務
本文主要介紹了Gin 框架快速創(chuàng)建靜態(tài)文件下載Web服務,文中通過示例代碼介紹的非常詳細,具有一定的參考價值,感興趣的小伙伴們可以參考一下2021-12-12
Go語言中利用http發(fā)起Get和Post請求的方法示例
這篇文章主要給大家介紹了關于Go語言中利用http發(fā)起Get和Post請求的相關資料,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧。2017-11-11

