字符串在 Go 中值得特别提及,因为与其他语言相比,它们在实现上有所不同。
什么是字符串? 字符串是Go 中的字节切片。可以通过将一组字符括在双引号" "
中来创建字符串。
让我们看一个创建string
并打印它的简单示例。
GO
1 2 3 4 5 6 7 8 9 10 package mainimport ( "fmt" ) func main () { name := "Hello World" fmt.Println(name) }
上面的程序将打印Hello World
.
Go 中的字符串是Unicode 兼容 的,并且是UTF-8 编码的 。
访问字符串的单个字节 由于字符串是字节切片,因此可以访问字符串的每个字节。
GO
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 package mainimport ( "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 的更多信息。
访问字符串的单个字符 让我们稍微修改一下上面的程序来打印字符串的字符。
GO
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 mainimport ( "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
尽管上面的程序看起来像是访问字符串中单个字符的合法方式,但它有一个严重的错误。让我们找出那个错误是什么。
GO
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 mainimport ( "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 个字节c3
和b1
. 我们试图打印字符,假设每个字符码都是一个字节长,这是错误的。在 UTF-8 编码中,一个字符码可以占用超过 1 个字节。 那么我们如何解决这个问题呢?这就是rune 拯救我们的地方。
Rune rune 是 Go 中的内置类型,它是 int32
的别名。Rune 表示 Go 中的 Unicode 字符码。代码点占用多少字节并不重要,它可以用一个符文来表示。让我们修改上面的程序以使用符文打印字符。
GO
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 mainimport ( "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 循环为我们提供了一种更简单的方法来执行此操作。
GO
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 package mainimport ( "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 个字节😀。
从字节切片创建字符串
GO
1 2 3 4 5 6 7 8 9 10 11 package mainimport ( "fmt" ) func main () { byteSlice := []byte {0x43 , 0x61 , 0x66 , 0xC3 , 0xA9 } str := string (byteSlice) fmt.Println(str) }
上面程序的第 8 行的byteSlice 包含以UTF-8 编码 的十六进制字节字符串Café
。程序打印
如果我们有十六进制值的十进制等值怎么办。上面的程序会起作用吗?让我们来看看。
GO
1 2 3 4 5 6 7 8 9 10 11 package mainimport ( "fmt" ) func main () { byteSlice := []byte {67 , 97 , 102 , 195 , 169 } str := string (byteSlice) fmt.Println(str) }
十进制值也可以,上面的程序也会打印Café
。
从切片runes创建一个字符串
GO
1 2 3 4 5 6 7 8 9 10 11 package mainimport ( "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
用于找出这些字符串的长度将返回不正确的字符串长度。
GO
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 package mainimport ( "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
。
GO
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 mainimport ( "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行使用==
运算符比较两个字符串str1
和str2
是否相等。如果它们相等,则打印相应的消息并且函数返回%s and %s are equal
。
上面的程序打印,
1 2 Go and Go are equal hello and world are not equal
字符串连接 在 Go 中有多种方法可以执行字符串连接。让我们来看看其中的几个。
执行字符串连接的最简单方法是使用+
运算符。
GO
1 2 3 4 5 6 7 8 9 10 11 12 package mainimport ( "fmt" ) func main () { string1 := "Go" string2 := "is awesome" result := string1 + " " + string2 fmt.Println(result) }
在上面的程序中,第 1 行。10,与中间的空格string1
连接。string2
该程序打印,
连接字符串的第二种方法是使用 fmt 包的Sprintf 函数。
Sprintf
函数根据输入格式说明符格式化字符串并返回结果字符串。让我们用Sprintf
函数重写上面的程序。
GO
1 2 3 4 5 6 7 8 9 10 11 12 package mainimport ( "fmt" ) func main () { string1 := "Go" string2 := "is awesome" result := fmt.Sprintf("%s %s" , string1, string2) fmt.Println(result) }
在上面程序的第 10 行,%s %s
是Sprintf
的格式说明符。 此格式说明符将两个字符串作为输入,并且在它们之间有一个空格。这将连接两个字符串,中间有一个空格。结果字符串存储在result
中。 该程序还打印:
字符串是不可变的 字符串在 Go 中是不可变的。一旦创建了字符串,就无法更改它。
GO
1 2 3 4 5 6 7 8 9 10 11 12 13 14 package mainimport ( "fmt" ) func mutate (s string ) string { s[0 ] = 'a' return s } func main () { h := "hello" fmt.Println(mutate(h)) }
在上述程序的第 8 行中,我们尝试将字符串的第一个字符更改为'a'
. 单引号内的任何有效 Unicode 字符都是rune(符文)。我们尝试将runea
分配到切片的第零位。这是不允许的,因为字符串是不可变的,因此程序无法编译并出现错误**./prog.go:8:7: cannot assign to s[0]**
要解决此字符串不变性,将字符串转换为符文切片。然后,该切片会根据需要进行任何更改并转换回新字符串。
GO
1 2 3 4 5 6 7 8 9 10 11 12 13 14 package mainimport ( "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
。
这就是字符串。祝你有美好的一天。
v1.5.2