型別別名&定製
型別別名
型別別名是Go
的1.9版本中新新增的功能。
大概意思就是給一個型別取一個別名,小名等,但是這個別名還是指向的相同型別。
如uint32
的別名rune
,其底層還是uint32
。
如uint8
的別名byte
,使用byte
實際上還是uint8
別名的作用在於在程式設計中更方便的進行使用型別,如下示例,我們為int64
取一個別名long
。
package main
import (
"fmt"
)
func main() {
type long = int64
var num long
num = 100000000
fmt.Printf("%T %v", num, num)
// int64 100000000
}
自定型別
自定型別類似於繼承某個內建的型別,它會以一種全新的型別出現,並且我們可以為該自定型別做一些定製方法。
如下定義的small
型別,是基於uint8
的一個型別。它是一種全新的型別,但是具有uint8
的特性。
package main
import (
"fmt"
)
func main() {
type small uint8
var num small = 32
fmt.Printf("%T %v", num, num)
// main.small 32
}
區別差異
可以看到上面示例中的列印結果
// int64 100000000
// main.small 32
結果顯示自定義型別是main.small
,其實自定義型別只在程式碼中存在,編譯時會將其轉換為uint8
。
結構體
結構體類似於其他語言中的物件導向,值得一提的是Go
語言是一種面向介面的語言,所以弱化了對物件導向方面的處理。
在結構體中,我們可以清晰的表示一個現實中的事物,注意:結構體其實就是一種自定義的型別。
在Go
中使用struct
來定義結構體。
以下是語法介紹,type
和struct
這兩個關鍵字用於定義結構體。
type 型別名 struct {
欄位名 欄位型別
欄位名 欄位型別
…
}
型別名:標識自定義結構體的名稱,在同一個包內不能重複。如果不是對外開放的介面,則首字母小寫,否則大寫。
欄位名:表示結構體欄位名。結構體中的欄位名必須唯一。
欄位型別:表示結構體欄位的具體型別。
下面我們來定義一個dog
的結構體。
// 命名小寫,表示dog結構體不對外開放
type dog struct{
dogName string
dogAge int8
dogGender bool
}
.例項化
當結構體定義完成後,必須對其進行例項化後才可使用。
單純的定義結構體是不會分配記憶體的。
以下將介紹通過.
進行例項化。
基本例項化
下面是基本例項化的示例。
首先定義一個變數,宣告它是dog
型別,再通過.
對其中的欄位進行賦值。
package main
import (
"fmt"
)
type dog struct{
dogName string
dogAge int8
dogGender bool
}
func main() {
var d1 dog
d1.dogName = "大黃"
d1.dogAge = 12
d1.dogGender = true
fmt.Println(d1)
// {大黃 12 true}
}
匿名結構體
有的結構體只使用一次,那麼就可以使用匿名結構體在定義之初對其進行例項化。
這個時候只使用stuct
即可,不必使用type
進行型別的自定義。
package main
import (
"fmt"
)
func main() {
var dog struct{
dogName string
dogAge int8
dogGender bool
}
dog.dogName = "大黃"
dog.dogAge = 12
dog.dogGender = true
fmt.Println(dog)
// {大黃 12 true}
}
指標例項化
通過new
可以對結構體進行例項化,具體步驟是拿到其結構體指標後通過.
對其欄位填充,進而達到例項化的目的。
package main
import (
"fmt"
)
type dog struct{
dogName string
dogAge int8
dogGender bool
}
func main() {
var d1 = new(dog) // 拿到結構體指標
d1.dogName = "大黃"
d1.dogAge = 12
d1.dogGender = true
fmt.Println(d1)
// &{大黃 12 true}
}
地址例項化
使用&
對結構體進行取地址操作相當於對該結構體型別進行了一次new
例項化操作。
與上面的方式本質都是相同的。
d1.dogName= "大黃"
其實在底層是(*d1).dogName= "大黃"
,這是Go
語言幫我們實現的語法糖。
package main
import (
"fmt"
)
type dog struct{
dogName string
dogAge int8
dogGender bool
}
func main() {
d1 := &dog{}
d1.dogName = "大黃"
d1.dogAge = 12
d1.dogGender = true
fmt.Println(d1)
// &{大黃 12 true}
}
{}例項化
例項化時,除開可以使用.
也可以使用{}
。
在實際開發中使用{}
例項化的普遍更多。
基本例項化
以下是使用{}
進行基本例項化的示例。
key
對應欄位名,value
對應例項化的填充值。
package main
import (
"fmt"
)
type dog struct{
dogName string
dogAge int8
dogGender bool
}
func main() {
d1 := dog{
dogName:"大黃",
dogAge:12,
dogGender:true,
}
fmt.Print(d1)
}
順序例項化
可以不填入key
對其進行例項化,但是要與定義結構體時的欄位位置一一對應。
- 必須初始化結構體的所有欄位。
- 初始值的填充順序必須與欄位在結構體中的宣告順序一致。
- 該方式不能和鍵值初始化方式混用。
package main
import (
"fmt"
)
type dog struct{
dogName string
dogAge int8
dogGender bool
}
func main() {
d1 := dog{
"大黃",
12,
true,
}
fmt.Print(d1)
}
地址例項化
下面是使用{}
進行地址例項化。
package main
import (
"fmt"
)
type dog struct{
dogName string
dogAge int8
dogGender bool
}
func main() {
d1 := &dog{
"大黃",
12,
true,
}
fmt.Print(d1)
}
記憶體佈局
連續記憶體
一個結構體中的欄位,都是佔據一整塊連續記憶體。
但有的欄位可能看起來不會與前一個欄位進行相鄰,這是受到型別的影響,具體可檢視:在 Go 中恰到好處的記憶體對齊
package main
import (
"fmt"
)
type dog struct {
dogName string
dogAge int8
dogGender bool
}
func main() {
d1 := &dog{
"大黃",
12,
true,
}
fmt.Printf("%p \n", &d1.dogName)
fmt.Printf("%p \n", &d1.dogAge)
fmt.Printf("%p \n", &d1.dogGender)
// 0xc0000044a0
// 0xc0000044b0
// 0xc0000044b1
}
空結構體
一個空的結構體是不佔據任何記憶體的。
package main
import (
"fmt"
"unsafe"
)
func main() {
var dog struct{}
fmt.Print(unsafe.Sizeof(dog)) // 0 檢視佔據的記憶體
}
建構函式
當一個函式返回一個結構體例項時,該函式將被稱為建構函式。
Go
語言中沒有建構函式,但是我們可以自己進行定義。
注意建構函式的命名方式要用new
進行開頭。
普通構造
由於函式的引數傳遞都是值傳遞,所以每次需要將結構體拷貝到建構函式中,這是非常消耗記憶體的。
所以在真實開發中不應該使用這種方式
package main
import (
"fmt"
)
type dog struct {
dogName string
dogAge int8
dogGender bool
}
// dog每次都會進行拷貝,消耗記憶體
func newDog(dogName string, dogAge int8, dogGender bool) dog {
return dog{
dogName: dogName,
dogAge: dogAge,
dogGender: dogGender,
}
}
func main(){
d1 := newDog("大黃",12,true)
fmt.Printf("%p \n",&d1)
}
指標構造
如果讓其使用指標構造,就不用每次都會進行拷貝了。推薦使用該方式。
只需要加上*
與&
即可。
package main
import (
"fmt"
)
type dog struct {
dogName string
dogAge int8
dogGender bool
}
// 每次的傳遞都是dog的指標,所以不會進行值拷貝
func newDog(dogName string, dogAge int8, dogGender bool) *dog {
return &dog{
dogName: dogName,
dogAge: dogAge,
dogGender: dogGender,
}
}
func main(){
d1 := newDog("大黃",12,true)
fmt.Printf("%p \n",&d1)
}
方法&接收者
為任意型別定製一個自定義方法,必須要為該型別進行接收者限制。
接收者類似於其他語言中的self
與this
。
定義方法格式如下:
func (接收者變數 接收者型別) 方法名(引數列表) (返回引數) {
函式體
}
接收者變數:接收者中的引數變數名在命名時,官方建議使用接收者型別名稱首字母的小寫,而不是
self
、this
之類的命名。例如,Person
型別的接收者變數應該命名為p
,Connector
型別的接收者變數應該命名為c
等。接收者型別:接收者型別和引數類似,可以是指標型別和非指標型別。
方法名、引數列表、返回引數:具體格式與函式定義相同。
普通接收者
以下示例是定義普通接收者方法。
注意,這是值拷貝,意味著你的d
會拷貝d1
的資料。
package main
import (
"fmt"
)
type dog struct {
dogName string
dogAge int8
dogGender bool
}
func newDog(dogName string, dogAge int8, dogGender bool) *dog {
return &dog{
dogName: dogName,
dogAge: dogAge,
dogGender: dogGender,
}
}
func (d dog)getAge() int8 {
return d.dogAge // 返回狗狗的年齡
}
func main(){
d1 := newDog("大黃",12,true)
age := d1.getAge()
fmt.Print(age) // 12
}
指標接收者
由於普通接收者方法無法做到修改原本例項化物件資料的需求,所以我們可以定義指標接收者方法進行引用傳遞。
如下,呼叫addAge()
方法會將原本的年齡加上十歲。
package main
import (
"fmt"
)
type dog struct {
dogName string
dogAge int8
dogGender bool
}
func newDog(dogName string, dogAge int8, dogGender bool) *dog {
return &dog{
dogName: dogName,
dogAge: dogAge,
dogGender: dogGender,
}
}
func (d *dog) addAge() {
d.dogAge += 10
}
func main() {
d1 := newDog("大黃", 12, true)
fmt.Printf("舊年齡:%v", d1.dogAge) // 12
d1.addAge()
fmt.Printf("新年齡:%v", d1.dogAge) // 22
}
關於使用d1
直接呼叫,這是一種語法糖形式。完整的形式應該是使用&
取到地址後再進行傳遞,但是這樣會出現一些問題。
所以直接使用例項化物件呼叫即可。
下面是關於指標方法的一些使用注意事項:
- 修改原本例項化物件中的值時,應該使用指標接收者方法
- 例項化物件的內容較多,拷貝代價較大時,應該使用指標接收者方法
- 如果該物件下某個方法是指標接收者方法,那麼為了保持一致性,其他方法也應該使用指標方法。
自定型別方法
下面將對自定義型別small
做一個方法,getBool
獲取其布林值。
package main
import (
"fmt"
)
type small uint8
func (s small) getBool() bool {
if s != 0 {
return true
}
return false
}
func main() {
var num small
num = 18
result := num.getBool()
fmt.Print(result) // true
}
匿名欄位
基本使用
匿名欄位即只使用欄位型別,不使用欄位名。
使用較少
注意:這裡匿名欄位的說法並不代表沒有欄位名,而是預設會採用型別名作為欄位名,結構體要求欄位名稱必須唯一,因此一個結構體中同種型別的匿名欄位只能有一個。
package main
import (
"fmt"
)
type dog struct {
string // 只能出現一次同型別的欄位。
int8
bool
}
func main() {
d1 := dog{
string: "大黃",
int8: 12,
bool: true,
}
fmt.Print(d1)
}
結構體巢狀
基本使用
一個結構體中可以巢狀另一個結構體。
通過這種方式,可以達到繼承的效果。
package main
import (
"fmt"
)
type details struct {
phone string // 電話
addr string // 地址
}
type person struct {
name string
gender bool
age int8
details // 匿名欄位,詳細資訊
}
func main() {
p1 := person{
name: "雲崖",
gender: true,
age: 18,
details: details{ // 對匿名欄位的巢狀結構體進行例項化
phone: "1008611",
addr: "北京市海淀區",
},
}
fmt.Print(p1)
// {雲崖 true 18 {1008611 北京市海淀區}}
}
匿名簡寫
如果要訪問上例中的電話,可以使用簡寫形式。也可以使用全寫形式。
查詢順序是先查詢具名欄位,再查詢匿名欄位。
要注意多個結構體巢狀產生的欄位名衝突問題。
package main
import (
"fmt"
)
type details struct {
phone string // 電話
addr string // 地址
}
type person struct {
name string
gender bool
age int8
details // 匿名欄位,詳細資訊
}
func main() {
p1 := person{
name: "雲崖",
gender: true,
age: 18,
details: details{ // 對匿名欄位的巢狀結構體進行例項化
phone: "1008611",
addr: "北京市海淀區",
},
}
fmt.Println(p1.phone) // 簡寫
fmt.Println(p1.details.phone) // 全寫
}
JSON
使用JSON
包可對結構體進行序列化操作。
常用於前後端資料互動。
欄位可見性
由於json
包是再encoding/json
中,所以我們要想讓main
包的結構體能被json
包訪問,需要將結構體名字,欄位名字等進行首字母大寫。
結構體中欄位大寫開頭表示可公開訪問,小寫表示私有(僅在定義當前結構體的包中可訪問)。
基本使用
以下是關於JSON
序列化與反序列化的基本使用。
package main
import (
"encoding/json" // 導包
"fmt"
)
// Details 詳情 對於大寫的結構體,應該具有註釋。注意空格
type Details struct {
Phone string // 電話
Addr string // 地址
}
// Person 人
type Person struct {
Name string
Gender bool
Age int8
Details // 匿名欄位,詳細資訊
}
func main() {
p1 := Person{
Name: "雲崖",
Gender: true,
Age: 18,
Details: Details{ // 對匿名欄位的巢狀結構體進行例項化
Phone: "1008611",
Addr: "北京市海淀區",
},
}
// 序列化 得到一個[]bytes型別
data, err := json.Marshal(p1)
if err != nil {
fmt.Println("json error")
return
}
fmt.Println(string(data)) // 檢視結果
// {"Name":"雲崖","Gender":true,"Age":18,"Phone":"1008611","Addr":"北京市海淀區"}
// 反序列化
p2 := Person{}
json.Unmarshal(data, &p2) // 反序列化時需要例項化出該結構體。通過地址對其進行賦值
fmt.Println(p2) // {雲崖 true 18 {1008611 北京市海淀區}}
}
標籤使用
我們可以看到上面的序列化後的結果欄位名都是大寫名字開頭的。
{"Name":"雲崖","Gender":true,"Age":18,"Phone":"1008611","Addr":"北京市海淀區"}
怎麼樣把它轉換為小寫?這個需要使用到結構體標籤。
示例如下:
package main
import (
"encoding/json" // 導包
"fmt"
)
// Details 詳情 對於大寫的結構體,應該具有註釋。注意空格
type Details struct {
// 代表在json轉換中,Phone更名為phone orm中更名為phone 配置檔案ini中更名為phone
Phone string `json:"phone" db:"phone" ini:"phone"`
Addr string `json:"addr"`
}
// Person 人
type Person struct {
Name string `json:"name"`
Gender bool `json:"gender"`
Age int8 `json:"age"`
Details // 匿名欄位,詳細資訊
}
func main() {
p1 := Person{
Name: "雲崖",
Gender: true,
Age: 18,
Details: Details{ // 對匿名欄位的巢狀結構體進行例項化
Phone: "1008611",
Addr: "北京市海淀區",
},
}
// 序列化 得到一個[]bytes型別
data, err := json.Marshal(p1)
if err != nil {
fmt.Println("json error")
return
}
fmt.Println(string(data)) // 檢視結果
// {"name":"雲崖","gender":true,"age":18,"phone":"1008611","addr":"北京市海淀區"}
// 反序列化
p2 := Person{}
json.Unmarshal(data, &p2) // 反序列化時需要例項化出該結構體。通過地址對其進行賦值
fmt.Println(p2) // {雲崖 true 18 {1008611 北京市海淀區}}
}