Funky's NoteBook

Go Core Dev Type-System

字数统计: 4,299阅读时长: 20 min
2019/01/23 Share

类型系统

命名类型 & 未命名类型

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
package main
import "fmt"
type Person struct {
name string
age int
}
func main() {
// 使用 struct 字面量声明的是未命名类型
a := struct {
name string
age int
}{"andes", 18}
fmt.Printf("%T\n", a)
fmt.Printf("%v\n", a)

b := Person{"Tom", 21}
fmt.Printf("%T\n", a)
fmt.Printf("%v\n", a)

}

底层类型

1
2
3
4
5
6
type T1 string
type T2 T1
type T3 []string
type T4 T3
type T5 []T1
type T6 T5

其中,T1、T2 底层类型都是 string, T3、T4 底层类型都是[]string,T6 和 T5 的底层类型都是[]T1。特别注意是,这里T6、T5 和T3、T4的底层类型不一样,一个是[]T1,一个是[]string。

类型相同和类型赋值

Go 1.9 引入了类型别名语法 type T1 = T2 ,T1 的类型完全和 T2 一样。引入别名主要有如下原因:

  • 未来解决新旧包的迁移兼容问题,比如 context 包先前并不在标准库里面,后面迁移到了标准库。
  • Go 的按包进行隔离的机制不太精细,有时我们需要将大包划分为几个小包进行开发,但是需要在大包里面保留全部的类型给使用者。
  • 解决新旧类型的迁移问题,新类型先是旧类型的别名,后续的软件都基于类型编程,在合适的时间将新类型升级为旧类型不兼容,常用于软件的柔性升级。
类型可直接赋值
1
2
3
// a 为 T1 的变量,或者 a 本身就是一个字面常量或 nil
// 如果如下语句可以执行,则称之为类型 T1 可以赋值类类型 T2
var b T2 = a

赋值需满足:

  • T1、T2类型相同
  • T1和 T2具有相同的底层类型,并且T1、T2里面至少有一个是未命名类型
  • T2 是接口类型,T1是具体类型,T1的方法集是T2方法集的超集
  • T1、T2都是通道类型,他们拥有相同的元素类型,并且T1和T2中至少有一个是未命名类型
  • a 是预声明标识符 nil,T2可以是pointer、function、slice、map、channel、interface类型中的一个
  • a 是一个字面量值,可以用来标识类型 T 的值
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
51
52
53
54
55
56
package main 
import (
"fmt"
)

type Map map[string]string

func (m Map) Print() {
for _, key := range m {
fmt.Println(key)
}
}

type iMap Map

// 只要底层类型是 slice、map 等支持 range 类型的字面量,新类型仍然可以使用 range 迭代

func (m iMap) Print (){
for _, key := range m {
fmt.Println(key)
}
}

type slice []int

func (s slice) Print() {
for _, v := range s {
fmt.Println(v)
}
}

func main() {
mp := make(map[string]string, 10)
mp["hi"] = "tata"

// mp ma 底层类型相同 mp 为没未命名类型
var ma Map = mp

// im 与 ma 虽然有相同的底层类型 map[string]string, 但它们中没有一个是未命名类型
// 不能赋值, 如下语句不能通过编译
ma.Print()
im.Print()

// Map 实现了 Print() 所以可以赋值给接口类型变量

var i interface {
Print()
} = ma

i.Print()

s1 := []int[1, 2, 3]
var s2 slice
s2 = s1
s2.Print()
}

类型强制转换

  • x 可以直接赋值给 T 类型变量
  • x 的类型和 T 具有相同的底层类型
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
package main
import (
"fmt"
)

type Map map[string]string

func (m Map) Print() {
for -, key := range m {
fmt.Println(key)
}
}

type iMap Map

// 只要底层类型是slice、map等支持 range 的类型字面量,新类型仍然可以使用 range 迭代

