在 Go 中所有需要被定义和声明的对象都是 Type,本文将介绍Go语言中的类型系统。

Basic Type

Composite TypesPre-declared Types 组成的复杂数据类型,其形式常常是 Type Literal ,比如 array 的字面量声明 var array [10]int ,map 的字面量 var dict map[int]string

Pre-declared types

和大多数计算机语言一样,Go Type 默认包含常用的基础数据类型,称为 Pre-declarered types 内置基本类型:

  • 内置字符串类型:string.
  • 内置布尔类型:bool.
  • 内置数值类型:
    • int8uint8byte)、int16uint16int32rune)、uint32int64uint64intuintuintptr
    • float32float64
    • complex64complex128

注意,byteuint8的一个内置别名,runeint32的一个内置别名。

Composite Type

基础数据类型又可以进一步构成更复杂的类型,称之为 Composite Types ,Go支持下列组合类型:

  • pointer types:类C指针
  • struct types:类C结构体
  • function types:函数类型在Go中是一种一等公民类别
  • container types,包括:
    • array types:定长容器类型
    • slice types:动态长度和容量容器类型
    • map types:在标准编译器中映射是使用哈希表实现的。
  • channel types:通道用来同步并发的协程
  • interface types:接口在反射和多态中发挥着重要角色

非定义组合类型可以用它们各自的字面表示形式来表示。 下面是一些各种不同种类的非定义组合类型字面表示形式的例子(非定义类型将在下面解释):

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
// 假设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 Declaration

Type Definition Declaration

在Go中,我们可以用如下形式来定义新的类型。在此语法中,type为一个关键字。

1
2
3
4
5
6
7
8
// 定义单个类型。
type NewTypeName SourceType

// 定义多个类型。
type (
	NewTypeName1 SourceType1
	NewTypeName2 SourceType2
)

新的类型名必须为标识符。但是请注意:包级类型(以及下一节将要介绍的类型别名)的名称不能为 init

上例中的第二个类型声明中包含两个类型描述(type specification)。 如果一个类型声明包含多于一个的类型描述,这些类型描述必须用一对小括号 ()括起来。

注意:

  • 一个新定义的类型和它的源类型为两个不同的类型。
  • 在两个不同的类型定义中定义的两个类型肯定为两个不同的类型。
  • 一个新定义的类型和它的源类型的底层类型(将在下面介绍)一致并且它们的值可以相互显式转换。
  • 类型定义可以出现在函数体内

一些类型定义的例子:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
// 下面这些新定义的类型和它们的源类型都是基本类型。
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中有两个内置类型别名:byte(类型 uint8的别名)和 rune(类型 int32的别名)。 在Go 1.9之前,它们是Go中仅有的两个类型别名。

从Go 1.9开始,我们可以使用下面的语法来声明自定义类型别名。此语法和类型定义类似,但是请注意其中多了一个等号 =

1
2
3
4
5
6
7
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]intmap[Name]Age也表示同一类型。 所以,tableTable一样表示同一个类型。

注意,尽管两个别名 tableTable表示同一个类型,但 Table是导出的,所以它可以被其它包引入使用,而 table却不可以。

类型别名声明在重构一些大的Go项目等场合很有用。 在通常编程中,类型定义声明使用得更广泛。

Types 分类

Defined Types and Undefined Types

一个定义类型是一个在某个类型定义声明中定义的类型。

所有的基本类型都是定义类型。一个非定义类型一定是一个组合类型。

在下面的例子中,别名 C和类型字面表示 []string都表示同一个非定义类型。 类型 A和别名 B均表示同一个定义类型。

1
2
3
type A []string
type B = A
type C = []string

Named Types and Unamed Types

Type 可以分为带名称和不带名称的,前者称之为 Named Type,后者称之为 Unnamed Type

  • Named Types 就是通过 type 关键字为一个已有的 type 起个别名,像这样 type NewType ExistingType NewType 就是别名。Pre-declared types 也是 Named Types,也可以为 Pre-Declared Types 起个别名: type Integer int.
  • Unamed Types 是一个 Literal Type,也就是没有名字只有 type 本身,像这样 [6]intmap[string]int

为了避免出现这样的困惑,从Go 1.9开始,一个新的术语定义类型被引入来填补移除有名类型后的空白。 然而此举也给一些概念解释造成了新的障碍,或者形成了一些尴尬的局面。 为了避免这些尴尬的局面和解释上的障碍,Go语言101中的文章将遵守如下原则:

  • 一个类型别名将不会被称为一个类型,尽管我们常说它表示着一个类型。
  • 术语有名类型定义类型将被视为完全相同的概念。(同样地,无名类型非定义类型亦为同一概念。) 换句话说,当提到“一个类型别名 T是一个有名类型”,其实际意义是类型别名 T表示着一个有名类型。 如果 T表示着一个无名类型,则我们不应该说 T是一个有名类型,即使别名 T它本身拥有一个名字。
  • 当我们提及一个类型名(称),它可能是一个定义类型的名称,也可能是一个类型别名的名称。

当 named types 被作为一个 function 的 receiver 时,它就拥有了自己的方法,unamed types 则不能,这是它们的重要区别。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
package main

import (
    "fmt"
)

type NewMap map[int]string

func (nm NewMap) add(key int, value string) {
    nm[key] = value
}

