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