我们为什么需要类型Type?
在回答这个问题之前,我们先了解一下编程语言的原始抽象层,尽管在日常生活当中我们已经不需要处理它们。
机器表示的数据离我们有多近?
机器能够理解二进制的0和1,但是我们却不能够理解。
所以我们将二进制的0和1抽象到更高一个层次。
思考一下这段汇编代码的片段:
你能告诉我寄存器R1, R2, R3分别是什么数据类型吗?
你可能希望他们是int类型,因为在汇编语言层次它们是可以被确定的。但在这里它们可能是任意一种类型,因为它们只是一堆存有0和1数据的寄存器。尽管没有什么意义,机器仍然会将R2和R3的数据相加,生成一个位模式并存入R1中。
所以,类型的概念是出现于更高层次的抽象语言中,比如 C
, Go
, Java
, Python
, JavaScript
等等。在这些语言中,类型是它们本身的一个特性。
有些语言是在编译时检验类型,而另一些语言是在运行时检验。
什么是类型?
类型的概念每种语言各不相同,能够以许多不同的方法表达,但总体来说,它们都有一些相同的特征:
- 类型是一组相似值的集合。
- 在这一组值上我们可以进行相同的操作,比如在integers类型的值上,我们能够将他们add (
+
), subtract (—
) 等,在string类型的值上,我们能够将他们拼接,进行空值检查等。
所以一个语言的类型系统指明:哪些操作对哪些类型是有效的。(一个类型允许进行哪些操作)
类型检查的目的,是为了确保在一个类型上只使用合法的操作。通过类型检查,可以强制按照预期的值进行解析,因为除了类型之外,没有其他的东西可以检查了,一旦我们得到机器码,它们就只是一堆0和1的数字,虽然机器很乐意处理这些0和1的运算。
类型系统用于强制这些二进制数据按照我们预期的值来解析,确保在一个整数的二进制格式上不会做任何非整数的操作,从而获取一些毫无意义的东西。
GO的类型系统
go语言类型系统中,有些必须遵守的基本的规范,我们来看其中一些重要的规范。
我不会一次性抛出所有的概念,我将用不同的例子来涵盖go语言类型系统中的一些基本概念,并通过这些例子来解释这些基本概念。
请您花一点时间看一下这些代码片段,哪些是可以编译通过的?为什么能或者不能?
我很希望你能够写下你的答案和原因,这样在文章末尾我们可以一起来推理它的答案。
命名类型(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
)。
所以:
第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
尽管在规范中,具体条件已经写的很明显,但让我们仔细研究一下这些规范中的一条:
当赋值时:
双方应该具有相同的基础类型,而且其中至少有一个不是命名类型(至少有一个是未命名变类型)。
我们再看一下图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
是命名类型int
,ai
是命名类型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
是未命名类型,同时m
和mMap
有相同的基础类型。
类型转换
查看图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