第十四篇 字符串string
欢迎来到Golang教程系列的第十四篇
相对于其它语言来说,Go的字符串的实现方式是不一样的,值得特别一提。
什么是字符串string?
在go中,字符串是字节的切片slice。string可以通过闭合双引号""圈起的字符来创建。
下面我们看一个简单的例子,创建一个string并且打印它。
package main
import (
"fmt"
)
func main() {
name := "Hello World"
fmt.Println(name)
}
上面的程序将会打印Hello World
在Go中string是Unicode兼容且是用UTF-8编码的,参考https://www.cnblogs.com/swong/articles/15850566.html
访问字符串的单个字节
因为字符串是字节的切片,所以可以访问字符串的每一个字节。
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 loop打印这些字节的二进制符号,可以使用%x格式符号,上面程序输出:
String: Hello World
Bytes: 48 65 6c 6c 6f 20 57 6f 72 6c 64
这些是Hello World的Unicode UTF8-encoded值,对于Unicode和UTF-8的基本理解是为了更好地了解字符串。我建议阅读https://www.cnblogs.com/swong/articles/15850566.html 去了解更多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)
}
上面程序中,在printChars方法中%c格式符号是用来打印字符串的字符,程序打印出:
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
尽管上面的程序看起来是用了合法的方式来访问字符串的单个字节,但其实它是有个严重的bug,让我们找出bug是什么。
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: 73 65 c3 b1 6f 72
在上面程序中,我们打印了señor的字符,但是给了错误的输出s e à ± o r,为什么这个程序打印Hello Wolrd的时候是正常的,但打印señor却是不正常了呢?原因就是ñ的Unicode code point是U+00F1,它的UTF-8 encoding占有两个字节c3和b1。所以当我们打印的字符超过一个字节的就会打印错误了。在UTF-8编码中,一个代码点(code point)会占据超过一个字节,那我们如何解决这个问题呢?rune会帮到我们。
Rune
rune是Go的内置类型,它是int32的别名。Rune在Go中表示的是一个Unicode code point。它不用管一个code point占据多少个字节,都可以用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)
}
在上面程序中,runes := []rune(s),字符串string转化成了runes的切片,然后我们遍历它并展示字符,这个程序会打印。
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: 73 65 c3 b1 6f 72
上面的输出完美展示了我们需要的。
用for rang循环访问单个runes
上面的代码完美地迭代了字符串的单个runes。但是Go给我们提供了一个更简单的方法来做这事,就是使用for range循环
package main
import (
"fmt"
)
func charsAndBytePositon(s string) {
for index, value := range s {
fmt.Printf("%c starts at byte %d\n", value, index)
}
}
func main() {
name := "señor"
charsAndBytePositon(name)
}
在上面程序中,使用了for range来迭代字符串,循环返回了字符串字节value开始的位置。下面是输出。
s starts at byte 0
e starts at byte 1
ñ starts at byte 2
o starts at byte 4
r starts at byte 5
根据上面的输出,很明显ñ是占据了两个字节,因为下一个符号o是从4开始,而不是3.
根据字节的切片创建字符串
package main
import (
"fmt"
)
func main() {
byteSlice := []byte{0x43, 0x61, 0x66, 0xC3, 0xA9}
str := string(byteSlice)
fmt.Println(byteSlice)
}
在上面程序中byteSlice包含Café的UTF-8 Encodeed十六进制字节。程序输出
Café
如果是跟十六进制相等的十进制的值呢?上面的程序会正常运行吗?我们看看。
package main
import (
"fmt"
)
func main() {
byteSlice := []byte{67, 97, 102, 195, 169} //十进制相等的是十六进制值 {'\x43', '\x61', '\x66', '\xC3', '\xA9'}
str := string(byteSlice)
fmt.Println(str)
}
十进制的值也一样会运行并且输出同样的值。
根据runes的切片创建字符串
package main
import "fmt"
func main() {
runeSlice := []rune{0x0053, 0x0065,0x00f1, 0x006f, 0x0072}
str := string(runeSlice)
fmt.Println(str)
}
上面的程序中,runeSlice包含了字符串Señor的用十六进制表示的Unicode code point。它的输出
Señor
字符串长度
在utf8 package中有个RuneCountInString(s string) (n int)函数可以获取字符串长度,这个方法接收一个字符串参数,并返回runes数据数目。
正如我们早先讨论的,len(s)用来获取string的bytes数量的,它并不会返回字符串长度。我们早已讨论过,一些Unicode字符 code point占据超过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 os bytes: %d\n",len(word2))
}
上面的程序输出如下;
String: Señor
Length: 5
Number of bytes: 6
String: Pets
Length: 4
Number os 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函数中,if str1 == str2 使用了==操作符比较str1和str2两个字符串是否相等,如果相等,那么就会打印相应的信息,并return。
上面程序输出:
Go and Go are equal
hello and world are not equal
字符串连结
在GO中有多种方式来实现连结字符串,我们看看其中的几个。
最简单实现字符串连结的方式是使用+操作符。
package main
import (
"fmt"
)
func main() {
string1 := "Go"
string2 := "is awsome"
result := string1 + " " + string2
fmt.Println(result)
}
上面程序中,string1连结string2,而且中间带有一个空格。程序输出如下:
Go is awsome
第二种连结字符串的方法是使用Sprintd函数,它来自fmt package。
Sprintf函数格式化一个字符串,根据输入格式符号并且返回结果字符串。我们用Sprintf重写上面的程序。
package main
import (
"fmt"
)
func main() {
string1 := "Go"
string2 := "is awsome"
result := fmt.Sprintf("%s %s", string1, string2)
fmt.Println(result)
}
在上面程序中,%s %s是Sprintf的输入格式符号,这个格式符号接受两个参数作为输入,并且在两个参数之间有一个空格。这样就会在两个连结的字符串之间带有一个空格。结果字符串保存在result。这个程序输出如下:
Go is awsome
字符串是不可变的
字符串在Go中是不可变的,一旦创建了字符串,就不可以改变。
package main
import (
"fmt"
)
func mutate(s string) string {
s[0] = 'a' // 任何带有单引号的有效的unicode字符是rune
return s
}
func main() {
h := "hello"
fmt.Println(mutate(h))
}
在上面程序中,s[0] = 'a',我们把字符串的第一个字符改变位a,任何带有单引号的有效的unicode字符是rune,我们试着把slice的第0个位置赋值为rune a.这是不允许的,因为字符串是不可改变的,因此,程序会编译失败并且报错 cannot assign to s[0] (strings are immutable)
要解决字符串不可改变的情况,可以把字符串转变成runes类型的切片。需要的时候,切片slice是可变的,修改后,然后再把它转变回字符串。
package main
import (
"fmt"
)
func mutate(s []rune) string {
s[0] = 'a' // 任何带有单引号的有效的unicode字符是rune
return string(s)
}
func main() {
h := "hello"
fmt.Println(mutate([]rune(h)))
}
在上面程序中,mutate函数接受一个rune切片作为参数,然后改变切片的第一个元素为a,最后把rune转变成string,并且返回。我们在main函数中调用它,h先是转变成rune类型的切片,然后传递给mutate函数。这个程序的输出如下:
aello
(ps: 今天是旧历2021的最后一天上班,希望来年继续努力,坚持努力,新年快乐!!)
以上就是本篇的全部,感谢阅读,这是我第一次翻译,难免会有翻译不当的地方,如果有什么反馈和评论,欢迎提出来!
浙公网安备 33010602011771号