GO学习笔记——字符和字符串(14)

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其实也是一个数组,只不过数组中存的都是字符罢了)

 

rune和遍历字符串

因为字符串本质是一个切片,所以也可以用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切片来遍历,那么得到的下标是按顺序的,不会出现直接遍历字符串这样跳下标的问题。它直接把表示中文的三个字节当成一个字符,它是按字符来遍历的。

所以这两种遍历方式,需要具体具体分析,到底用哪个是区分于不同的使用场景的,如果只是想使用字符,而不使用前面的下标,那就无所谓了,只要把前面的变量用下划线_代替就可以了。
 

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值