文章目錄
- 一、結構體定義
- 二、初始化結構體
- 三、結構體的通路
- 四、結構體指針
- 五、結構體可見性
- 六、結構體标簽
- 七、結構體嵌套
- 八、結構體方法
- 九、結構體特性
Go語言中提供了對struct的支援,struct,中文翻譯稱為結構體,與數組一樣,屬于複合類型,并非引用類型。
Go語言的struct,與C語言中的struct或其他面向對象程式設計語言中的類(class)類似,可以定義字段(屬性)和方法,但也有很不同的地方,需要深入學習,才能區分他們之間的差別。
一、結構體定義
Go語言中數組可以存儲同一類型的資料,但在結構體中我們可以為不同項定義不同的資料類型。
結構體是由一系列具有相同類型或不同類型的資料構成的資料集合
結構體成員是由一系列的成員變量構成,這些成員變量也被稱為“字段”
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//所有字段均為空值
另外也有使用字面量建立變量,這種使用方式,可以在大括号中為結構體的成員賦初始值,有兩種賦初始值的方式:
- 按照順序提供初始化值:
var m2 = Member{
1,
"傑哥的技術雜貨鋪",
"[email protected]",
1,
18,
}
或者
// 簡短變量聲明方式:
m2 := Member{
1,
"傑哥的技術雜貨鋪",
"[email protected]",
1,
18
}
這種方式要求所有的字段都必須指派,是以如果字段太多,每個字段都要指派,會很繁瑣,另一種則使用字段名為指定字段指派,如下面代碼中變量m3的建立,使用這種方式,對于其他沒有指定的字段,則使用該字段類型的零值作為初始化值。
- 通過field:value的方式初始化,這樣可以任意順序
var m3 = Member{
id:2,
"name":"傑哥的技術雜貨鋪"
}
或者
// 簡短變量聲明方式:
m3 := Member{
id:2,
"name":"傑哥的技術雜貨鋪"
}
三、結構體的通路
通過變量名,使用符号點(.),可以通路結構體類型中的字段,或為字段指派,也可以對字段進行取址(&)操作。
package main
import "fmt"
//定義結構體
type Persion struct {
name string
age int
sex string
address string
}
func main() {
//1.方法一
var p1 Persion
p1.name = "傑哥的技術雜貨鋪"
p1.age = 18
p1.sex = "男"
p1.address = "技術部落格"
fmt.Printf("姓名:%s,年齡:%d,性别:%s,位址:%s\n",p1.name,p1.age,p1.sex,p1.address)
//2.方法二
p2 := Persion{}
p2.name = "黃風怪"
p2.age = 3000
p2.sex = "男"
p2.address = "黃風洞"
fmt.Printf("姓名:%s,年齡:%d,性别:%s,位址:%s\n",p2.name,p2.age,p2.sex,p2.address)
//3.方法三:建立結構體對象時,直接進行指派
p3 := Persion{name: "蜘蛛精",age: 500,sex: "女",address: "盤絲洞"}
fmt.Printf("姓名:%s,年齡:%d,性别:%s,位址:%s\n",p3.name,p3.age,p3.sex,p3.address)
p4 := Persion{
name:"黃眉大王",
age: 1000,
sex: "男",
address: "小雷音寺",
}
fmt.Println(p4)
//4.方法四:建立結構體對象時,不寫字段名,直接賦予數值
//此種方式需要注意順序
p5 := Persion{"白骨精",300,"女","白骨洞"}
fmt.Println(p5)
}
四、結構體指針
結構體與數組一樣,都是值傳遞,比如當把數組或結構體作為實參傳給函數的形參時,會複制一個副本,是以為了提高性能,一般不會把數組直接傳遞給函數,而是使用切片(引用類型)代替,而把結構體傳給函數時,可以使用指針結構體。
指針結構體,即一個指向結構體的指針,聲明結構體變量時,在結構體類型前加*号,便聲明一個指向結構體的指針,如:
注意,指針類型為引用類型,聲明結構體指針時,如果未初始化,則初始值為nil,隻有初始化後,才能通路字段或為字段指派。
var m1 *Member
m1.name = "傑哥的技術雜貨鋪"//錯誤用法,未初始化,m1為nil
m1 = &Member{}
m1.name = "傑哥的技術雜貨鋪"//初始化後,結構體指針指向某個結構體位址,才能通路字段,為字段指派。
另外,使用Go内置new()函數,可以配置設定記憶體來初始化結構休,并傳回配置設定的記憶體指針,因為已經初始化了,是以可以直接通路字段。
var m2 = new(Member)
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,
"傑哥的技術雜貨鋪",
"[email protected]",
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"
七、結構體嵌套
結構體嵌套,可以了解為定義一個結構體中,其字段可以是其他的結構體,這樣,不同的結構體就可以共用相同的字段。
注意, 結構體不能包含自身,但可能包含指向自身的結構體指針。
package main
import (
"fmt"
)
//1.定義一個書的結構體
type Book struct {
bookname string
price float64
}
//2.定義學生的結構體
type Student struct {
name string
age int
book Book
}
type Student2 struct {
name string
age int
book *Book //book結構體的位址
}
func main() {
b1 := Book{}
b1.bookname = "西遊記"
b1.price = 66.6
s1 := Student{}
s1.name = "紅孩兒"
s1.age = 18
s1.book = b1 //值傳遞
fmt.Println(b1)
fmt.Println(s1)
fmt.Printf("學生姓名:%s,學生年齡:%d,看的書是:《%s》,書的價格是:%.2f\n",s1.name,s1.age,s1.book.bookname,s1.book.price)
s2 := Student{name: "武松",age: 28,book: Book{bookname: "《Go語言從入門到放棄》",price: 88.8}}
fmt.Println(s2.name,s2.age)
fmt.Println("\t",s2.book.bookname,s2.book.price)
s3 := Student{
name: "jack",
age: 17,
book: Book{
bookname: "十萬個為啥",
price: 35.5,
},
}
fmt.Println(s3.name,s3.age)
fmt.Println("\t",s3.book.bookname,s3.book.price)
b4 := Book{bookname: "射雕英雄傳",price: 76.0}
s4 := Student2{name:"張三",age: 20,book: &b4}
fmt.Println(b4)
fmt.Println(s4)
fmt.Println("\t",s4.book)
s4.book.bookname = "挪威的森林"
fmt.Println(b4)
fmt.Println(s4)
fmt.Println("\t",s4.book)
}
可以看到,我們定義Student結構體時,可以把Book結構體作為Student的字段。
八、結構體方法
在Go語言中,将函數綁定到具體的類型中,則稱該函數是該類型的方法,其定義的方式是在func與函數名稱之間加上具體類型變量,這個類型變量稱為方法接收器,如:
注意,并不是隻有結構體才能綁定方法,任何類型都可以綁定方法,隻是我們這裡介紹将方法綁定到結構體中。
func setName(m Member,name string){//普通函數
m.Name = name
}
func (m Member)setName(name string){//綁定到Member結構體的方法
m.Name = name
}
從上面的例子中,我們可以看出,通過方法接收器可以通路結構體的字段,我們可以任意命名方法接收器。
調用結構體的方法,與調用字段一樣:
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語言是支援面向對象程式設計的,但卻沒有繼承的概念,在結構體中,可以通過組合其他結構體來建構更複雜的結構體。
- 結構體不能包含自己