字符串
Q7nl1s admin

字符串在 Go 中值得特别提及,因为与其他语言相比,它们在实现上有所不同。

什么是字符串?

字符串是Go 中的字节切片。可以通过将一组字符括在双引号" "中来创建字符串。

让我们看一个创建string并打印它的简单示例。

1
2
3
4
5
6
7
8
9
10
package main

import (
"fmt"
)

func main() {
name := "Hello World"
fmt.Println(name)
}

上面的程序将打印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)
}

%s 是打印字符串的格式说明符。在第16行打印字符串。在上面程序的第 9 行中,len(s) 返回字符串中的字节数,我们使用for 循环以十六进制表示法打印这些字节。**%x 是十六进制的格式说明符。**上述程序输出:

1
2
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 的更多信息。

访问字符串的单个字符

让我们稍微修改一下上面的程序来打印字符串的字符。

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)
}

在上面程序的第 17 行, %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)
}

上述程序的输出是

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

在行号。上面程序的第 30 行,我们试图打印Señor的字符,它输出S e à ± or ,此处为错误点。为什么这个程序打印Señor失败而打印Hello World成功? 原因是ñU+00F1 并且它的 UTF-8 编码占用 2 个字节c3b1. 我们试图打印字符,假设每个字符码都是一个字节长,这是错误的。在 UTF-8 编码中,一个字符码可以占用超过 1 个字节。那么我们如何解决这个问题呢?这就是rune拯救我们的地方。

Rune

rune 是 Go 中的内置类型,它是 int32 的别名。Rune 表示 Go 中的 Unicode 字符码。代码点占用多少字节并不重要,它可以用一个符文来表示。让我们修改上面的程序以使用符文打印字符。

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
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)
}

在上面的程序的第 16 行中,字符串被转换为符文切片。然后我们循环它并显示字符。该程序打印,

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
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循环迭代字符串。循环返回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)
}

上面程序的第 8 行的byteSlice包含以UTF-8 编码的十六进制字节字符串Café。程序打印

1
Café  

如果我们有十六进制值的十进制等值怎么办。上面的程序会起作用吗?让我们来看看。

1
2
3
4
5
6
7
8
9
10
11
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é

从切片runes创建一个字符串

1
2
3
4
5
6
7
8
9
10
11
package main

import (
"fmt"
)

func main() {
runeSlice := []rune{0x0053, 0x0065, 0x00f1, 0x006f, 0x0072}
str := string(runeSlice)
fmt.Println(str)
}

在上面的程序中包含十六进制runeSlice字符串的 Unicode 代码点。Señor程序输出

1
Señor  

字符串长度

utf8包RuneCountInString(s string) (n int)函数可以用来求字符串的长度。此方法将字符串作为参数并返回其中的rune数。

正如我们之前讨论的,len(s)用于查找字符串中的字节数,它不返回字符串长度。又正如我们已经讨论过的,一些 Unicode 字符的编码占据了超过 1 个字节。len用于找出这些字符串的长度将返回不正确的字符串长度。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
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))
}

上述程序的输出是

1
2
3
4
5
6
7
String: Señor  
Length: 5
Number of bytes: 6

String: Pets
Length: 4
Number of bytes: 4

上面的输出证实了这一点————len(s)RuneCountInString(s)返回不同的值😀。

字符串比较

==运算符用于比较两个字符串是否相等。如果两个字符串相等,则结果为true否则为false

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
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函数中,在第8行使用==运算符比较两个字符串str1str2是否相等。如果它们相等,则打印相应的消息并且函数返回%s and %s are equal

上面的程序打印,

1
2
Go and Go are equal  
hello and world are not equal

字符串连接

在 Go 中有多种方法可以执行字符串连接。让我们来看看其中的几个。

执行字符串连接的最简单方法是使用+运算符。

1
2
3
4
5
6
7
8
9
10
11
12
package main

import (
"fmt"
)

func main() {
string1 := "Go"
string2 := "is awesome"
result := string1 + " " + string2
fmt.Println(result)
}

在上面的程序中,第 1 行。10,与中间的空格string1连接。string2该程序打印,

1
Go is awesome  

连接字符串的第二种方法是使用 fmt 包的Sprintf函数。

Sprintf函数根据输入格式说明符格式化字符串并返回结果字符串。让我们用Sprintf函数重写上面的程序。

1
2
3
4
5
6
7
8
9
10
11
12
package main

import (
"fmt"
)

func main() {
string1 := "Go"
string2 := "is awesome"
result := fmt.Sprintf("%s %s", string1, string2)
fmt.Println(result)
}

在上面程序的第 10 行,%s %sSprintf的格式说明符。 此格式说明符将两个字符串作为输入,并且在它们之间有一个空格。这将连接两个字符串,中间有一个空格。结果字符串存储在result中。 该程序还打印:

1
Go is awesome  

字符串是不可变的

字符串在 Go 中是不可变的。一旦创建了字符串,就无法更改它。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
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(符文)。我们尝试将runea分配到切片的第零位。这是不允许的,因为字符串是不可变的,因此程序无法编译并出现错误**./prog.go:8:7: cannot assign to s[0]**

要解决此字符串不变性,将字符串转换为符文切片。然后,该切片会根据需要进行任何更改并转换回新字符串。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
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函数接受一个符文切片作为参数。然后它将切片的第一个元素更改为'a',将符文转换回字符串并返回它。此方法在第 13 行调用。h被转换为切片符文并传递给第 13 行的mutate调用,这个程序最终输出aello

这就是字符串。祝你有美好的一天。

 Comments
Comment plugin failed to load
Loading comment plugin
Powered by Hexo & Theme Keep
Unique Visitor Page View