Go 语言类型 整数型
基本概念
整数型数据(Integer)是计算机中基本数据类型,用来表示没有小数部分的数字。整型可以存储正数、负数以及零。
整数类型
在 Go 语言中,int 型表示有符号整数,而 uint 型表示无符号整数。共有 10 种精确大小的整数类型:
| 类型 | 字节长度 | 取值范围 | 
|---|---|---|
int | 
4 或 8 | 同 int32 或 int64 类型 | 
int8 | 
1 | -128~127 即 -27~(27-1) | 
int16 | 
2 | -32768~32767 即 -215~(215-1) | 
int32 | 
4 | -231~(231-1) | 
int64 | 
8 | -263~(263-1) | 
uint | 
4 或 8 | 同 uint32 或 uint64 类型 | 
uint8 | 
1 | 0~255 即 0~(28-1) | 
uint16 | 
2 | 0~65535 即 0~(216-1) | 
uint32 | 
4 | 0~(232-1) | 
uint64 | 
8 | 0~(264-1) | 
日常开发中直接使用 int 类型即可。
表示方法
整数型可以用四种数制表示:
- 十六进制:前缀为 
0x或0X,后跟十六进制数字 0-9 以及 A-F,不区分大小写。 - 八进制:前缀为 
0,后跟八进制数字 0-7。 - 十进制:前缀为正负号,后跟数字 0-9。正号很少用,但是完全合法。
 - 二进制:前缀为 
0b或0B,后跟数字 0 或 1。Go 语言 1.13 版本以上才支持。 
下面是各种数字表示法示例:
package main
import "fmt"
func main() {
	var decimal int = +42      // 十进制
	var octal int = 052        // 八进制
	var hexadecimal int = 0x2A // 十六进制
	var binary int = 0b101010  // 二进制
	fmt.Println(decimal, octal, hexadecimal, binary)
}
此外,整型部分支持使用指数形式来表示。例如,十进制 1500 可以表示为 1.5e3 或 15e2。只要科学计数法表示的值可以精确地转换为整数,类似于把 1.0 赋值给整数,编译时会自动识别为整数字面量:
package main
import "fmt"
func main() {
	// 不指定类型时为浮点数
	var a = 2e3
	fmt.Printf("%T: %v\n", a, a)
	// 编译成功,把 1.5e3 等于 1500,是个整数。
	var b int = 1.5e3
	fmt.Printf("%T: %v\n", b, b)
}
整数符号
除了十进制可以直接在数字前使用正负号来表示正负,其他数制在语法上仅能定义正数。先跳过二进制补码概念,看用其他数制来表示负数:
package main
import "fmt"
func main() {
	// 先将正数 42 赋给变量
	var hexadecimal int = 0x2A // 十六进制正数
	fmt.Println("Hexadecimal:", hexadecimal) // 输出为:42
	// 对变量使用负号,就得到了 -42
	fmt.Printf("Negative Hexadecimal: %x", -hexadecimal) // 输出为:-2a
}
可以看到在 Go 语言中,用十六进制表示负数,只用将对应正数赋值给变量,然后对变量使用负号来动态地产生负数形式。这个处理方法在编程中非常普遍,不管数值是以哪种数制表示,底层都以二进制形式存储,负数通过补码处理,所有整型都可以使用相同运算符和函数进行操作。
二进制补码用于统一二进制加减运算,而且还能正确表示零。补码计算规则只有两条:
- 正数补码:是其本身二进制表示。
 - 负数补码:是其对应正数二进制表示的反码(每个二进制位取反)加一。
 
下面用一个例子展示推导过程:
- 有符号整型使用二进制的最高位来表示正负。例如 
int8类型,整数表示范围从-128到+127,二进制表示范围从00000000到11111111。最高位为0和最高位为1的数都有 128 个,但00000000被0占据了,所以最高位0代表的正数比最高位1代表的负数少 1 个。 - 从 
00000001到01111111代表正数 1 到 127。 10000000代表 -128,也是 127 的反码。- 对 
10000000加 1 得到10000001,代表 -127,符合负数补码规则。 - 从 
10000000到11111111代表负数 -128 到 -1。 - 计算公式 
127-2转为加法表示等于127+(-2),用二进制表示等于01111111+11111110,结果为101111101。由于int8类型只能取最低 8 位,结果是01111101代表 125,刚好比01111111代表的 127 少 2,结果正确。 - 对 0 应用负数补码规则还是 0,不存在 -0 问题。
 
