3.1. 整形

Go 语言同时提供了有符号和无符号类型的整数运算。这里有 int8int16int32int64 四种截然不同大小的有符号整数类型,分别对应 8、16、32、64bit 大小的有符号整数,与此对应的是 uint8uint16uint32uint64 四种无符号整数类型。

这里还有两种一般对应特定 CPU 平台机器字大小的有符号和无符号整数 intuint,其大小会根据当前运行环境来确定。

Unicode 字符 rune 类型是和 int32 等价的类型,通常用于表示一个 Unicode 码点。这两个名称可以互换使用。同样 byte 也是 uint8 类型的等价类型,byte 类型一般用于强调数值是一个原始的数据而不是一个小的整数。

最后,还有一种无符号的整数类型 uintptr,没有指定具体的 bit 大小但是足以容纳指针。

intint32 也是不同的类型,即使 int 的大小也是 32bit,在需要将 int 当作 int32 类型的地方需要一个显式的类型转换操作。

任何大小的整数字面值都可以用以 0 开始的八进制格式书写,例如 0666;或用以 0x 或 0X 开头的十六进制格式书写,例如 0xdeadbeef

fmt 使用技巧

我们能够通过格式化标志的副词来告诉 fmt 要用哪一个参数进行格式化的输出:

1
2
x := int64(0xdeadbeef)
fmt.Printf("%d %[1]x %#[1]x %#[1]X\n", x) // 3735928559 deadbeef 0xdeadbeef 0XDEADBEEF

同时,打印字符时,使用 %q 能够打印带单引号的字符:

1
2
ascii := 'a'
fmt.Printf("%d %[1]c %[1]q\n", ascii) // "97 a 'a'"

3.2. 浮点数

Go 语言提供了两种精度的浮点数,float32float64

浮点数的范围极限值可以在 math 包找到。常量 math.MaxFloat32 表示 float32 能表示的最大数值,大约是 3.4e38;对应的 math.MaxFloat64 常量大约是 1.8e308

3.3. 复数

Go 语言提供了两种精度的复数类型:complex64complex128,分别对应 float32float64 两种浮点数精度。内置的 complex 函数用于构建复数,内建的 realimag 函数分别返回复数的实部和虚部:

1
2
3
4
5
var x complex128 = complex(1, 2) // 1+2i
var y complex128 = complex(3, 4) // 3+4i
fmt.Println(x*y) // "(-5+10i)"
fmt.Println(real(x*y)) // "-5"
fmt.Println(imag(x*y)) // "10"

3.4. 布尔型

布尔值并不会隐式转换为数字值 01,反之亦然。

3.5. 字符串

一个字符串是一个不可改变的字节序列。字符串可以包含任意的数据,包括 byte0,但是通常是用来包含人类可读的文本。文本字符串通常被解释为采用 UTF8 编码的 Unicode 码点(rune)序列。

内置的 len 函数可以返回一个字符串中的字节数目(不是 rune 字符数目),索引操作 s[i] 返回第 i 个字节的字节值。

第 i 个字节并不一定是字符串的第 i 个字符,因为对于非 ASCII 字符的 UTF8 编码会要两个或多个字节。

因为字符串是不可修改的,因此尝试修改字符串内部数据的操作也是被禁止的:

1
2
s = "hello world"
s[0] = 'L' // compile error: cannot assign to s[0]

不变性意味着如果两个字符串共享相同的底层数据的话也是安全的,这使得复制任何长度的字符串代价是低廉的。同样,一个字符串 s 和对应的子字符串切片 s[7:] 的操作也可以安全地共享相同的内存,因此字符串切片操作代价也是低廉的。在这两种情况下都没有必要分配新的内存。

3.5.1. 字符串面值

一个原生的字符串面值形式是 `…`,使用反引号代替双引号。在原生的字符串面值中,没有转义操作;全部的内容都是字面的意思,包含退格和换行,因此一个程序中的原生字符串面值可能跨越多行

