编辑
2025-02-28
技术学习
00

目录

关于golang中不同方式遍历字符串的反思
最最低级的错误
Unicode和UTF-8
言归正传
总结

关于golang中不同方式遍历字符串的反思

Tags: Golang

Published: 2024年6月19日

概要: This blog reflects on the different ways of iterating over strings in Golang, highlighting common mistakes and explaining the differences between Unicode and UTF-8 encoding.

起初学习时,并没有关注string的存储方式以及unicode和utf-8编码的区别,故出现了很滑稽的问题,在处理字符串时,使用错误的方法,导致得到的类型不正确,在继续追究错误原因时,发现一些帖子写的并不是很清晰。

最最低级的错误

首先介绍一个最低级的错误:

go
s := "abc你好" for v := range s{     fmt.Println(v) }

起初我以为这样遍历得到的v会是每个字符(新手狠狠拍脑子!),然而这样得到的v是每个字符位置的下标:0、1、2、3、6。

Unicode和UTF-8

接下来应该主要讨论另外两种方式,但是在此之前先介绍一下unicodeutf-8的区别。此处引用(Unicode和UTF-8有什么区别 • Worktile社区)的内容。

  • 基本概念不同。Unicode是一种字符集,而UTF-8是Unicode的一种实现方式。此外,Unicode定义了每个字符的编号和名称,而UTF-8则是一种用于在计算机上存储和传输Unicode字符的编码方式。
  • 编码方式不同。Unicode使用的是固定长度的编码方式,即每个字符占用相同的字节数;而UTF-8则采用可变长度的编码方式,不同字符所占用的字节数不同,根据字符的Unicode编号来确定字节数。
  • 字符范围不同。Unicode能够表示的字符范围比UTF-8更广泛,包括世界上所有语言中的字符以及许多特殊字符和符号。而UTF-8只是Unicode的一种编码方式,它只能够表示Unicode字符集中的一部分。
  • 存储空间不同。在存储和传输数据时,UTF-8通常比Unicode更节省空间。因为UTF-8采用可变长度的编码方式,对于ASCII字符,只需要一个字节即可表示,而Unicode则需要两个字节。对于非ASCII字符,UTF-8的存储空间通常也比Unicode要小。

在了解上面内容之后,还需要知道Go语言的默认编码是UTF-8。 在Go语言中,字符串的默认编码就是UTF-8,这也是Go语言内部使用的字符编码格式。 Go语言的源代码也默认使用UTF-8编码格式。这也是后面出现问题的原因所在。

言归正传

先看for range的这个方法。iv分别是s的每个字符的下标和他的unicode码点(后面解释)。

go
s := "abc你好" for i,v := range s{     fmt.Printf("index:%d, value: %d\n", i, v) } /* index:0, value: 97 index:1, value: 98 index:2, value: 99 index:3, value: 20320 index:6, value: 22909 */

观察以上输出,我们会发现对于前三个字符,即英文字符,index分别为0、1、2,但是到了后面中文部分出现了变化,index变为了3、6。上面介绍了UTF-8编码是不定长的,即一个字符可能需要不同字节的空间存储。结合index的值,可以猜测,此处index的值为字符串的每个字符的第一个字节相对于字符串首地址的索引。出现上面这样的结果说明对于abc字符各只占用一个字节,而后面的中文占用了3个字节。

换一种方式打印:

go
s := "abc你好" for i, v := range s {   fmt.Printf("index:%d, value: %c\n", i, v) } /* index:0, value: a index:1, value: b index:2, value: c index:3, value: 你 index:6, value: 好 */

使用%c打印的是对应的 Unicode 码点表示的字符。因此可以得到结论,上面的value值为字符所对应的unicode码点的值。

那么在讨论清楚上面的内容之后,我们再看另外一个方式。

go
s := "abc你好" for i := 0; i < len(s); i++ {   fmt.Printf("index:%d, value: %d\n", i, s[i]) } /* index:0, value: 97 index:1, value: 98 index:2, value: 99 index:3, value: 228 index:4, value: 189 index:5, value: 160 index:6, value: 229 index:7, value: 165 index:8, value: 189 */

我们发现index一直从0打印到8,很奇怪的现象,但是结合for range打印的结果分析来看,恰好字符串s的字节数为9,abc各占一个字节,而你好各占用三个字节。因此,很容易想到此处value的值为UTF-8编码中,每个字节的值。

换用%c打印:

go
s := "abc你好" for i := 0; i < len(s); i++ {   fmt.Printf("index:%d, value: %c\n", i, s[i]) } /* index:0, value: a index:1, value: b index:2, value: c index:3, value: ä index:4, value: ½ index:5, value: index:6, value: å index:7, value: ¥ index:8, value: ½ */

果然从c之后,出现了6个奇怪的结果,因为将你好的UTF-8编码转化为了6个编码,再分别将他们在unicode字符集中代表的字符打印出来,出现这些结果。

总结

简单总结一下:

  • 第一种,遍历字符串的每一个字符的起始位置相对于字符串首地址的索引。
  • 第二个,index同第一种的结果相同,而v为每个字符对应的unicode码点。这种方式遍历得到的v的类型可以认为是rune(是可以认为,起码这样比较类型是正确的,处理中文字符串时这样使用)。
  • 第三个,i会遍历字符串的每一个字节的下标,对,是每个字节。而s[i]表示的每个字节的值。这种方式不会以字符为单位遍历,因此对于不止需要一个字节空间表示的字符不能正确表示出来。这种方式遍历得到的s[i]的类型可以认为是byte

至此,遇到的问题解释清楚。写的很啰嗦,因为自己之前基础很差,很少深究过代码出现错误的底层原因,所以借此次机会写的详细一些,希望能够对看到此文的人有所帮助。

本文作者:AstralDex

本文链接:

版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!