func (m iMap) Print() {
for _, key := range m {
fmt.Println(key)
}
}

func main() {
mp := make(map[string]string, 10)
mp["hi"] = "tata"
// mp 与 ma 有相同的底层类型 map[string]string,并且 mp 是未命名类型
var ma Map = mp

// im 与 ma 虽然有相同的底层类型,但是二者中没有一个是字面量类型,不能直接赋值
// 可以通过类型转换
// var im iMap = ma
var im iMap = (iMap) (ma)

ma.Print()
im.Print()
}
  • x 的类型、T 都是未命名指针类型,并且指针指向相同底层类型
  • x、T 都是整值、或者都是浮点型
  • x 的类型和 T 都是复数类型
  • x 是整数值或 []byte 类型的值,T 是 string 类型
  • x 是一个字符串,T 是 []byte 或 []rune

字符串和字节切片之间的转换最常见,示例如下:

1
2
3
4
5
6
7
s := "hello,world"
var a []byte
a = []byte(s)
var b string
b = string(a)
var c []rune
c = []rune

注意:

  • 数值类型和 string 类型之间相互转换可能造成值部分丢失
  • Go 语言没有语言机制支持指针和 interger 之间的直接转换

类型方法

自定义类型

1
2
3
4
type INT int // INT 是一个使用预声明类型声明的自定义类型
type Map map[string]string // Map 是一个使用类型字面量声明的自定义类型
type myMap Map // myMap 是一个自定义类型 Map 声明的自定义类型
// INT、Map、myMap 都是命名类型

自定义 struct 类型

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// 使用 type 自定义的结构类型属于命名类型
struct Xname struct {
Field1 type1
}

// errorString 是一个自定义结构类型,也是命名类型
type errorString struct {
s string
}

// 结构字面量属于未命名类型

struct {
Field1 type1
}

// struct {} 是未命名类型空结构

var s = struct {} {}

struct 初始化

1
2
3
4
type Person struct {
name string
age int
}
推荐写法(指定字段名初始化)
1
2
3
4
5
6
7
8
9
10
a := Person{ name: "andes", age:18 }

b := Person{
name: "andes",
age:18,
}

c b := Person{
name: "andes",
age:18} //这里不需要逗号
使用构造函数进行初始化

推荐方法,当结构发生变化时,构造函数可以屏蔽细节。

1
2
3
4
5
6
7
func New(text string) error {
return & errorString{text}
}

type errorString struct {
s string
}
结构字段的特点

结构的字段可以是任意类型,结构字段的类型名必须唯一, struct 字段类型可以是普通类型也可以是指针。另外结构支持内嵌自身的指针。

1
2
3
4
5
type Element struct {
next, prev *Element
list *List
Value interface{}
}
匿名字段
1
2
3
type File struct {
*file // os specific
}
自定义接口类型
1
2
3
4
var i interface{}
type Reader interface {
Read(p []byte) (n int, err error)
}

方法

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
// 类型方法接受者是值类型
func (t TypeName) MethodName (ParamList) (Return List) {
// method body
}
// 类型方法接收者是指针
func (t *TypeName) MethodName (ParamList) (Return List) {
// method body
}

// 改写成为常规函数
func TypeName_MethodName(t TypeName, otherParamList) (Return List) {
// method body
}
func TypeName_MethodName(t *TypeName, otherParamList) (Return List) {
// method body
}
// Example
type SliceInt []int

func (s SliceInt) Sum() int {
sum := 0
for _, i := range s {
sum += i
}
return sum
}

// 等价于

func SliceInt_Sum(s SliceInt) int {
sum := 0
for _, i := range s {
sum += i
}
return sum
}
var s SliceInt = []int{1, 2, 3, 4}
s.Sum()
SliceInt_Sum(s)

