5.go基础入门-字符(byte、rune)、字符串(string)

前言

什么是字符?什么是字符串?

// 输出一句话
fmt.Println("ab吃饭")
/*
输出结果:
ab吃饭
*/

// 字符
a := 'a'
复制代码

我们先看一下这句话 "ab吃饭",这一句话就是字符串,这一串字符由四个字符组成,分别是 'a','b','吃','饭';所以单独拿出来,每一个字就是一个字符,字符类型单引号引用,并且只包含一个字符;而字符串,顾名思义就是可以多个字符串联在一起,双引号引用;(我们可以把字符串理解成是一个由双引号组成的容器,把它看做是长方形凹槽;可以把字符理解成乒乓球,一个字符就是一个乒乓球;我们可以往凹槽里放入乒乓球)

[ 'a', 'b', '吃', '饭' ]

  • 字符: 单引号组成、内容只有一个字符(它就是一个乒乓球,对应一个字符,所以内容必然是有值的,内容为空时会报错)
  • 字符串: 双引号组成(它只是一个容器,乒乓球放不放都行,所以里面没内容也不会报错)

字符 byte

我们在前面的篇章中说过,我们的一切数据,都是以二进制的形式存在于内存当中的,所以我们所说的字符类型也是如此;我们前面讲过 uint8 类型的整数,他是无符号,8个比特位(1个字符)的整数,所表示的范围是 0到255,一共256个整数;那这个 uint8 跟我们讲的 byte有什么关系呢?

还记得上一章我们讲的浮点数吗?虽然我们直观看到的是小数,但是实际上他是用IEEE754标准把浮点数转换成了二进制数据之后,再存储在内存中的;也就是说 我们的 byte 字符型数据也是通过某种方式,转换成了二进制,再存储进内存;所以我们在讲解 byte、rune、string之前,先把 ‘字符’ 转 ‘二进制’ 的方式讲明白,后面我们就能很简单理解这三种类型。

编码是信息从一种形式或格式转换为另一种形式的过程,也称为计算机编程语言的代码简称编码。这是什么意思呢?

我们计算机中有很多本字典,我们拿 ASCII 这本字典做个列子, 我们键盘上的 26个大写字母、26个小写字母、符号、数字0-9等这些字符都在ASCII这个字典中,每个字符在字典中都有一个位置,字符 'a' 在 ASCII字典中的位置是 97,所以我们把 'a' 转成 97,再把97转成二进制,然后存储到内存中;同理,我们拿到内存中的二进制值,再通过二进制转换成97,我们在 ASCII 字典中取出位置97的字符,我们就得到了字符 'a'。是不是既简单又易懂,所以我们把这种方式成为 编码(信息从一种形式或格式转为另一种形式的过程)。

ASCII(美国信息交换标准代码) 这本比较早的字典用于显示新英语和其他西欧语言,这本字典的大小是一个字节,也就是256个位置,范围是0-255,刚好和uint8一样,无符号256个数字;我们上面已经说过, uint8和byte大小和存储都是一样的,无符号256个数字,只是名称不一样;之所以byte是字符型,是因为byte中存储的数字其实是对应字典中的位置的,为了在字面上可以更直观的表达出字符型,所以没有使用 uint8 这个类型来存储,而是创建多一种名称不同,而实际与uint8一样的类型 byte。

其所我们的 byte 类型就是对应 ASCII 编码表的一种类型,他存储的是ASCII编码表的字符位置。

// 创建一个字符型 'a'
var a byte = 'a'
fmt.Println(a)
/*
输出结果:
97
*/

var b byte = ''
/* 报错 empty rune literal or unescaped ' */

var c byte = '措'
/* 报错 constant 25514 overflows byte */
复制代码

我们 fmt.Println 函数打印 a 变量的值,结果是 97,而非 'a',由此而知我们 a 变量存储的值实际上是 97,而我们所看到的 'a',其实是计算机在编码表中取出来显示的符号。

变量 b 报错,上面说过 byte 中存储的实际上就是 ASCII 编码标准中的字符编号(位置),字符不能为空,所以报错。

变量 c 报错,byte 是一个字节 8 个bit,无符号 整型,256个数字,字符 '措' 是 Unicode 编码标准(下文讲述)中的字符,他在 Unicode 标准中的字符编号(位置)是 25514, byte 所能存储的最大数字是 255,而我们把数字 25514 存储到 byte 类型中就会报错 “constant 25514 overflows byte”,就是溢出,放不下那么大的数字。

到这里我们应该明白 byte 类型了,我们下面做个小总结:

  • byte 1字节,无符号,256个正数,范围0-255
  • ASCII 编码表,256个字符,对应 byte,只适用美国使用电脑
  • 编码表字符与计算机二进制之间的转换,我们把这个过程称之为 编码

