需求=过去困惑+当下渴望+未来希望 ——实干《实干日记》
- 5-1 为什么len函数获取中文字符串长度有问题?
我们将讲解go语言的字符串相关的操作。
因为字符串本身是非常重要,而且它相对的方法也比较多,所以说我们这里边单独拿一个关卡出来专门讲解字符串。
字符串不管在任何语言当中,它的使用都是非常频繁的。
在Python当中基本上采用的字符串操作,在go当中基本上也是这么回事,它也是这些基本操作。
1.求解字符串的长度。
它有一个内置的函数叫len,名字跟Python是一样的。
比如说这里边我们定义一个字符串,字符串的定义我们可以直接这样来定义,比如说name,然后它是有类型的叫string,然后赋值赋值跟Python是一样的。
注意一下,这里不能用单引号,单引号 引起来/包裹起来 的单个符号是字符。
那就引出了一个问题:
在go语言中,字符和字符串有啥区别?
2.字符和字符串有啥区别
我们通过代码来说明,就不用文字来表述了。
文件位置:c06/demo1.go
package main
import "fmt"
var global int = 1024
func main(){
str := "keegan"
fmt.Printf("%T,%v,%sn",str,str,str)
var ch1 rune = 'A'
fmt.Printf("%T,%v,%cn",ch1,ch1,ch1)
var ch2 byte = 'B'
fmt.Printf("%T,%v,%cn",ch2,ch2,ch2)
var ch3 int32 = 'C'
fmt.Printf("%T,%v,%cn",ch3,ch3,ch3)
var (
name string = "程咬铁"
age int32 = 18
salary float32 = 12580
)
fmt.Printf("name=%s,age=%d,salary=%fn",name,age,salary)
fmt.Printf("global=%dn",global)
global += 1024
fmt.Printf("global=%dn",global)
}
解读代码信息:
回答开始的问题:字符和字符串有啥区别?
字符定义:
形如 var ch3 int32 = 'C'
字符变量 ch3 的值是 用单引号 括起来的 大写的 C
字符串定义:
形如 str := "keegan"
字符串变量 str 的值 使用双引号 括起来的 keegan 这 6个字符。
字符串 是 一连串字符的集合。
特别地还有 空白字符的字符串【空字符串】,单个字符的字符串。
可以这样定义:
到这里,处理完问题2之后,再继续看 1.求解字符串的长度
3.继续看 1.求解字符串的长度
我们先定义以下字符串:
部分代码如下:
var name string = "keegan:写博客" // 一共由10个字符组成 fmt.Println(len(name)) // 16
这里面怎么求它的长度呢?
用len函数。跟Python中的求字符串的长度用的函数都是len函数。
前置信息铺垫:
存储空间的基本单位是 字节。
比如
在go中,1个英文的字符占用1个字节,1个中文的字符占用3个字节。【自己写字符串测试结果】
对于len函数:
用途:用来获取字符串的字节长度,返回的是字节的长度。
处理方法:在go的字符串中,1个英文的字符占用1个字节,1个中文的字符占用3个字节。【自己写字符串测试结果】
我们直接来打印一下,它返回的是16。
为什么是16呢?这里面的逻辑是什么?
用Python来打印:
print(len("keegan:写博客")) # 10
这里又引出了一个问题:
在go语言中,英文字符和中文字符计算长度的方法是怎样的?
在go中,1个英文的字符占用1个字节,1个中文的字符占用3个字节。【自己写字符串测试结果】
我们要知道这个英文字符和中文字符它们的编码规则是不一样的,我们如果把中文内容 写博客 给它去掉,只要是英文加上特殊符号,它的长度就是准确的:
var name string = "keegan:" fmt.Println(len(name)) // 7
我们只要不是中文,然后这里边当然其他国家的语言也不行,一旦涉及到中文或其他国家的语言例如日文,韩文就不准确了。
对于英文来说,英文它实际上是ASCII码来做的,ASCII是一套编码标准,用于显示英文的。128个字符
通过上图,我们知道了,不同的语言,用的编码标准是不一样的,计算语言符号占用的字节也不一样。
我们这里简化问题一下:
英文在计算机里用的是ASCII编码标准,1个英文字符占用1个字节。
中文在go语言中采用的是Unicode,1个汉字占用3个字节。
Unicode实现方式又包括UTF-8、UTF-16、UTF-32。
以上编码标准有啥意义?
就告诉了计算机 汉字/英文字符 用什么数字代替的?
因为计算机的存储单位是位(bit),每1位的状态用0或1表示。
英文字符 用数字表示:
A:65 B:66 C:67 …依次递增
a:97 b:98 c:99 …依次递增
汉字的话:
首先,计算机存储的是用二进制来实现的。
把所有国家的字符(英文,中文等)跟数字做好了映射,统一了标准之后,就不存在语言的编码冲突了【一说65对应的啥字母你就能根据标准计算出是A,一说97对应的啥字母你就能根据标准计算出是a】,计算机会根据这套标准帮我们实现了跨语言,跨平台进行的文本转换和处理,这样,在电脑屏幕下,就能显示任何语言的内容了。
回归正文:
到这里,我们知道了:
英文在计算机里用的是ASCII编码标准,1个英文字符占用1个字节。
中文在go语言中采用的是Unicode字符集,1个汉字占用3个字节。
1字节 = 8 位
一共有 2的8次方 种组合方式,即256种组合方式。
对于中文来说,一个字节肯定是不能够把所有中文编进来的,因为一个字节的存储上限是256,所以说对于中文来说,它要用3个字节才能够把所有的汉字全部包括进来,或者说是在Unicode中文的数字区域内,它用3个字节才能把所有的中文的汉字给囊括进来。
所以说我们就要知道UTF-8编码,它实际上是用来保存Unicode的这种字符集的一种实现方式。【上文提到过还有UTF-16】
对于go语言来说,go语言对于字符串它采用了UTF-8编码,那UTF-8编码除了能够用三个字节表示一个中文之外,它还能够用一个字节表示英文,这什么意思呢?
实际上很简单,对于英文字符来说它的数字区域,大家可以去看一下Unicode的字符集或查看上文内容,用一个字节就已经能够存储英文字符了,而且是绰绰有余。
对于utf8编码来说,utf8又是一个动态编码,也就是说它不是说所有的字符全部采用等长的这种直接占用,那这样的话会浪费空间,所以说对于英文的编码来说,因为英文的它的编号正好是小于256的,所以说它就用一个字节来表示,就够了。
到这里,就知道了在go语言当做计算英文和中文的算法原因。
再看代码:
var name string = "keegan:写博客" // 一共由10个字符组成 fmt.Println(len(name)) // 16
为什么是7*1 + 3*3 = 16?而不是10 * 3 = 30?
我们可以试想一下,如果说每一个字符它都占3个字节的话,那这里边总共有10个字符,那它整个就应该占用10 * 3 = 30个对不对?
但是输出结果是16个,这说明后边3个中文总共占用9个字节,前边7个字符它总共占用7个字节,
总共加起来就是16个对吧?我们也可以只使用中文来验证一下。
var name string = "写博客" fmt.Println(len(name)) //9
然后再一点一点地加点东西:
var name string = ":写博客" fmt.Println(len(name)) //10 var name string = "n写博客" //转义字符也是一个字符,占用1个字节 fmt.Println(len(name)) //10
好了,那对于我们来说这个问题它就产生了一个需求,怎么能得到它真正的长度呢?
这实际上就要涉及到类型转换。
我们可以将name给它转换一下,把它转换成什么类型呢?
把它转换成我们的数组的类型,数组的类型我们在后边也会介绍到。这个数组和我们平时写的数组也有很大的差异,我们把它转成什么数组?
rune
rune它实际上本质上是int32,那是因为它对于这种字符的处理,它主要用于字符的处理,所以说对于int32,我们专门又给它命了个别名rune,专门用于这种字符的处理。
int32是4个字节,int64是8个字节
实际上我们把rune写成int32也是一样的。
看代码:
var name string = "keegan:写博客" fmt.Println(len(name)) // 16 name_arr := []int32(name) fmt.Println(len(name_arr )) // 10
在这里边什么意思呢?
这里边的意思就是将你的每一个字符,注意一下,每一个字符都转换成一个rune。
对于这种只有占用一个字节的字符的情况,即英文字符,它也会转成一个rune。
对于汉字这种来说,本身你占用3个字节,现在把你转化成int32了,已经占4个字节了,那这样的话大家看看你的中文,我全部都能包含进来对不对?无非多浪费一个空间。【4-3=1】
对于英文字符这种只占用1个字节来说,我给它又加了3个字节,但是它们加起来是一个rune。
对于这种只有3个字节的中文来说,我再给它加1个字节,让它凑够4个字节,也就是1个rune。
这样的话总共有多少个rune呢?
10个。因为这里是10个字符,每个字符都转化成rune了。
我们通过将字符串进行类型转换,转换成rune,再借用len函数计算出rune的长度,这个长度就字符串的长度。
即 rune的长度 = 字符串的长度
其实就是通过类型转换将求字符串长度转化成求rune的长度的问题。
rune的长度就像求数组的长度一样,里面有几个字符rune的长度就是几。
对于len来说,它能够求出数组的元素的个数,就能得到数组的长度。跟Python很相似。
再来看代码:
var name string = "keegan:写博客" fmt.Println(len(name)) // 16 name_arr := []rune(name) // []int32(name) fmt.Println(len(name_arr )) // 10
现在我们就能够得到它的长度是10个了,对不对?
通过这样的类型转化,我们做到了可以从数字符串有几个字符/元素就能准确计算出字符串的长度就是几了。
即通过这样的类型转化, 字符串的个数 = 字符串的长度
但是我们也知道了这样做它实际上是浪费空间,你本身占的字节没有那么多,因为转成rune的话它占用的空间会多很多。
int32是4个字节,int64是8个字节。
以上是求长度的问题。
多说一点:
字符串可以看成字符数组,用索引取值:
var name string = "keegan:写博客"
fmt.Println(len(name)) // 16
name_arr := []rune(name)
fmt.Println(len(name_arr )) // 10
fmt.Printf("%T,%cn",name_arr,name_arr[9]) // []int32,客



