深入研究Go(golang) Type类型系统

我们为什么需要类型Type?

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

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

img

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


img

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

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

img

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

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

所以,类型的概念是出现于更高层次的抽象语言中,比如 C, Go, Java, Python, JavaScript 等等。在这些语言中,类型是它们本身的一个特性。

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

什么是类型?

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

  1. 类型是一组相似值的集合。
  2. 在这一组值上我们可以进行相同的操作,比如在integers类型的值上,我们能够将他们add ( + ), subtract (  ) 等,在string类型的值上,我们能够将他们拼接,进行空值检查等。

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

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

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

GO的类型系统

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

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

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

img
img

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

命名类型(named (defined) type)

具有名称的类型:例如:int, int64, float32, string, bool等。这些已经是GO中预先声明好的类型。

我们通过类型声明(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都有基本类型

如果T 是预先声明类型:boolean, numeric, or string(布尔,数值,字符串)中的一个,或者是一个类型字面量(type literal),他们对应的基础类型就是T自身。

否则,T的基础类型就是 T所引用的那个类型的类型声明(type declaration)。

img

所以:

第3,8行:他们的类型声明为 string的预先声明的类型,所以他们的基础类型就是T它自身: string

第5,7行:他们有类型字面量,所以他们的基础类型也是T它自身:map[string]int*N 指针。注意:这些类型字面量还是 未命名类型(unnamed type)

第4,6,10行:T的基本类型是T所引用的那个类型的类型声明(type declaration)。

  • 4行:B引用了A,因此B的基础类型是A的类型声明:string
  • 6行:N引用了M, 因此N的基础类型是M的类型声明:map[string]int

需要注意的是第9行:

type T map[S]int. 由于s的基础类型是string,那么是否type T map[S]int的基础类型应该是 map[string]int 而并非map[S]int呢?确定吗? 在此,我们讨论的是基础未命名类型map[S]int,因为基础类型只追溯到它的未命名类型的最外层(或者就像说明上说的:如果T是一个类型字面量,它的基础类型就是T自身),所以U的基础类型是map[S]int

你可能在想我为什么如此强调unnamed type, named (defined) type , underlying type 这几个概念,因为它们在go语言规范中扮演着重要的角色,他们能帮助我们更好的理解上面的那些代码片段为何有些可以编译通过,有些却不通过。

可赋值性

什么情况下变量V可以赋值给另一个类型T

img

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

当赋值时:

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

我们再看一下图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

因为i是命名类型intai是命名类型aInt, 虽然他们的基础类型是相同的,但是没有未命名类型,所以不能赋值。

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)
}

图4编译通过,因为m是未命名类型,同时mmMap有相同的基础类型。

类型转换

1_RRtpi00SuC7fRqzfB0ePIA.png

查看图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)
}

因为Meter Centimeter 的基础类型都是integers类型,所以他们的基础类型是可以相互转换,所以编译通过。

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

类型一致性

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

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

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

因此,预先声明的命名类型 int, int64, 等都是不一致的。

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

忽略结构体的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.value 的基础类型是 int64 , 字段Centimeter.value 的基础类型是 int32 ,因为一个命名类型一定和其它任意类型都不同。所以他们不一致,不是相同的基础类型,所以不能转换。

在图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.value 的基础类型是 int64 ,字段Centimeter.value 的基础类型是 int64。一致的基础类型,所以可以相互转换,编译通过。

原文链接:https://medium.com/@ankur_anand/a-closer-look-at-go-golang-type-system-3058a51d1615

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 211,884评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,347评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,435评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,509评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,611评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,837评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,987评论 3 408
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,730评论 0 267
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,194评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,525评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,664评论 1 340
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,334评论 4 330
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,944评论 3 313
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,764评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,997评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,389评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,554评论 2 349

推荐阅读更多精彩内容