Go 的数据类型分四大类:基础类型,聚合类型,引用类型和接口类型。
整数
Go 同时具备有符号整数和无符号整数。有符号整数分四种大小:8位、16位、32位、64位,用 int8、int16、int32、int64 表示,对应的无符号整数是 uint8、uint16、uint32、uint64。
有符号整数以补码表示,保留最高位作为符号位。
在移位运算 x << n 和 x >> n 中,操作数 n 决定位移量,而且 n 必须为无符号型;操作数x 可以是有符号型也可以是无符号型。算术上,左移运算 x<<n 等价于 x 乘以 2^n;而右移运算 x>>n 等价于x 与其相除,向下取整。
- len 函数
在下面的示例中,循环中使用的 len 函数,它返回有符号整数
medals := []string{"gold", "silver", "bronze"}
for i := len(medals); i >= 0; i-- {
fmt.Println(medals[i])
}
相反,假若len 返回的结果是无符号整数,就会导致严重的错误,因为 i 也会成为 uint 型,根据定义,条件 i >= 0 将恒成立。第3轮迭代后,有 i == 0,语句 i-- 使得i 成为 uint 型的最大值(例如,可能为 2^64 - 1),而非 -1 ,导致 medals[i]试图越界访问元素,超出 slice 范围,引发运行失败而宕机。
- fmt 函数输出技巧
在使用 fmt 进行输出的时候,通常 Printf 的格式化字符串含有多个 % 谓词,这要求提供相同数目的操作数,而 % 后的副词 [1] 告知 Printf 重复使用第一个操作数。其次,%o、%x 或 %X 之前的副词 # 告知 Printf 输出相应的前缀0、0x、0X。
用 %c 输出文字符号,如果希望输出带有单引号则使用 %q。
o := 0666
fmt.Printf("%d %[1]o %#[1]o\n", o) // 438 666 0666
x := int64(0xdeadbeef)
fmt.Printf("%d %[1]x %#[1]x %#[1]X\n", x)
// 3735928559 deadbeef 0xdeadbeef 0XDEADBEEF
字符串
字符串操作如下:
s := "hello world"
fmt.Println(s[:5])
fmt.Println(s[7:])
fmt.Println(s[:])
//hello
//world
//hello world
字符串具有不可变性,则其意味着两个字符串能安全地公用同一段底层内存,使得复制任何长度的字符串的开销都很低廉。类似地,字符串s 及其子串(如 s[7:])可以安全地共用数据,因此子串生成操作的开销低廉。这两种操作都没有分配新内存。
字符串操作例子:
func comma(s string) string {
n := len(s)
if n <= 3 {
return s
}
return comma(s[: n - 3]) + "," + comma(s[n - 3:])
}
由于字符串不可变,因此按增量方式构建字符串会导致多次内存分配和复制。这种情况使用 bytes.Buffer 类型会更高效。
常量
常量是一种表达式,其可以保证在编译阶段就计算出表达式的值,并不需要等到运行时,从而使编译器得以知晓其值。所有常量本质上都属于基本类型:布尔型、字符串或数字。
常量的声明可以使用常量生成器 iota,它创建一系列相关值,而不是逐个值显式写出。常量声明中, iota 从0开始取值,逐项加1.
const (
_ = 1 << (10 * iota)
KiB // 1024
MiB // 1048576
GiB // 1073741824
TiB // 1099511627776
PiB // 1125899906842624
EiB // 1152921504606846976
ZiB // 1180591620717411303424
YiB // 1208925819614629174706176
)
编译器将从属类型待定的常量表示成某些值,这些值比基本类型的数字精度更高,且算术精度高于原生的机器精度,可以认为它们的精度至少达到256位。从属类型待定的常量共有6种,分别是无类型布尔、无类型整数、无类型文字符号、无类型浮点数、无类型复数、无类型字符串。
借助推迟确定从属类型,无类型常量不仅能暂时维持更高的精度,与类型已确定的常量相比,它们还能写进更多表达式而无需转换类型。比如,上例中 ZiB 和 YiB 的值过大,用哪种整型都无法存储,且它们都是合法常量并且可以用在下面的表达式中:
fmt.Println(YiB/ZiB) // "1024"
根据除法运算中操作数的类型,除法运算的结果可能是整型或浮点型。所以,常量除法表达式中,操作数选择不同的字面写法会影响结果:
var f float64 = 212
fmt.Println((f - 32) * 5 /9) // 100; (f - 32) * 5 的结果是 float64 型
fmt.Println(5 / 9 * (f - 32)) // 0; 5/9 的结果是无类型整数, 0
fmt.Println( 5.0 / 9.0 * (f - 32)) // 100; 5.0/9.0 的结果是无类型浮点数
只有常量才可以是无类型的。若将无类型常量声明为变量(如下面的第一条语句所示),或在类型明确的变量赋值的右方出现无类型常量(如下面的其他三条语句所示),则常量会被隐式转换成该变量的类型。
var f float64 = 3 + 0i // 无类型复数 -> float64
f = 2 // 无类型整数 -> float64
f = 1e123 // 无类型浮点数 -> float64
f = 'a' // 无类型 -> float64
Go 存在各类型的不对对称性:无类型整数可以转换为 int,其大小不确定,但无类型浮点数和无类型复数被转换成大小确定的 float64 和 complex128。Go 中,只有大小不确定的int 类型,却不存在大小不确定的 float 类型和 complex 类型。