来自公众号:新世界杂货铺
比拟运算不简略啊
咱们先看一下上一期的投票后果:
首先,笔者本人抉择了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]stringvar 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 = map1fmt.Println(map2)
map1和map2变量的原始类型为map[int]string
,且满足只有map2是defined type
,所以可能失常赋值。
var map3 m2 = map1fmt.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 = c1fmt.Println(c2 == c1) // truec1 = 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 = s1var s4 str2 = s1fmt.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 intvar i1 int = 1const i2 int = 1var i3 int1 = i1 // cannot use i1 (variable of type int) as int1 value in variable declarationvar 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 ttttype ST1 = struct { F string}var A = ST1{ F: "1111",}package maintype st1 struct { F string}var st11 st1 = ttt.Afmt.Println(st11) // output: {1111}
依据类型雷同大节的第7点和类型可赋值大节的第1点知,ST1和st1类型雷同且可赋值,因而上述代码可能失常运行
- 构造体在不同包,且蕴含未导出字段
package ttttype ST2 = struct { F string a string}var B = ST2{ F: "1111",}package maintype st2 struct { F string a string}var st21 st2 = ttt.Bfmt.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-...