在 Go 中,String 值得特别一提,因为与其他语言相比,它们在实现上有所不同。
String 是什么?
在 Go 中,一个字符串是字节的一个切片。字符串可以通过将一组字符放在双引号内来创建
让我们看看一个简单的例子,创建一个 string
并打印出来。
1 2 3 4 5 6 7 8 9 10
| package main
import ( "fmt" )
func main() { name := "Hello World" fmt.Println(name) }
|
Run in playground
上述程序将打印 Hello World
。
Go 中的字符串是 符合 Unicode 标准 并且是 UTF-8 编码 的。
访问一个字符串的单个字节
由于字符串是字节的一个切片,所以可以访问字符串的每个字节。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| package main
import ( "fmt" )
func printBytes(s string) { fmt.Printf("Bytes: ") for i := 0; i < len(s); i++ { fmt.Printf("%x ", s[i]) } }
func main() { name := "Hello World" fmt.Printf("String: %s\n", name) printBytes(name) }
|
Run in playground
%s
是用于打印字符串的格式化标识符。len(s)
返回字符串中的字节数,我们使用 for
循环以十六进制符号打印这些字节。%x
是十六进制的格式指定符。上述程序的输出结果是:
1 2
| String: Hello World Bytes: 48 65 6c 6c 6f 20 57 6f 72 6c 64
|
这是 Hello World
的 Unicode UT8 编码 值. 为了更好地理解字符串,需要对 Unicode 和 UTF-8 有一个基本的了解。 我推荐阅读 https://naveenr.net/unicode-character-set-and-utf-8-utf-16-utf-32-encoding/ 了解更多 Unicode 和 UTF-8 的知识。
访问字符串的单个字符
让我们对上述程序稍作修改,以打印字符串的字符。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
| package main
import ( "fmt" )
func printBytes(s string) { fmt.Printf("Bytes: ") for i := 0; i < len(s); i++ { fmt.Printf("%x ", s[i]) } }
func printChars(s string) { fmt.Printf("Characters: ") for i := 0; i < len(s); i++ { fmt.Printf("%c ", s[i]) } }
func main() { name := "Hello World" fmt.Printf("String: %s\n", name) printChars(name) fmt.Printf("\n") printBytes(name) }
|
Run in playground
%c
格式化标识符用于打印 printChars
方法中字符串参数中的字符。该程序打印的是:
1 2 3
| String: Hello World Characters: H e l l o W o r l d Bytes: 48 65 6c 6c 6f 20 57 6f 72 6c 64
|
虽然上面的程序看起来是访问字符串的单个字符的合法方式,但这有一个严重的错误。让我们来看看这个错误是什么。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33
| package main
import ( "fmt" )
func printBytes(s string) { fmt.Printf("Bytes: ") for i := 0; i < len(s); i++ { fmt.Printf("%x ", s[i]) } }
func printChars(s string) { fmt.Printf("Characters: ") for i := 0; i < len(s); i++ { fmt.Printf("%c ", s[i]) } }
func main() { name := "Hello World" fmt.Printf("String: %s\n", name) printChars(name) fmt.Printf("\n") printBytes(name) fmt.Printf("\n\n") name = "Señor" fmt.Printf("String: %s\n", name) printChars(name) fmt.Printf("\n") printBytes(name) }
|
Run in playground
上述程序的输出是
1 2 3 4 5 6 7
| String: Hello World Characters: H e l l o W o r l d Bytes: 48 65 6c 6c 6f 20 57 6f 72 6c 64
String: Señor Characters: S e à ± o r Bytes: 53 65 c3 b1 6f 72
|
我们试图打印 Señor 的字符,但它输出 S e à ± o r,这是错误的。为什么这个程序对 Señor
会出错,而对 Hello World
却能完全正常工作。原因是 ñ
的 Unicode 码位是 U+00F1
,其 UTF-8编码 占用了 2 个字节 c3
和 b1
。我们试图打印字符,假设每个代码点是一个字节,这是错误的。在 UTF-8 编码中,一个代码点可以占用 1个以上的字节。那么我们如何解决这个问题?这就需要 rune 拯救我们的地方了。
Rune
Rune 是 Go 中的一个内置类型,它是 int32
的别名。Rune 在 Go 中代表一个 Unicode 代码点。不管这个代码点占用多少字节,它都可以用 Rune 来表示。让我们修改上面的程序,用 Rune 来打印字符。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35
| package main
import ( "fmt" )
func printBytes(s string) { fmt.Printf("Bytes: ") for i := 0; i < len(s); i++ { fmt.Printf("%x ", s[i]) } }
func printChars(s string) { fmt.Printf("Characters: ") runes := []rune(s) for i := 0; i < len(runes); i++ { fmt.Printf("%c ", runes[i]) } }
func main() { name := "Hello World" fmt.Printf("String: %s\n", name) printChars(name) fmt.Printf("\n") printBytes(name) fmt.Printf("\n\n") name = "Señor" fmt.Printf("String: %s\n", name) printChars(name) fmt.Printf("\n") printBytes(name) }
|
Run in playground
上述程序打印出:
1 2 3 4 5 6 7
| String: Hello World Characters: H e l l o W o r l d Bytes: 48 65 6c 6c 6f 20 57 6f 72 6c 64
String: Señor Characters: S e ñ o r Bytes: 53 65 c3 b1 6f 72
|
上述输出是完美的。只是我们想要的😀。
使用 for range
循环访问单个 Rune
上面的程序是一个完美的方式来迭代一个字符串的各个 Rune。但是 Go 为我们提供了一种更简单的方法,即使用 for range
循环来实现这一目的。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| package main
import ( "fmt" )
func charsAndBytePosition(s string) { for index, rune := range s { fmt.Printf("%c starts at byte %d\n", rune, index) } }
func main() { name := "Señor" charsAndBytePosition(name) }
|
Run in playground
循环返回 Rune 开始的字节的位置,同时返回 Rune 的位置。这个程序输出:
1 2 3 4 5
| S starts at byte 0 e starts at byte 1 ñ starts at byte 2 o starts at byte 4 r starts at byte 5
|
从上面的输出可以看出,ñ
占用了 2 个字节,因为下一个字符 o
是从第 4 字节开始的,而不是第 3 字节😀。
从一个字节片中创建一个字符串
1 2 3 4 5 6 7 8 9 10 11
| package main
import ( "fmt" )
func main() { byteSlice := []byte{0x43, 0x61, 0x66, 0xC3, 0xA9} str := string(byteSlice) fmt.Println(str) }
|
Run in playground
byteSlice
包含字符串 Café
的 UTF-8编码 十六进制字节。该程序打印出
如果我们有相当于十六进制的十进制值,怎么办?上面的程序能工作吗?让我们来看看。
1 2 3 4 5 6 7 8 9 10 11
| package main
import ( "fmt" )
func main() { byteSlice := []byte{67, 97, 102, 195, 169} str := string(byteSlice) fmt.Println(str) }
|
Run in playground
小数点值也可以,上述程序也会打印出 Café
。
原文地址 Golang tutorial series Strings
原文作者:Naveen Ramanathan
译文出自:紫升翻译计划