来自公众号:新世界杂货铺
比拟运算不简略啊
咱们先看一下上一期的投票后果:
首先,笔者本人抉择了 true
,所以理论后果是41%
的读者都抉择了谬误的答案。看到这个后果,笔者置信上一篇文章还是可能帮忙到大家。
通过含辛茹苦终于明确了上一道面试题是咋回事儿,这个时候却见面试官微微一笑道:“上面的输入后果是什么”。
type blankSt struct {
a int
_ string
}
bst1 := blankSt{1, "333"}
bst2 := struct {
a int
_ string
}{1, "555"}
fmt.Println(bst1 == bst2)
这里笔者先留个悬念,后果见后文。
类型申明
留神:本节不介绍语法等根底内容,次要形容一些名词以便于后文的了解。
类型申明将 标识符(类型名称)
绑定到 类型
,其两种模式为类型定义和类型别名。
上面咱们通过一个例子对 标识符
、 类型
和defined type
(后文会应用这个名词)进行解释:
// 类型别名
type t1 = struct{x, y float64}
// 类型定义
type t2 struct{x, y float64}
标识符(类型名称)
:在下面的例子中,t1,t2 为标识符。
类型
:struct{x, y float64}
为类型。
类型别名不会创立新的类型 。在上述例子中t1
和struct{x, y float64}
是雷同的类型。
类型定义会创立新的类型,且这个新类型又被叫做 defined type
。在上述例子中,新类型t2
和struct{x, y float64}
是不同的类型。
underlying type
定理一 :
每一个类型 T 都有一个 underlying type
(笔者称之为 原始类型,在前面文章中的原始类型均代表 underlying type)。
定理二 :如果 T 是预约义的boolean
、numeric
和string
类型之一,或者是类型字面量则 T 的原始类型是其自身,否则 T 的原始类型为 T 在其类型申明中援用的类型的原始类型。
Go 中的数组、构造体、指针、函数、interface{}、slice、map 和 channel 类型均由类型字面量形成。上面以 map 为例:
type T map[int]string
var a map[int]string
在下面的例子中,T 为 map 类型,a 为 map 类型的变量,类型字面量均为 map[int]string
且依据定理二可知 T 的原始类型为map[int]string
。
上面再看一个例子加深对原始类型的了解:
type (
A1 = string
A2 = A1
)
type (
B1 string
B2 B1
B3 []B1
B4 B3
)
上述例子中,string
、A1
、A2
、B1
和 B2
的原始类型为 string
、[]B1
、B3
和B4
的原始类型为[]B1
。
类型雷同
在 Go 中一个 defined type
类型总是和其余类型不同。类型雷同状况如下:
1、两个数组长度雷同且元素类型雷同则这两个数组类型雷同。
2、两个切片元素类型雷同则这两个切片类型雷同。
3、两个函数有雷同数量的参数和雷同数量的返回值,且对应地位的参数类型和返回值类型均雷同则这两个函数类型雷同。
4、如果两个指针具备雷同的根本类型则这两个指针类型雷同。
5、如果两个 map 具备雷同类型的 key 和雷同类型的元素则这两个 map 类型雷同。
6、如果两个 channel 具备雷同的元素类型且方向雷同则这两个 channel 类型雷同。
7、如果两个构造体具备雷同数量的字段,且对应字段名称雷同,类型雷同并且标签雷同则这两个构造体类型雷同。对于不同包上面的构造体,只有蕴含未导出字段则这两个构造体类型不雷同。
8、如果两个接口的办法数量和名称均相等,且雷同名称的办法具备雷同的函数类型则这两个接口类型雷同。
类型可赋值
满足下列任意条件时,变量 x 可能赋值给类型为 T 的变量。
1、x 的类型和 T 类型雷同。
2、x 的类型 V 和 T 具备雷同的原始类型,并且 V 和 T 至多有一个不是defined type
。
type (m1 map[int]string
m2 m1
)
var map1 map[int]string = make(map[int]string)
var map2 m1 = map1
fmt.Println(map2)
map1 和 map2 变量的原始类型为map[int]string
,且满足只有 map2 是defined type
,所以可能失常赋值。
var map3 m2 = map1
fmt.Println(map3)
var map4 m2 = map2
map3 和 map1 同样满足条件,所以可能失常赋值。然而 map4 和 map2 不满足至多有一个不是 defined type
这一条件,故会编译报错。
3、T 是 interface{} 并且 x 的类型实现了 T 的所有办法。
4、x 是双向通道,T 是通道类型,x 的类型 V 和 T 具备雷同的元素类型,并且 V 和 T 中至多有一个不是defined type
。
依据下面咱们能够晓得一个暗藏逻辑是,双向通道可能赋值给单向通道,然而单向通道不能赋值给双向通道。
var c1 chan int = make(chan int)
var c2 chan<- int = c1
fmt.Println(c2 == c1) // true
c1 = c2 // 编译谬误:cannot use c2 (variable of type chan<- int) as chan int value in assignment
因为 c1 可能失常赋值给 c2,所以依据前一篇文章的定理“在任何比拟中,至多满足一个操作数能赋值给另一个操作数类型的变量”知 c1 和 c2 可比拟。
5、x 是预申明标识符 nil
,T 是指针、函数、切片、map、channel 或 interface{} 类型。
6、x 是可由类型 T 的值示意的无类型常量。
type (
str1 string
str2 str1
)
const s1 = "1111"
var s3 str1 = s1
var s4 str2 = s1
fmt.Println(s3, s4) // 1111 1111
上述例子中,s1 是无类型字符串常量故 s1 能够赋值给类型为 str1 和 str2 的变量。
下图是在 vscode 中当鼠标悬浮在变量 s1 上时给的提醒。
留神:笔者在理论的验证过程中发现局部有类型的常量和变量在赋值时会编译报错。
const s2 string = "1111"
var s5 str1 = s2
上述代码在 vscode 中的谬误为cannot use s2 (constant "1111" of type string) as str1 value in variable declaration
。
看到上述编译报错,笔者登时惊了,就算不满足第 6 点也应该满足第 2 点呀。抱着满是纳闷的情绪笔者利用代码跳转,最初在 builtin.go
发现了 type string string
这样一条语句。
联合上述代码咱们晓得 str1
和string
是由类型定义创立的新类型即 defined type
,所以var s5 str1 = s2
也不满足第 2 点。
builtin.go
文件对 boolean
、numeric
和string
的类型均做了类型定义,上面以 int
做近一步验证:
type int1 int
var i1 int = 1
const i2 int = 1
var i3 int1 = i1 // cannot use i1 (variable of type int) as int1 value in variable declaration
var i4 int1 = i2 // cannot use i2 (constant 1 of type int) as int1 value in variable declaration
上述后果合乎预期,因而咱们在平时的开发中对于变量赋值的细节还需牢记于心。
剖析总结
有了后面 类型雷同 和类型可赋值 两大节的基础知识咱们依照上面步骤对本篇的面试题进行剖析总结。
1、类型是否雷同?
咱们先列出面试题中须要比拟的两个构造体:
type blankSt struct {
a int
_ string
}
struct {
a int
_ string
}
依据 类型雷同 大节的第 7 点知,这两个构造体具备雷同数量的字段,且对应字段名称雷同、类型雷同并且标签也雷同,因而这两个构造体类型雷同。
2、是否满足可赋值条件?
依据 类型可赋值 大节的第 1 点知,这两个构造体类型雷同因而满足可赋值条件。
面试题中的两个构造体比较简单,上面笔者对构造体的不同场景进行补充。
- 构造体 tag 不同
type blankSt1 struct {
a int `json:"a"`
_ string
}
bst11 := struct {
a int
_ string
}{1, "555"}
var bst12 blankSt1 = bst11
上述代码在 vscode 中的报错为cannot use bst11 (variable of type struct{a int; _ string}) as blankSt1 value in variable declaration
。两个构造体只有 tag 不同则这两个构造体类型不同,此时这两个构造体不满足任意可赋值条件。
- 构造体在不同包,且所有字段均导出
package ttt
type ST1 = struct {F string}
var A = ST1{F: "1111",}
package main
type st1 struct {F string}
var st11 st1 = ttt.A
fmt.Println(st11) // output: {1111}
依据 类型雷同 大节的第 7 点和 类型可赋值 大节的第 1 点知,ST1 和 st1 类型雷同且可赋值,因而上述代码可能失常运行
- 构造体在不同包,且蕴含未导出字段
package ttt
type ST2 = struct {
F string
a string
}
var B = ST2{F: "1111",}
package main
type st2 struct {
F string
a string
}
var st21 st2 = ttt.B
fmt.Println(st21)
运行上述代码时呈现 cannot use ttt.B (type struct { F string; ttt.a string}) as type st2 in assignment
谬误。
因为 st2 和 ST2 类型不同且他们的原始类型别离为 struct {F string a string}
和 struct {F string; ttt.a string}
,所以 ttt.b 无奈赋值给 st21。
3、总结
blankSt
和 struct {a int _ string}
类型雷同且满足可赋值条件,因而依据“在任何比拟中,至多满足一个操作数能赋值给另一个操作数类型的变量”这肯定理知面试题中的 bst1
和bst2
可比拟。
接下来依据上一篇文章提到的构造体比拟规定知 bst1
和bst2
相等,所以面试题最终输入后果为true
。
如果不是再去研读一篇 Go 的根底语法,笔者还不晓得已经脱漏了这么多细节。“读书百遍其义自见”,今人诚不欺我!
最初,衷心希望本文可能对各位读者有肯定的帮忙。
注:
- 写本文时,笔者所用 go 版本为: go1.14.2
- 文章中所用残缺例子:https://github.com/Isites/go-…