又一道比较运算符相关的面试题让我明白基础很重要

来自公#众#号:新世界杂货铺

比较运算不简单啊

我们先看一下上一期的投票结果:

image

首先,笔者自己选择了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 }为类型。

类型别名不会创建新的类型。在上述例子中t1struct{ x, y float64 }是相同的类型。

类型定义会创建新的类型,且这个新类型又被叫做defined type。在上述例子中,新类型t2struct{ x, y float64 }是不同的类型。

underlying type

定理一
每一个类型T都有一个underlying type(笔者称之为原始类型,在后面文章中的原始类型均代表underlying type)。

定理二:如果T是预定义的booleannumericstring类型之一,或者是类型字面量则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
)

上述例子中,stringA1A2B1B2的原始类型为string[]B1B3B4的原始类型为[]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上时给的提示。

image

注意:笔者在实际的验证过程中发现部分有类型的常量和变量在赋值时会编译报错。

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这样一条语句。

结合上述代码我们知道str1string是由类型定义创建的新类型即defined type,所以var s5 str1 = s2也不满足第2点。

builtin.go文件对booleannumericstring的类型均做了类型定义,下面以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、总结

blankStstruct { a int _ string }类型相同且满足可赋值条件,因此根据“在任何比较中,至少满足一个操作数能赋值给另一个操作数类型的变量”这一定理知面试题中的bst1bst2可比较。

接下来根据上一篇文章提到的结构体比较规则知bst1bst2相等,所以面试题最终输出结果为true

如果不是再去研读一篇Go的基础语法,笔者还不知道曾经遗漏了这么多细节。“读书百遍其义自见”,古人诚不欺我!

最后,衷心希望本文能够对各位读者有一定的帮助。

注:

  1. 写本文时, 笔者所用go版本为: go1.14.2
  2. 文章中所用完整例子:https://github.com/Isites/go-coder/blob/master/types/main.go
©著作权归作者所有,转载或内容合作请联系作者
禁止转载,如需转载请通过简信或评论联系作者。
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 156,265评论 4 359
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 66,274评论 1 288
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 106,087评论 0 237
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,479评论 0 203
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 51,782评论 3 285
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,218评论 1 207
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,594评论 2 309
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,316评论 0 194
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 33,955评论 1 237
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,274评论 2 240
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 31,803评论 1 255
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,177评论 2 250
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 32,732评论 3 229
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 25,953评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,687评论 0 192
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,263评论 2 267
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,189评论 2 258