注:本文基于Windos系统上Go SDK v1.18进行讲解
type bool bool
type uint8 uint8
type uint16 uint16
type uint32 uint32
type uint64 uint64
type int8 int8
type int16 int16
type int32 int32
type int64 int64
type float32 float32
type float64 float64
type complex64 complex64
type complex128 complex128
type string string
type int int
type uint uint
type uintptr uintptr
type byte = uint8
type rune = int32
type any = interface{}
built-in下的都是内置类型,通过type的都是自定义类型;
给内置类型定义方法事不被允许的,但是可以对自定义类型进行定方法(接口类型是无效的方法接收者);
所以我们不能像类型T这样给内置类型和接口定义方法
例如:
int
,int64
,float32
,string
,bool
等。这些已经是GO中预先声明好的类型;
在Go语言规范中对做了明确规定:
所有数值类型都是defined type;(这里面就包含int)(注:rune、byte是alias类型,不是defined type)
字符串类型string是defined type;
布尔类型bool是defined type;
这就意味着map、数组、切片、结构体、channel等原生复合类型(composite type)都不是defined type
在Go中,使用下述类型声明语句定义的类型T1、T2被称为defined type。
//在Go中,我们定义一个类型一般通过type关键字进行,比如以下两行
type T1 int
type T2 T1
命名类型:有名字的类型
一个命名类型一定和其它类型不同!
注:预先声明类型defined type属于命名类型;
alias type也属于命名类型;
var的也是命名类型
var i int // named type
type myInt int // named type
var b bool // named type
数组,结构体,指针,函数,接口,切片,map,通道 都是未命名类型;
虽然他们没有名字,但却有一个类型字面量(type literal
)来描述他们由什么构成
注意:类型字面量(type literal
)==未命名类型
[]string // unnamed type
map[string]string // unnamed type
[10]int // unnamed type
我们基于一个类型创建一个新类型,称之为defintion;基于一个类型创建一个别名,称之为alias,这就是他们最大的区别。
type MyInt1 int//defintion
type MyInt2 = int//alias
任何类型T都有基本类型;
如果T 是预先声明类型:boolean, numeric, or string(布尔,数值,字符串)中的一个,或者是一个类型字面量(type literal
),他们对应的基础类型就是T自身。
//他们的类型声明为 string的预先声明的类型,所以他们的基础类型就是T它自身: string
type A string
type S string
type M map[string]int //有类型字面量,所以他的基础类型也是T它自身:map[string]int
type N M //N引用了M, 因此N的基础类型是M的类型声明:map[string]int
type P *N //有类型字面量,所以他的基础类型也是T它自身:*N指针
type A string
type B A //B引用了A,因此B的基础类型是A的类型声明:string
type S string
type T map[S]int //T的基础类型应该是 map[string]int 并非map[S]int; 我们讨论的是基础未命名类型map[S]int,因为基础类型只追溯到它的未命名类型的最外层(或者就像说明上说的:如果T是一个类型字面量,它的基础类型就是T自身),
type U T //U的基础类型是map[S]int
问题所在:
MyInt与int是不同的两个类型,MyMap与map[string]int也是不同的两个类型,为何将int型变量赋值给MyInt型变量时需要做显式转型,而将map[string]int变量赋值给MyMap型变量就不需要显式转型呢?
Go是强调类型安全的静态编译型语言,在Go语言中,不同类型变量是不能在一起进行混合计算的,这是因为Go希望开发人员明确知道自己在做什么,这与C语言的“信任程序员”原则完全不同,因此你需要以显式的方式通过转型统一参与计算各个变量的类型。比如:上面问题中MyInt虽然底层类型(underlying type)是int,但MyInt与int是两个不同的类型,因此它们之间的相互赋值需要通过显式转型来进行,否则Go编译器将报错,这个没有任何疑问;
但是MyMap与map[string]int也是两个不同的类型,却可以,为什么呢?
package main
type MyInt int
type MyMap map[string]int
func main() {
var x MyInt
var y int
x = y // 会报错: cannot use y (type int) as type MyInt in assignment;
_ =x
var m1 MyMap
var m2 map[string]int
m1 = m2 // 不会报错
m2 = m1 // 不会报错
}
package main
type MyInt int
type MyMap map[string]int
func main() {
var x MyInt
var y int
y = x
_ = y
var m1 MyMap
var m2 map[string]int
m1 = m2 // 不会报错
m2 = m1 // 不会报错
}
package main
type MyInt int
type MyMap map[string]int
func main() {
var x MyInt
var y int
//如果使用x = MyInt(y) 的话则不会报错
x = MyInt(y)
_ =x
var m1 MyMap
var m2 map[string]int
m1 = m2 // 不会报错
m2 = m1 // 不会报错
}
预先声明类型defined type赋值规则:
x’s type V and T have identical underlying types and at least one of V or T is not a defined type:
如果x的类型V与类型T具有相同的底层类型,并且V和T至少有一个不是defined type,那么x可以赋值给类型T的变量;
我们用问题中的代码来套一下这个规则。我们有一个MyMap类型的变量m1,MyMap类型与map[string]int类型具有相同的底层类型map[string]int,并且map[string]int类型不是一个defined type,那么我们可以将m1直接赋值给map[string]int类型的变量m2,反之亦可
注:”V和T至少有一个不是defined type”是正确的,“V和T至少有一个不是命名类型”是错误的
//以下代码是可以通过的
package main
type a = int
func main() {
var x a
var y int
y = x
_ = y
}
总结:
Go总体来说是推崇显式哲学的,那怎么来理解这种隐式转型呢?我觉得至少有两点:
(1)首先这种转型更多是在编译器保证类型安全性的前提下进行的,不会出现溢出或未定义行为;
(2)其次,这种隐式转型一定程度减少了代码输入,对开发体验的提升有帮助
例子中这些赋值都不会报编译错误
package main
type MyMap map[string]int
type MySlice []byte
type MyArray [10]int
type MyStruct struct {
a int
b string
}
type MyChannel chan int
func main() {
var m1 MyMap
var m2 map[string]int
m1 = m2 // 不会报错
m2 = m1 // 不会报错
var sl1 MySlice
var sl2 []byte
sl1 = sl2 // 不会报错
sl2 = sl1 // 不会报错
var arr1 MyArray
var arr2 [10]int
arr1 = arr2 // 不会报错
arr2 = arr1 // 不会报错
var s1 MyStruct
var s2 struct {
a int
b string
}
s1 = s2 // 不会报错
s2 = s1 // 不会报错
var c1 MyChannel
var c2 chan int
c1 = c2 // 不会报错
c2 = c1 // 不会报错
}
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)
}
以下代码编译通过,因为
m
是未命名类型,同时m
和mMap
有相同的基础类型
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)
}
因为
Meter
和Centimeter
的基础类型都是integers类型,所以他们的基础类型是可以相互转换,所以编译通过
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)
}
忽略结构体的tags,只要结构体X和T拥有相同的基础类型,就可以转换
字段Meter.value
的基础类型是int64
,字段Centimeter.value
的基础类型是int64
。一致的基础类型,所以可以相互转换,编译通过
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
的基础类型是int32
,因为一个命名类型一定和其它任意类型都不同,即int32与int64一定不同。所以他们不一致,不是相同的基础类型,所以不能转换
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)
}