$ ./goBible --type=书籍 --name=Go 语言圣经(二)--chapter=Basic Data Type
复制代码

字符串编码相关章节在 #2.4。

(不得不说,go 看源码真的太方便了)

1. number(integer/float/complex)

1.1 类型

32 << (^uint(0) >> 63)src/strconv/atoi.gosrc/builtin/builtin.go

1.2 计算

  1. % 计算结果的符号和 % 左边的值保持一致。-5%3 == -2, -5%-3 == -2
  2. / 计算结果是整数还是小数要看两边有没有小数,只要有一方是小数那就是小数。小数的情况下,类型为 float64.
  3. 这里要提一下,如果要使用 float,就请使用 float64 而不是 float32,后者经常会出各种奇怪的错误。

1.3 unsigned 和 signed

var uint32 length = getLength()
for i := length; i >= 0; i-- {
    // do sth
}
复制代码

1.4 浮点数

  1. 把 float 类型转为 int 类型,相当于 floor
  2. float 类型有几个特殊值,0, -0, Inf, -Inf,NaN。

1.5 复数

go 在 builtin.go 里提供了 complex 函数,可以用于创建 complex number。结果到底是 complex64 还是 complex128 看赋值表达式(complex64 和 complex128 分别对应 float32 和 float64):

var a complex64 = complex(1, 2) // a 为 complex64 类型
var b complex128 = complex(1, 2) // b 为 complex128 类型
复制代码

或者使用字面量:

a := 1 + 2i
复制代码

你猜一下这个 a 是什么类型,64 还是 128? 答案是 128,原因也很简单,因为 128 对应的是 float64,而 float 类型默认是 float64。

2. 字符串

2.1 在 golang 里,字符串是 byte 构成的,不是 char 构成的。

s[i]

2.1 字符串的各种取值方式,以及一些基础操作

s[i]string(s[i])s[0:3]s[i:j]substrings := "human""h"s[0]s[0:1]s := "人类""人"s[0:3]+

2.2 字符串不可变

s[i]

字符串不可变,不代表不能共享内存,正相反,不可变性让字符串能够安全地共享内存。

s := "human"
t := s[0:3]
// t 和 s 共享了前三个字符的内存。但是由于无论如何你都改变不了这块内存,因此是安全的。
// 如果你改变了 t,那么 t 用的也是新的内存(或者共享其他字符串的内存)
// s 的前三个字符的内存并不会收到影响。
复制代码

2.3 literal

\n

2.4 编码知识

2.4.1 ASCII

在很久以前,计算机编码使用 ASCII 来表示一切。但是只能用来表达英文,无法表达其他语言,以及各种符号。 ASCII 占用 7 bits,也就是 128 个不同字符。

2.4.2 Unicode

于是出现了 Unicode,或者说 UTF-32,或者 UCS-4。
在 GO 中,Unicode 就是 rune,或者说 int32。
但是 UTF-32 也有缺陷,就是无论什么字符都是 32 bits/4 bytes,即使是只占一个 byte 的英文字符也是。而实际上,现实情况是,目前为止世界上的所有字符,也不过 2 bytes。

在 Go 中,单引号括起来的字符就是 rune 类型。写法类似于其他语言中的 char 类型,但是不同的是 rune 是四个字节的 unicode。

2.4.3 UTF-8

于是出现了 UTF-8。UTF-8 是对 Unicode 再次编码(所以说你看,多加一层可以解决一切问题)。UTF-8 是一个变长的编码方式。对于 ASCII 字符,就只用一个 byte 来表示。对于 1 个 byte 不够的,就用多个 byte 来表示。
这也正是 Go 选择的字符串编码方式。

双引号括起来的,也就是字符串,内部使用的是 UTF-8 编码。不过使用 range 会自动解码成 UTF-32 就是了。使用 fori 循环可以遍历 UTF-8 的 bytes slice。

2.5 特殊的 range

如果使用 range ,range 会为我们自动解码,把 UTF-8 解码成 UTF-32 格式,这样就不用担心每个字符占用多少个字节,要怎么改变 index 了。比如:

s := "Hi, 人类"
for i, r := range s {
    fmt.Println(i, r, string(r))
}
// 输出
// 0 72 H
// 1 105 i
// 2 44 ,
// 3 32  
// 4 20154 人
// 7 31867 类
复制代码

