刚刚在studygolang上看了一个面试题,这里就来聊一下这个题目,看似简单却暗藏玄机:
package main
import "fmt"
func main() {
fmt.Println(string(65))
}
请问上面的代码会输出什么?
- A. A
- B. 65
- C. compilation error
是数字"65"还是字母"A"?如果你认为是"65",那恭喜你——答错了!
实际上,这段代码的输出是大写字母"A"。
为什么是A?
这个结果背后是Go语言的一个重要设计决策:当使用string()进行类型转换时,如果传入整数,这个整数会被解释为Unicode代码点(code point),并返回对应的字符。
因为65是字母"A"的Unicode代码点(同时也是ASCII码),所以string(65)的结果就是字符"A"。
这就像是在查字典:整数65是字典中的页码,而string(65)就是翻到第65页,看看那个位置是什么字。
常见的误解
很多初学者会认为string(65)应该得到字符串"65",这是因为他们在其他语言中有类似的经验,或者直觉上认为这是将数字转换为它的字符串表示形式。
但实际上,在Go语言中,string()函数并不是这样工作的。它不会将整数65转换为十进制字符串"65",而是将其视为字符编码。
如何正确转换?
如果你确实需要将整数65转换为字符串"65",应该使用strconv包中的Itoa函数:
package main
import (
"fmt"
"strconv"
)
func main() {
fmt.Println(strconv.Itoa(65)) // 输出:"65"
}
或者使用fmt包的Sprintf函数:
package main
import "fmt"
func main() {
fmt.Println(fmt.Sprintf("%d", 65)) // 输出:"65"
}
注意事项
在实际编程中,如果你不小心混淆了这两种转换,可能会导致难以发现的bug。比如处理用户ID或数字编码时,错误地使用string()转换会导致数据错误。
Go的编译器工具链甚至包含了针对这个问题的检查。如果你使用go vet命令检查代码,当它发现你将整数直接转换为字符串时,会给出警告:
conversion from int to string yields a string of one rune,
not a string of digits (did you mean fmt.Sprint(x)?)
这个提示告诉你:从int到string的转换会得到一个单字符的字符串,而不是数字字符串(你是不是想用fmt.Sprint(x)?)。
深入理解
为了更好地理解这个现象,我们需要了解Go语言中字符和字符串的表示:
在Go中,字符(rune)是int32的别名,表示一个Unicode代码点。而字符串则是字节的序列。
当我们使用'A'这样的字符字面量时,它实际上是一个rune类型,值为65:
a := 'A'
fmt.Printf("%T, %d\n", a, a) // 输出:int32, 65
而当我们使用"A"这样的字符串字面量时,它才是真正的字符串类型。
小结
通过这个小小的面试题,我们可以学到Go语言中重要的类型转换规则:
- string(整数) 将整数解释为Unicode代码点,返回对应字符
- strconv.Itoa(整数) 将整数转换为它的十进制字符串表示
- 在实际编程中要明确自己的需求,选择正确的转换方法
这个知识点虽然小,但却反映了Go语言设计的一致性和明确性:每种操作都有明确的语义,不会因为"看起来合理"而改变行为。