首页天道酬勤这题我会52(这题我会60)

这题我会52(这题我会60)

admin 12-11 23:47 192次浏览

今天在《Go语言爱好者周刊:第62期》贴了一个Go101的话题。原标题如下:

packagemain

consts='Go101.org '

//len(s)=9

//19==512

//512/128==4

varabyte=人/128人

varbbyte=1len(s[:])/128

func main(){ 0

println(a,b)

}

答案是4 0。

很多人应该对这个结果感到惊讶,因为只有不到一半的人做对了。再者,如果我们只给出var b byte=1 len(s[:])/128,不做比较,我想得到正确答案的人会更少。因为对比,虽然很多人的直觉是4/4,但他们认为一定有陷阱,所以会重新思考。

几个小组问为什么结果是4 0。希望能解释一下。因此,有了这篇文章。

这个小问题涉及几个知识点。

len 函数的结果

注意,镜头是内置功能。官方标准库文档中有一句关于len函数[1]的说法:

对于某些参数,如字符串或简单数组表达式,结果可以是常数。有关详细信息,请参见Go语言规范的“长度和容量”部分。

很明显,当参数是文字字符串和简单的数组表达式时,len函数的返回值是常量,这非常重要。

在标题中,如果const s='Go101.org '改为var s='Go101.org ',会是什么结果?

packagemain

vars='Go101.org '

varabyte=人/128人

varbbyte=1len(s[:])/128

func main(){ 0

println(a,b)

}

结果是0 0。

但是把它改成这样:

packagemain

vars=[9]字节{'G ',' o ',' 1 ',' 0 ',' 1 ',',' o ',' r ',' g'}

varabyte=人/128人

varbbyte=1len(s[:])/128

func main(){ 0

println(a,b)

}

结果又是4 0。

然后,看文档中的后半句,看Go语言规范中对长度和容量的描述[2]。

内置函数len和cap获取各种类型的参数并返回int类型的结果。实现将确保结果始终是int值。

如果s是字符串常量,那么len就是常量。如果类型s是数组或指向数组的指针,并且表达式s不包含通道接收或(非常数)函数调用,则表达式len和cap是常量;在这种情况下,不计算s。否则,len和cap的调用结果不是常数,将对s进行求值。

可见主题:

varabyte=人/128人

varbbyte=1len(s[:])/128

第一句中的Len(s)是常量(因为s是字符串常量);而第二句中的len(s[:])不是常量。这是这两个语句唯一的区别:两个LENs的返回值没有区别,都是9,但是一个是常量,另一个不是。

关于位移操作

根据以上分析,现在的关键问题在于置换操作。Go语言规范中有一句话[3]:

移位表达式中的右操作数必须是整数类型,或者是可以用uint类型的值表示的非类型常量。如果非常数移位表达式的左操作数是非类型化常数,则它首先被隐式转换为如果移位表达式被其左操作数单独替换时它所假定的类型。

位移表达式右侧的操作数必须是整数类型或非类型常量,可以用uint类型的值表示。如果非常数置换表达式的左操作数是无符号常数,如果置换表达式仅由其左操作数替换,它将隐式转换为类型。

这里的关键是常数位移表达式。根据以上分析,1 le

n(s) 是常量位移表达式,而 1 << len(s[:]) 不是。

规范上关于常量表达式中,还有这么一句[4]:

If the left operand of a constant shift expression is an untyped constant, the result is an integer constant; otherwise it is a constant of the same type as the left operand, which must be of integer type.

大意是:如果常量 位移表达式 的左侧操作数是一个无类型常量,那么其结果是一个整数常量;否则就是和左侧操作数同一类型的常量(必须是 整数类型 )

因此对于 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。

小结

一道很具迷惑性的题目引出这么多小知识点。可能有人要喷:讨论这些有什么用?这也太细节了。我想说的是,Go 语言规范,细节点很多,能多掌握一些没坏处,说不定将来实际工作就遇到了类似的问题呢?!以上的知识点,很细节,但我认为也是挺有价值的。

当然了,你怎么说都行,你都是对的,你开心就好!

参考资料

[1]

关于 len 函数: https://docs.studygolang.com/pkg/builtin/#len

[2]

关于长度和容量的说明: https://hao.studygolang.com/golang_spec.html#id221

[3]

这么一句: https://docs.studygolang.com/ref/spec#Operators

[4]

这么一句: https://docs.studygolang.com/ref/spec#Constant_expressions

python匿名函数lambda(lambda用法) 文本解读核心素养(十八项核心制度)