类型方法有如下特点:

  • 可以为命名类型增加方法(除了接口),非命名类型不能自定义方法。
  • 为类型增加方法有一个限制,方法定义必须和类型定义在同一个包中。
  • 方法的命名空间的可见性和变量一样,大写开头的方法可以在包外被访问,否则只能在保内可见。
  • 使用 type 定义的自定义类型是一个新类型,新类型不能调用原有类型方法,但是底层类型支持的运算可以被新类型继承。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
type Map map[string]string

func (m Map) Print() {
for _, key := range m {
fmt.Println(key)
}
}

type MyInt int
func main() {
var a MyInt = 10
var b MyInt = 10

// int 类型支持加减乘除运算,新类型同样可用
c := a + b
d := a * b
}

方法调用

一般调用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
type T struct {
a int
}

func (t T) Get() int {
return t.a
}

func (t *T) Set(i int) {
t.a = i
}

var t = &T{}
// 普通方法调用
t.Set(2)
t.Get()

方法值

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
type T struct {
a int
}

func (t T) Get() int {
return t.a
}

func (t *T) Set(i int) {
t.a = i
}

func (t *T) Print() {
fmt.Printf("%p, %v, %d \n", t, t, t.a)
}

var t = &T{}

// method value

f := t.Set

// 方法值调用

f(2)
t.Print()

// 方法值调用
f(3)
t.Print()

方法表达式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
type T struct {
a int
}

func (t T) Get() int {
return t.a
}

func (t *T) Set(i int) {
t.a = i
}

func (t *T) Print() {
fmt.Printf("%p, %v, %d \n", t, t, t.a)
}

表达式 T.Get() (T).Set 被称之为方法表达式,方法表达式也可以被看做函数名,只不过这个函数的首个参数是接收者的实例活指针。 T.Get 的函数签名是 func(t T) int, (\T).Set 的签名是 func(t *T, i int)。 注意这里是T.Get不能写成 (*T).Get,同理 (*T).Set不能写成 T.Set。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// 如下方法表达式调用都是等价的
t := T {a:1}

// 普通方法调用
t.Get(t)

// 方法表达式调用
(T).Get(t)

// 方法表达式调用

f1 := T.Get
f1(t)

// 方法表达式调用
f2 := (T).Get
f2(t)

// 如下方法表达式调用等价
(*T).Set(&t, 1)
f3 := (*T).Set
f(&t, 1)

方法集

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
package main
import "fmt"
type Int int
func (a Int) Max(b Int) Int{
if a >= b {
return a
} else {
return b
}
}

func (i *Int) Set(a Int) {
*i = a
}

func (i Int) Print() {
fmt.Printf("value = %d\n", i)
}

func main() {
var a Int = 10
var b Int = 20

c := a.Max(b)
c.Print()
(&c).Print()

a.Set(10) // 编译器自动转换为 (&a).Set(10)
a.Print()

(&a).Set(30)
a.Print()
}

值调用和表达式调用的方法集

  • 通过类型字面量显式进行调用
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
type Date struct {}
func (Data) TestValue() {}
func (*Data) TestPointer () {}

// 显式调用,无论值调用还是表达式调用,
// 编辑器都不会进行方法集的自动转换,编译器会严格校验方法集

// *Data 方法集是 TestPointer 和 TestValue
// Date 方法集为 TestValue

// 显示调用
(*Data) (&struct {} {}).TestPointer()
(*Data) (&struct {} {}).TestValue
// 方法值
(Data) (struct {} {}).TestValue ()
// 方法表达式
Data.TestValue(struct {} {})

// 失败调用示例
//Data.TestPointer(struct {} {})
// (Data) (struct {} {}).TestPointer()
  • 通过类型变量进行值调用和表达式调用
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
type Data struct {}
func (Data) TestValue() {}
func (*Data) TestPointer () {}

var a Data = struct {}{}
// 表达式调用编辑器不会进行自动转换
Data.TestValue(a)
// Data.TestValue(&a)
(*Data).TestPointer(&a)
// (Data).TestPointer(&a) //error

