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
}