注意最后一个的 index,可以看到直接跳了 3 个,因为人占了 3 个 bytes。所以类从 7 开始。
当然,r 是 rune 类型,所以想要看字符串还得使用 string 转换,或者 Printf 格式化。

2.5.1 使用 range 得到字符串的显示长度

之前提到过 len 计算的是 bytes 数量。因此这里借用 range 的特性,计算字符串实际展示出来的长度。

length := 0
for _, _ := range s {
    length++
}
复制代码

或者可以使用 range 的特殊语法,当不需要初始化任何变量的时候可以这样写:

length := 0
for range s {
    length++
}
复制代码
utf8.RuneCountInString()

2.5.2 解码失败

\uFFFD
[]rune

show my code:

s := "东方"

r := []rune(s) // 解码后变成 []rune
复制代码
[]byte[]rune

2.7 int 类型转 string 的坑

i := 65; s := string(i)"65""A"

如果想要转化成字符串,还是乖乖用 itoa。

小测试:为什么 string() 不直接转换为 byte 类型?而是要这么麻烦先转 unicode,再 utf-8?

[]byte[]byte[]byte
[]byte

操作 string 有很多种方式,也有很多库来帮我们完成。
其中有四个库特别重要,分别是 string,bytes,unicode,strconv。

2.8.1 strings 库

string 库顾名思义,是对 string 进行各种操作的库。基本上该有的都有了。

2.8.2 bytes 库

[]byte[]byte
[]bytestring[]byte

2.8.3 strconv 库

由于 string() 类型转换的坑,go 提供了这个库,用于把各种类型转化成 string,或者反过来。比如说大名鼎鼎的 itoa 和 atoi。此外还提供了加引号和去引号的函数。

  • Itoa 只是对 FormatInt 的一个包装。FormatInt 第一个参数得是 int64 格式,Itoa 会进行一次类型转换;第二个参数是 base。
  • Atoi 稍微有点不一样,虽然也有对 ParseInt 的包装,但是对于一些比较小的数字的字符串(和 intSize 有关【intSize 在本文第一节里讲过】,32 位小于 10,64 位小于 19),会直接在 Atoi 里进行转换。如果比较长的话,Atoi 才会调用 ParseInt。
    • image.png

2.8.4 unicode 库

unicode 是对 rune 进行操作的库。提供了很多类似于 IsDigit,IsUpper 这种函数(很不幸,只能判断一个,所以想要判断一整个字符串就要在 for 循环里一个一个判断。好在 range 会帮我们自动解码成 rune,节省了一些解码的工作量)。

func isLetterString(s string) bool {
	for _, r := range s {
		if !unicode.IsLetter(r) {
			return false
		}
	}
	return true
}
复制代码

2.8.5 bytes.Buffer

[]byte
var buf bytes.Buffer // 无需初始化,此时一个空的 buf 就可以使用了
buf.WriteRune('A')
fmt.Fprintf(&buf, "%d", byte(65))
// 输出 "A65"

// fmt.Fprintf 也可以改为使用 writeString
// 看了下源代码,Fprintf 内部也使用了 WriteString,那不如直接用 WriteString
buf.WriteString(strconv.FormatInt(65, 10))
复制代码
3. 常量

3.1 iota

用法就不说了,大家都知道。

不是 itoa,是 iota。
iota 是一个希腊字母,是最小的字母。

3.2 untyped constansts

常量虽然必须有值,但是可以不写类型(虽然大概的类型会被推断出来,如下面的例子会被推断出是一个 integer,但是不会被当做 int,int32,int64 等中的任何一个)

const a = 27891723897213897219837219
const b = 123213213213213213213
复制代码

3.2.1 untyped constanst 的计算精度

a/ba/b

3.2.2 untyped constanst 的类型

untyped constanst 可以被当做这个大的类型中的所有小的类型来使用。

var f = 0.0ff = getFloat32(); f = 0.0

3.2.3 当给变量赋值的时候,右边的其实就是 untyped constant

比如说:

var a = 0 // 右边这个 0 其实就是 untyped int,赋值的时候被自动转成 int 类型。
var b = 0.0 // 右边这个 0.0 是 untyped float,赋值的时候被自动转成 float64 类型
复制代码