本文將介紹Go中的各個類型種類。Go類型系統(tǒng)中的各種概念也將被介紹。如果不熟知這些概念,則很難精通Go編程。
內(nèi)置基本類型已經(jīng)在前面的文章基本類型和它們的字面量表示一文中介紹過了。 為了本文的完整性,這些內(nèi)置類型重新被列在這里:
string
?.bool
?.int8
?、?uint8
?(?byte
?)、?int16
?、?uint16
?、?int32
?(?rune
?)、?uint32
?、?int64
?、?uint64
?、?int
?、?uint
?、?uintptr
?。float32
?、?float64
?。complex64
?、?complex128
?。注意,byte
是uint8
的一個內(nèi)置別名,rune
是int32
的一個內(nèi)置別名。 下面將要提到如何聲明自定義的類型別名。
除了字符串類型,《Go語言101》后續(xù)其它文章將不再對其它基本類型做詳細講解。
這17個內(nèi)置基本類型屬于預(yù)聲明類型(predeclared type)。
Go支持下列組合類型:
無名組合類型可以用它們各自的字面表示形式來表示。 下面是一些各種不同種類的無名組合類型字面表示形式的例子(具名和無名類型將在下面解釋):
// 假設(shè)T為任意一個類型,Tkey為一個支持比較的類型。
*T // 一個指針類型
[5]T // 一個元素類型為T、元素個數(shù)為5的數(shù)組類型
[]T // 一個元素類型為T的切片類型
map[Tkey]T // 一個鍵值類型為Tkey、元素類型為T的映射類型
// 一個結(jié)構(gòu)體類型
struct {
name string
age int
}
// 一個函數(shù)類型
func(int) (bool, string)
// 一個接口類型
interface {
Method0(string) int
Method1() (int, bool)
}
// 幾個通道類型
chan T
chan<- T
<-chan T
支持和不支持比較的類型將在下面介紹。
每種上面提到的基本類型和組合類型都對應(yīng)著一個類型種類(kind)。除了這些種類,今后將要介紹的非類型安全指針類型屬于另外一個新的類型種類。
所以,目前(Go 1.19),Go有26個類型種類。
(類型定義又稱類型定義聲明。在Go 1.9之前,類型定義被稱為類型聲明并且是唯一的一種類型聲明形式。 但是自從Go 1.9,類型定義變成了兩種類型聲明形式之一。另一種新的類型聲明形式為后面的一節(jié)中將要介紹的類型別名聲明。)
在Go中,我們可以用如下形式來定義新的類型。在此語法中,type
為一個關(guān)鍵字。
// 定義單個類型。
type NewTypeName SourceType
// 定義多個類型(將多個類型描述合并在一個聲明中)。
type (
NewTypeName1 SourceType1
NewTypeName2 SourceType2
)
新的類型名必須為標識符。但是請注意:包級類型(以及下一節(jié)將要介紹的類型別名)的名稱不能為init。
上例中的第二個類型聲明中包含兩個類型描述(type specification)。 如果一個類型聲明包含多于一個的類型描述,這些類型描述必須用一對小括號?()
?括起來。
每個類型描述創(chuàng)建了一個全新的定義類型(defined type)。
注意:
一些類型定義的例子:
// 下面這些新定義的類型和它們的源類型都是基本類型。
// 它們的源類型均為預(yù)聲明類型。
type (
MyInt int
Age int
Text string
)
// 下面這些新定義的類型和它們的源類型都是組合類型。
// 它們的源類型均為無名類型(見下下節(jié))。
type IntPtr *int
type Book struct{author, title string; pages int}
type Convert func(in0 int, in1 bool)(out0 int, out1 string)
type StringArray [5]string
type StringSlice []string
func f() {
// 這三個新定義的類型名稱只能在此函數(shù)內(nèi)使用。
type PersonAge map[string]int
type MessageQueue chan string
type Reader interface{Read([]byte) int}
}
請注意:從Go 1.9到Go 1.17,Go白皮書曾經(jīng)把預(yù)聲明類型視為定義類型。 但是從Go 1.18開始,Go白皮書明確說明預(yù)聲明類型不再屬于定義類型。
從Go 1.18開始,Go開始支持自定義泛型類型(和函數(shù))。 一個泛型類型必須被實例化才能被用做值類型。
一個泛型類型是一個定義類型;它的實例化類型為具名類型。具名類型將在下一節(jié)解釋。
自定義泛型中的另外兩個重要的概念為類型約束(constarint)和類型參數(shù)(type parameter)。
本書不詳細闡述自定義泛型。關(guān)于如何聲明和使用泛型類型和函數(shù),請閱讀《Go自定義泛型101》。
在Go 1.9之前,具名類型這個術(shù)語在Go白皮書中是精確定義的。 在那時,一個具名類型被定義為一個可以用標識符表示的類型。 隨著在Go 1.9中引入了自定義類型別名(見下一節(jié)),具名類型這個術(shù)語被從白皮書中刪除了;取而代之的是定義類型。 隨著Go 1.18中引入了自定義泛型,具名類型這個術(shù)語又被重新加回到白皮書。
其它類型稱為無名類型。一個無名類型肯定是一個組合類型(反之則未必)。
從Go 1.9開始,我們可以使用下面的語法來聲明自定義類型別名。此語法和類型定義類似,但是請注意每個類型描述中多了一個等號=
。
type (
Name = string
Age = int
)
type table = map[string]int
type Table = map[Name]Age
類型別名也必須為標識符。同樣地,類型別名可以被聲明在函數(shù)體內(nèi)。
在上面的類型別名聲明的例子中,Name
是內(nèi)置類型string
的一個別名,它們表示同一個類型。 同樣的關(guān)系對下面的幾對類型表示也成立:
Age
?和內(nèi)置類型?int
?。table
?和映射類型?map[string]int
?。Table
?和映射類型?map[Name]Age
?。事實上,文字表示形式?map[string]int
?和?map[Name]Age
?也表示同一類型。 所以,?table
?和?Table
?一樣表示同一個類型。
注意:盡管一個類型別名有一個名字,但是它可能表示一個無名類型。 比如,?table
?和?Table
?這兩個別名都表示同一個無名類型?map[string]int
?。
unsafe
?標準庫包中定義的?Pointer
?類型的底層類型是它自己。 (至少我們可以認為是這樣。事實上,關(guān)于?unsafe.Pointer
?類型的底層類型,官方文檔中并沒有清晰的說明。我們也可以認為?unsafe.Pointer
?類型的底層類型為?*T
?,其中?T
?表示一個任意類型。) ?unsafe.Pointer
?也被視為一個內(nèi)置類型。一個例子:
// 這四個類型的底層類型均為內(nèi)置類型int。
type (
MyInt int
Age MyInt
)
// 下面這三個新聲明的類型的底層類型各不相同。
type (
IntSlice []int // 底層類型為[]int
MyIntSlice []MyInt // 底層類型為[]MyInt
AgeSlice []Age // 底層類型為[]Age
)
// 類型[]Age、Ages和AgeSlice的底層類型均為[]Age。
type Ages AgeSlice
如何溯源一個聲明的類型的底層類型?規(guī)則很簡單,在溯源過程中,當遇到一個內(nèi)置類型或者無名類型時,溯源結(jié)束。 以上面這幾個聲明的類型為例,下面是它們的底層類型的溯源過程:
MyInt → int Age → MyInt → int IntSlice → []int MyIntSlice → []MyInt → []int AgeSlice → []Age → []MyInt → []int Ages → AgeSlice → []Age → []MyInt → []int
在Go中,
bool
?的類型稱為布爾類型;float32
?或者?float64
?的類型稱為浮點數(shù)類型;complex64
?或?complex128
?的類型稱為復(fù)數(shù)類型;string
?的類型稱為字符串類型。底層類型這個概念在類型轉(zhuǎn)換、賦值和比較規(guī)則中扮演著重要角色。
一個類型的一個實例稱為此類型的一個值。一個類型可以有很多不同的值,其中一個為它的零值。 同一類型的不同值共享很多相同的屬性。
每個類型有一個零值。一個類型的零值可以看作是此類型的默認值。 預(yù)聲明的標識符?nil
?可以看作是切片、映射、函數(shù)、通道、指針(包括非類型安全指針)和接口類型的零值的字面量表示。 我們以后可以在Go中的nil一文中了解到關(guān)于?nil
?的各種事實。
在源代碼中,值可以呈現(xiàn)為若干種形式,包括字面量、具名常量、變量和表達式。前三種形式可以看作是最后一種形式的特例。
值分為類型確定的和類型不確定的。
基本類型和它們的字面量表示已經(jīng)在前面一文中介紹過了。 另外,Go中還有另外兩種的字面量表示形式:函數(shù)字面量表示形式和組合字面量表示形式(composite literal)。
函數(shù)字面量表示形式用來表示函數(shù)值。事實上,一個函數(shù)聲明是由一個標識符(函數(shù)名)和一個函數(shù)字面量表示形式組成。
組合字面量表示形式用來表示結(jié)構(gòu)體類型值和容器類型(數(shù)組、切片和映射)值。 詳見結(jié)構(gòu)體和容器類型兩文。
指針類型、通道類型和接口類型的值沒有字面量表示形式。
在運行時刻,很多值是存儲在內(nèi)存的。每個這樣的值都有一個直接部分,但是有一些值還可能有一個或多個間接部分。每個值部分在內(nèi)存中都占據(jù)一段連續(xù)空間。 通過安全或者非安全指針,一個值的間接部分被此值的直接部分所引用。
值部這個術(shù)語并沒有在Go白皮書中定義。它僅使用在《Go語言101》這本書中,用來簡化一些解釋并幫助Go程序員更好地理解Go類型和值。
一個值存儲在內(nèi)存中是要占據(jù)一定的空間的。此空間的大小稱為此值的尺寸。值尺寸是用字節(jié)數(shù)來衡量的。 在Go中,當我們談及一個值的尺寸,如果沒有特殊說明,我們一般是指此值的直接部分的尺寸。 某個特定類別的所有類型的值的尺寸都是一樣的。因為這個原因,我們也常將一個值的尺寸說成是它的類型的尺寸(或值尺寸)。
我們可以用?unsafe
?標準庫包中的?Sizeof
?函數(shù)來取得任何一個值的尺寸。
Go白皮書沒有規(guī)定非數(shù)值類型值的尺寸。對數(shù)值類型值的尺寸的要求已經(jīng)在基本類型和它們的字面量表示一文中提及了。
如果一個指針類型的底層類型表示為?*T
?,則此指針類型的基類型為?T
?所表示的類型。
指針類一文詳細解釋了指針類類型和指針值。
一個結(jié)構(gòu)體類型由若干成員變量組成。每個這樣的成員變量稱為此結(jié)構(gòu)體的一個字段。 比如,下面這個結(jié)構(gòu)體類型含有三個字段:author
、title
和pages
。
struct {
author string
title string
pages int
}
結(jié)構(gòu)體一文詳細解釋了結(jié)構(gòu)體類型和結(jié)構(gòu)體值。
一個函數(shù)和其類型的簽名由此函數(shù)的輸入?yún)?shù)和返回結(jié)果的類型列表組成。 函數(shù)名稱和函數(shù)體不屬于函數(shù)簽名的構(gòu)成部分。
函數(shù)一文詳細解釋了函數(shù)類型和函數(shù)值。
在Go中,我們可以給滿足某些條件的類型聲明方法。方法也常被稱為成員函數(shù)。 一個類型的所有方法組成了此類型的方法集。
接口類型的值稱為接口值。一個接口值可以包裹裝載一個非接口值。包裹在一個接口值中的非接口值稱為此接口值的動態(tài)值。此動態(tài)值的類型稱為此接口值的動態(tài)類型。 一個什么也沒包裹的接口值為一個零值接口值。零值接口值的動態(tài)值和動態(tài)類型均為不存在。
一個接口類型可以指定若干個(可以是零個)方法,這些方法形成了此接口類型的方法集。
如果一個類型(可以是接口或者非接口類型)的方法集是一個接口類型的方法集的超集,則我們說此類型實現(xiàn)了此接口類型。
接口一文詳細解釋了接口類型和接口值。
對于一個(類型確定的)非接口值,它的具體類型就是它的類型,它的具體值就是它自己。
一個零值接口值沒有具體類型和具體值。 對于一個非零值接口值,它的具體類型和具體值就是它的動態(tài)類型和動態(tài)值。
數(shù)組、切片和映射是Go中的三種正式意義上的內(nèi)置容器類型。
有時候,字符串和通道類型也可以被非正式地看作是容器類型。
(正式和非正式的)容器類型的每個值都有一個長度屬性。
數(shù)組、切片和映射一文詳細解釋了各種正式容器類型和它們的值。
如果一個映射類型的底層類型表示為?map[Tkey]T
?,則此映射類型的鍵值類型為?Tkey
?。 ?Tkey
?必須為一個可比較類型(見下)。
存儲在一個容器值中的所有元素的類型必須為同一個類型。此同一類型稱為此容器值的(容器)類型的元素類型。
[N]T
?,則此數(shù)組類型的元素類型為?T
?所表示的類型。[]T
?,則此切片類型的元素類型為?T
?所表示的類型。map[Tkey]T
?,則此映射類型的元素類型為?T
?所表示的類型。chan T
?、?chan<- T
?或者?<-chan T
?,則此通道類型的元素類型為?T
?所表示的類型。byte
?(亦即?uint8
?)。一個通道值可以被看作是先入先出(first-in-first-out,F(xiàn)IFO)隊列。一個通道值可能是可讀可寫的、只讀的(receive-only)或者只寫的(send-only)。
chan T
?。chan<- T
?。<-chan T
?。通道一文詳細解釋了通道類型和通道值。
目前(Go 1.19),下面這些類型的值不支持(使用==
和!=
運算標識符)比較。這些類型稱為不可比較類型。
其它類型稱為可比較類型。
映射類型的鍵值類型必須為可比較類型。
我們可以在類型轉(zhuǎn)換、賦值和值比較規(guī)則大全一文中了解到更詳細的比較規(guī)則。
Go并不全面支持面向?qū)ο缶幊?,但是Go確實支持一些面向?qū)ο缶幊痰脑亍U堥喿x以下幾篇文章以獲取詳細信息:
在1.18版本以前,Go中泛型支持只局限在內(nèi)置類型和內(nèi)置函數(shù)中。 從1.18版本開始,Go也支持自定義泛型。 請閱讀泛型一文來了解內(nèi)置泛型和《Go自定義泛型101》一書來了解自定義泛型。
更多建議: