true != true?面試官,你坑人?。?!
閱讀本文大概需要 5 分鐘。
本文總結(jié)一些初學(xué)者很容易犯錯的知識點(diǎn)。本文作者:橘中秘士。
NULL
SQL中 NULL 是一個特殊的值,其不滿足自反性,也就是NULL != NULL,如果用column = NULL試圖查詢值為NULL的數(shù)據(jù)注定悲劇。所以SQL單獨(dú)為其準(zhǔn)備了IS NULL語法。
NaN
IEEE754規(guī)定,有且僅有NaN不滿足自反性。假設(shè)NaN作為 key 存放了一個元素,那么該元素就像掉進(jìn)了黑洞,再也找不回來了。針對NaN也有自己的專用方法:IsNaN。
true != true
這種場景需要刻意地去構(gòu)造,平時是遇不到的。舉一個例子:
func main() {
var j1 int8 = 1
var b1 bool = *(*bool)(unsafe.Pointer(&j1))
var j2 int8 = 2
var b2 bool = *(*bool)(unsafe.Pointer(&j2))
if b1 { // 編譯為TESTB
println("b1 =", b1)
}
if b2 == true { // true是常量,編譯階段會自行處理,不會編譯為CMPB
println("b2 = true")
}
if b1 == b2 { // 編譯為CMPB
println("b1 = b2")
} else {
println("b1 != b2")
}
}
b1和b2都是通過unsafe “構(gòu)造” 的bool值,當(dāng)其與 if 或者其它需要bool類型的操作符搭配時,其表現(xiàn)出bool類型,當(dāng)其與==操作符或者其它(可以被bool類型和int8類型同時使用的)操作符搭配時,其表現(xiàn)出int8類型。
根本原因在于 CPU 只認(rèn)指令,它的概念里沒有數(shù)據(jù)類型更不必說int8或者bool,所謂的類型是高級語言對于 CPU 指令的抽象[^0]。譬如TESTB指令的操作數(shù),編譯器把它稱為bool;CMPB對應(yīng)的操作數(shù),編譯器把它稱為int8或者uint8;CPU 指令有一組有符號移位和無符號移位,編譯器將其抽象為signed和unsigned。
反過來,編譯器把if還原成TESTB,將==還原成CMP,于是就出現(xiàn)了同樣的數(shù)值(同樣的內(nèi)存)在不同的指令下表現(xiàn)出了不一樣的行為。
nil != nil
這個場景初學(xué)者會經(jīng)常遇到,即使是大佬如果不注意也會被坑。
但是如果理解了背后的原理,即使遇到也可以一笑帶過。
package main
type MyErr struct {
}
func (MyErr) Error() string {
return "MyErr"
}
func main() {
var e error = GetErr()
println(e == nil)
}
func GetErr() *MyErr {
return nil
}
出現(xiàn)這種“異?!爆F(xiàn)象一定存在函數(shù)調(diào)用,在單一函數(shù)內(nèi)部不存在“反?!爆F(xiàn)象。譬如在
GetErr函數(shù)內(nèi)部,怎么造都不會出現(xiàn)nil != nil的怪胎。出現(xiàn)這種“異?!爆F(xiàn)象一定存在 interface type 引用 concrete type 的背景。譬如
var e error改為var e *MyErr則不會出現(xiàn)nil != nil的怪胎。interface type 引用 concrete type 時,隱含了
convT2I[^1]。convT2I的主要作用在于用interface 的 type和concrete 的 value合成了一個新值。如果是 concrete type 和 nil 比較,因?yàn)?type 是確定的,只要 value 對應(yīng)的內(nèi)存為 0 就可以認(rèn)定。
如果是 interface type 和 nil 比較,需要 type 和 value 對應(yīng)的內(nèi)存同時為 0 才可認(rèn)定。由于允許只有 type 沒有 value[^2],不允許只有 value 沒有 type,所以可以簡化為 type 是否為指定。
由于
nil相對于編譯器來說相當(dāng)于字面常量,各個階段都存在不同程度的優(yōu)化。讓我們觀察一下interface type和interface type之間的比較、interface type和concrete type之間的比較,來感受一下編譯器是怎么處理 type 和 value 的。interface type和interface type之間的比較
func walkcompareInterface(n *Node, init *Nodes) *Node {
n.Right = cheapexpr(n.Right, init)
n.Left = cheapexpr(n.Left, init)
eqtab, eqdata := eqinterface(n.Left, n.Right)
var cmp *Node
if n.Op == OEQ { // x == y
cmp = nod(OANDAND, eqtab, eqdata) // eqtab && eqdata
} else { // x != y
eqtab.Op = ONE
cmp = nod(OOROR, eqtab, nod(ONOT, eqdata, nil)) // !eqtab || !eqdata
}
return finishcompare(n, cmp, init)
}
interface type和concrete type
if n.Left.Type.IsInterface() != n.Right.Type.IsInterface() { // 一個interface一個concrete
l := cheapexpr(n.Left, init)
r := cheapexpr(n.Right, init)
// 如果需要,交換左右雙方,保證左側(cè)為interface右側(cè)為concrete。
if n.Right.Type.IsInterface() {
l, r = r, l
}
// 如果是x == y,用&&連接;如果是x != y,用||連接。
eq := n.Op
andor := OOROR
if eq == OEQ {
andor = OANDAND
}
// Check for types equal.
// For empty interface, this is:
// l.tab == type(r)
// For non-empty interface, this is:
// l.tab != nil && l.tab._type == type(r)
var eqtype *Node
tab := nod(OITAB, l, nil)
rtyp := typename(r.Type)
// 首先比較type
if l.Type.IsEmptyInterface() {
tab.Type = types.NewPtr(types.Types[TUINT8])
tab.SetTypecheck(1)
eqtype = nod(eq, tab, rtyp)
} else {
nonnil := nod(brcom(eq), nodnil(), tab)
match := nod(eq, itabType(tab), rtyp)
eqtype = nod(andor, nonnil, match)
}
// 其次比較value
eqdata := nod(eq, ifaceData(n.Pos, l, r.Type), r)
// 使用&&或者||連接兩個結(jié)果
expr := nod(andor, eqtype, eqdata)
n = finishcompare(n, expr, init)
return n
}
[^0]: 譬如無符號長整型,C 稱之為 unsigned long int,Go 稱之為 uint64,Rust 稱之為 u64。
[^1]: convT2I 是一類函數(shù)調(diào)用,譬如 convT2Inoptr,convT2E,convT2Enoptr,convT64,convT32,convT16 等等,包括被編譯器優(yōu)化掉的函數(shù)調(diào)用。
[^2]: 譬如 var err *MyErr,指明了類型,但尚未賦值。
歡迎關(guān)注我
都看到這里了,隨手點(diǎn)個贊支持下唄!