字符 rune

上面有一个重点,大家应该也注意到了就是, ASCII 编码,他主要用于 新英语和其他西欧语言的转换。常用的 符号、字母、数字的确可以使用 ASCII 编码就能满足,但如果是显示我们的中文呢?我们的中文汉字呢?只有256个怎么够我们使用?

汉字的数量并没有准确数字,大约将近十万个,日常所使用的汉字只有几千字。据统计,1000个常用字能覆盖约92%的书面资料,2000字可覆盖98%以上,3000字则已到99%,简体与繁体的统计结果相差不大。(百度百科)

所以 ASCII 编码只是适用于美国、西欧等国家,我们国家如果要使用电脑并且显示中文,就要有一个包含中文的编码标准,所以我们上面说了 技术机中有很多本字典;为了让电脑显示中文,我国家又发布了 GB2312-80 标准、GBK 编码标准、GB18030编码标准等;如果每个拥有自己独特字符的国家都去发布一份自己的标准,就会出现一种问题,比如我们国家电脑程序使用的是GBK进行编码的,那么程序和数据到别的国家的电脑上运行时就会出现乱码的问题,因为两台电脑使用的不是同一个编码标准。

为了解决上述问题,有一个国际组织提出创建 Unicode 编码标准(统一码、万国码、单一码),由世界各国合作把自己国家的字符都添加到这个编码标准中,这个标准有 1114112 码位,可以容纳全世界所有国家的字符,最多使用4个字节进行存储,前面部分兼容 ASCII 标准中的字符,然后再加上各国自己的字符;另外 Unicode 标准中使用 UTF-8 格式去编码时,中文是使用 3 个字节的,而我们的 Golang 是默认使用 UTF-8 格式的,我们编程的时候,如果我们的程序仅仅只用到中文、中文符号、英文、英文符号(使用的都是中国人),那么使用GBK进行编码也是可以的,因为GBK标准中,汉字字符的编码是使用 2 个字符的,比 Unicode UTF-8 要节省内存空间。(我们平常所说的 UTF-8,实际上说的是 使用Unicode编码标准UTF-8转换格式)

我们这里简单提一嘴UTF-8(UCS)格式,这里代表的是以1个byte1个byte地去处理,这样可以对不同范围的字符使用不同长度编码;比如Unicode前面部分 ASCII 字符,用一个字节就可以表示,这个时候我们 UTF-8 格式就能根据字符串度使用一个字节去处理;又比如某个汉字字符需要三个字节才能表示,这个时候 UTF-8 格式就用三个字节去处理;这样可以通俗的理解了吧?以前老版的Unicode只有两种编码格式 UCS2 和 UCS4 就是用2个字节表达或者用4个字节表达,后面使用了 UTF 的处理方式才真正让 Unicode 名副其实的称为万国码。 这里只简单的提一嘴,编码方面详细的知识如果有兴趣的可以自行查阅关于计算机编码方面的资料进行学习,这里就不再详细说了,后续找个时间再专门写一篇编码相关的文章。

言归正传 rune 字符型

我们讲 byte 字符型的时候说他其实就是 uint8类型,只是类型的名称不同,存储的数据都是一样的。 而我们的 rune 字符类型,4个字节、有符号、整型,是不是跟我们的 int32 一样?实际上 rune 等价于 int32 ,只是类型名称不同。rune 存储的是 Unicode 编码标准中的字符编号,Unicode 最大用 4 个字节来表示字符,而 rune 使用的是 4 个字节,意味着 rune 可以存储以及表示出 Unicode 中的所有字符。

// rune
var a rune = 'a'
fmt.Println(a)
/*
输出结果:
97
*/

var b rune = '措'
fmt.Println(b)
/*
输出结果:
25514
*/
复制代码

所以 rune 能存储 '措' 字符,自然也能存储下占用1个字节的 'a'字符,rune 跟 byte一样不能为空,单引号内一定要有内容。如果我们只是表示 ASCII 编码标准中的字符,直接使用 byte 类型就可以了,没必要使用 rune 类型,该类型是直接使用4个字节的,内存使用的是 byte 的四倍。

说白了, rune 就是一个空间更大容器,可以放得下编号更大字符, '措' 字符的 编号是数字 25514,而我们的 rune 的空间是4个字节32个bit,存储数字 25514 绰绰有余。

有byte的铺垫,rune应该很容易看到,到此我们对 rune 做过一个小总结:

  • rune 4个字节 32bit、对应 int32 类型
  • Unicode 万国码、统一码、最大使用4个字节表示一个字符、有多种编码格式(UTF-8、UTF-16、UTF-32)
  • UTF-8 golang默认使用的编码方式、UTF-8格式使用3个字节表示1个汉字字符

