golang中使用for range 遍历字符串时常遇到的bug,byte和rune类型,源码分析
在leetcode刷题时,刷到第13题罗马数字转整数时
在如下代码中可以通过,
func romanToInt(s string) int {
ans := 0
// 1.创建一个map表示字母到数字的映射
cnt := map[byte]int{
'I' : 1,
'V' : 5,
'X' : 10,
'L' : 50,
'C' : 100,
'D' : 500,
'M' : 1000,
}
n := len(s)
for i := range s{
value := cnt[s[i]]
if i < n-1 && value < cnt[s[i+1]]{
ans -= value
}else{
ans += value
}
}
return ans
}
但是将上述代码中for i := range s{}
转换成为for i, x := range s {}
后代码通过不了,即为如下代码
func romanToInt(s string) int {
ans := 0
// 1.创建一个map表示字母到数字的映射
cnt := map[byte]int{
'I' : 1,
'V' : 5,
'X' : 10,
'L' : 50,
'C' : 100,
'D' : 500,
'M' : 1000,
}
n := len(s)
for i, x := range s{
value := cnt[x] //通过 x直接引用该元素
if i < n-1 && value < cnt[s[i+1]]{
ans -= value
}else{
ans += value
}
}
return ans
}
会提示如下错误
Line 15: Char 22: cannot use x (variable of type rune) as byte value in map index (solution.go)
根据错误提示为显示为,数据类型不符,我们都知道在go语言中字符串为字符数组,我们也可以将它理解成一个由字符组成的数组,字符串是由字符组成的数组,C 语言中的字符串使用字符数组 char[]
表示。数组会占用一片连续的内存空间,而内存空间存储的字节共同组成了字符串,Go 语言中的字符串只是一个只读的字节数组
查阅资料后发现:遍历字符串的过程与数组、切片和哈希表非常相似,只是在遍历时会获取字符串中索引对应的字节并将字节转换成 rune
。我们在遍历字符串时拿到的值都是 rune
类型的变量,for i, r := range s {}
的结构都会被转换成如下所示的形式:
ha := s
for hv1 := 0; hv1 < len(ha); {
hv1t := hv1
hv2 := rune(ha[hv1])
if hv2 < utf8.RuneSelf {
hv1++
} else {
hv2, hv1 = decoderune(ha, hv1)
}
v1, v2 = hv1t, hv2
}
字符串是一个只读的字节数组切片,所以范围循环在编译期间生成的框架与切片非常类似,只是细节有一些不同。
使用下标访问字符串中的元素时得到的就是字节,但是这段代码会将当前的字节转换成 rune
类型。如果当前的 rune
是 ASCII 的,那么只会占用一个字节长度,每次循环体运行之后只需要将索引加一,但是如果当前 rune
占用了多个字节就会使用 runtime.decoderune
函数解码,