Go語(yǔ)言 類型系統(tǒng)概述

2023-02-16 17:41 更新

本文將介紹Go中的各個(gè)類型種類。Go類型系統(tǒng)中的各種概念也將被介紹。如果不熟知這些概念,則很難精通Go編程。

概念:基本類型(basic type)

內(nèi)置基本類型已經(jīng)在前面的文章基本類型和它們的字面量表示一文中介紹過(guò)了。 為了本文的完整性,這些內(nèi)置類型重新被列在這里:

  • 內(nèi)置字符串類型:?string?.
  • 內(nèi)置布爾類型:?bool?.
  • 內(nèi)置數(shù)值類型:
    • ?int8?、?uint8?(?byte?)、?int16?、?uint16?、?int32?(?rune?)、?uint32?、?int64?、?uint64?、?int?、?uint?、?uintptr?。
    • ?float32?、?float64?。
    • ?complex64?、?complex128?。

注意,byteuint8的一個(gè)內(nèi)置別名,runeint32的一個(gè)內(nèi)置別名。 下面將要提到如何聲明自定義的類型別名。

除了字符串類型,《Go語(yǔ)言101》后續(xù)其它文章將不再對(duì)其它基本類型做詳細(xì)講解。

這17個(gè)內(nèi)置基本類型屬于預(yù)聲明類型(predeclared type)。

概念:組合類型(composite type)

Go支持下列組合類型:

  • 指針類型 - 類C指針
  • 結(jié)構(gòu)體類型 - 類C結(jié)構(gòu)體
  • 函數(shù)類型 - 函數(shù)類型在Go中是一種一等公民類別
  • 容器類型,包括:
    • 數(shù)組類型 - 定長(zhǎng)容器類型
    • 切片類型 - 動(dòng)態(tài)長(zhǎng)度和容量容器類型
    • 映射類型(map)- 也常稱為字典類型。在標(biāo)準(zhǔn)編譯器中映射是使用哈希表實(shí)現(xiàn)的。
  • 通道類型 - 通道用來(lái)同步并發(fā)的協(xié)程
  • 接口類型 - 接口在反射和多態(tài)中發(fā)揮著重要角色

無(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

支持和不支持比較的類型將在下面介紹。

事實(shí):類型的種類

每種上面提到的基本類型和組合類型都對(duì)應(yīng)著一個(gè)類型種類(kind)。除了這些種類,今后將要介紹的非類型安全指針類型屬于另外一個(gè)新的類型種類。

所以,目前(Go 1.19),Go有26個(gè)類型種類。

語(yǔ)法:類型定義(type definition declaration)

類型定義又稱類型定義聲明。在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)。

注意:

  • 一個(gè)新定義的類型和它的源類型為兩個(gè)不同的類型。
  • 在兩個(gè)不同的類型定義中所定義的兩個(gè)類型肯定是兩個(gè)不同的類型。
  • 一個(gè)新定義的類型和它的源類型的底層類型(將在下面介紹)一致并且它們的值可以相互顯式轉(zhuǎn)換。
  • 類型定義可以出現(xiàn)在函數(shù)體內(nèi)。

一些類型定義的例子:

// 下面這些新定義的類型和它們的源類型都是基本類型。
// 它們的源類型均為預(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ù)聲明類型不再屬于定義類型。

概念:自定義泛型類型和實(shí)例化類型(generic type and instantiated types)

從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》

概念:具名類型和無(wú)名類型(named type and unnamed type)

在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ǔ)又被重新加回到白皮書。

一個(gè)具名類型可能為

  • 一個(gè)預(yù)聲明類型;
  • 一個(gè)定義(非自定義泛型)類型;
  • 一個(gè)(泛型類型的)實(shí)例化類型;
  • 一個(gè)類型參數(shù)類型(使用在自定義泛型中)。

其它類型稱為無(wú)名類型。一個(gè)無(wú)名類型肯定是一個(gè)組合類型(反之則未必)。

語(yǔ)法:類型別名聲明(type alias declaration)

從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?。

概念:底層類型(underlying type)

在Go中,每個(gè)類型都有一個(gè)底層類型。規(guī)則:

  • 一個(gè)內(nèi)置類型的底層類型為它自己。
  • ?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è)無(wú)名類型(必為一個(gè)組合類型)的底層類型為它自己。
  • 在一個(gè)類型聲明中,新聲明的類型和源類型共享底層類型。

