Go語言中提供了對struct的支援;struct
,中文翻譯稱為結構體
,與陣列一樣,屬於複合型別類型,並非引用類型。 【相關推薦:Go影片教學】
Go語言的struct,與C語言中的struct或其他物件導向程式語言中的類別(class)類似,可以定義欄位(屬性)和方法,但也有很不同的地方,需要深入學習,才能區分他們之間的差異。
注意複合型別與參考型別之間的區別,這應該也是值傳遞和參考傳遞的區別吧。
使用struct關鍵字可以定義一個結構體,結構體中的成員,稱為結構體的欄位或屬性。
type Member struct { id int name, email string gender, age int }
上面的程式碼中,我們定義了一個包含5個欄位的結構體,可以看到,相同型別name
和email
、gender
和age
在同一行中定義,但比較好的程式設計習慣是每一行只定義一個字段,如:
type Member struct { id int name string email string gender int age int }
當然,結構體也可以不包含任何字段,稱為空結構體
,struct{}表示一個空的結構體,注意,直接定義一個空的結構體並沒有意義,但在並發程式設計中,channel之間的通訊,可以使用一個struct{}作為信號量。
ch := make(chan struct{}) ch <- struct{}{}
上面的範例中,我們定義了Member結構體類型,接著就可以這個自訂的類型建立變數了。
直接定義變量,這個使用方式並沒有為字段賦初始值,因此所有字段都會自動賦予自已類型的零值,例如
name
的值為空字符串"",age的值為0。
var m1 Member//所有字段均为空值
使用字面量建立變量,這種使用方式,可以在大括號中為結構體的成員賦初始值,有兩種賦初始值的方式,一種是按字段在結構體中的順序賦值,下面程式碼中
m2
就是使用這種方式,這種方式要求所有的欄位都必須賦值,因此如果欄位太多,每個欄位都要賦值,會很繁瑣,另一種則使用字段名為指定字段賦值,如下面代碼中變量m3
的創建,使用這種方式,對於其他沒有指定的字段,則使用該字段類型的零值作為初始化值。
var m2 = Member{1,"小明","xiaoming@163.com",1,18} // 简短变量声明方式:m2 := Member{1,"小明","xiaoming@163.com",1,18} var m3 = Member{id:2,"name":"小红"}// 简短变量声明方式:m3 := Member{id:2,"name":"小红"}
透過變數名,使用逗號(.)
,可以存取結構體類型中的字段,或為字段賦值,也可以對字段進行取址(&)操作。
fmt.Println(m2.name)//输出:小明 m3.name = "小花" fmt.Println(m3.name)//输出:小花 age := &m3.age *age = 20 fmt.Println(m3.age)//20
結構體與陣列一樣,都是值傳遞,例如當把陣列或結構體當作實參傳給函數的形參時,會複製一個副本,所以為了提高效能,一般不會把陣列直接傳遞給函數,而是使用切片(引用型別)代替,而把結構體傳給函數時,可以使用指標結構體
。
指標結構體,也就是一個指向結構體的指標,宣告結構體變數時,在結構體型別前加上*號,宣告一個指向結構體的指標,如:
注意,指標類型為參考類型,宣告結構體指標時,如果未初始化,則初始值為nil,只有初始化後,才能存取欄位或為欄位賦值。
var m1 *Member m1.name = "小明"//错误用法,未初始化,m1为nil m1 = &Member{} m1.name = "小明"//初始化后,结构体指针指向某个结构体地址,才能访问字段,为字段赋值。
另外,使用Go內建new()函數,可以分配記憶體來初始化結構休,並返回分配的記憶體指針,因為已經初始化了,所以可以直接存取欄位。
var m2 = new(Member) m2.name = "小红"
我們知道,如果將結構體轉給函數,只是複製結構體的副本,如果在函數內修改結構體字段值,外面的結構體並不會受影響,而如果將結構體指標傳給函數,在函數中使用指標針對結構體所做的修改,都會影響指標指向的結構體。
func main() { m1 := Member{} m2 := new(Member) Change(m1,m2) fmt.Println(m1,m2) } func Change(m1 Member,m2 *Member){ m1.Name = "小明" m2.Name = "小红" }
上面的範例中,我們定義結構體欄位名稱首字母是小寫的,這表示這些欄位在套件外不可見
,因而無法在其他包中被訪問,只允許包內訪問。
下面的例子中,我們將Member聲明在member包中,而後在main包中創建一個變量,但由於結構體的字段包外不可見,因此無法為字段賦初始值,無法按字段還是按索引賦值,都會引發panic錯誤。
package member type Member struct { id int name string email string gender int age int } package main fun main(){ var m = member.Member{1,"小明","xiaoming@163.com",1,18}//会引发panic错误 }
因此,如果想在一個包中訪問另一個包中結構體的字段,則必須是大寫字母開頭的變量,即可導出的變量,如:
type Member struct { Id int Name string Email string Gender int Age int }
在定義結構體欄位時,除欄位名稱和資料類型外,還可以使用反引號為結構體欄位宣告元訊息,這種元資訊稱為Tag,用於編譯階段關聯到欄位當中,如我們將上面例子中的結構體修改為:
type Member struct { Id int `json:"id,-"` Name string `json:"name"` Email string `json:"email"` Gender int `json:"gender,"` Age int `json:"age"` }
上面例子演示的是使用encoding/json包编码或解码结构体时使用的Tag信息。
Tag由反引号括起来的一系列用空格分隔的key:"value"键值对组成,如:
Id int `json:"id" gorm:"AUTO_INCREMENT"`
下面总结几点结构体的相关特性:
结构体与数组一样,是复合类型,无论是作为实参传递给函数时,还是赋值给其他变量,都是值传递,即复一个副本。
Go语言是支持面向对象编程的,但却没有继承的概念,在结构体中,可以通过组合其他结构体来构建更复杂的结构体。
一个结构体,并没有包含自身,比如Member中的字段不能是Member类型,但却可能是*Member。
在Go语言中,将函数绑定到具体的类型中,则称该函数是该类型的方法,其定义的方式是在func与函数名称之间加上具体类型变量,这个类型变量称为方法接收器
,如:
注意,并不是只有结构体才能绑定方法,任何类型都可以绑定方法,只是我们这里介绍将方法绑定到结构体中。
func setName(m Member,name string){//普通函数 m.Name = name } func (m Member)setName(name string){//绑定到Member结构体的方法 m.Name = name }
从上面的例子中,我们可以看出,通过方法接收器
可以访问结构体的字段,这类似其他编程语言中的this关键词,但在Go语言中,只是一个变量名而已,我们可以任意命名方法接收器
。
调用结构体的方法,与调用字段一样:
m := Member{} m.setName("小明") fmt.Println(m.Name)//输出为空
上面的代码中,我们会很奇怪,不是调用setName()方法设置了字段Name的值了吗?为什么还是输出为空呢?
这是因为,结构体是值传递,当我们调用setName时,方法接收器接收到是只是结构体变量的一个副本,通过副本对值进行修复,并不会影响调用者,因此,我们可以将方法接收器定义为指针变量,就可达到修改结构体的目的了。
func (m *Member)setName(name string){/将Member改为*Member m.Name = name } m := Member{} m.setName("小明") fmt.Println(m.Name)//小明
方法和字段一样,如果首字母为小写,则只允许在包内可见,在其他包中是无法访问的,因此,如果要在其他包中访问setName
,则应该将方法名改为SetName
。
我们知道,结构体中并没有继承的概念,其实,在Go语言中也没有继承的概念,Go语言的编程哲学里,推荐使用组合
的方式来达到代码复用效果。
组合,可以理解为定义一个结构体中,其字段可以是其他的结构体,这样,不同的结构体就可以共用相同的字段。
注意,在记得我们前面提过的,结构体不能包含自身,但可能包含指向自身的结构体指针。
例如,我们定义了一个名为Animal表示动物,如果我们想定义一个结构体表示猫,如:
type Animal struct { Name string //名称 Color string //颜色 Height float32 //身高 Weight float32 //体重 Age int //年龄 } //奔跑 func (a Animal)Run() { fmt.Println(a.Name + "is running") } //吃东西 func (a Animal)Eat() { fmt.Println(a.Name + "is eating") } type Cat struct { a Animal } func main() { var c = Cat{ a: Animal{ Name: "猫猫", Color: "橙色", Weight: 10, Height: 30, Age: 5, }, } fmt.Println(c.a.Name) c.a.Run() }
可以看到,我们定义Cat结构体时,可以把Animal结构体作为Cat的字段。
上面的例子,我们看到,把Animal结构体作为Cat的字段时,其变量名为a,所以我们访问Animal的方法时,语法为c.a.Run()
,这种通过叶子属性访问某个字段类型所带的方法和字段用法非常繁琐。
Go语言支持直接将类型作为结构体的字段,而不需要取变量名,这种字段叫匿名字段
,如:
type Lion struct { Animal //匿名字段 } func main(){ var lion = Lion{ Animal{ Name: "小狮子", Color: "灰色", }, } lion.Run() fmt.Println(lion.Name) }
通过上面例子,可以看到,通过匿名字段组合其他类型,而后访问匿名字段类型所带的方法和字段时,不需要使用叶子属性,非常方便。
在Go语言编程中,结构体大概算是使用得最多的数据类型了,通过定义不同字段和方法的结构体,抽象组合不同的结构体,这大概便是Go语言中对面向对象编程了。
原文地址:https://juejin.cn/post/6844903814168838151
更多编程相关知识,请访问:编程视频!!
以上是詳解Golang中的Struct(結構體)的詳細內容。更多資訊請關注PHP中文網其他相關文章!