2 变量与数
变量定义
前面提到 Go 是静态类型语言:变量的类型必须显式定义或者赋初值以自动推断;类型与变量绑定,对于已定义的变量,其类型不能变动。
变量有几种定义方式:
var foo int // 无初值,指定类型
var foo int = 3 // 有初值,指定类型
var foo = 3 // 有初值,类型自动推断
foo := 3 // 有初值,类型自动推断这里 var 是定义变量用的关键字,foo 是变量名,int 是类型。如果直接赋初值,也可以略去类型不写,直接自动推断。整数会默认推断为 int。
:= 是 Go 提供的定义变量用的缩写符号。算是其简洁性的一个代表了。
在 JS 中,如果定义变量却不赋初值,则初值为 undefined。Go 的做法则是:如果你不写初值,它帮你赋了。不同类型会有自己的默认初值,例如,int 型的默认初值为 0。
如果要定义常量,则把 var 换成 const。常量必须有初值。
const bar int = 8
const bar = 8至于变量的作用域、:= 的限制等等,我们稍后对 Go 更熟悉一些之后再讨论。
整型
刚才提到了整型 int,Go 中有以下几种整型:
- 有符号整型:
int、int8、int16、int32、int64 - 无符号整型:
uint、uint8、uint16、uint32、uint64
int 与 uint 的长度取决于当前平台。对于带数字的整型,那个数字就是整型所占的二进制位数,例如 int16 就是 16 个二进制位,取值范围为
也就是说相比于 C / C++ 中 short、long、long long 这些长度全部由平台决定,Go 中只有 int、uint 的长度由平台决定,其余整型均指明长度。
整型的默认初值均为 0。
整数的存储与运算这里和 C / C++ 是一致的,负数以补码形式存储,运行时溢出不视为错误。只有在写字面量赋值的时候如果溢出,编译器会报错。
var foo int16 = 32768 // 编译期报溢出错误 (overflow)
var bar int16 = 32767
bar++ // 运行时不报错
fmt.Println(bar) // -32768这里附上一份各整型取值范围表。基本上见多了之后都记住了。
| 长度 | 有符号范围 | 无符号范围 |
|---|---|---|
| 8 位 | ||
| 16 位 | ||
| 32 位 | ||
| 64 位 |
浮点
Go 中提供 IEEE 754 的单精度和双精度浮点:float32 和 float64。注意没有不带数字的 float。两种浮点的默认初值均为 0。
其中 float64 就是 JS 中日常使用的数字类型,8 字节浮点中 1 位符号位、11 位阶码、52 位尾数。float32 则是 4 字节,1 位符号位、8 位阶码和 23 位尾数。
所以换算到十进制,它们的精度分别是
float32:,十进制下保证 7 位有效数字可靠 float64:,十进制下保证 15 位有效数字可靠
JS 中有浮点导致的最大安全整数
关于浮点数长度、精度、数值安全性这一部分的详细推导,可以看我此前的笔记:JS 中的数值存储与安全。
Go 中浮点运算法则与 IEEE 754 中的一致。非零数除以零会产生 +Inf 和 -Inf,零除以零会产生 NaN。但是要注意,Go 中没有 Inf 与 NaN 字面量,需要通过 math 包获取:
import "math"
math.NaN() // NaN
math.Inf(1) // +Inf
math.Inf(-1) // -Inf需要注意的点
整型除法
不同于 JS 只有浮点的世界,整型的除法与浮点有不同:
- 整型除法的结果是整数,结果为向零截断的商;
- 取模使用
%符号,结果也是整数; - 除以零会产生运行时错误!而不是产生
Inf或NaN而不报错。
除此之外,Go 没有在浮点数上定义模运算!例如下面的操作会产生错误:
foo := 3.0
bar := 2.0
result := foo % bar // Err: operator % not defined不允许跨类型运算
Go 不允许跨类型运算,这是其类型安全性的一个体现。例如下面的操作会产生错误:
floatNum := 10.0
intNum := 5
result := floatNum / intNum // Err: mismatched types即使都是整型,也不允许直接运算:
var foo int32 = 1
var bar int64 = 2
result := foo + bar // Err: mismatched types正确的做法是执行类型转换,语法是和 C 系类似的 Type(value):
floatNum := 10.0
intNum := 5
result := floatNum / float64(intNum)禁止跨类型运算能让数值语义保持明确,能解决很多问题,例如
- 不会误把数字和字符串
+在一起 - 避免不同精度的数混用导致精度丢失
- 不会因为隐式类型转换产生难以发现的逻辑问题
+0 与 -0
如果在 Go 中尝试定义 -0 字面量:
negZero := -0.0编译器一般会提示 -0.0 字面量与 0.0 等价:in Go, the floating-point literal '-0.0' is the same as '0.0', it does not produce a negative zero。
但既然遵循 IEEE 754,那就必然会有 +0 与 -0 两兄弟。尽管不能字面定义,在运算过程中仍然会产生:
zero := 0.0
negZero := -zero // -0
negZero2 := -1 / (1 / zero) // -0同样地,根据 IEEE 754,+0 和 -0 是相等的:
zero := 0.0
fmt.Println(zero == -zero) // true如果需要「就地」产生 -0,需要使用 math 包:
negZero := math.Copysign(0, -1.0)划重点
- 类型举足轻重
- 未赋初值的会按类型自动赋初值
- 变量的类型始终不变
- 类型决定了能完成什么运算
- 不同类型值严格区分,不允许混合运算
- 整型和浮点的心智模型不同于 JS 只有浮点
- 需要多大的整数(8、16、32、64,有无符号)可以自己定义
- 除以零会产生错误
- 浮点数不能模(
%)