3.5.3. UTF-8

UTF8 是一个将 Unicode 码点编码为字节序列的变长编码。UTF8 编码使用 1 到 4 个字节来表示每个 Unicode 码点,每个符号编码后第一个字节的高端 bit 位用于表示编码总共有多少个字节。

Go 语言的源文件采用 UTF8 编码,并且 Go 语言处理 UTF8 编码的文本也很出色。unicode 包提供了诸多处理 rune 字符相关功能的函数(比如区分字母和数字,或者是字母的大写和小写转换等),unicode/utf8 包则提供了用于 rune 字符序列的 UTF8 编码和解码的功能。

得益于 UTF8 编码优良的设计,诸多字符串操作都不需要解码操作,对于 UTF8 编码后文本的处理和原始的字节处理逻辑是一样的。

另一方面,如果我们真的关心每个 Unicode 字符,我们可以使用其它处理方式:

1
2
3
4
5
import "unicode/utf8"

s := "Hello, 世界"
fmt.Println(len(s)) // "13"
fmt.Println(utf8.RuneCountInString(s)) // "9"

Go 语言的 range 循环在处理字符串的时候,会自动隐式解码 UTF8 字符串,但是其对于非 ASCII,索引更新的步长将超过 1 个字节。

3.5.4. 字符串和 Byte 切片

标准库中有四个包对字符串处理尤为重要:bytesstringsstrconvunicode 包。

  • strings 包提供了许多如字符串的查询、替换、比较、截断、拆分和合并等功能。
  • bytes 包也提供了很多类似功能的函数,但是针对和字符串有着相同结构的 []byte 类型。因为字符串是只读的,因此逐步构建字符串会导致很多分配和复制。在这种情况下,使用 bytes.Buffer 类型会更有效。
  • strconv 包提供了布尔型、整型数、浮点数和对应字符串的相互转换,还提供了双引号转义相关的转换。
  • unicode 包提供了 IsDigitIsLetterIsUpperIsLower 等类似功能,它们用于给字符分类。

bytes 包还提供了 Buffer 类型用于字节 slice 的缓存。一个 Buffer 开始是空的,但是随着 stringbyte[]byte 等类型数据的写入可以动态增长。

3.6. 常量

常量表达式的值在编译期计算,而不是在运行期。每种常量的潜在类型都是基础类型:booleanstring 或数字。

一个常量的声明语句定义了常量的名字,和变量的声明语法类似,常量的值不可修改,这样可以防止在运行期被意外或恶意的修改。

1
const pi = 3.14159 // approximately; math.Pi is a better approximation

常量间的所有算术运算、逻辑运算和比较运算的结果也是常量,对常量的类型转换操作或以下函数调用都是返回常量结果:lencaprealimagcomplexunsafe.Sizeof

3.6.1. iota 常量生成器

常量声明可以使用 iota 常量生成器初始化,它用于生成一组以相似规则初始化的常量,但是不用每行都写一遍初始化表达式。在一个 const 声明语句中,在第一个声明的常量所在的行,iota 将会被置为 0,然后在每一个有常量声明的行加一。

1
2
3
4
5
6
7
8
9
10
11
type Weekday int

const (
Sunday Weekday = iota
Monday
Tuesday
Wednesday
Thursday
Friday
Saturday
)

3.6.2. 无类型常量

Go 语言的常量有个不同寻常之处。虽然一个常量可以有任意一个确定的基础类型,例如 intfloat64,或者是类似 time.Duration 这样命名的基础类型,但是许多常量并没有一个明确的基础类型。编译器为这些没有明确基础类型的数字常量提供比基础类型更高精度的算术运算;你可以认为至少有 256bit 的运算精度。这里有六种未明确类型的常量类型,分别是

  • 无类型的布尔型
  • 无类型的整数
  • 无类型的字符
  • 无类型的浮点数
  • 无类型的复数
  • 无类型的字符串