Go 中的字符串值得特别提及,因为与其他语言相比,它们在实现上有所不同。
什么是字符串?
字符串是Go 中的一段字节。可以通过将一组字符括在双引号内来创建字符串" "
。
Go 中的字符串符合 Unicode且采用 UTF-8 编码。
让我们看一个创建 string
并打印它的简单示例。
package main
import (
"fmt"
)
func main() {
name := "Hello World"
fmt.Println(name)
}
上面的程序将打印Hello World
.
访问String的各个字节
由于字符串是字节切片,因此可以访问字符串的每个字节。
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)
}
%s 是打印字符串的格式说明符。在上面的程序中,len(s) 返回字符串中的字节数,我们使用for 循环以十六进制表示法打印这些字节。
**%x 是十六进制的格式说明符。**上述程序输出
String: Hello World
Bytes: 48 65 6c 6c 6f 20 57 6f 72 6c 64
这些是的[Unicode UT8 编码](https://mothereff.in/utf-8#Hello World)值Hello World
。
为了更好地理解字符串,需要对 Unicode 和 UTF-8 有基本的了解。
我建议阅读https://naveenr.net/unicode-character-set-and-utf-8-utf-16-utf-32-encoding/以了解有关 Unicode 和 UTF-8 的更多信息。
访问字符串的各个字符
让我们稍微修改一下上面的程序来打印字符串的字符。
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)
}
在上面程序中,%c格式说明符用于在printChars
方法中打印字符串的字符。程序打印出
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
尽管上面的程序看起来是访问字符串各个字符的合法方法,但这有一个严重的错误。让我们找出这个错误是什么。
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)
}
上述程序的输出是
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
在上面程序的第 30 行中,我们尝试打印 Señor 的字符,它输出 S e à ± o r,这是错误的。为什么这个程序在它工作得很好时会中断.原因是 的 Unicode 码位及其 UTF-8 编码占用 2 个字节我们尝试打印字符,假设每个代码点将是一个字节长,这是错误的。
在 UTF-8 编码中,一个码位可以占用 1 个以上的字节。那么我们如何解决这个问题呢?这就是Rune拯救我们的地方。
Rune
rune 是 Go 中的内置类型,它是 int32 的别名。Rune 代表 Go 中的 Unicode 代码点。不管代码点占用多少字节,它都可以用Rune来表示。让我们修改上面的程序以使用Rune打印字符。
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)
}
上面程序中,字符串被转换为Rune切片。然后我们循环它并显示字符。该程序打印,
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循环来做到这一点。
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)
}
在上面程序的第 8 行中,使用for range
循环迭代字符串。该程序输出
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 😀。
从字节切片创建字符串
package main
import (
"fmt"
)
func main() {
byteSlice := []byte{0x43, 0x61, 0x66, 0xC3, 0xA9}
str := string(byteSlice)
fmt.Println(str)
}
程序打印出
Café
如果我们是十进制怎么办?上面的程序能运行吗?让我们来看看。
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
字符串的 Unicode 代码点。程序输出
Señor
字符串长度
utf8包RuneCountInString(s string) (n int)
的函数可以用来求字符串的长度。该方法接受一个字符串作为参数并返回其中的rune数量。
正如我们之前讨论的, len(s)
用于查找字符串中的字节数,但它不返回字符串长度。
正如我们已经讨论过的,某些 Unicode 字符的代码点占用的空间超过 1 个字节。使用len
找出这些字符串的长度将返回不正确的字符串长度。
package main
import (
"fmt"
"unicode/utf8"
)
func main() {
word1 := "Señor"
fmt.Printf("String: %s\n", word1)
fmt.Printf("Length: %d\n", utf8.RuneCountInString(word1))
fmt.Printf("Number of bytes: %d\n", len(word1))
fmt.Printf("\n")
word2 := "Pets"
fmt.Printf("String: %s\n", word2)
fmt.Printf("Length: %d\n", utf8.RuneCountInString(word2))
fmt.Printf("Number of bytes: %d\n", len(word2))
}
上述程序的输出是
String: Señor
Length: 5
Number of bytes: 6
String: Pets
Length: 4
Number of bytes: 4
上面的输出证实了这一点len(s)
和RuneCountInString(s)
返回不同的值😀。
字符串比较
该==
运算符用于比较两个字符串是否相等。如果两个字符串相等,则结果为true
否则为false
。
package main
import (
"fmt"
)
func compareStrings(str1 string, str2 string) {
if str1 == str2 {
fmt.Printf("%s and %s are equal\n", str1, str2)
return
}
fmt.Printf("%s and %s are not equal\n", str1, str2)
}
func main() {
string1 := "Go"
string2 := "Go"
compareStrings(string1, string2)
string3 := "hello"
string4 := "world"
compareStrings(string3, string4)
}
在compareStrings
上面的函数中,使用运算符比较两个字符串str1
和str2
是否相等==
。如果它们相等,则打印相应的消息并且函数返回。
上面的程序打印出,
Go and Go are equal
hello and world are not equal
字符串连接
Go 中有多种执行字符串连接的方法。让我们看一下其中的几个。
执行字符串连接的最简单方法是使用+
运算符。
package main
import (
"fmt"
)
func main() {
string1 := "Go"
string2 := "is awesome"
result := string1 + " " + string2
fmt.Println(result)
}
该程序打印,
Go is awesome
连接字符串的第二种方法是
使用fmt 包的Sprintf函数。
该Sprintf
函数根据输入格式说明符格式化字符串并返回结果字符串。让我们使用Sprintf
函数重写上面的程序。
package main
import (
"fmt"
)
func main() {
string1 := "Go"
string2 := "is awesome"
result := fmt.Sprintf("%s %s", string1, string2)
fmt.Println(result)
}
上面程序中,%s %s
是 的格式说明符输入。此格式说明符采用两个字符串作为输入,并且中间有一个空格。这会将两个字符串连接起来,中间有一个空格。结果字符串存储在result
. 该程序还打印,
Go is awesome
字符串是不可变的
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行,我们尝试将字符串的第一个字符改为'a'
。
单引号内的任何有效 Unicode 字符都是rune。
我们尝试将a
分配到切片的第 0 个位置。这是不允许的,因为字符串是不可变的,因此程序无法编译并出现错误**./prog.go:8:7: 无法分配给 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)))
}
在上述程序中,该mutate
函数接受rune切片作为参数。然后它将切片的第一个元素更改为'a'
,将rune转换回字符串并返回它。
该程序输出
aello