在计算机中,整型数据(如整数)是最常见的数据类型之一,它们在计算机内部以 二进制 的形式存储。我们常见的整型数据类型包括 有符号整型(int
)和 无符号整型(unsigned int
),它们在存储方式上有所不同。本文将介绍计算机如何存储这些整型数据,讲解有符号和无符号整型的区别,特别是 int8
的有符号与无符号的区别,并通过实例展示它们之间的转换以及它们的别名和作用。
1. 计算机如何存储整型数据
在计算机中,所有数据都以 二进制 形式存储,整数也不例外。整数的存储方式依赖于其位数和是否有符号。常见的整型数据类型包括:
-
int8
:8 位有符号整数 -
uint8
:8 位无符号整数 -
int16
:16 位有符号整数 -
uint16
:16 位无符号整数 -
int32
:32 位有符号整数 -
uint32
:32 位无符号整数 -
int64
:64 位有符号整数 -
uint64
:64 位无符号整数
这些类型在计算机中都是以 二进制补码 的形式存储,补码 是用来表示负数的一种方式,补码表示的优势是使加法和减法运算更加高效且不容易出错。
有符号整数的存储
在有符号整数(如 int8
, int16
, int32
)的表示中,计算机使用 补码 来存储数据。补码表示法的一个特点是使用 最高位(即符号位)来表示数值的正负:
-
正数:符号位为 0,数值的其余部分直接表示整数的值。
-
负数:符号位为 1,表示负数的补码是通过对其绝对值的二进制表示取反(按位反转)并加 1 得到的。
举个例子,假设我们用 8 位二进制来表示一个整数:
-
+5
的二进制表示:00000101
(最高位是 0,表示正数) -
-5
的二进制表示:11111011
(首先表示 5 的二进制00000101
,然后按位取反11111010
,最后加 1 得到11111011
)
无符号整数的存储
无符号整数(如 uint8
, uint16
, uint32
)没有符号位,所有位都用来表示数值本身。所以它们能表示更大的正整数,但不能表示负数。无符号整数的范围从 0 开始,直到该位数的最大值。
-
uint8
的范围:0 到 255(2^8 - 1
) -
uint16
的范围:0 到 65535(2^16 - 1
)
2. 有符号整型 int
和 无符号整型 uint
的区别
有符号整数(int
)和无符号整数(uint
)之间的区别主要在于 符号位 的存在。简单来说:
-
有符号整数:可以表示正数、负数和零。其表示范围比无符号整数小,因为最高位作为符号位使用。
-
无符号整数:只能表示正数和零。由于没有符号位,它能表示更大的正整数。
例子:int8
与 uint8
-
int8
(有符号 8 位整数) 的范围是 -128 到 127:var a int8 = -128 var b int8 = 127
-
uint8
(无符号 8 位整数) 的范围是 0 到 255:var a uint8 = 0 var b uint8 = 255
转换示例:
如果你将 int8
的值转换为 uint8
,它会以 补码的方式 进行转换。由于 uint8
无法表示负数,因此转换会出现意外结果。
var i int8 = -1
fmt.Println(uint8(i)) // 输出:255(因为 -1 对应的二进制补码是 11111111,即 255)
反之,如果将 uint8
转换为 int8
,会直接截取低 8 位,并解释为有符号整数:
var u uint8 = 255
fmt.Println(int8(u)) // 输出:-1(255 在补码表示中是 -1)
例子:int32
与 uint32
类似地,int32
和 uint32
的区别也在于它们的符号位:
-
int32
的范围是 -2,147,483,648 到 2,147,483,647 -
uint32
的范围是 0 到 4,294,967,295
var a int32 = -12345
var b uint32 = 12345
fmt.Println(a, b) // 输出:-12345 12345
转换示例:
var i int32 = -1
fmt.Println(uint32(i)) // 输出:4294967295(-1 在 `uint32` 中表示为 4294967295)
3. int8
的有符号与无符号的区别
int8
与 uint8
的区别:
-
int8
:表示的是有符号 8 位整数,能够表示从 -128 到 127 之间的值。 -
uint8
:表示的是无符号 8 位整数,能够表示从 0 到 255 之间的值。
实例展示:
var signed int8 = -100 // 有符号整数
var unsigned uint8 = 156 // 无符号整数
fmt.Println(signed, unsigned) // 输出:-100 156
转换示例:
var a int8 = -100
fmt.Println(uint8(a)) // 输出:156(-100 转为无符号整数时会得到 156)
别名和应用场景
在 Go 语言中,某些类型有别名:
-
byte
是uint8
的别名,通常用于表示一个字节,常用于字节数组和字符串的存储。 -
rune
是int32
的别名,通常用于表示一个 Unicode 字符。在处理字符或文本时,rune
可以存储所有 Unicode 字符,而不仅限于 ASCII 字符。
1. byte
的作用
byte
是 uint8
的别名,通常用于表示 原始字节。它通常用于字符串、文件 I/O 或者网络数据等场景:
var b byte = 65 // 'A' 的 ASCII 码
fmt.Println(string(b)) // 输出:A
2. rune
的作用
rune
是 int32
的别名,用于表示 Unicode 字符。在 Go 中,字符串实际上是由 rune
类型的值组成,每个 rune
代表一个字符。
var r rune = 'A' // 'A' 是 Unicode 码点 65
fmt.Println(r) // 输出:65(Unicode 码点)
fmt.Println(string(r)) // 输出:A
总结
-
int8
和uint8
之间的主要区别在于符号位:int8
能表示负数,而uint8
只能表示正数和零。int8
的范围是 -128 到 127,而uint8
的范围是 0 到 255。 -
byte
和rune
是 Go 中的别名,分别代表无符号字节(uint8
)和 Unicode 字符(int32
)。 -
转换时,
int8
和uint8
之间的转换会导致不符合预期的结果(如负数转换为大正数),这与计算机的补码表示法有关。