golang字符串
string类型单独提取为一篇教程是因为在Go中,string的实现方式同其他语言的不同。
什么是字符串
在Go中字符串是byte数组。可以通过将内容放在双引号""
之间的方式来创建一个字符串。让我们看一个简单的例子,该例子创建并打印一个字符串:
packagemain import( "fmt" ) funcmain(){ name:="HelloWorld" fmt.Println(name) }12345678910
上面的[程序]输出:HelloWorld
。
Go中的字符串符合Unicode标准,并以UTF-8编码。
访问字符串中的字节
因为字符串是字节数组,因此可以访问一个字符串中的字节。
packagemain import( "fmt" ) funcprintBytes(sstring){ fori:=0;i<len(s);i++{ fmt.Printf("%x",s[i]) } } funcmain(){ name:="HelloWorld" printBytes(name) }12345678910111213141516
在上面的程序中,len(s)返回字符串中的字节数,我们用一个for循环以16进制打印这些字节。%x格式化指示符用来以16进制打印参数。上面的程序打印:48656c6c6f20576f726c64
。它们是"HelloWorld"
以UTF-8方式编码的Unicode值。对Unicode字符集和UTF-8编码有一个基本的了解会更好的理解string类型。我(原文作者)建议大家阅读:https://naveenr.net/unicode-character-set-and-utf-8-utf-16-utf-32-encoding/来学习什么是Unicode和UTF-8。
让我们修改上面的程序以打印字符串中的字符:
packagemain import( "fmt" ) funcprintBytes(sstring){ fori:=0;i<len(s);i++{ fmt.Printf("%x",s[i]) } } funcprintChars(sstring){ fori:=0;i<len(s);i++{ fmt.Printf("%c",s[i]) } } funcmain(){ name:="HelloWorld" printBytes(name) fmt.Printf("\n") printChars(name) }12345678910111213141516171819202122232425
在第16行的printChars
函数中,%c格式化指示符用来打印字符串中的字符。上面的程序输出为:
48656c6c6f20576f726c64
HelloWorld12
虽然上面的程序看起来是一种合法的打印字符串中各个字符的方法,但是这里有一个严重的错误。让我们深入这段代码看看究竟是哪里不对。
packagemain import( "fmt" ) funcprintBytes(sstring){ fori:=0;i<len(s);i++{ fmt.Printf("%x",s[i]) } } funcprintChars(sstring){ fori:=0;i<len(s);i++{ fmt.Printf("%c",s[i]) } } funcmain(){ name:="HelloWorld" printBytes(name) fmt.Printf("\n") printChars(name) fmt.Printf("\n") name="Señor" printBytes(name) fmt.Printf("\n") printChars(name) }1234567891011121314151617181920212223242526272829
上面程序的输出为:
48656c6c6f20576f726c64 HelloWorld 5365c3b16f72 Señor12345
在上面的程序第28行,我们试图打印Señor中的字符串,但是结果却是Señor,这明显是错的。为什么打印HelloWorld
是对的,打印Señor
是错的呢?原因是ñ
的Unicode码点是U+00F1
而它的UTF-8编码占了两个字节:c3和b1。我们试图将一个字节视为一个字符来打印字符串的方法是错误的。在UTF-8
编码中一个码点编码后可能占多于1个字节的空间。所以我们必须要解决这个问题。rune是我们的救星。
rune
rune是Go中的内置类型,它是int32的别名。在Go中,rune表示一个Unicode码点。无论一个码点会被编码为多少个字节,它都可以表示为一个rune。让我们修改上面的程序,使用rune来打印字符串中的字符。
packagemain import( "fmt" ) funcprintBytes(sstring){ fori:=0;i<len(s);i++{ fmt.Printf("%x",s[i]) } } funcprintChars(sstring){ runes:=[]rune(s) fori:=0;i<len(runes);i++{ fmt.Printf("%c",runes[i]) } } funcmain(){ name:="HelloWorld" printBytes(name) fmt.Printf("\n") printChars(name) fmt.Printf("\n\n") name="Señor" printBytes(name) fmt.Printf("\n") printChars(name) }123456789101112131415161718192021222324252627282930
在上面的程序中,第14行,字符串被转换为tune切片。然后我们遍历该切片并打印其中的字符。程序的输出如下:
48656c6c6f20576f726c64
HelloWorld
5365c3b16f72
Señor12345
上面的输出是正确的。这正是我们想要的结果。
使用rangefor遍历字符串
上面的程序是遍历字符串中字符的一个正确方式。但是Go提供了一种更简单的方式来做到这一点:使用rangefor。
packagemain import( "fmt" ) funcprintCharsAndBytes(sstring){ forindex,rune:=ranges{ fmt.Printf("%cstartsatbyte%d\n",rune,index) } } funcmain(){ name:="Señor" printCharsAndBytes(name) }12345678910111213141516
在上面的程序中,第8行通过使用rangefor遍历字符串。range返回一个rune(在byte数组中)的位置,以及rune本身。上面的程序输出为:
Sstartsatbyte0
estartsatbyte1
ñstartsatbyte2
ostartsatbyte4
rstartsatbyte512345
从上面的输出可以看到,ñ
占两个字节:)
通过byte切片创建字符串
packagemain import( "fmt" ) funcmain(){ byteSlice:=[]byte{0x43,0x61,0x66,0xC3,0xA9} str:=string(byteSlice) fmt.Println(str) } 123456789101112
在上面的程序中,byteSlice是"Café"
经过UTF-8编码后得到的切片(用16进制表示)。上面的程序输出为:Café
。
如果我们换成对应的十进制数程序会正常工作吗?答案是:Yes。让我们测试一下:
packagemain import( "fmt" ) funcmain(){ byteSlice:=[]byte{67,97,102,195,169}//decimalequivalentof{'\x43','\x61','\x66','\xC3','\xA9'} str:=string(byteSlice) fmt.Println(str) }1234567891011
上面的程序同样输出:Café
。
通过rune切片创建字符串
packagemain import( "fmt" ) funcmain(){ runeSlice:=[]rune{0x0053,0x0065,0x00f1,0x006f,0x0072} str:=string(runeSlice) fmt.Println(str) }1234567891011
在上面的程序中,runeSlice
包含了字符串"Señor"
的Unicode码点(以16进制表示)。程序的输出为:Señor
。
字符串的长度
utf8包提供了funcRuneCountInString(sstring)(nint)
来获取字符串的长度,该方法接受一个字符串作为参数,并返回该字符串中rune的数量。
(译者注:RuneCountInString
返回字符串中Unicode字符的个数,而len
返回字符串中byte的个数,注意两者的区别。)
packagemain import( "fmt" "unicode/utf8" ) funclength(sstring){ fmt.Printf("lengthof%sis%d\n",s,utf8.RuneCountInString(s)) } funcmain(){ word1:="Señor" length(word1) word2:="Pets" length(word2) }12345678910111213141516171819
上面程序的输出为:
lengthofSeñoris5
lengthofPetsis412
字符串是不可变的
在Go中字符串是不可变的。字符串一旦被创建就无法改变。
packagemain import( "fmt" ) funcmutate(sstring)string{ s[0]='a'//anyvalidunicodecharacterwithinsinglequoteisarune returns } funcmain(){ h:="hello" fmt.Println(mutate(h)) }1234567891011121314
上面的程序中,第8行我们试图改变字符串的第一个字符为a
。因为字符串是不可变的,因此这是非法的,将会报错:main.go:8:cannotassigntos[0]
。
为了改变一个字符串中的字符,我们需要先把字符串转换为rune切片,然后修改切片中的内容,最后将这个切片转换回字符串。
packagemain import( "fmt" ) funcmutate(s[]rune)string{ s[0]='a' returnstring(s) } funcmain(){ h:="hello" fmt.Println(mutate([]rune(h))) }1234567891011121314
在上面的程序中,第7行mutate
函数接受一个rune切片作为参数。然后将该切片的第一个元素改为a
,最后再转换回字符串并返回。该函数在程序中的第13行被调用。h
被转换为一个rune切片传递给mutate
。程序的输出为:aello
。