概念:基本类型(basic type)
- 内置字符串类型:
string
. - 内置布尔类型:
bool
. - 内置数值类型:
int8
、uint8
(byte
)、int16
、uint16
、int32
(rune
)、uint32
、int64
、uint64
、int
、uint
、uintptr
。float32
、float64
。complex64
、complex128
。
注意,byte
是uint8
的一个内置别名,rune
是int32
的一个内置别名。 下面将要提到如何声明自定义的类型别名。
除了字符串类型,《Go语言101》后续其它文章将不再对其它基本类型做详细讲解。
概念:组合类型(composite type)
// 假设T为任意一个类型,Tkey为一个支持比较的类型。 *T // 一个指针类型 [5]T // 一个元素类型为T、元素个数为5的数组类型 []T // 一个元素类型为T的切片类型 map[Tkey]T // 一个键值类型为Tkey、元素类型为T的映射类型 // 一个结构体类型 struct { name string age int } // 一个函数类型 func(int) (bool, string) // 一个接口类型 interface { Method0(string) int Method1() (int, bool) } // 几个通道类型 chan T chan<- T <-chan T
语法:类型定义(type definition declaration)
(类型定义又称类型定义声明。在Go 1.9之前,类型定义被称为类型声明并且是唯一的一种类型声明形式。 但是自从Go 1.9,类型定义变成了两种类型声明形式之一。另一种新的类型声明形式为下一节将要介绍的类型别名声明。)
type
为一个关键字。// 定义单个类型。 type NewTypeName SourceType // 定义多个类型。 type ( NewTypeName1 SourceType1 NewTypeName2 SourceType2 )
的类型名必须为标识符。但是请注意:包级类型(以及下一节将要介绍的类型别名)的名称不能为init
。
上例中的第二个类型声明中包含两个类型描述(type specification)。 如果一个类型声明包含多于一个的类型描述,这些类型描述必须用一对小括号()
括起来。
- 一个新定义的类型和它的源类型为两个不同的类型。
- 在两个不同的类型定义中的定义的两个类型肯定为两个不同的类型。
- 一个新定义的类型和它的源类型的底层类型(将在下面介绍)一致并且它们的值可以相互显式转换。
- 类型定义可以出现在函数体内。
一些类型定义的例子:
// 下面这些新定义的类型和它们的源类型都是基本类型。 type ( MyInt int Age int Text string ) // 下面这些新定义的类型和它们的源类型都是组合类型。 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() { // 这三个新定义的类型名称只能在此函数内使用。 type PersonAge map[string]int type MessageQueue chan string type Reader interface{Read([]byte) int} }
语法:类型别名声明(type alias declaration)
类型别名声明是一种在Go 1.9中新增的类型声明形式。)
上面已经提到了,Go中有两个内置类型别名:byte
(类型uint8
的别名)和rune
(类型int32
的别名)。 在Go 1.9之前,它们是Go中仅有的两个类型别名。
=
。type ( Name = string Age = int ) type table = map[string]int type Table = map[Name]Age
类型别名也必须为标识符。同样地,类型别名可以被声明在函数体内。
Name
是内置类型string
的一个别名,它们表示同一个类型。 同样的关系对下面的几对类型表示也成立:
- 别名
Age
和内置类型int
。 - 别名
table
和映射类型map[string]int
。 - 别名
Table
和映射类型map[Name]Age
。
事实上,文字表示形式map[string]int
和map[Name]Age
也表示同一类型。 所以,table
和Table
一样表示同一个类型。
注意,尽管两个别名table
和Table
表示同一个类型,但Table
是导出的,所以它可以被其它包引入使用,而table
却不可以。
类型别名声明在重构一些大的Go项目等场合很有用。 在通常编程中,类型定义声明使用得更广泛。
概念:定义类型和非定义类型(defined type and undefined type)
一个定义类型是一个在某个类型定义声明中定义的类型。
所有的基本类型都是定义类型。一个非定义类型一定是一个组合类型。
C
和类型字面表示[]string
都表示同一个非定义类型。 类型A
和别名B
均表示同一个定义类型。type A []string type B = A type C = []string
概念:有名类型和无名类型(named type and unnamed type)
在Go 1.9之前,有名类型这一术语准确地定义在Go白皮本中。它曾被定义为一个有名字的类型。 随着Go 1.9中引入的类型别名新特性,此术语被从白皮书中删除了,原因是它可能会造成一些理解上的困惑。 比如,一些类型字面表示(比如上一节出现中的别名C
)是一个标识符(即一个名称),但是它们所表示的类型(比如[]string
)在Go 1.9之前却被称为无名类型。
- 一个类型别名将不会被称为一个类型,尽管我们常说它表示着一个类型。
- 术语有名类型和定义类型将被视为完全相同的概念。(同样地,无名类型和非定义类型亦为同一概念。) 换句话说,当提到“一个类型别名
T
是一个有名类型”,其实际意义是类型别名T
表示着一个有名类型。 如果T
表示着一个无名类型,则我们不应该说T
是一个有名类型,即使别名T
它本身拥有一个名字。 - 当我们提及一个类型名(称),它可能是一个定义类型的名称,也可能是一个类型别名的名称。
概念:底层类型(underlying type)
- 一个内置类型的底层类型为它自己。
unsafe
标准库包中定义的Pointer
类型的底层类型是它自己。(至少我们可以认为是这样。事实上,关于unsafe.Pointer
类型的底层类型,官方文档中并没有清晰的说明。我们也可以认为unsafe.Pointer
类型的底层类型为*T
,其中T
表示一个任意类型。)- 一个非定义类型(必为一个组合类型)的底层类型为它自己。
- 在一个类型声明中,新声明的类型和源类型共享底层类型。
// 这四个类型的底层类型均为内置类型int。 type ( MyInt int Age MyInt ) // 下面这三个新声明的类型的底层类型各不相同。 type ( IntSlice []int // 底层类型为[]int MyIntSlice []MyInt // 底层类型为[]MyInt AgeSlice []Age // 底层类型为[]Age ) // 类型[]Age、Ages和AgeSlice的底层类型均为[]Age。 type Ages AgeSlice
如何溯源一个声明的类型的底层类型?规则很简单,在溯源过程中,当遇到一个内置类型或者非定义类型时,溯源结束。 以上面这几个声明的类型为例,下面是它们的底层类型的溯源过程:
MyInt → int Age → MyInt → int IntSlice → []int MyIntSlice → []MyInt → []int AgeSlice → []Age → []MyInt → []int Ages → AgeSlice → []Age → []MyInt → []int
- 底层类型为内置类型
bool
的类型称为布尔类型; - 底层类型为任一内置整数类型的类型称为整数类型;
- 底层类型为内置类型
float32
或者float64
的类型称为浮点数类型; - 底层类型为内置类型
complex64
或complex128
的类型称为复数类型; - 整数类型、浮点数类型和复数类型统称为数字值类型;
- 底层类型为内置类型
string
的类型称为字符串类型。
底层类型这个概念在类型转换、赋值和比较规则中扮演着重要角色。