字符串 string

字符串顾明思意,就是多个字符串联起来,使用双引号表示。

// string 类型
var a string = "a措"
fmt.Println(a)
fmt.Println("长度:", len(a))
/**
输出结果
a措
长度:4
**/
复制代码

打印出内容 a措 的时候,是不是还能理解,当使打印出 长度:4 的时候是不是就有点懵了?有 byte 和 rune 的铺垫,我们再讲一下 string 字符串就能很简单的理解了。

我们上面篇章说过 Go 语言默认使用 Unicode UTF-8 编码的,UTF-8 格式 对不同范围的字符使用不同长度编码,变量 a 字符串中有两个字符 'a' 和 '措',字符 'a' 所在的范围用1个字节就可以存储了;上面也说过 汉字字符,在使用 UTF-8 格式时,一个汉字字符使用3个字节表示。我们把这两个字符看做乒乓球,'a' 乒乓球的大小是1byte, '措' 乒乓球的大小是3个byte,而我们的双引号就是一个凹槽容器,这时我们往凹槽容器依次放入乒乓球 'a',再放入乒乓球 '措' ,我们代码中获取长度的 len() 函数,其实是获取变量的 字节数量,那么我们的 'a' 字符1byte,而 '措' 字符3byte,所以打印的结果是 4。这能明白了吧,字符串之所以叫字符串,就是把字符串联起来。

而我们比喻的这个 凹槽容器,其实他也是一种类型,我们叫他 数组,但这是下一篇的内容,我们把它看做是一个容器就可以了。

"a措"[ 'a', '措' ][ b1, b2, b3, b4 ]

  • "a措" 编码后我们所看到的字符串内容
  • [ 'a', '措' ] 我们所理解的 凹槽中存放乒乓球 的样子
  • [ b1, b2, b3, b4 ] string类型变量 a 中实际的值,其中 b1 是存储 'a' 字符编号的字节, b2 b3 b4 是用来存储 '措' 字符所用的字节;这个 [ ] 中括号就是我们说的 数组,中括号内的 b1 b2 b3 b4 就是数组中的元素(值),数组中有4个byte字节类型的元素,那么他的大小就是 4,所以 len() 函数获取的是这个数组元素的数量,而我们数组中元素的类型都是字节型,所以我们称他为 字节型数组,也就是 byte[];这里就先这样简单的了解一下数组,下一章我们再详细的去说。

上面代码打印出 a 变量的长度是 4,这个我们已经可以理解了,但是我们该怎么用代码输出 字符 的数量呢?

a := "a措"

// 使用 utf8 包的函数
b := utf8.RuneCountInString(a)
fmt.Println(a)
/*
输出结果:
2
*/

// 把字符串转换成 rune类型的数组
c := []rune(a)
fmt.Println(len(c))
/*
输出结果:
2
*/
复制代码
  • utf8.RuneCountInString(a) 函数使用 utf-8 的规则统计出 a 变量的字符数量,把统计出的数量 2,赋值给 b 变量,然后打印出来 输出结果:2
  • b := [] rune(a) 把 a字符串 转换成 rune类型数组,这个rune类型的数组可以理解成数组的内容只存储 rune字符,然后再赋值给 c 变量,所以此时的 c变量 [ 'a', '措' ]
  • len(b) 函数获取数组中的元素数量,这是一个 rune字符类型的数组,里面有两个元素,所以结果是 2,打印出来 输出结果:2

我们字符串中所看到的内容是经过编码后显示出来的内容,实际上字符串的实际值就是一个 byte[] 数组,把每个字符通过 UTF-8 格式编码得到字节放到这个数组中,然后再把这数组的内容存储到内存中;反之,我们拿到这个数组的内容,再通过 UTF-8 编码又能再得到对应的字符,然后再把字符组成成字符串显示出来。

至此我们的字符串部分也讲完了,我们做一个小总结:

  • 字符串 多个字符串联在一起
  • 字符串的实际值是 byte[] 数组,当字符串为空时是允许的(a := ""),因为实际值是[ ],表示一个没有元素的数组,所以不会报错
  • Unicode UTF-8 格式把得到的字符转换成编号,并存储在字节中,然后再把字节存入数组中,这样 byte[] 就能跟 string 字符串通过使用 UTF-8 格式相互转换了。

至此,本章内容 byte字符、rune字符、string字符串,三种类型都讲解完成。 (java中的字符和字符串本质上也是一样的)

猜你喜欢

转载自juejin.im/post/7031453565266231332