我们为什么需要类型Type?

在回答这个问题之前,我们先了解一下编程语言的原始抽象层,尽管在日常生活当中我们已经不需要处理它们。

机器表示的数据离我们有多近?

机器能够理解二进制的0和1,但是我们却不能够理解。


所以我们将二进制的0和1抽象到更高一个层次。

思考一下这段汇编代码的片段:

你能告诉我寄存器R1, R2, R3分别是什么数据类型吗?

你可能希望他们是int类型,因为在汇编语言层次它们是可以被确定的。但在这里它们可能是任意一种类型,因为它们只是一堆存有0和1数据的寄存器。尽管没有什么意义,机器仍然会将R2和R3的数据相加,生成一个位模式并存入R1中。

CGoJavaPythonJavaScript

有些语言是在编译时检验类型,而另一些语言是在运行时检验。

什么是类型?

类型的概念每种语言各不相同,能够以许多不同的方法表达,但总体来说,它们都有一些相同的特征:

+—

所以一个语言的类型系统指明:哪些操作对哪些类型是有效的。(一个类型允许进行哪些操作)

类型检查的目的,是为了确保在一个类型上只使用合法的操作。通过类型检查,可以强制按照预期的值进行解析,因为除了类型之外,没有其他的东西可以检查了,一旦我们得到机器码,它们就只是一堆0和1的数字,虽然机器很乐意处理这些0和1的运算。

类型系统用于强制这些二进制数据按照我们预期的值来解析,确保在一个整数的二进制格式上不会做任何非整数的操作,从而获取一些毫无意义的东西。

GO的类型系统

go语言类型系统中,有些必须遵守的基本的规范,我们来看其中一些重要的规范。

我不会一次性抛出所有的概念,我将用不同的例子来涵盖go语言类型系统中的一些基本概念,并通过这些例子来解释这些基本概念。

请您花一点时间看一下这些代码片段,哪些是可以编译通过的?为什么能或者不能?

我很希望你能够写下你的答案和原因,这样在文章末尾我们可以一起来推理它的答案。

命名类型(named (defined) type)

intint64float32stringbool
type declaration
var i int // named type
type myInt int // named type
var b bool // named type

一个命名类型一定和其它类型不同!

未命名类型(unnamed type)

组合类型:数组,结构体,指针,函数,接口,切片,map,通道 都是未命名类型。

[]string // unnamed type
map[string]string // unnamed type
[10]int // unnamed type
type literal

基础类型(underlying type)

任何类型T都有基本类型

type literaltype declaration

所以:

stringTstring
Tmap[string]int*Nunnamed type
TTtype declaration
BABAstringNMNMmap[string]int

需要注意的是第9行:

type T map[S]intsstringtype T map[S]intmap[string]intmap[S]intmap[S]intTTUmap[S]int
unnamed typenamed (defined) typeunderlying type

可赋值性

VT

尽管在规范中,具体条件已经写的很明显,但让我们仔细研究一下这些规范中的一条:

当赋值时:

双方应该具有相同的基础类型,而且其中至少有一个不是命名类型(至少有一个是未命名变类型)。

我们再看一下图4和图5的问题:

package main

import "fmt"

type aInt int

func main() {
    var i int = 10
    var ai aInt = 100
    i = ai
    printAiType(i)
}

func printAiType(ai aInt) {
    fmt.print(ai)
}

所以,上面的代码将不会 通过编译并且会报以下编译错误:

8:4: cannot use ai (type aInt) as type int in assignment
9:13: cannot use i (type int) as type aInt in argument to printAiType
iintaiaInt
package main

import "fmt"

type MyMap map[int]int

func main() {
    m := make(map[int]int)
    var mMap MyMap
    mMap = m
    printAiType(mMap)
    fmt.Print(m)
}

func printAiType(mMap MyMap) {
    fmt.print(mMap)
}
mmmMap

类型转换

查看图3中的代码:

package main

type Meter int64

type Centimeter int32

func main() {
    var cm Centimeter = 1000
    var m Meter
    m = Meter(cm)
    print(m)
    cm = Centimeter(m)
    print(cm)
}
MeterCentimeter

在看图1和图2之前,我们先进一步了解在GO语言中另一个控制类型系统的基本规范。

类型一致性

两种类型要么相同,要么不相同。

  • 一个命名类型一定和其它类型都不同。

  • 如果他们基础类型的字面量在结构上是等价的,他们就是相同的类型。

intint64

现在看一看结构体转换规则:

忽略结构体的tags,只要结构体X和T拥有相同的基础类型,就可以转换。

package main

type Meter struct {
    value int64
}

type Centimeter struct {
    value int32
}

func main() {
    cm := Centimeter{
        value: 1000,
    }

    var m Meter
    m = Meter(cm)
    print(m.value)
    cm = Centimeter(m)
    print(cm.value)
}
Meter.valueint64Centimeter.valueint32

在图2中,我们会得到编译错误。

图1的代码:

package main

type Meter struct {
    value int64
}

type Centimeter struct {
    value int64
}

func main() {
    cm := Centimeter{
        value: 1000,
    }

    var m Meter
    m = Meter(cm)
    print(m.value)
    cm = Centimeter(m)
    print(cm.value)
}
Meter.valueint64Centimeter.valueint64