

[译] 仔细研究 Golang 类型系统
source link: https://www.tuicool.com/articles/QzUvErB
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

仔细研究 Go(golang) 类型系统
通过示例详解 Go 的类型系统
让我们从一个非常基本的问题开始吧!
为什么我们需要类型
在回答这个问题之前,我们需要先看看编程语言的一些原始抽象层,日常的工作我们并不需要处理这些层。
我们如何才能获得数据的机器表示呢?
机器所能理解的是二进制 0 和 1。 但对我们来说,意义何在? 直到看到这样的东西,我才知道(有人是黑客帝国迷吗? )
我们将这些二进制抽象出来,并进一步考虑。
看下这段汇编代码
你能说出寄存器 R1、R2、R3 中的数据类型是什么吗 ?
你可能希望他们是整数,因为在汇编语言层面也无法确定。没有什么能阻止 R1、R2、R3 具有任意类型,它们只是一堆 0 和 1 的寄存器。即使没有意义,加法运算也会将 R2 和 R3 加在一起,产生一个位模式,并将结果存储在 R1 中。
所以类型的概念从更高层次的抽象开始,在更高级的语言中,比如 C、Go、Java、Python、JavaScript 等等,这是语言本身的特性。
什么是类型
类型的概念在不同的编程语言之间是不同的,可以用许多不同的方式来表达,但是大体上它们都有一些相同点。
-
类型是一组值;
-
在这些值上可以执行一组操作,例如:int 类型可以执行
+
和-
等运算,而对于字符类型,可以执行连接、空检查等操作;
因此,语言类型系统指定哪些运算符对哪些类型有效。
类型检查的目标是确保运算符使用在正确的类型上。通过执行类型检查,可以强制执行预期的值解释,因为一旦我们得到一堆只是 0 和 1 的机器码,就执行不了任务检查。机器也会很愉快地在这些机器码上执行我们告诉它的任何操作。
类型系统用来强制执行位模式的预期解释,确保整数的位模式不会有任何非整数操作,从而得到无意义的结果。
Go 的类型系统
有一些基本规范控制 Go 的类型系统,我们会看一些重要的。
但我不会一次把所有的概念都列出来,在这里我将尝试用不同的例子来涵盖 Go 类型系统的一些基本概念,然后在讲解一些基本概念的同时带你浏览这些例子。
花点时间看看这些代码片段。其中哪一个会编译,为什么 ?
我希望你写下你的答案和理由,这样,我们最后就可以一起来验证它。
命名类型
具有名称的类型:例如 int、int64、float32、string、bool 等,这些都是预先声明的。
另外,使用 type 关键字声明的任意类型也称为命名类型。
1var i int // named type 2type myInt int // named type 3var b bool // named type
命名 ( 定义 ) 类型总是与任何其他类型不同。
未命名类型
复合类型,包括 array、struct、pointer、function、interface、slice、Map 和 channel,都是未命名类型。
1[]string // unnamed type 2map[string]string // unnamed type 3[10]int // unnamed type
因为它们没有名称,但是有关于它们是如何组成的类型字面量描述符
底层类型
每种类型 T 都有底层类型
如果 T 是预定义类型的一种,包括 bool、数字、string 或者 字面量类型,则对应的底层类型是 T 本身;否则,T 的底层类型是 T 在其类型声明中引用的类型的底层类型。
第 3、 8 行,预声明的字符串类型,因此底层类型是 T 本身,即字符串;
第 5 、7 行,是字面量类型,因此底层类型就是 T 本身,即 map[string]int 和 指针 *N。注意:字面量类型也是未命名类型;
第 4 、6、10 行,T 的底层类型是 T 在其类型声明中引用的类型的底层类型,例如:B 引用了 A,所以 B 的底层类型是字符串类型,其他情况同理;
我们再来看下第 9 行的例子:type T map[S]int ,由于 S 的底层类型是 string,难道 type T map[S]int 的底层类型不应该是 map[string]int 而不是 map[S]int 吗?因为我们在谈论 map[S]int 的底层未命名类型,所以向下追溯到未命名类型(或者,正如 Go 语言规范上写的一样:如果 T 是类型字面量,则对应的底层类型就是 T 本身)。
你可能会想,为什么我会如此重视未命名类型、命名类型和底层类型的规范。因为它们在 Go 语言规范中扮演重要的角色,我们将进一步讨论,以帮助我们理解为什么上面展示的代码片段有的能编译而有的不能编译,即使它们的意思基本相同。
可赋值性
当变量 a 可以赋值给类型 T 的变量时。
虽然这些条件已经在规范里解释过了,我们来看其中的一条规范,当分配时, 两者都应该具有相同的底层类型,并且至少其中一个不是命名类型 。
我们来看下图 4、 5 所示代码段的问题 :
1package main 2 3type aInt int 4 5func main() { 6 var i int = 10 7 var ai aInt = 100 8 i = ai 9 printAiType(i) 10} 11 12func printAiType(ai aInt) { 13 print(ai) 14}
上面的代码编译不通过,编译时报错:
18:4: cannot use ai (type aInt) as type int in assignment 29:13: cannot use i (type int) as type aInt in argument to printAiType
因为 i 是命名类型 int,而 ai 是命名类型 aInt,虽然它们的底层类型相同。
1package main 2 3type MyMap map[int]int 4 5func main() { 6 m := make(map[int]int) 7 var mMap MyMap 8 mMap = m 9 printMyMapType(mMap) 10 print(m) 11} 12 13func printMyMapType(mMap MyMap) { 14 print(mMap) 15}
上面这段代码编译通过,因为 m 是未命名类型并且 m 和 mMap 的底层类型相同。
类型转化
看下图3的代码:
1package main 2 3type Meter int64 4 5type Centimeter int32 6 7func main() { 8 var cm Centimeter = 1000 9 var m Meter 10 m = Meter(cm) 11 print(m) 12 cm = Centimeter(m) 13 print(cm) 14}
上面的代码可以编译通过,因为 Meter 和 Centimeter 都是整型,并且它们的底层类型可以相互转化。
在看图 1 和图 2 的代码之前,我们来看看 Go 控制类型系统的另一个基本规范。
类型一致性
两种类型要么相同要么不同。
已定义类型与其他任意类型总是不同。否则,如果两种类型对应的底层类型在结构上是等效的,则它们是相同的。因此,即使预先声明的命名类型 int、int64 等也是不相同的。
看下结构体的一条转换规则:
不考虑结构体标签,x 和 T 具有相同的底层类型(x 赋值给 T)。
1package main 2 3type Meter struct { 4 value int64 5} 6 7type Centimeter struct { 8 value int32 9} 10 11func main() { 12 cm := Centimeter{ 13 value: 1000, 14 } 15 16 var m Meter 17 m = Meter(cm) 18 print(m.value) 19 cm = Centimeter(m) 20 print(cm.value) 21}
记住一点: 相同的底层类型 。由于成员 Meter.value 的底层类型是 int64,而成员 Centimeter.value 的底层类型是 int32,所以它们不相同,因为 已定义类型与其他任意类型总是不同 。所以图 2 的代码片段编译会出错。
来看下图 1 的代码段:
1package main 2 3type Meter struct { 4 value int64 5} 6 7type Centimeter struct { 8 value int64 9} 10 11func main() { 12 cm := Centimeter{ 13 value: 1000, 14 } 15 16 var m Meter 17 m = Meter(cm) 18 print(m.value) 19 cm = Centimeter(m) 20 print(cm.value) 21} 22view raw
成员 Meter.value 和 Centimeter.value 的底层类型都是 int64,所以它们相同,编译可以通过。
希望这篇文章对你理解 Go 类型系统有所帮助,这也是我一直写文章的目的。
via: https://medium.com/@ankur_anand/a-closer-look-at-go-golang-type-system-3058a51d1615
作者:Ankur Anand
译者:Seekload
校对:polaris1119
本文由 GCTT 原创编译,Go 中文网 荣誉推出
Recommend
-
78
针对 Golang 中的 int64 类型优化 abs() 函数,原文:Optimized abs() for int64 in Go
-
8
Perl数据类型安全研究【翻译】 路人甲 ...
-
43
对比10系显卡纷纷提价、尤其是涨幅达到72%的RTX 2080 Ti,你要买账吗? 对此,知名硬媒ExtremeTech谨慎地表示,“实时光线追踪”有点被过度包装和吹捧的嫌疑。且对于第一次引入新技术还大肆冲击钱包的产品,保持理性的观望态度似乎更明智。 光追可...
-
81
@恶魔奶爸Sam:我来仔细阐述下饭局劝酒的主要模式
-
43
我们为什么需要类型Type? 在回答这个问题之前,我们先了解一下编程语言的原始抽象层,尽管在日常生活当中我们已经不需要处理它们。 机器表示的数据离我们有多近?
-
57
问与答 - @petelin - 我的观点是微服务从技术上使得每个服务不在和其他服务分享资源,所以更加独立和健壮一点(更分布式一点)但是,只要有依赖关系我看不出来通过 rpc 调用和直接方法调用有什么区别诸如吐槽单体应用存
-
8
现在几乎已经很少会看到关于恶意字符串所造成的漏洞了,更不用说那些可利用的字符串了 这并不奇怪,因为SDL(安全开发生命周期)已经禁止了所有不安全的函数 但是,如果开发人员错误地使用了增强的安全功能,仍然可能造成严重的安全漏洞。...
-
10
好物第12期:这些产品有点怪?你再仔细看看!
-
4
贝达药业闪崩,仔细想想也没那么意外 • 2023-05-31 14:...
-
6
Linux文件与目录管理了解和熟悉Linux系统的文件存储结构、权限设置和命令行工具,将有助于快速定位和解决与日志相关的问题,提高系统管理员和开发人员的工作效率。
About Joyk
Aggregate valuable and interesting links.
Joyk means Joy of geeK