GO中用string表示字符串,它是一个内置类型,而C++中的string是一个标准类,这是一个区别。因为字符串操作非常多,另外GO中还引入了rune来支持国际化的字符(中文字符等),因此这里单独开一篇文章来将字符和字符串。
先来简单地看一个中英文都有的字符串
func main() {
str := "我叫lyb"
fmt.Println(len(str))
}
输出结果
9
输出9的原因是因为,一个中文在utf-8编码的情况下占了3个字符(当然也有可能是2个字符的),加上lyb所以输出了9。
GO中的字符串其实是一个字节切片(很好理解,C++中是string其实也是一个数组,只不过数组中存的都是字符罢了)
因为字符串本质是一个切片,所以也可以用range来遍历字符串
打印一些每个字节的值看看。
func main() {
str := "我叫lyb"
for _,b := range []byte(str){ //这里把字符串看为一个字节数组
fmt.Printf("%X ",b) //以十六进制的方式打印
}
}
输出结果
E6 88 91 E5 8F AB 6C 79 62
可以看到总共是9个字节,其中(E6 88 91)表示中文“我”,(E5 8F AB)表示中文“叫”,后面三个字节分别表示l,y,b。
再来遍历一下原字符串
func main() {
str := "我叫lyb"
for i,ch := range str{
fmt.Printf("(%d,%x) ",i,ch)
}
for i,ch := range str{
fmt.Printf("(%d,%c) ",i,ch) //%c表示以字符形式打印
}
}
输出结果
(0,6211) (3,53eb) (6,6c) (7,79) (8,62)
(0,我) (3,叫) (6,l) (7,y) (8,b)
在这里,这个ch其实就是一个rune类型,在存的时候,“我”占了3个字节,它的值是6211;“叫”占了3个字节,它的值是53eb。
但是在之前的输出结果中,这个“我”明明是(E6 88 91),这里确是6211。因为在按字节编码的时候,GO默认使用的是utf-8编码,而(E6 88 91)就是utf-8编码的“我”,而6211是Unicode编码的。
所以编译器做的事情是这样的,“我”以uft-8的形式存储在每一个字节中,编译器对其进行解码之后,又将其转成了Unicode编码,转完了之后又放在了rune类型中,这是一个int32类型。所以字符串中的每个字符都是rune类型,rune就是GO中的char类型。
之前我们打印了“我叫lyb”的长度,得到的是9,因为len函数求的只是所占字节长度。
但是按常理我们应该得到的是5,中文也应该算是一个字符。
在utf8包中有一个函数可以帮助我们得到总共有多少字符
func main() {
str := "我叫lyb"
for i,ch := range str{
fmt.Printf("(%d,%c) ",i,ch)
}
fmt.Println()
fmt.Println("总共有多少个字节: ",len(str))
fmt.Println("总共有多少个rune: ",utf8.RuneCountInString(str))
bytes := []byte(str)
for len(bytes) > 0 {
ch, size := utf8.DecodeRune(bytes)
bytes = bytes[size:]
fmt.Printf("%c ",ch)
}
fmt.Println()
//如果直接对每个字节按%c输出,会出现乱码
for i,ch := range []byte(str){
fmt.Printf("(%d,%c) ",i,ch)
}
}
输出结果
(0,我) (3,叫) (6,l) (7,y) (8,b)
总共有多少个字节: 9
总共有多少个rune: 5
我 叫 l y b
(0,æ) (1,) (2,) (3,å) (4,) (5,«) (6,l) (7,y) (8,b) //这就乱码了
GO中的字符串是不可变的,一个字符串一旦被定义就不可以改变,如果想要改变只能通过rune切片。
func main() {
str := "我叫lyb"
str[0] = 'a'
fmt.Println(str)
}
上述代码会报错
.\main.go:7:9: cannot assign to str[0]
可以通过rune切片来改
func main() {
str := "我叫lyb"
fmt.Println(str)
//将str转为一个rune切片,这个切片是重新开辟空间分配的,不是在原地址上直接改变的
runes := []rune(str)
runes[3] = 'a'
str = string(runes) //将rune切片再转为string类型赋值回去
fmt.Println(str)
}
输出结果
我叫lyb
我叫lab
最后再来看一下下面两种遍历方式的区别
func main() {
str := "我叫lyb"
for i,ch := range []rune(str){ //将字符串转为rune切片遍历
fmt.Printf("(%d,%c) ",i,ch)
}
fmt.Println()
for i,ch := range str{ //直接遍历字符串
fmt.Printf("(%d,%c) ",i,ch)
}
}
输出结果
(0,我) (1,叫) (2,l) (3,y) (4,b)
(0,我) (3,叫) (6,l) (7,y) (8,b)
可见,如果当成rune切片来遍历,那么得到的下标是按顺序的,不会出现直接遍历字符串这样跳下标的问题。它直接把表示中文的三个字节当成一个字符,它是按字符来遍历的。
所以这两种遍历方式,需要具体具体分析,到底用哪个是区分于不同的使用场景的,如果只是想使用字符,而不使用前面的下标,那就无所谓了,只要把前面的变量用下划线_代替就可以了。