0
点赞
收藏
分享

微信扫一扫

Go类型系统概述

概念:基本类型(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)

Go支持下列组合类型:

  • ​​指针类型​​ - 类C指针
  • ​​结构体类型​​ - 类C结构体
  • ​​函数类型​​ - 函数类型在Go中是一种一等公民类别
  • ​​容器类型​​,包括:
  • 数组类型 - 定长容器类型
  • 切片类型 - 动态长度和容量容器类型
  • 映射类型(map)- 也常称为字典类型。在标准编译器中映射是使用哈希表实现的。
  • ​​通道类型​​ - 通道用来同步并发的协程
  • ​​接口类型​​ - 接口在反射和多态中发挥着重要角色

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

// 假设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,类型定义变成了两种类型声明形式之一。另一种新的类型声明形式为下一节将要介绍的类型别名声明。)

在Go中,我们可以用如下形式来定义新的类型。在此语法中,​​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中仅有的两个类型别名。

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

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之前却被称为无名类型。

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

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

概念:底层类型(underlying type)

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

  • 一个内置类型的底层类型为它自己。
  • ​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

在Go中,

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

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











举报

相关推荐

0 条评论