golangType

深入理解golang类型系统

正文开始之前,想抛出一些小问题,读者可以看下,且带着小问题继续往下看

1
2
3
4
5
6
7
8
9
10
11
12
13
14
type Pig struct {
Age int
}
type Dog struct {
Age int
}
func DogEatPig() {
d := Dog{}
p := Pig{Age: 12}
d = Dog(p)
log.Println(d)
}

can it be compiled? that’s question ==1==

1
2
3
4
5
6
7
8
9
10
11
12
13
14
type Pig struct {
Age int32
}
type Dog struct {
Age int64
}
func DogEatPig() {
d := Dog{}
p := Pig{Age: 12}
d = Dog(p)
log.Println(d)
}

can it be compiled? that’s question ==2==

1
2
3
4
5
6
7
8
9
10
type Pig int32
type Dog int
func DogEatPig() {
var d Dog
p := 12
d = Dog(p)
log.Println(d)
}

can it be compiled? that’s question ==3==

1
2
3
4
5
6
7
8
type MyMap map[int]int
func foo() {
s := make(map[int]int)
mm := MyMap{}
mm = s
log.Println(mm)
}

can it be compiled? that’s question ==4==

1
2
3
4
5
6
7
8
9
10
type mInt int
type mInt2 = int
func foo() {
i := 1
mi := mInt(1)
mi = i
mi2 := mInt2(1)
mi2 = i
}

can it be compiled? that’s question ==5==

可能问题有点多,希望读者不要失去耐心~

命名类型(named (defined) type)

具有名称的类型:例如:

1
int,int64,float32,string,bool

等。这些已经是 GO 中预先声明好的类型,
我们通过类型声明(type declaration)创建的所有类型都是命名类型。

1
2
3
4
5
var i int // named type
type myInt int // named type
var b bool // named type

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

可以理解成:命名类型是能够直切确定 这个变量是什么数据类型的

未命名类型(unnamed type)

组合类型:

1
2
3
4
5
6
7
array,struct,point,func,interface,slice,map,channel 都是命名类型。
[]string // unnamed type
map[string]string // unnamed type
[10]int // unnamed type

虽然他们没有名字,但却有一个类型字面量(type literal)来描述他们由什么构成。

可以理解成:未命名类型是不能直接确定这个变量是什么类型的,例如:channel 需要知道 是channel int 还是 channel string

基础类型(underlying type)

任何类型 T 都有基本类型

如果 T 是预先声明类型:

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

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

1
2
3
4
5
6
7
8
type A string //基础类型[underlying type]为string 且是命名类型
type B A //基础类型[underlying type]为string 且是命名类型
type M map[string]int //基础类型[underlying type]为 map[string]int 且是未命名类型
type N M //基础类型[underlying type]为 map[string]int 且是未命名类型
type P *N //基础类型[underlying type]为 *N 且是未命名类型
type S string //基础类型[underlying type]为string 且是命名类型
type T map[S]string //基础类型[underlying type]为map[S]string 且是命名类型
type U T //基础类型[underlying type]为map[S]string 且是命名类型

解释:

  • 第 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 语言规范中扮演着重要的角色,他们能帮助我们更好的理解上面的那些代码片段为何有些可以编译通过,有些却不通过。

可赋值性

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

这是golang编译规范中的一条

对于question 4:

1
2
3
4
5
6
7
8
9
10
type mInt int
type mInt2 = int
func foo() {
i := 1
mi := mInt(1)
mi = i
mi2 := mInt2(1)
mi2 = i
}

显然不能编译通过 双方的基础类型不同

类型一致性

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

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

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

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

结构体转换规则:

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

所以question1可以编译通过,question2不能编译通过

溜了~