类型系统

Written by with ♥ on in Go

Go 语言是静态类型的编程语言,意味着编译器需要在编译时确定每个值的类型。Go 语言中的类型分为基本类型和复合类型,它们都是语言的内置类型,我们在内置类型的基础上开发程序。

基本类型

  • 字符串类型:string
  • 布尔类型:bool
  • 数字类型:
    • int8, uint8 (byte), int16, uint16, int32 (rune), uint32, int64, uint64, int, uint, uinptr
    • float32, float64
    • complex64, complex128

复合类型

Go 支持以下多种复合类型(composite types):

  • 指针类型(类似 C 指针):pointer
  • 结构体类型(类似 C 结构体):struct
  • 函数类型(Go 中的函数是第一类公民):function
  • 容器类型:
    • 数组类型(固定长度):array
    • 切片类型(动态长度,动态扩容):slice
    • 字典类型(标准 Go 编译器将 map 实现为 hashtable):map
  • 通道类型:channel
  • 接口类型:interface

上面这些类型都有各自的字面量表示法:

// T 表示某个类型
*T // pointer type
[5]T // array type
[]T // slice type
map[key]T // map type

// struct type
struct {
    name string
    age int
}

// function type
func(int) (bool, string)

// interface type
interface {
    Call(string) int
}

// channel type
chan T
chan<- T
<-chan T

unsafe 包可以直接操作内存,在 Go 中也是一种类型 unsafe.Pointer

类型一览

Go 语言中的函数名、变量名、常量名、类型名、语句标号和包名等命名,都遵循一个简单的命名规则:

  • 一个名字必须以一个字母或下划线开头,后面可以跟任意数量的字母、数字或下划线
  • 大写字母和小写字母是不同的命名
  • 不能与关键字重名
  • 最好不要与内置类型、内置常量、内置函数重名,虽然可以重新定义
类型 ⻓度 默认值(零值) 说明
bool 1 false
byte 1 0 uint8
rune 4 0 Unicode Code Point, int32
int, uint 4 或 8 0 32 或 64 位
int8, uint8 1 0 -128 ~ 127, 0 ~ 255
int16, uint16 2 0 -32768 ~ 32767, 0 ~ 65535
int32, uint32 4 0 -21亿 ~ 21 亿, 0 ~ 42 亿
int64, uint64 8 0
float32 4 0.0
float64 8 0.0
complex64 8
complex128 16
uintptr 4 或 8 ⾜以存储指针的 uint32 或 uint64 整数
array 值类型
struct 值类型
string ”” UTF-8 字符串
slice nil 引⽤类型
map nil 引⽤类型
channel nil 引⽤类型
interface nil 接⼝
function nil 函数

更详细的类型信息可以参考 Go语言规范-Type

int、uint、uintpter 类型在 32 位系统上一般是 32 位,在 64 位系统上是 64 位。

类型定义

类型定义(Type definition)又叫做类型声明(Type declaration)。

在 Go 1.9 版本之前只有类型声明一种方式定义类型,在 1.9 版本之后新增了一种名为类型别名声明(Type alias declaration)的方式。

使用 type 关键字定义新类型:

type NewType SourceType
  • 通过 type 定义的 NewTypeSourceType 是完全不同的两个类型;
  • NewTypeSourceType 共享相同的底层类型,两者之间可以相互转换
  • 类型可以在函数作用域中定义,也只能在函数作用域中使用。
type MyInt int
type Age int
type Text string

type IntPtr *int
type Book struct{name string; age int}
type Convert func(int, bool) (error)
type StringArray [5]string
type StringSlice []string

func f() {
    // 函数内定义
    type MyMap map[string]int
    type Queue chan string
    type Reader interface{
        Read([]byte) int
    }
}

类型别名

之前说到 1.9 版本之后新增的类型声明方式 Type Alias Declaration,下面就详细解释该方式的作用。

在 1.9 版本之前其实也有两个内置类型别名,byteuint8 的别名,runeint32 的别名,但不能自定义类型别名!

在 1.9 版本之后,新增的语法可以自定义类型别名,只需要在声明类型时加一个 = 号:

type Name = string
type Age = int
type table = map[string]int
type Table = map[Name]Age
  • Name 是 string 的别名,表示同一个类型(无需转换就可以通用)。
  • Table 和 table 是同一个类型

::: tip 这种方式与 C 语言中的宏类似,在编译时作文本替换。 :::

定义类型与未定义类型

Defined Types vs Non-Defined Types

type A []string // A 是定义类型
type B = A // B 是定义类型 A 的别名,也是定义类型
type C = []string // C 是非定义类型 []string 的别名,也是非定义类型
  • 类型字面量 []string 是非定义类型;
  • 非定义类型都是复合类型;
  • C 是非定义类型:因为非定义类型的别名是也是非定义类型。

命名类型与未命名类型

引用一段官方的讨论,大概意思是为什么 float64 是命名类型,但 []float64 是未命名类型。

官方的解释是:有名字的类型是命名类型,例如 int 是命名类型,*int 是未命名类型,使用 type PI *int 定义一个类型 PI,则 PI 是命名类型。

下面是我总结的规律:

  • 基本类型都是命名类型;
  • 未命名类型一定是复合类型;
  • 复合类型字面量都是未命名类型。

理解的难点主要在于,int 是命名类型,[]int 是未命名类型,难道 []int 不应该是命名类型?它的名字不是 int slice?不是!

类型字面量如 []int、map[int]int 永远不是有效的 Go 标识符,它们都是匿名类型,匿名的当然是未命名类型。

我认为在 Go 语言的规范中 slice、map 等复合类型没有确定的类型名字,它们的类型随着内部存储的类型产生变化,所以是未命名类型。

复合类型字面量都是未命名类型。

同一个未命名类型定义的不同命名类型变量可以相互赋值,例如:

[]int // 未命名类型
int // 命名类型
type T []int // 命名类型 T

var t T
t = []int{1, 2, 3} // []int 类型 可以赋值给 T 类型

为什么可以赋值?因为 T 的底层类型是 []int

分辨未命名类型

未命名类型一定是复合类型,区分未命名类型就是区分复合类型,每个复合类型都可以细分成多个不同的类型。例如 []int[]string 就是两个不同的类型。

下面是判断未命名类型是否是同一个类型的规则:

  • 具有相同元素类型和⻓度的 array
  • 具有相同元素类型的 slice
  • 具有相同键值类型的 map
  • 具有相同元素类型和传送方方向的 channel
  • 具有相同字段序列 (字段名、类型、标签、顺序) 的匿名 struct
  • 签名相同 (参数和返回值) 的 function
  • 方法集相同 (方法名和方法签名相同与次序无无关) 的 interface
  • 具有相同基类型的 pointer

底层类型

每个类型都有对应的底层类型(underlying type):

如何查找用户定义类型的底层类型?下面是查找规则:

  • 内建类型的底层类型是自己;
  • unsafe.Pointer 的底层类型是自己;
  • 未命名类型的底层类型是自己;
  • 自定义的 NewTypeSourceType 共享相同的底层类型。
// The underlying types of the following ones are both int.
type (
	MyInt int
	Age   MyInt
)

// The following new types have different underlying types.
// 下面的 []int、[]MyInt、[]Age 都是未命名类型
// IntSlice、MyIntSlice、AgeSlice 都是命名类型
type (
	IntSlice = []int   // underlying type is []int
	MyIntSlice []MyInt // underlying type is []MyInt
	AgeSlice   []Age   // underlying type is []Age
)

// The underlying types of Ages and AgeSlice are both []Age.
// Ages 是命名类型
type Ages AgeSlice

MyInt → int // 找到内建基本类型时停止查找
Age → MyInt → int // 找到内建基本类型时停止查找
IntSlice → []int // 找到未命名类型 []int 停止查找
MyIntSlice → []MyInt → | stop []int // 找到未命名类型 []MyInt 停止查找
AgeSlice → []Age → | stop []MyInt → []int // 找到未命名类型 []Age 停止查找
Ages → AgeSlice → []Age → | stop []MyInt → []int // 找到未命名类型 []Age 停止查找

不同类型之间的值转换、赋值、比较(compare)与底层类型密切相关。

参考资料