// 值调用编辑器会自动进行转换
f := a.TestValue
f()

y := (&a).TestValue // 编辑器会自动转换为 a.TestValue
y()

g := a.TestPointer
g()

x := (&a).TestPointer
x()

组合和方法集

组合

  • 内嵌字段的初始化和访问
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
package main
type X struct {
a int
}
type Y struct {
X
b int
}

type Z struct {
Y
c int
}

func main() {
x := X{a: 1}
y := Y{
X: x,
b: 2,
}
z := Z{
Y: y,
c: 3,
}
// z.a,z.Y.a,z.Y.X.a 三者等价
println(z.a, z.Y.a, z.Y.X.a) // 1 1 1
z = Z{}
z.a = 2
println(z.a, z.Y.a, z.Y.X.a) // 2 2 2
}

嵌套层次可以有相同字段,但是最好避免,以免出现歧义

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
package main
type X struct {
a int
}
type Y struct {
X
a int
}

type Z struct {
Y
a int
}

func main() {
x := X{a: 1}
y := Y{
X: x,
a: 2,
}
z := Z{
Y: y,
a: 3,
}
// z.a,z.Y.a,z.Y.X.a 三者等价
println(z.a, z.Y.a, z.Y.X.a) // 3 2 1
z = Z{}
z.a = 4
z.Y.a = 5
z.Y.X.a = 6
println(z.a, z.Y.a, z.Y.X.a) // 4 5 6
}
内嵌字段的方法调用
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
package main
import "fmt"
type X struct {
a int
}
type Y struct {
X
b int
}
type Z struct {
Y
c int
}
func (x X) Print() {
fmt.Printf("In = X, a = %d\n", x.a)
}

func (x X) XPrint() {
fmt.Printf("In = X, a = %d\n", x.a)
}

func (y Y) Print() {
fmt.Printf("In = Y, b = %d\n", y.b)
}

func (z Z) Print() {
fmt.Printf("In = Z, c = %d\n", z.c)

// 显示的完全路径调用内嵌字段方法
z.Y.Print()
z.Y.X.Print()
}

func main() {
x := X{a: 1}
y := Y{
X: x,
b: 2,
}
z := Z{
Y: y,
c: 3,
}
// 从外向内查找,首先找到 Z 的 Print方法
z.Print()
// 从外向内查找,首先找到 X 的 XPrint方法
z.XPrint()
z.Y.XPrint()
}

不推荐在多层的 struct 类型中内嵌多个同名的字段;但是并不反对 struct 定义和内嵌字段同名方法的用法,因为这提供了一种一种编程技术,使得 struct 能够重写内嵌字段的方法,提供面向对象编程中子类覆盖父类的同名方法的功能。

组合的方法集

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
package main
type X struct {
a int
}
type Y struct {
X
}
type Z struct {
*X
}

func (x X) Get() int {
return x.a
}

func (x *X) Set(i int) {
x.a = i
}

func main() {
x := X{a: 1}
y := Y{
X: x,
}
println(y.Get()) //1
//此处编译器做了自动转换
y.Set(2)
println(y.Get())
// 为了不让编译器做自动转换。使用方法表达式调用方式
// Y内嵌字段 X 所以 type Y 的方法集是Get,Type *Y的方法集是Set Get
(*Y).Set(&y, 3)

// type Y 的方法集合并没有 Set 方法,所以下一句编译不能通过
//Y.Set(y, 3)
println(y.Get()) //3
z := Z{
X: &x,
}

// 按照嵌套字段的方法集的规则
// Z 内嵌字段 *X,所以 type Z 和 type *Z 方法集都包含类型 X 定义的方法 Get 和 Set
// 为了不让编译器做自动转换,仍然使用方法表达式调用方式
Z.Set(z, 4)
println(z.Get()) // 4
(*Z).Set(&z, 5)
println(z.Get()) // 5
}

