导言
- 原文链接: Part 14: Strings
- If translation is not allowed, please leave me in the comment area and I will delete it as soon as possible.
字符串
Go
语言 的字符串值得一提,因为与其它语言相比,Go
语言 字符串的实现方式有所不同。
字符串是什么?
在 Go
语言 中,字符串是 字节 的集合。通过将内容放置于 " "
之间,我们可以创建一个字符串。看看下面的例子。
package main
import (
"fmt"
)
func main() {
name := "Hello World"
fmt.Println(name)
}
上面的程序将会输出: Hello World
。
Go
中的字符串符合 Unicode
,并通过 UTF-8
进行编码。
访问字符串的单个字节
因为字符串是 字节 的集合,所以我们可以访问字符串的每个字节。
package main
import (
"fmt"
)
func printBytes(s string) {
for i:= 0; i < len(s); i++ {
fmt.Printf("%x ", s[i])
}
}
func main() {
name := "Hello World"
printBytes(name)
}
在上面程序的第 8
行,len(s) 返回字符串有多少个字节,并且,利用 for
循环,我们打印出了这些字节的十六进制形式。%x
是十六进制的格式限定符。最终,程序将会输出:
48 65 6c 6c 6f 20 57 6f 72 6c 64
这就是 Hello World
的 UTF-8
编码。
对 Unicode
和 UTF-8
的基本理解,将有利于我们认识字符串。我推荐你阅读 这篇文章,它能你知道什么是Unicode
、什么是 UTF-8
。
上面的这篇文章是全英文的。
修改一下上面的代码,我们就能够打印字符串的 字符 了。
package main
import (
"fmt"
)
// 打印字节
func printBytes(s string) {
for i:= 0; i < len(s); i++ {
fmt.Printf("%x ", s[i])
}
}
// 打印字符
func printChars(s string) {
for i:= 0; i < len(s); i++ {
fmt.Printf("%c ",s[i])
}
}
func main() {
name := "Hello World"
printBytes(name)
fmt.Printf("\n")
printChars(name)
}
在上面程序的第 16
行,%c
格式限定符,能被使用去输出字符串的字符。程序输出如下:
48 65 6c 6c 6f 20 57 6f 72 6c 64
H e l l o W o r l d
虽然看起来,上面的程序能够访问字符串的每个字符,但这其实存在一个严重的 BUG
。
让我们修改一下代码,看看到底存在什么 BUG
。
package main
import (
"fmt"
)
func printBytes(s string) {
for i:= 0; i < len(s); i++ {
fmt.Printf("%x ", s[i])
}
}
func printChars(s string) {
for i:= 0; i < len(s); i++ {
fmt.Printf("%c ",s[i])
}
}
func main() {
name := "Hello World"
printBytes(name)
fmt.Printf("\n")
printChars(name)
fmt.Printf("\n")
name = "Señor"
printBytes(name)
fmt.Printf("\n")
printChars(name)
}
在上面程序的第 28
行,我们试图打印 Señor
,然而输出却是 S e à ± o r
。
为什么打印 Señor
会出错,而打印 Hello World
却不会呢?这是因为 ñ
的 Unicode
码点 是 U+00F1
,它的 UTF-8
编码 占用 2
个字节:c3
和 b1
,而当我们输出字符时,我们认为每个字符都只占用 1
个字节,显然,这是错误的 — 在 UTF-8
编码中,有的码点需要占用多个字节。
那我们如何解决这个问题呢?rune
出现了!
rune
rune
是一个 Go
语言 的内建类型,它是 int32
的别名。在 Go
中,rune
表示 1
个 Unicode
码点。无论码点占用了多少个字节,rune
都能表示它。
通过 rune
,我们修改一下上面的代码,实现输出字符串字符的目的。修改后的代码如下:
package main
import (
"fmt"
)
func printBytes(s string) {
for i:= 0; i < len(s); i++ {
fmt.Printf("%x ", s[i])
}
}
func printChars(s string) {
runes := []rune(s)
for i:= 0; i < len(runes); i++ {
fmt.Printf("%c ",runes[i])
}
}
func main() {
name := "Hello World"
printBytes(name)
fmt.Printf("\n")
printChars(name)
fmt.Printf("\n\n")
name = "Señor"
printBytes(name)
fmt.Printf("\n")
printChars(name)
}
在上面程序的第 14
行,s
被转换为 rune
类型 的切片。之后,我们遍历这个切片,输出它的内容。
程序输出如下:
48 65 6c 6c 6f 20 57 6f 72 6c 64
H e l l o W o r l d
53 65 c3 b1 6f 72
S e ñ o r
上面的输出很完美,这正是我们需要的~
通过 for range
循环 遍历字符串
通过rune
,上面的代码可以完美地遍历字符串的字符。但 Go
提供了一种更简洁的方式 — for range
循环,通过这个循环,我们也可以实现相同的目的。
package main
import (
"fmt"
)
func printCharsAndBytes(s string) {
for index, rune := range s {
fmt.Printf("%c starts at byte %d\n", rune, index)
}
}
func main() {
name := "Señor"
printCharsAndBytes(name)
}
在上面程序的第 8
行,我们使用 for range
遍历了 字符串s
。该循环返回 rune
对应字节的起始索引,以及 rune
的值。
程序输出如下:
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
个字节。
将 字节切片 构建为 字符串
package main
import (
"fmt"
)
func main() {
byteSlice := []byte{0x43, 0x61, 0x66, 0xC3, 0xA9}
str := string(byteSlice)
fmt.Println(str)
}
上面程序的 byteSlice
,它的内容是 Café
的 十六进制UTF-8
编码。这个程序会输出:Café
。
假如我们采用对应的 十进制UTF-8
编码,程序是否还能正常运行呢?来检验一下:
package main
import (
"fmt"
)
func main() {
byteSlice := []byte{67, 97, 102, 195, 169}//decimal equivalent of {'\x43', '\x61', '\x66', '\xC3', '\xA9'}
str := string(byteSlice)
fmt.Println(str)
}
这个程序同样会打印:Café
。
将 rune
切片 构建为 字符串
package main
import (
"fmt"
)
func main() {
runeSlice := []rune{0x0053, 0x0065, 0x00f1, 0x006f, 0x0072}
str := string(runeSlice)
fmt.Println(str)
}
在上面的程序中,runeSlice
包含了 字符串Señor
的 十六进制Unicode
码点。该程序输出也为:Señor
。
字符串的长度
utf8
包 的 RuneCountInString
函数 能够获取字符串的长度。这个函数将字符串作为参数,并返回该字符串的 rune
个数。
package main
import (
"fmt"
"unicode/utf8"
)
func length(s string) {
fmt.Printf("length of %s is %d\n", s, utf8.RuneCountInString(s))
}
func main() {
word1 := "Señor"
length(word1)
word2 := "Pets"
length(word2)
}
程序输出为:
length of Señor is 5
length of Pets is 4
len
函数 也能获取字符串的长度,但它获取的是该字符串的 byte
个数。
fmt.Println(len("你好"), utf8.RuneCountInString("你好"))
此时输出是:
6 2
字符串是不可修改的
在 Go
语言 中,字符串一经创建就不能修改。
package main
import (
"fmt"
)
func mutate(s string)string {
s[0] = 'a'//any valid unicode character within single quote is a rune
return s
}
func main() {
h := "hello"
fmt.Println(mutate(h))
}
在上面程序的第 8
行,我们试图去修改 字符串s
的第 1
个字符,这是不允许的,因为字符串无法修改。于是,程序会抛出错误:main.go:8: cannot assign to s[0]
。
为了解决字符串的不可修改性,我们可以将字符串转为 rune
类型 切片,之后修改这个切片,最后将该切片转换为一个新的字符串。
package main
import (
"fmt"
)
func mutate(s []rune) string {
s[0] = 'a'
return string(s)
}
func main() {
h := "hello"
fmt.Println(mutate([]rune(h)))
}
在上面程序的第 7
行,mutate
函数 接收一个rune
切片,随后将该切片的第 1
个元素修改为 a
,最后将该切片转换为 string
进行返回。在程序的第 13
行,h
转换为 rune
切片 后,传入了 mutate
函数。程序最终会输出:aello
。
这就是字符串了。
祝你早生贵子~
原作者留言
我已经将上面的东西整合到了一个程序,你可以在 github 下载它。
优质内容来之不易,您可以通过该 链接 为我捐赠。
最后
感谢原作者的优质内容。
欢迎指出文中的任何错误。