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

2023-02-16 17:41 更新

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

概念:基本類型(basic type)

內(nèi)置基本類型已經(jīng)在前面的文章基本類型和它們的字面量表示一文中介紹過了。 為了本文的完整性,這些內(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的一個內(nèi)置別名,runeint32的一個內(nèi)置別名。 下面將要提到如何聲明自定義的類型別名。

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

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

概念:組合類型(composite type)

Go支持下列組合類型:

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

無名組合類型可以用它們各自的字面表示形式來表示。 下面是一些各種不同種類的無名組合類型字面表示形式的例子(具名和無名類型將在下面解釋):

// 假設(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個類型種類。

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

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

注意:

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

一些類型定義的例子:

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

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

從Go 1.18開始,Go開始支持自定義泛型類型(和函數(shù))。 一個泛型類型必須被實例化才能被用做值類型。

一個泛型類型是一個定義類型;它的實例化類型為具名類型。具名類型將在下一節(jié)解釋。

自定義泛型中的另外兩個重要的概念為類型約束(constarint)和類型參數(shù)(type parameter)。

本書不詳細闡述自定義泛型。關(guān)于如何聲明和使用泛型類型和函數(shù),請閱讀《Go自定義泛型101》。

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

在Go 1.9之前,具名類型這個術(shù)語在Go白皮書中是精確定義的。 在那時,一個具名類型被定義為一個可以用標識符表示的類型。 隨著在Go 1.9中引入了自定義類型別名(見下一節(jié)),具名類型這個術(shù)語被從白皮書中刪除了;取而代之的是定義類型。 隨著Go 1.18中引入了自定義泛型,具名類型這個術(shù)語又被重新加回到白皮書。

一個具名類型可能為

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

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

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

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

概念:底層類型(underlying type)

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

  • 一個內(nèi)置類型的底層類型為它自己。
  • ?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中,

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

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

概念:值(value)

一個類型的一個實例稱為此類型的一個值。一個類型可以有很多不同的值,其中一個為它的零值。 同一類型的不同值共享很多相同的屬性。

每個類型有一個零值。一個類型的零值可以看作是此類型的默認值。 預(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)體容器類型兩文。

指針類型、通道類型和接口類型的值沒有字面量表示形式。

概念:值部(value part)

在運行時刻,很多值是存儲在內(nèi)存的。每個這樣的值都有一個直接部分,但是有一些值還可能有一個或多個間接部分。每個值部分在內(nèi)存中都占據(jù)一段連續(xù)空間。 通過安全或者非安全指針,一個值的間接部分被此值的直接部分所引用。

值部這個術(shù)語并沒有在Go白皮書中定義。它僅使用在《Go語言101》這本書中,用來簡化一些解釋并幫助Go程序員更好地理解Go類型和值。

概念:值尺寸(value size)

一個值存儲在內(nèi)存中是要占據(jù)一定的空間的。此空間的大小稱為此值的尺寸。值尺寸是用字節(jié)數(shù)來衡量的。 在Go中,當我們談及一個值的尺寸,如果沒有特殊說明,我們一般是指此值的直接部分的尺寸。 某個特定類別的所有類型的值的尺寸都是一樣的。因為這個原因,我們也常將一個值的尺寸說成是它的類型的尺寸(或值尺寸)。

我們可以用?unsafe?標準庫包中的?Sizeof?函數(shù)來取得任何一個值的尺寸。

Go白皮書沒有規(guī)定非數(shù)值類型值的尺寸。對數(shù)值類型值的尺寸的要求已經(jīng)在基本類型和它們的字面量表示一文中提及了。

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

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

指針類一文詳細解釋了指針類類型和指針值。

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

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

struct {
	author string
	title  string
	pages  int
}

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

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

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

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

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

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

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

接口類型的值稱為接口值。一個接口值可以包裹裝載一個非接口值。包裹在一個接口值中的非接口值稱為此接口值的動態(tài)值。此動態(tài)值的類型稱為此接口值的動態(tài)類型。 一個什么也沒包裹的接口值為一個零值接口值。零值接口值的動態(tài)值和動態(tài)類型均為不存在。

一個接口類型可以指定若干個(可以是零個)方法,這些方法形成了此接口類型的方法集。

如果一個類型(可以是接口或者非接口類型)的方法集是一個接口類型的方法集的超集,則我們說此類型實現(xiàn)了此接口類型。

接口一文詳細解釋了接口類型和接口值。

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

對于一個(類型確定的)非接口值,它的具體類型就是它的類型,它的具體值就是它自己。

一個零值接口值沒有具體類型和具體值。 對于一個非零值接口值,它的具體類型和具體值就是它的動態(tài)類型和動態(tài)值。

概念:容器類型

數(shù)組、切片和映射是Go中的三種正式意義上的內(nèi)置容器類型。

有時候,字符串和通道類型也可以被非正式地看作是容器類型。

(正式和非正式的)容器類型的每個值都有一個長度屬性。

數(shù)組、切片和映射一文詳細解釋了各種正式容器類型和它們的值。

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

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

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

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

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

概念:通道類型的方向

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

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

通道一文詳細解釋了通道類型和通道值。

事實:可比較類型和不可比較類型

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

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

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

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

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

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

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

事實:Go對泛型(generics)的支持

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


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

掃描二維碼

下載編程獅App

公眾號
微信公眾號

編程獅公眾號