Golang⼊门:字符串及底层字符类型
字符串
基本使⽤
在 Go 语⾔中,字符串是⼀种基本类型,默认是通过UTF-8编码的字符序列,当字符为ASCII码时则占⽤ 1 个字节,其它字符根据需要占⽤2-4 个字节,⽐如中⽂编码通常需要 3 个字节。
声明和初始化
字符串的声明和初始化⾮常简单,举例如下:
var str string        // 声明字符串变量
str = "Hello World"    // 变量初始化
str2 := "你好呀"  // 也可以同时进⾏声明和初始化
格式化输出
还可以通过 Go 语⾔内置的len()函数获取指定字符串的长度,以及通过fmt包提供的Printf进⾏字符串格式化输出:
fmt.Printf("The length of \"%s\" is %d \n", str, len(str))
fmt.Printf("The first character of \"%s\" is %c.\n", str, ch)
转义字符
Go 语⾔的字符串不⽀持单引号,只能通过双引号定义字符串字⾯值,如果要对特定字符进⾏转义,可以通过 \ 实现,就像我们上⾯在字符串中转义双引号和换⾏符那样,常见的需要转义的字符如下所⽰:
\n:换⾏符
\r:回车符
\t:tab 键
\u或 \U :Unicode 字符
\\:反斜杠⾃⾝
所以,上述打印代码输出结果为:
The length of "Hello world" is 11
The first character of "Hello world" is H.
除此之外,你可以通过如下⽅式在字符串中包含 ":
label := `Search results for "Golang":`
多⾏字符串
对于多⾏字符串,也可以通过 ` 构建:
results := `Search results for "Golang":
- Go
- Golang
Golang Programming
`
fmt.Printf("%s", results)
打印结果如下:
Search results for "Golang":
- Go
- Golang
- Golang Programming
当然,使⽤ + 连接符也是可以的:
results := "Search results for \"Golang\":\n" +
"- Go\n" +
"- Golang\n" +
"- Golang Programming\n"
fmt.Printf("%s", results)
打印结果是⼀样的,但是要多输⼊不少字符,也不如上⼀种实现优雅。
不可变值类型
虽然可以通过数组下标⽅式访问字符串中的字符:
ch := str[0] // 取字符串的第⼀个字符
但是和数组不同,在 Go 语⾔中,字符串是⼀种不可变值类型,⼀旦初始化之后,它的内容不能被修改,⽐如看下⾯这个例⼦:
str := "Hello world"
str[0] = 'X' // 编译错误
编译器会报类似如下的错误:
cannot assign to str[0]
字符编码
Go 语⾔中字符串默认是UTF-8编码的Unicode字符序列,所以可以包含⾮ANSI字符,⽐如「Hello, 世界」可以出现在 Go 代码中。
但需要注意的是,如果你的 Go 代码需要包含⾮ANSI字符,保存源⽂件时请注意编码格式必须选择UTF-8。特别是在Windows下⼀般编辑器都默认保存为本地编码,⽐如中国地区可能是GBK编码⽽不是UTF-8,如果没注意到这点在编译和运⾏时就会出现⼀些意料之外的情况。
字符串的编码转换是处理⽂本⽂档(⽐如 TXT、XML、HTML 等)时⾮常常见的需求,不过 Go 语⾔默认仅⽀持UTF-8和Unicode编码,对于其他编码,Go 语⾔标准库并没有内置的编码转换⽀持。所幸的是我们可以很容易基于iconv库包装⼀个,这⾥有⼀个开源项⽬可供参考:
字符串操作
字符串连接
Go 内置提供了丰富的字符串函数,常见的操作包含连接、获取长度和指定字符,获取长度和指定字符前⾯已经介绍过,字符串连接只需要通过 + 连接符即可:
str = str + ", 世界"
str += ", 世界"  // 上述语句也可以简写为这样,效果完全⼀样
另外,还有⼀点需要注意的是如果字符串长度较长,需要换⾏,则+连接符必须出现在上⼀⾏的末尾,否则会报错:
str = str +
", 世界"
字符串切⽚
在 Go 语⾔中,可以通过字符串切⽚实现获取⼦串的功能:
str := "hello, world"
str1 := str[:5]  // 获取索引5(不含)之前的⼦串
str2 := str[7:]  // 获取索引7(含)之后的⼦串
str3 := str[0:5]  // 获取从索引0(含)到索引5(不含)之间的⼦串
fmt.Println("str1:", str1)
fmt.Println("str2:", str2)
fmt.Println("str3:", str3)
Go 切⽚区间可以对⽐数学中的区间概念来理解,它是⼀个左闭右开的区间,⽐如上述str[0:5]对应到字符串元素的区间是[0,5),str[:5]对应的区间是[0,5)(数组索引从 0 开始),str[7:]对应的区间是[7:len(str)](这是闭区间,是个例外,因为没有指定区间结尾)。
所以,上述代码打印结果如下:
str1: hello
str2: world
str3: hello
综上所述,字符串切⽚通过 : 连接的起始点和结束点索引对字符串进⾏切⽚,冒号之前的数字代表起始点,为空表⽰从 0 开始,之后的数字代表结束点,为空表⽰到字符串最后,⽽不是⼦串的长度。所以str[:]会打印出完整的字符串来。
此外 Go 字符串也⽀持字符串⽐较、是否包含指定字符/⼦串、获取指定⼦串索引位置、字符串替换、⼤⼩写转换、trim 等操作,更多操作API,请参考标准库strings 包,这⾥就不⼀⼀展⽰了。
字符串遍历
Go 语⾔⽀持两种⽅式遍历字符串。
⼀种是以字节数组的⽅式遍历:
str := "Hello, 世界"
n := len(str)
for i := 0; i < n; i++ {
ch := str[i]    // 依据下标取字符串中的字符,ch 类型为 byte
fmt.Println(i, ch)
}
这个例⼦的输出结果为:
0 72
1 101
2 108
3 108
4 111
5 44
6 32
7 228
8 184
9 150
10 231
11 149
12 140
可以看出,这个字符串长度为 13,尽管从直观上来说,这个字符串应该只有 9 个字符。这是因为每个中⽂字符在 UTF-8 中占 3 个字节,⽽不是 1 个字节。
另⼀种是以 Unicode 字符遍历:
str := "Hello, 世界"
for i, ch := range str {
fmt.Println(i, ch)    // ch 的类型为 rune
}
输出结果为:
0 72
1 101
2 108
3 108
4 111
5 44
6 32
7 19990
10 30028
这个时候,打印的就是 9 个字符了,因为以Unicode字符⽅式遍历时,每个字符的类型是rune,⽽不是byte。
看到这⾥可能你有点懵,会好奇 Go 底层到底是如何存储字符串的,为什么不同遍历⽅式获取的结果不同呢?下⾯就来给⼤家简单掰扯掰扯。
底层字符类型
Go 语⾔对字符串中的单个字符进⾏了单独的类型⽀持,在 Go 语⾔中⽀持两种字符类型:
⼀种是byte,代表UTF-8编码中单个字节的值(它也是uint8类型的别名,两者是等价的,因为正好占据 1 个字节的内存空间),它可⽤于区分字节值和8位⽆符号整数值。;
另⼀种是rune,代表单个Unicode字符(它也是uint32类型的别名,因为正好占据 4 个字节的内存空间。关于rune相关的操作,可查阅Go 标准库的unicode包),它可⽤于区分字符值和整数值。。
rune、byte和string都是 Go 的内置类型。string是所有8位字节字符串的集合,通常但不⼀定代表UTF-8编码的⽂本,字符串可能为空,但是不能为 nil,字符串类型的值是不可变的。
由上⾯得解释我们⼤概可以明⽩,rune可以表⽰得⽐byte多,string类型的底层是⼀个byte 数组
字节和字符
刚刚上⾯标注了字节和字符,现在我们来梳理字符和字节的概念
存储单位字节
计算机存储信息的最⼩单位,称之为位 bit,⼆进制的⼀个0或1叫⼀位
字符串函数源码
计算机存储容量基本单位是字节 Byte,8个⼆进制位组成 1 个字节
信息表⽰单位字符
字符是⼀种符号,像英⽂a和中⽂阿就是不同字符
不同的字符在不同的编码格式下,所需要的存储单位不⼀样
ASCLII 编码中⼀个英⽂字母⼀字节,⼀个汉字两字节
UTF-8 编码中⼀个英⽂字母⼀字节,⼀个常见汉字3字节,不常⽤的超⼤字符集汉字4字节
UTF-8 和 Unicode 的区别
说到这⾥,我们需要区分UTF-8和Unicode的区别。
Unicode是⼀种字符集,囊括了⽬前世界上所有语⾔的所有字符,与之类似的术语还有ASCII字符集(仅包含 256 个字符)、ISO 8859-1字符集等(包含所有西⽅拉丁字母),⼴义的Unicode既包含了字符集,也包含了编码规则,⽐如UTF-8、UTF-16、UTF8MB4、GBK等。
因此UTF-8是Unicode字符集的实现⽅式之⼀,它会将Unicode字符以某种⽅式进⾏编码。在具体实现时,
UTF-8是⼀种变长的编码规则,从
1~4 个字节不等,⽐如英⽂字符是 1 个字节,中⽂字符是 3 个字节。通过UTF-8编码的Unicode字符以最⼤长度 4 个字节作为单个字符固定占据的内存空间,在 Go 语⾔中可以通过unicode/utf8包进⾏UTF-8和Unicode之间的转换。
所以如果从Unicode字符集的视⾓看,字符串的每个字符都是⼀个字符的独⽴单元,但如果从UTF-8编码的视⾓看,⼀个字符可能是由多个字节编码⽽来的。
我们通过len函数获取到的是字符串的字节长度,再据此通过字符数组的⽅式遍历字符串时,是以UTF-8编码的⾓度切⼊的;⽽当我们通过range关键字遍历字符串时,⼜是从Unicode字符集的⾓度切⼊的,如此⼀来就得到了不同的结果。
出于简化语⾔的考虑,Go 语⾔的多数API都假设字符串为UTF-8编码。
Go 源码⽂件默认采⽤Unicode字符集,Unicode码点和内存中字节序列的变换实现使⽤了UTF-8,这使得Go编程⽆需考虑编码转换的问题⾮常⽅便
从编码上来分析
byte⽤来强调⼀个字节代表的数据(例如字符 a 就是 97),⽽不是数字;
byte的操作单位是⼀个字节,可以理解为⼀个英⽂字符
rune⽤来表⽰Unicode的码点,即⼀个字符
rune的操作单位是⼀个字符,不管这个字符是什么字符
通俗⼀点
byte只能操作简单的字符,不⽀持中⽂操作
rune能操作任何字符
将 Unicode 编码转化为可打印字符
如果你想要将 Unicode 字符编码转化为对应的字符,可以使⽤string函数进⾏转化::
str := "Hello, 世界"
for i, ch := range str {
fmt.Println(i, string(ch))
}
对应的打印结果如下:
0 H
1 e
2 l
3 l
4 o
5 ,
6
7 世
10 界
UTF-8编码不能这样转化,英⽂字符没问题,因为⼀个英⽂字符就是⼀个字节,中⽂字符则会乱码,因为⼀个中⽂字符编码需要三个字节,转化单个字节会出现乱码。

版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。