本文内容基于《Go语言核心编程》,李文塔著。
1 类型系统
Go语言的类型系统可以分为命名类型、非命名类型、底层类型、动态类型和静态类型等。
1.1 类型简介
1.1.1 命名类型和未命名类型
命名类型:类型可以通过标识符来表示,这种类型称为命名类型,Go语言的基本类型中有20个预声明简单类型和用户自定义类型都是命名类型。
未命名类型:一个类型由预声明类型、关键字和操作符组合而成,这个类型成为未命名类型,又称为类型字面量,Go语言的基本类型中的复合类型都是未命名类型(数组、切片、字典、通道、指针、函数字面量、结构、接口),注意这里指的是没有使用type格式来定义的。
type Person struct {
name string
age int
}
func main() {
// 未命名类型
a := struct {
name string
age int
}{"张三", 18}
// 命名类型
b := Person{
name: "李四",
age: 20,
}
fmt.Printf("a = %T\n", a) // a = struct { name string; age int }
fmt.Printf("b = %T\n", b) // b = main.Person
}
- 基本类型=简单类型+复合类型;
- 命名类型=预声明类型+自定义类型;
- 预声明类型=简单类型;
- 自定义类型的old_type可以是预声明类型、自定义类型和未命名类型中的一种;
- 未命名类型=类型字面量=复合类型。
1.1.2 底层类型
所有类型都有一个底层类型:
- 预声明类型和未命名类型的底层类型是它们自身;
- 自定义类型中的new_type的底层类型是逐层递归向下查找的,知道查到的old_type是预声明类型或未命名类型为止。
// T1和T2的底层类型是string
type T1 string
type T2 T1
// T3和T4的底层类型是[]string
type T3 []string
type T4 T3
// T5和T6的底层类型是[]T1
type T5 []T1
type T6 T5
1.1.3 类型相同
两个类型是否相同,参考如下:
- 两个命名类型相同的条件是两个类型声明的语句完全相同;
- 命名类型和未命名类型永远不相同;
- 两个未命名类型相同的条件是它们的类型声明字面量的结构相同,并且内部元素的类型相同;
- 通过类型别名语句声明的两个类型相同(type T1 = T2)。
引入别名主要有如下原因:
- 为了解决新旧包的迁移兼容问题,比如context包先前并不在标准库里面,后面迁移到了标准库;
- Go的按包进行隔离的机制不太精细,有时我们需要将大包划分为几个小包进行开发,但需要在大包里面暴露全部的类型给使用者;
- 解决新旧类型的迁移问题,新类型先是旧类型的别名,后续的软件都基于新类型编程,在合适的时间将新类型升级为和旧类型不兼容,常用于软件的柔性升级。
1.1.4 类型强制转换
字符串和字节切片之间的转换最为常见:
- 数值类型和string类型之间的相互转换可能造成值部分丢失,其他的转换仅是类型的转换,不会造成值的改变;
- string和数字之间的转换可以使用标准库strconv;
- Go语言没有语言机制支持指针和interger之间的直接转换,可以使用标准库中的unsafe包进行处理。
// 顺便体会[]byte和[]rune的区别(中文占几个字节?)
func main() {
s := "hello 世界"
var a []byte
a = []byte(s)
fmt.Printf("string -> []byte = %v -> %v\n", s, a) // string -> []byte = hello 世界 -> [104 101 108 108 111 32 228 184 150 231 149 140]
var b string
b = string(a)
fmt.Printf("[]byte -> string = %v -> %v\n", a, b) // []byte -> string = [104 101 108 108 111 32 228 184 150 231 149 140] -> hello 世界
var c []rune
c = []rune(s)
fmt.Printf("string -> []rune =