![0ceacafed853f9e7dd14e24326b5c296.png](https://i-blog.csdnimg.cn/blog_migrate/feb91f47d71118836bffa65de995263a.jpeg)
Go101 作者发布了一道有意思的题目,这道题主要考察细节点,Go101 这本书也是以抠细节著称。看看这道题,以下程序输出什么?(单选)
package main
const s = "Go101.org"
// len(s) == 9
// 1 << 9 == 512
// 512 / 128 == 4
var a byte = 1 << len(s) / 128
var b byte = 1 << len(s[:]) / 128
func main() {
println(a, b)
}
A、0 0;B、0 4;C:4 0;D:4 4
答案:C
解析:
不少人对这个结果应该很吃惊,如果只给var b byte = 1 << len(s[:]) / 128
,没有 a 对比,我想答对的人会更少。因为有对比,很多人虽然直觉是 4 4,但想到一定有陷阱,所以会重新思考。
这个小题涉及到几个知识点。
首先len函数
要注意,len 是一个内置函数。在官方标准库文档关于 len 函数[1]有这么一句:
明确支持,当参数是字符串字面量和简单 array 表达式,len 函数返回值是常量,这很重要
上题中,如果const s = "Go101.org”
改为var s = "Go101.org"
结果又会是什么呢?
package main
var s = "Go101.org"
var a byte = 1 << len(s) / 128
var b byte = 1 << len(s[:]) / 128
func main() {
println(a, b)
}
答案是 0 0
但改为这样:
package main
var s = [9]byte{'G', 'o', '1', '0', '1', '.', 'o', 'r', 'g'}
var a byte = 1 << len(s) / 128
var b byte = 1 << len(s[:]) / 128
func main() {
println(a, b)
}
结果又是 4 0
接着看文档那句话的后半句,查看 Go 语言规范中关于长度和容量的说明。
内置函数 len 和 cap 获取各种类型的实参并返回一个 int 类型结果。实现会保证结果总是一个 int 值。
如果 s 是一个字符串常量,那么 len(s) 是一个常量 。如果 s 类型是一个数组或到数组的指针且表达式 s
不包含 信道接收 或(非常量的) 函数调用的话, 那么表达式 len(s) 和 cap(s) 是常量;这种情况下,
s 是不求值的。否则的话, len 和 cap 的调用结果不是常量且 s 会被求值。
可见题目中:
var a byte = 1 << len(s) / 128
var b byte = 1 << len(s[:]) / 128
第一句的 len(s) 是常量(因为 s 是字符串常量)
而第二句的 len(s[:]) 不是常量。
这是这两条语句的唯一区别:两个 len 的返回结果数值并无差异,都是 9,但一个是常量一个不是。
其次,位移操作
根据上面的分析,现在问题的关键在于位移运算这里。Go 语言规范中有这么一句:
在位移表达式的右侧的操作数必须为整数类型,或者可以被 uint 类型的值所表示的无类型的常量。如果一个非
常量位移表达式的左侧的操作数是一个无类型常量,那么它会先被隐式地转换为假如位移表达式被其左侧操作数
单独替换后的类型。
这里的关键在于常量位移表达式。根据上文的分析,1 << len(s)
是常量位移表达式,而 1 << len(s[:])
不是。
规范上关于常量表达式中,还有这么一句:
如果常量 位移表达式 的左侧操作数是一个无类型常量,那么其结果是一个整数常量;否则就是和左侧操作数同
一类型的常量(必须是 整数类型 )
因此对于 var a byte = 1 << len(s) / 128
,因为 1 << len(s)
是一个常量位移表达式,因此它的结果也是一个整数常量,所以是 512,最后除以 128,最终结果就是 4。
而对于 var b byte = 1 << len(s[:]) / 128
,因为 1 << len(s[:])
不是一个常量位移表达式,而做操作数是 1,一个无类型常量,根据规范定义它是 byte 类型(根据:如果一个非常量位移表达式的左侧的操作数是一个无类号常量,那么它会先被隐式地转换为假如位移表达式被其左侧操作数单独替换后的类型)。
为什么是 byte 类型,大家可能还是有点晕。这要回到关于常量的说明上。
常量是在编译的时候进行计算的。在 Go 语言中,常量分两种:无类型和有类型。Go 规范上说,字面值常量,
true , false , iota 以及一些仅包含无类型的恒定操作数的 常量表达式 是无类型的。
那有类型常量是怎么来的呢?一般有两种:显示声明或隐式得到。比如:
const a int32 = 23
const b float32 = 0.1
无类型常量都有一个默认类型(无类型常量的默认类型分别是 bool , rune , int , float64 , complex128 或 string)。当在上下文中需要请求该常量为一个带类型的值时,这个 默认类型 便指向该常量隐式转换后的类型。
所以 var b byte = 1 << len(s[:]) / 128
中,根据规范定义,1 会隐式转换为 byte 类型,因此 1 << len(s[:])
的结果也是 byte 类型,而 byte 类型最大只能表示 255,很显然 512 溢出了,结果为 0,因此最后 b 的结果也是 0。