编译器的自动转换仅适用于直接通过类型实例调用方法时才有效,类型实例传递给接口时,编辑器不会进行自动转换,而是会进行严格的方法集校验。

函数类型

  • 函数命名类型
1
2
type NewFuncType FuncLiteral
type NewFuncType OldFuncType
  • 函数声明

Go 语言函数不需要声明,可以直接调用,但是Go调用汇编语言编写的函数还是要使用函数声明语句

1
2
3
4
5
6
// 函数声明=函数名+函数签名
// 函数签名
func (InputTypeList) OutputTypeList

// 函数声明
func FuncName (InputTypeList) OutputTypeList

示例:

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
// 有名函数定义,函数名是add
// add 类型是函数字面量类型 func (int, int) int
func add(a, b int) int {
return a + b
}

// 函数声明语句,用于Go代码调用汇编代码

func add(int, int) int

// add 函数的签名,实际上就是 add 的字面量类型
func (int, int) int

// 匿名函数不能独立存在,常作为函数参数、返回值或者赋值给某个变量
// 匿名函数可以直接显式初始化
// 匿名函数的类型也是函数字面量类型 func (int, int) int

func (a, b int) int {
return a + b
}

// 新定义函数类型 ADD
// ADD 底层类型是函数字面量类型 func (int, int) int
type ADD func (int, int) int
// addADD 的类型相同,并且 add 是字面量类型
// 所以 add 可直接赋值给 ADD 类型的变量 g
var g ADD = add

func main() {
f := func (a, b int) int {
return a + b
}
g(1, 2)
f(1, 2)

// f 和 add 函数签名相同
fmt.Printf("%T\n", f) // func (int, int) int
fmt.Printf("%T\n", add) // func (int, int) int
}

HTTP 标准库范例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// 定义一个有名函数类型 HanderFunc
type HandlerFunc func (ResponseWriter, *Request)

// 为有名函数类型添加方法
// 这是一种包装器的编程技法
// ServerHTTP calls f(w, r)
func (f HandlerFunc) ServerHTTP(w ResponseWriter, r *Request){
f(w, r)
}

// 函数类型 HandlerFunc 实现了接口 Handler 的方法
type Handler interface {
ServerHTTP(ResponseWriter, *Request)
}

func (mux *ServeMux) Handle(patterm string, handler Handler)
// 所以 HanderFunc 类型的变量可以传递给 Handler 接口变量
func (mux *ServeMux) HandleFunc(patterm string, handler func(ResponseWriter, *Request)){
mux.Handle(pattern, HandlerFunc(handler))
}
CATALOG
  1. 1. 类型系统
    1. 1.1. 命名类型 & 未命名类型
    2. 1.2. 底层类型
      1. 1.2.1. 类型相同和类型赋值
        1. 1.2.1.1. 类型可直接赋值
      2. 1.2.2. 类型强制转换
    3. 1.3. 类型方法
      1. 1.3.1. 自定义类型
      2. 1.3.2. 自定义 struct 类型
      3. 1.3.3. struct 初始化
        1. 1.3.3.1. 推荐写法(指定字段名初始化)
        2. 1.3.3.2. 使用构造函数进行初始化
        3. 1.3.3.3. 结构字段的特点
        4. 1.3.3.4. 匿名字段
        5. 1.3.3.5. 自定义接口类型
      4. 1.3.4. 方法
    4. 1.4. 方法调用
      1. 1.4.1. 一般调用
      2. 1.4.2. 方法值
      3. 1.4.3. 方法表达式
      4. 1.4.4. 方法集
      5. 1.4.5. 值调用和表达式调用的方法集
    5. 1.5. 组合和方法集
      1. 1.5.1. 组合
        1. 1.5.1.1. 内嵌字段的方法调用
      2. 1.5.2. 组合的方法集
    6. 1.6. 函数类型