func main() {
    var p NewMap = make(map[int]string)
    p.add(10, "a")
    fmt.Println(p) //map[10:a]
}

有个一例外是 Pre-Declared Types 不能拥有自己的方法。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
package main

func (n int) name(){   
    print(n)
}

func main() {
    var n int
    n.name()
}

编译器会抛出 cannot define new methods on non-local type int 错误,不能对包之外的 type 定义方法,解决这个问题就是对 pre-declared types 重新定义别名。

Underlying Type

每一个类型都有自己的 Underlying Type ,如果 T 是 Pre-declared type 或者 Type Literal,它们对应的 Underlying Type 就是自身 T,否则 T 的 Underlying Type 是 T 定义时引用的类型的 Underlying Type,比如 type T int 的 Underlying Type 是 intint 是 Pre-declared;type T map[int]string 的 Underlying Type 是 map[int]stringmap[int]string 是 Type Literal。

在Go中,每个类型都有一个底层类型。规则:

  • 一个内置类型的底层类型为它自己。
  • unsafe标准库包中定义的 Pointer类型的底层类型是它自己。(至少我们可以认为是这样。事实上,关于 unsafe.Pointer类型的底层类型,官方文档中并没有清晰的说明。我们也可以认为 unsafe.Pointer类型的底层类型为 *T,其中 T表示一个任意类型。)
  • 一个非定义类型(必为一个组合类型)的底层类型为它自己。
  • 在一个类型声明中,新声明的类型和源类型共享底层类型。

一个例子:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
// 这四个类型的底层类型均为内置类型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

在Go中,

  • 底层类型为内置类型 bool的类型称为布尔类型
  • 底层类型为任一内置整数类型的类型称为整数类型
  • 底层类型为内置类型 float32或者 float64的类型称为浮点数类型
  • 底层类型为内置类型 complex64complex128的类型称为复数类型
  • 整数类型、浮点数类型和复数类型统称为数字值类型
  • 底层类型为内置类型 string的类型称为字符串类型

底层类型这个概念在类型转换、赋值和比较规则中扮演着重要角色。

如果两个 type 的 Underlying Type 相同,则它们可以有以下特性。

1.如果两个 type 都是 named type ,彼此之间不能相互赋值

1
2
3
type NewString string
var my string ="a"
var you NewString = my //cannot use my (type string) as type NewString in assignment

虽然它们的 Underlying Type 都是 string,但 string 类型的 my 不能赋值给 NewString 类型的 you。

2.如果两个 type 其中一个是 Unnamed Type,彼此之间可以相互赋值

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
package main

type Ptr *int
//named type
type Map map[int]string
type MapMap Map

func main() {
    var p *int
    var mm Map
    var mmm MapMap
    //m1 m2 是 Unnamed Type
    var m1 map[int]string = mm
    var m2 map[int]string = mmm
    var ptr Ptr = p
    print(ptr)
    print(m1)
    print(m2)
}

为什么 type 之间会有这样的差别?

如果为一个类型起了名字,说明你想要做区分,所以两个 named types 即使 Underlying Type 相同也是不能相互赋值的。

详见Google Group Topic

type 的属性继承

直接继承

已经声明的 Named Type 不会从它的 Underlying Type 或 existing type 继承 method,但是会继承 field。

package main

import (
    "fmt"
)

type Person struct {
    name string
}

func (p *Person) Speak() {
    fmt.Println("I am a person")
}

//Student 是 Named Type
type Student Person

func main() {
    var p Person
    p.Speak()
    var s Student
    s.name = "jone"
    fmt.Println(s.name)
    // s.Speak()
}

Named Type Student 不会继承来自 Person 的 Speak 的方法,打开注释执行报错 s.Speak undefined (type Student has no field or method Speak),但是 Student 可以继承 Person 的 filed: s.name 可以使用。

The declared type does not inherit any methods bound to the existing type, but the method set of an interface type or of elements of a composite type remains unchanged:

但是也有例外的情况。

如果 existing type 是 interface,它的 method set 会被继承

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
package main

import (
    "fmt"
)

type I interface {
    Talk()
}

// existing type 是 I,I 是个接口,可以直接继承 I 的方法,II 等同于 I
type II I

type Person struct {
    name string
}

func (p *Person) Speak() {
    fmt.Println("I am a person")
}

func (p *Person) Talk() {
    fmt.Println("I am talking")
}

func main() {
    var p Person
    p.Speak()
    p.Talk()
    var i I
    i = &p
    i.Talk()
    var ii II
    ii = &p
    ii.Talk()
}

II 继承了 I 的 method,所以 Person 也实现了 II。

如果 existing type 被嵌入新 type 作为 filed

如果一个 type TB 被嵌入另一个 type T 作为它的 filed,TB 的所有 field 和 method 都可以在 T 中使用,这种方法称之为 type embedding。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
package main

import (
    "fmt"
)

type I interface {
    Talk()
}

type Person struct {
    name string
}

func (p *Person) Speak() {
    fmt.Println("I am a person")
}

func (p *Person) Talk() {
    fmt.Println("I am talking")
}

type People struct {
    Person
}

func main(){
    var people People
    people.name = "people"
    people.Speak()
    people.Talk()
}

Type Conversion

Type 之间是可以相互转换的,但要遵循一定的转换规则,详细请看官方规范 https://golang.org/ref/spec#Conversions

参考资料