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 }