一個(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中,

  • 底層類型為內(nèi)置類型?bool?的類型稱為布爾類型;
  • 底層類型為任一內(nèi)置整數(shù)類型的類型稱為整數(shù)類型
  • 底層類型為內(nèi)置類型?float32?或者?float64?的類型稱為浮點(diǎn)數(shù)類型;
  • 底層類型為內(nèi)置類型?complex64?或?complex128?的類型稱為復(fù)數(shù)類型
  • 整數(shù)類型、浮點(diǎn)數(shù)類型和復(fù)數(shù)類型統(tǒng)稱為數(shù)字值類型;
  • 底層類型為內(nèi)置類型?string?的類型稱為字符串類型

底層類型這個(gè)概念在類型轉(zhuǎn)換、賦值和比較規(guī)則中扮演著重要角色。

概念:值(value)

一個(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)有字面量表示形式。

概念:值部(value part)

在運(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類型和值。

概念:值尺寸(value size)

一個(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)在基本類型和它們的字面量表示一文中提及了。

概念:指針類型的基類型(base type)

如果一個(gè)指針類型的底層類型表示為?*T?,則此指針類型的基類型為?T?所表示的類型。

指針類一文詳細(xì)解釋了指針類類型和指針值。

概念:結(jié)構(gòu)體類型的字段(field)

一個(gè)結(jié)構(gòu)體類型由若干成員變量組成。每個(gè)這樣的成員變量稱為此結(jié)構(gòu)體的一個(gè)字段。 比如,下面這個(gè)結(jié)構(gòu)體類型含有三個(gè)字段:author、titlepages。

struct {
	author string
	title  string
	pages  int
}

結(jié)構(gòu)體一文詳細(xì)解釋了結(jié)構(gòu)體類型和結(jié)構(gòu)體值。

概念:函數(shù)類型的簽名(signature)

一個(gè)函數(shù)和其類型的簽名由此函數(shù)的輸入?yún)?shù)和返回結(jié)果的類型列表組成。 函數(shù)名稱和函數(shù)體不屬于函數(shù)簽名的構(gòu)成部分。

函數(shù)一文詳細(xì)解釋了函數(shù)類型和函數(shù)值。

概念:類型的方法(method)和方法集(method set)

在Go中,我們可以給滿足某些條件的類型聲明方法。方法也常被稱為成員函數(shù)。 一個(gè)類型的所有方法組成了此類型的方法集。

概念:接口類型的動(dòng)態(tài)類型和動(dòng)態(tài)值

接口類型的值稱為接口值。一個(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ì)解釋了接口類型和接口值。

概念:一個(gè)值的具體類型(concrete type)和具體值(concrete value)

對(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ì)解釋了各種正式容器類型和它們的值。

概念:映射類型的鍵值(key)類型

如果一個(gè)映射類型的底層類型表示為?map[Tkey]T?,則此映射類型的鍵值類型為?Tkey?。 ?Tkey?必須為一個(gè)可比較類型(見(jiàn)下)。

概念:容器類型的元素(element)類型

存儲(chǔ)在一個(gè)容器值中的所有元素的類型必須為同一個(gè)類型。此同一類型稱為此容器值的(容器)類型的元素類型。

  • 如果一個(gè)數(shù)組類型的底層類型表示為?[N]T?,則此數(shù)組類型的元素類型為?T?所表示的類型。
  • 如果一個(gè)切片類型的底層類型表示為?[]T?,則此切片類型的元素類型為?T?所表示的類型。
  • 如果一個(gè)映射類型的底層類型表示為?map[Tkey]T?,則此映射類型的元素類型為?T?所表示的類型。
  • 如果一個(gè)通道類型的底層類型表示為?chan T?、?chan<- T?或者?<-chan T?,則此通道類型的元素類型為?T?所表示的類型。
  • 一個(gè)字符串類型的元素類型總是內(nèi)置類型?byte?(亦即?uint8?)。

概念:通道類型的方向

一個(gè)通道值可以被看作是先入先出(first-in-first-out,F(xiàn)IFO)隊(duì)列。一個(gè)通道值可能是可讀可寫的、只讀的(receive-only)或者只寫的(send-only)。

  • 一個(gè)可讀可寫的通道值也稱為一個(gè)雙向通道。 一個(gè)雙向通道類型的底層類型可以被表示為?chan T?。
  • 我們只能向一個(gè)只寫的通道值發(fā)送數(shù)據(jù),而不能從其中接收數(shù)據(jù)。 只寫通道類型的底層類型可以被表示為?chan<- T?。
  • 我們只能從一個(gè)只讀的通道值接收數(shù)據(jù),而不能向其發(fā)送數(shù)據(jù)。 只讀通道類型的底層類型可以被表示為?<-chan T?。

通道一文詳細(xì)解釋了通道類型和通道值。

事實(shí):可比較類型和不可比較類型

目前(Go 1.19),下面這些類型的值不支持(使用==!=運(yùn)算標(biāo)識(shí)符)比較。這些類型稱為不可比較類型。

  • 切片類型
  • 映射類型
  • 函數(shù)類型
  • 任何包含有不可比較類型的字段的結(jié)構(gòu)體類型和任何元素類型為不可比較類型的數(shù)組類型。

其它類型稱為可比較類型。

映射類型的鍵值類型必須為可比較類型。

我們可以在類型轉(zhuǎn)換、賦值和值比較規(guī)則大全一文中了解到更詳細(xì)的比較規(guī)則。

事實(shí):Go對(duì)面向?qū)ο缶幊蹋╫bject-oriented programming)的支持

Go并不全面支持面向?qū)ο缶幊?,但是Go確實(shí)支持一些面向?qū)ο缶幊痰脑亍U?qǐng)閱讀以下幾篇文章以獲取詳細(xì)信息:

事實(shí):Go對(duì)泛型(generics)的支持

在1.18版本以前,Go中泛型支持只局限在內(nèi)置類型和內(nèi)置函數(shù)中。 從1.18版本開(kāi)始,Go也支持自定義泛型。 請(qǐng)閱讀泛型一文來(lái)了解內(nèi)置泛型和《Go自定義泛型101》一書來(lái)了解自定義泛型。


以上內(nèi)容是否對(duì)您有幫助:
在線筆記
App下載
App下載

掃描二維碼

下載編程獅App

公眾號(hào)
微信公眾號(hào)

編程獅公眾號(hào)