Golang中的字符串跟其他编程语言区别较大,因此这里专门抽出一节来介绍。
什么是字符串
Golang中的字符串数据类型是string
。Golang中字符串本质上是一个由字节 组成的切片 。可以通过双引号来创建字符串。下面我们来创建一个字符串并打印。
Copy package main
import "fmt"
func main() {
name := "Hello World"
fmt.Println(name)
}
Go Playground在线运行
该程序输出为:Hello World
。
Golang中字符串是通过UTF-8 编码的(UTF-8是Unicode的实现方式之一)。
访问单个字节
前面说过,GOlang中字符串本质上是一个由字节组成的切片,因此我们当然可以访问组成字符串的单个字节。
Copy 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)
}
Go Playground在线运行 这里len(s)
返回字符串的字节个数,然后我们通过for循环按照十六进制的格式打印出了每个字节。该程序输出为:48 65 6c 6c 6f 20 57 6f 72 6c 64
。输出的这些字节其实就是Hello World
这个字符串的UTF-8编码。通过这篇文章 可以了解下什么是Unicode以及UTF-8编码。了解下Unicode和UTF-8的知识点非常有助于理解Golang中的字符串。
现在我们将前面这段代码修改如下:
Copy 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.Println()
printChars(name)
}
Go Playground在线运行 这里的%c
用来按字符打印字符串。该程序输出如下:
Copy 48 65 6c 6c 6f 20 57 6f 72 6c 64
H e l l o W o r l d
这里使用%c
来打印字符串中的每个字符看起来是合法的,但其实是有问题的。我们换个字符串来打印试试看:
Copy 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 := "Señor"
printBytes(name)
fmt.Println()
printChars(name)
}
Go Playground在线运行 该程序输出如下:
Copy 53 65 c3 b1 6f 72
S e à ± o r
这里我们本来期望打印出Señor
,却打印出了S e à ± o r
。你肯定会疑惑,为什么打印Hello World
能正常打印,换成Señor
却不行。其实我们应该能看出来,这里是字符ñ
打印成了Ã ±
。为什么会这样呢?是因为字符ñ
对应的Unicode码点为U+00F1
,采用UTF-8编码会占用两个字节:c3
和b1
,而前面程序中直接按照%c
格式打印字符串是按照每个字符一个字节来打印的,因此才打印出了错误的结果。那怎么避免这种bug呢?Golang提供了rune
(符文)数据类型来解决这个问题。
rune(符文)
Golang中rune
也是一种基本数据类型,本质上是int32
的别名,这一点在前面基本数据类型部分也提到过。Golang中rune
用来表示一个Unicode码点,无论一个Unicode码点由几个字节组成(int32即为4个字节,4个字节来表示一个Unicode码点已经足够了),该码点均可由rune
来表示。我们修改下前面的程序,使用rune
来打印输出一个字符串。
Copy 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.Println()
printChars(name)
fmt.Println()
name = "Señor"
printBytes(name)
fmt.Println()
printChars(name)
}
Go Playground在线运行 这里runes := []rune(s)
将字符串强制转换成了rune
切片。然后我们基于rune
来打印出了正常的字符。该程序执行结果如下,正是我们想要的。
Copy 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
循环访问字符串
前面几段程序中循环访问字符串每个字符的方法都挺好,但是Golang中还有一种更加简洁的方法:for range
。
Copy 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)
}
Go Playground在线运行 这里我们使用for range
来迭代访问字符串s
。打印出了迭代访问到的每个字符以及其字节索引位置。该程序输出如下:
Copy 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个字节。
基于byte(字节)切片构建字符串
Copy package main
import "fmt"
func main() {
byteSlice := []byte{0x43, 0x61, 0x66, 0xC3, 0xA9}
str := string(byteSlice)
fmt.Println(str)
}
Go Playground在线运行 这里创建了一个切片byteSlice
,该切片由十六进制形式的字节组成(一个字节包含8比特,这里的0x43
就是一个字节)。这些字节本身是字符串Café
UTF-8的UTF-8编码。因此该程序输出为Café
。
当然,这里其实没必要非的用十六进制形式,比如下面换成十进制形式也可以:
Copy package main
import "fmt"
func main() {
byteSlice := []byte{67, 97, 102, 195, 169}
str := string(byteSlice)
fmt.Println(str)
}
Go Playground在线运行 输出结果仍然是Café
。
基于rune(符文)切片构建字符串
Copy package main
import "fmt"
func main() {
runeSlice := []rune{0x0053, 0x0065, 0x00f1, 0x006f, 0x0072}
str := string(runeSlice)
fmt.Println(str)
}
Go Playground在线运行 该程序中runeSlice
由字符串Señor
的Unicode编码码点组成,格式为十六进制形式。输出为:Señor
。
字符串长度
注意,之类说的字符串长度为字符串中字符(rune)的长度。 utf8
包中的函数func RuneCountInString(s string) (n int)
用来获取字符串长度。
Copy 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)
}
Go Playground在线运行 该程序执行结果如下:
Copy length of Señor is 5
length of Pets is 4
immutable
immutable 即不可变类型 ,一旦创建完就不可修改。Golang中字符串就是不可变类型。
Copy package main
import "fmt"
func mutate(s string) string {
s[0] = 'a'
return s
}
func main() {
str := "hello"
fmt.Println(mutate(str))
}
Go Playground在线运行 这里我们尝试将字符串的第一个字符改为a
,由于字符串类型是immutable的,因此执行报错:
Copy cannot assign to s[0]
如果我们确实想改变字符串中的某个字符,可以先将字符串转换为符文(rune)切片,通过切片去修改,完事之后再转换回字符串即可。
Copy package main
import "fmt"
func mutate(s []rune) string {
s[0] = 'a'
return string(s)
}
func main() {
str := "hello"
fmt.Println(mutate([]rune(str)))
}
Go Playground在线运行 该程序先将字符串str
转换成了符文切片,通过切片将第一个字符改为a
,然后再将切片转换回字符串。输出为:aello
。