GO学习笔记——字符和字符串(14)

xiaoxiao2025-06-17  12

GO中用string表示字符串,它是一个内置类型,而C++中的string是一个标准类,这是一个区别。因为字符串操作非常多,另外GO中还引入了rune来支持国际化的字符(中文字符等),因此这里单独开一篇文章来将字符和字符串。


先来简单地看一个中英文都有的字符串

func main() { str := "我叫lyb" fmt.Println(len(str)) }

输出结果

9

输出9的原因是因为,一个中文在utf-8编码的情况下占了3个字符(当然也有可能是2个字符的),加上lyb所以输出了9。

GO中的字符串其实是一个字节切片(很好理解,C++中是string其实也是一个数组,只不过数组中存的都是字符罢了)

 

rune和遍历字符串

因为字符串本质是一个切片,所以也可以用range来遍历字符串

打印一些每个字节的值看看。

func main() { str := "我叫lyb" for _,b := range []byte(str){ //这里把字符串看为一个字节数组 fmt.Printf("%X ",b) //以十六进制的方式打印 } }

输出结果

E6 88 91 E5 8F AB 6C 79 62

可以看到总共是9个字节,其中(E6 88 91)表示中文“我”,(E5 8F AB)表示中文“叫”,后面三个字节分别表示l,y,b。

再来遍历一下原字符串

func main() { str := "我叫lyb" for i,ch := range str{ fmt.Printf("(%d,%x) ",i,ch) } for i,ch := range str{ fmt.Printf("(%d,%c) ",i,ch) //%c表示以字符形式打印 } }

输出结果

(0,6211) (3,53eb) (6,6c) (7,79) (8,62) (0,我) (3,叫) (6,l) (7,y) (8,b)

在这里,这个ch其实就是一个rune类型,在存的时候,“我”占了3个字节,它的值是6211;“叫”占了3个字节,它的值是53eb。

但是在之前的输出结果中,这个“我”明明是(E6 88 91),这里确是6211。因为在按字节编码的时候,GO默认使用的是utf-8编码,而(E6 88 91)就是utf-8编码的“我”,而6211是Unicode编码的。

所以编译器做的事情是这样的,“我”以uft-8的形式存储在每一个字节中,编译器对其进行解码之后,又将其转成了Unicode编码,转完了之后又放在了rune类型中,这是一个int32类型。所以字符串中的每个字符都是rune类型,rune就是GO中的char类型。

 

字符串的长度

之前我们打印了“我叫lyb”的长度,得到的是9,因为len函数求的只是所占字节长度。

但是按常理我们应该得到的是5,中文也应该算是一个字符。

在utf8包中有一个函数可以帮助我们得到总共有多少字符

func main() { str := "我叫lyb" for i,ch := range str{ fmt.Printf("(%d,%c) ",i,ch) } fmt.Println() fmt.Println("总共有多少个字节: ",len(str)) fmt.Println("总共有多少个rune: ",utf8.RuneCountInString(str)) bytes := []byte(str) for len(bytes) > 0 { ch, size := utf8.DecodeRune(bytes) bytes = bytes[size:] fmt.Printf("%c ",ch) } fmt.Println() //如果直接对每个字节按%c输出,会出现乱码 for i,ch := range []byte(str){ fmt.Printf("(%d,%c) ",i,ch) } }

输出结果

(0,我) (3,叫) (6,l) (7,y) (8,b) 总共有多少个字节: 9 总共有多少个rune: 5 我 叫 l y b (0,æ) (1,) (2,) (3,å) (4,) (5,«) (6,l) (7,y) (8,b) //这就乱码了

字符串是不可变的 

GO中的字符串是不可变的,一个字符串一旦被定义就不可以改变,如果想要改变只能通过rune切片。

func main() { str := "我叫lyb" str[0] = 'a' fmt.Println(str) }

上述代码会报错

.\main.go:7:9: cannot assign to str[0]

 可以通过rune切片来改

func main() { str := "我叫lyb" fmt.Println(str) //将str转为一个rune切片,这个切片是重新开辟空间分配的,不是在原地址上直接改变的 runes := []rune(str) runes[3] = 'a' str = string(runes) //将rune切片再转为string类型赋值回去 fmt.Println(str) }

 输出结果

我叫lyb 我叫lab

最后再来看一下下面两种遍历方式的区别

func main() { str := "我叫lyb" for i,ch := range []rune(str){ //将字符串转为rune切片遍历 fmt.Printf("(%d,%c) ",i,ch) } fmt.Println() for i,ch := range str{ //直接遍历字符串 fmt.Printf("(%d,%c) ",i,ch) } }

输出结果

(0,我) (1,叫) (2,l) (3,y) (4,b) (0,我) (3,叫) (6,l) (7,y) (8,b)

可见,如果当成rune切片来遍历,那么得到的下标是按顺序的,不会出现直接遍历字符串这样跳下标的问题。它直接把表示中文的三个字节当成一个字符,它是按字符来遍历的。

所以这两种遍历方式,需要具体具体分析,到底用哪个是区分于不同的使用场景的,如果只是想使用字符,而不使用前面的下标,那就无所谓了,只要把前面的变量用下划线_代替就可以了。  

转载请注明原文地址: https://www.6miu.com/read-5032000.html

最新回复(0)