运算统一不仅使编程模型更为简单,还使得硬件设计大为简化,不需要为减法单独设计电路。
声明和初始化
整数型变量声明和初始化有下面 4 种方法:
package main
import "fmt"
func main() {
	// 声明但不赋值,默认值为 0,用于零值初始化
	var a int8
	// 显式声明同时赋值
	var b int64 = -13
	// 使用短变量声明并初始化,自动推断类型为 int
	c := 30
	// 通过表达式赋值,用于强调类型
	d := uint32(101)
	fmt.Println(a, b, c, d)
}
整数运算
整数型支持算术运算、比较运算和位运算。算术运算和位运算结果还是整型,结果是浮点数时,小数部分会被截断,不进行四舍五入:
package main
import "fmt"
func main() {
	// 整数算术运算
	fmt.Println("加法结果 addition:", 5+3)
	fmt.Println("减法结果 subtraction:", 5-3)
	fmt.Println("乘法结果 multiplication:", 5*3)
	fmt.Println("除法结果 division:", 5/3) // 输出:1
	fmt.Println("模运算结果 modulus:", 5%3) // 输出:2
	// 位运算
	fmt.Println("按位与结果 and:", 5&3)        // 输出:1
	fmt.Println("按位或结果 or:", 5|3)         // 输出:7
	fmt.Println("按位异或结果 xor:", 5^3)       // 输出:6
	fmt.Println("左移结果 shiftLeft:", 5<<1)  // 输出:10
	fmt.Println("右移结果 shiftRight:", 5>>1) // 输出:2
	// 比较运算
	fmt.Println("5 == 3:", 5 == 3) // 输出:false
	fmt.Println("5 != 3:", 5 != 3) // 输出:true
	fmt.Println("5 > 3:", 5 > 3)   // 输出:true
	fmt.Println("5 < 3:", 5 < 3)   // 输出:false
	fmt.Println("5 >= 3:", 5 >= 3) // 输出:true
	fmt.Println("5 <= 3:", 5 <= 3) // 输出:true
}
除法运算中,除数为 0 会引发编译错误或运行时异常:
package main
import "fmt"
func main() {
	// 运行时报错
	a := 0
	fmt.Println(5 / a)
	// 编译报错
	fmt.Println(5 / 0)
}
类型转换
将浮点型转为整型时,要注意小数部分损失:
package main
import "fmt"
func main() {
	floatValue := 3.9
	integerValue := int(floatValue)
	fmt.Println("浮点数转换为整数,截断小数部分,得到:", integerValue) // 输出:3
	// 编译报错,因为字面量 3.9 类型未定,不能用于转换
	integerValue = int(3.9)
}
虽然 int 类型大小等于 int32 或 int64,但它是独立类型,必须显式转换后才能进行互相运算:
package main
import "fmt"
func main() {
	var x int = 100
	var y int32 = 100
	// 报错:mismatched types int and int32
	fmt.Println("x 直接与 y 比较:", x == y)
	// 将 int 转换为 int32,再进行比较运算
	fmt.Println("x 转换为 int32 后与 y 相等:", int32(x) == y)
	// 将 int32 转换为 int,再进行算术运算
	fmt.Println("y 转换为 int 后与 x 相乘:", x*int(y))
}
uint 类型同理。
数值溢出
当整型数值发生溢出时,编译运行均不会报错:
package main
import "fmt"
func main() {
	var a uint8 = 255  // 8 位无符号整数最大值为 255
	fmt.Println(a + 2) // 没有报错,输出为 1。257 对于 uint8 类型来说溢出,计数从 0 开始,导致输出为 1。
}
上面 uint8 类型变量 a 最大只能表示到 255,255 再加 2 等于 257,发生了数值溢出,计数会从 0 开始,编译器会简单地将超出位数抛弃,得到运算结果 1。这种现象有个专业术语叫做整数回绕(wrap around),指当一个整数超过其类型所能表示的最大值时,会从该类型能表示的最小值开始再次计数。
整数回绕是无符号整型的特性,而不是错误。但在现实世界中,发生数值溢出可能导致严重后果,必须阻止。在 math 包中能找到一些常量,对应不同整型类型最大值和最小值,活用它们来检测数值溢出情况:
package main
import (
	"fmt"
	"math"
)
// safeAdd 对两个 int8 类型整数进行加法运算,并检查是否溢出
func safeAdd(a, b int8) (int8, bool) {
	sum := int(a) + int(b) // 首先将操作数转换为更大的整数类型
	if sum > math.MaxInt8 {
		return 0, true // 最大值溢出
	} else if sum < math.MinInt8 {
		return 0, true // 最小值溢出
	}
	return int8(sum), false // 未溢出
}
func main() {
	a, b := int8(127), int8(1)
	result, overflow := safeAdd(a, b)
	if overflow {
		fmt.Println("发生溢出") // 结果 128,大于 127,溢出发生
	} else {
		fmt.Println("运算结果:", result)
	}
}
常量定义为无类型(untyped)时,在编译时被认为具有任意精度,不受基本数据类型限制。当无类型常量与变量进行运算时,常量类型和精度会根据上下文自动调整,结果类型由表达式中其他操作数类型决定,也能有效地避免数值溢出:
package main
import "fmt"
func main() {
	const distance = 24000000000000000000000000 // 定义一个非常大的无类型常量
	time := distance / 299792 / 3600 / 24 / 365 // 除以光速,计算时间
	fmt.Printf("类型为:%T,结果为光年:%v", time, time)   // 输出 int 类型值:2538543415469,在 int 类型范围内没有溢出
}
除了整型,浮点型也会发生数值溢出。浮点型溢出会导致计算结果变为无穷大或负无穷大,处理方式类似整型。
大数类型
math/big 包中提供大数(big number)类型,专门处理超出整型或浮点型大小的数值,以应对高精度计算场景。例如大整数类型 big.Int :
package main
import (
	"fmt"
	"math/big"
)
func main() {
	// 创建两个大整数变量
	firstBigInt, secondBigInt := new(big.Int), new(big.Int)
	// 指定为十进制,必须通过字符串来赋值
	firstBigInt.SetString("12345678901234567890", 10)
	secondBigInt.SetString("98765432109876543210", 10)
	// 执行大整数加法运算
	sum := new(big.Int).Add(firstBigInt, secondBigInt)
	// 执行大整数乘法运算
	product := new(big.Int).Mul(firstBigInt, secondBigInt)
	// 输出结果也是大数类型
	fmt.Println("大整数加法结果:", sum)
	fmt.Println("大整数乘法结果:", product)
	fmt.Printf("运算结果类型为:%T\n", product) // 输出类型:*big.Int
}
此外大数类型还有大浮点数 big.Float 和分数(有理数) big.Rat 类型:
package main
import (
	"fmt"
	"math/big"
)
func main() {
	// 创建大浮点数,指定精度为 100,但依然有误差,只是变得很小
	f1, _ := new(big.Float).SetPrec(100).SetString("5.01")
	f2, _ := new(big.Float).SetPrec(100).SetString("3.10")
	// 大浮点数运算和整型一样
	resultF := new(big.Float).Sub(f1, f2)
	fmt.Println("大浮点数减法结果:", resultF) // 输出 1.910000000000000000000000000003
	// 创建分数 1/3 和 2/3,需要指定分子和分母
	r1 := new(big.Rat).SetFrac(big.NewInt(1), big.NewInt(3))
	r2 := new(big.Rat).SetFrac(big.NewInt(2), big.NewInt(3))
	// 分数除法
	resultR := new(big.Rat).Quo(r1, r2)
	fmt.Println("Result of addition:", resultR) // 输出 1/2
}