go语言中的类型和别名

在go语言中, 定义类型有两种方式, 一种是定义一个全新的类型, 一种是定义一个类型的别名.

语法区别

定义全新的数据类型

1
type A int

定义类型的别名

1
type B = int

两者语法上的区别仅仅是有没有=

赋值区别

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
package main

import "fmt"

type A int
type B = int

func main() {
var number int = 10

var a A
var b B

a = number

b = number

fmt.Println(a, b)
}
``

Goland IDE 会有如下提示:

![](https://20150509.oss-cn-qingdao.aliyuncs.com/2020/04/15/15869403820693.jpg)

不用编译, IDE就告诉你这么搞不可以了. 所以定义新类型和别名, 在赋值上的区别是, 新的类型需要显式的类型转换. 而别名不需要

以下是正确的代码:

```go
package main

import "fmt"

type A int
type B = int

func main() {
var number int = 10

var a A
var b B

// 需要类型转换才可以赋值
a = A(number)

b = number

fmt.Println(a, b)
}

使用区别

如果你给一个非本地的数据类型定义方法时, 编译时会报错, IDE也会提示你错误

1
2
3
4
5
6
7
8
9
10
package main

import "fmt"

type A int
type B = int

func (b B) sayHi() {

}

错误提示: Cannot define new methods on non-local type ‘builtin.int’

但是如果你用的是你自定义的数据类型的别名, 是完全可以的

1
2
3
4
5
6
7
8
9
type C struct {
// none
}

type MiniC = C

func (mc MiniC) sayHi() {
fmt.Println("Hi")
}

别名不仅拥有原数据类型的全部方法, 而且在别名中定义的方法, 会自动出现在原数据类型的方法中, 原类型也可以正常调用别名定义的方法. 因为别名的本质, 就是同一数据类型, 只是名字不同罢了

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
package main

import "fmt"

type C struct {
// none
}

func (c C) sayHello() {
fmt.Println("Hello")
}

type MiniC = C

func (mc MiniC) sayHi() {
fmt.Println("Hi")
}


func main() {
var c C
var mc MiniC

c.sayHello()
c.sayHi()

mc.sayHello()
mc.sayHi()
}

执行结果:

1
2
3
4
Hello
Hi
Hello
Hi

而定义全新类型则跟别名的情况完全不同. 定义全新的类型, 不管你用的是不是本地类型, 都可以为新的类型添加方法. 以下操作是合法的

1
2
3
4
5
6
type A int
type B = int

func (a A) sayHi() {

}

在全新类型下定义的方法, 不会出现在原类型上. 原类型的方法也不会出现在新类型中.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
type C struct {
// none
}

func (c C) sayHello() {
fmt.Println("Hello")
}

type MicroC C

func (mc MicroC) sayHi() {
fmt.Println("Hi")
}


func main() {
var c C
var mc MicroC

c.sayHello()
c.sayHi()

mc.sayHello()
mc.sayHi()
}

执行结果:

1
2
./a2.go:25:3: c.sayHi undefined (type C has no field or method sayHi)
./a2.go:27:4: mc.sayHello undefined (type MicroC has no field or method sayHello)

新类型的应用场景

在使用有限状态机的情况下, 我们可以为能预见的所有状态定义为一个单独的类型. 拿一台kvm的虚拟机来说. 描述虚拟机的运行状态, 就是一种有限状态机的场景. 虚拟机可以是如下任意一种状态:

  • up
  • down
  • poweringup
  • poweringdown
  • rebooting
  • shutingdown

再比如给虚拟机创建的磁盘文件, 可以有以下格式可选:

  • cow
  • raw

我们拿磁盘来举例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
package main

import "fmt"

type DiskFormat string

const (
DiskFormatCow DiskFormat = "cow"
DiskFormatRaw DiskFormat = "raw"
)

func (df DiskFormat) IsValid() bool {
switch df {
case DiskFormatCow:
return true
case DiskFormatRaw:
return true
default:
return false
}
}


func createDisk(format DiskFormat) {
fmt.Println(format.IsValid())
fmt.Printf("传递的磁盘格式为: %T\n", format)
fmt.Println("创建磁盘文件")
fmt.Println("磁盘创建完成")
}

func main() {
var diskType = "a"
createDisk(diskType)
}

执行结果:

1
./a1.go:33:12: cannot use diskType (type string) as type DiskFormat in argument to createDisk

创建磁盘传参的时候, 我传递了一个string "a", 但是实际参数需要的数据类型为DiskFormat, 如果你非要使用自定义字符串传参, 那么你需要类型转换, 就像以下这样:

1
2
3
4
func main() {
var diskType = "a"
createDisk(DiskFormat(diskType))
}

但是更推荐的做法是使用预定义的常量来传参

1
2
3
func main() {
createDisk(DiskFormatCow)
}

别名应用场景

别名的一个典型的应用场景是重构项目. 重构项目时, 可能会有代码使用老包中的方法, 也可能会使用扩展的新包中的方法. 使用别名可以灵活的”继承”原数据类型的所有方法, 并做扩展.

总结

别名的本质是一个数据类型的不同的名字. 比如我有一个中文名叫极地瑞雪, 我还有一个英文名叫PolarSnow, 英文名就是中文名的一个别名而已, 实际本尊只有一个. 极地瑞雪新学了个滑雪的技能, PolarSnow新学了个游泳的技能. 本质上, 就是我学会了 滑雪游泳, 极地瑞雪PolarSnow都可以施展这两项技能

定义全新类型真的就是全新类型, 不夹带任何原数据类型的方法. 就好比是我的克隆人, 从头培养, 从吃饭睡觉打豆豆开始培养, 我现在已有的任何技能, 都和克隆人没有任何关系. 克隆人学会了特殊技能也跟我本尊没有任何关系. 注意, 我的吃饭方法和克隆人的吃饭方法可以同时存在, 各自有各自的吃饭方法. 但是别名是不允许同名方法的情况存在的