机器级程序将内存视为一个非常大的字节数组,称为虚拟内存(virtual memory)。内存的每个字节都由一个唯一的数字来标识,称为它的地址(address),所有可能地址的集合就称为虚拟地址空间(virtual memory space)。
所以,从内存管理上看,编程语言中的数据类型表示的是存储的何种类型的数据,就是一个类型占用的内存大小。也就是说,数据类型的出现是为了把数据分成所需内存大小不同的数据
在程序中,我们通过指定其类型,能实现以特定字节数为单位来进行读写。
Go语言提供了丰富的数据组织形式,其数据类型可以分为四类:基础类型、复合类型、引用类型和接口类型。这里,我们将探索最基础的一类—基础数据类型。
总的来说,基本的数据类型有如下几种:
数字类型
Go语言同时提供了有符号和无符号类型的整数运算。这里有 int8
、int16
、int32
和 int64
四种截然不同大小的有符号整数类型,分别对应8、16、32、64 bit 大小的有符号整数,与此对应的是uint8
、uint16
、uint32
和 uint64
四种无符号整数类型。
示例:
从上面的代码,我们可以看到,其实int
和 int64
是等价的。而且,从上面可以看出,int
是占用了 8 个byte(字节) 的,我们知道:
1 Byte(字节) = 8 b(bit)
1 KB = 1024 B
1 MB = 1024 KB
1 GB = 1024 MB
所以,我们可以计算出 int
的取值范围。在此之前,我们需要补充一些知识:
无符号数的编码
假设有一个整数数据类型有
w
w
w 位,我们将位向量写成
x
⃗
\vec{x}
x ,表示整个向量,或写成
[
x
w
−
1
,
x
w
−
2
,
.
.
.
,
x
0
]
[x_{w-1},x_{w-2},...,x_0]
[xw−1,xw−2,...,x0] ,表示向量中的每一位。把
x
⃗
\vec{x}
x看成一个二进制表示的数,就获得了
x
⃗
\vec{x}
x 的无符号表示。由于是无符号,则不需要考虑负数的情况,于是有,对二进制向量:
x
⃗
=
[
x
w
−
1
,
x
w
−
2
,
.
.
.
,
x
0
]
\vec{x} = [x_{w-1},x_{w-2},...,x_0]
x=[xw−1,xw−2,...,x0]
有:
B
2
T
w
(
x
⃗
)
=
∑
i
=
0
w
−
1
x
i
2
i
B2T_w(\vec x) = \sum_{i=0}^{w-1} x_{i}2^i
B2Tw(x)=i=0∑w−1xi2i
举个例子:
B
2
T
4
(
[
0101
]
)
=
0
⋅
2
3
+
1
⋅
2
2
+
0
⋅
2
1
+
1
⋅
2
0
=
0
+
4
+
0
+
1
=
5
B
2
T
4
(
[
1011
]
)
=
1
⋅
2
3
+
0
⋅
2
2
+
1
⋅
2
1
+
1
⋅
2
0
=
8
+
0
+
2
+
1
=
11
\begin{aligned} B2T_4([0101]) &= 0\cdot2^3 + 1 \cdot2^2 + 0\cdot2^1 + 1\cdot2^0 = 0 + 4 + 0 + 1 = 5 \\ B2T_4([1011]) &= 1\cdot2^3 + 0 \cdot2^2 + 1\cdot2^1 + 1\cdot2^0 = 8 + 0 + 2 + 1 = 11 \end{aligned}
B2T4([0101])B2T4([1011])=0⋅23+1⋅22+0⋅21+1⋅20=0+4+0+1=5=1⋅23+0⋅22+1⋅21+1⋅20=8+0+2+1=11
补码编码
在许多应用中,我们希望表示负数值。最常见的有符号的计算表示方式就是 补码 形式。在这个定义中,将字的最高位解释为 负权(negative weight):
对二进制向量:
x
⃗
=
[
x
w
−
1
,
x
w
−
2
,
.
.
.
,
x
0
]
\vec{x} = [x_{w-1},x_{w-2},...,x_0]
x=[xw−1,xw−2,...,x0]
有:
B
2
T
w
(
x
⃗
)
=
−
x
w
−
1
2
w
−
1
+
∑
i
=
0
w
−
2
x
i
2
i
B2T_w(\vec x) = -x_{w-1}2^{w-1} + \sum_{i=0}^{w-2} x_{i}2^i
B2Tw(x)=−xw−12w−1+i=0∑w−2xi2i
其中,最高有效位
w
−
1
w-1
w−1 也称为 符号位,当值为 1 时,表示负值,当值为 0 时,表示非负。我们可以看看例子:
B
2
T
4
(
[
0101
]
)
=
−
0
⋅
2
3
+
1
⋅
2
2
+
0
⋅
2
1
+
1
⋅
2
0
=
0
+
4
+
0
+
1
=
5
B
2
T
4
(
[
1011
]
)
=
−
1
⋅
2
3
+
0
⋅
2
2
+
1
⋅
2
1
+
1
⋅
2
0
=
−
8
+
0
+
2
+
1
=
−
5
\begin{aligned} B2T_4([0101]) &= -0\cdot2^3 + 1 \cdot2^2 + 0\cdot2^1 + 1\cdot2^0 = 0 + 4 + 0 + 1 = 5 \\ B2T_4([1011]) &= -1\cdot2^3 + 0 \cdot2^2 + 1\cdot2^1 + 1\cdot2^0 = -8 + 0 + 2 + 1 = -5 \end{aligned}
B2T4([0101])B2T4([1011])=−0⋅23+1⋅22+0⋅21+1⋅20=0+4+0+1=5=−1⋅23+0⋅22+1⋅21+1⋅20=−8+0+2+1=−5
取值范围
有了上面的知识,我们就可以求出这些基础整数值的取值范围了。以 int8
为例子:
int8 占有 1 Byte,在二进制中是 8 bit
由于是有符号整数,在二进制中,在高位使用 0 或 1 表示正负号: 0--正号,1--负号
于是,最大数为:0 1 1 1 1 1 1 1
根据上面的补码知识,我们可以计算出:
B2T([01111111]) = -0*2^7 + 1*2^6 + 1*2^5 + ... + 1*2^0 = 127
B2T([10000000]) = -1*2^7 + 0*2^6 + 0*2^5 + ... + 0*2^0 = -128
验证如下:
func main() {
var x int8 = 1
fmt.Printf("占用字节: %d\n", unsafe.Sizeof(x)) //占用字节: 1
fmt.Printf("最小值: %d,最大值: %d\n", math.MinInt8, math.MaxInt8) //最小值: -128,最大值: 127
}
其他的数值也如是计算即可:
go 数据类型 | 最小值 | 最大值 |
---|---|---|
int/int64 | -9223372036854775808 | 9223372036854775807 |
int8 | -128 | 127 |
int16 | -32768 | 32767 |
int32 | -2147483648 | 2147483647 |
uint8 | 0 | 255 |
uint16 | 0 | 65535 |
uint32 | 0 | 4294967295 |
uint64 | 0 | 18446744073709551615 |
浮点数
关于浮点数,本文还没有进行深入的探究,可以参考其他人的文章描述:Golang 笔记之深入浮点数
探究其取值范围:
func main() {
fmt.Println("双精度浮点数表示正数的最小值是:", math.SmallestNonzeroFloat64) //5e-324
fmt.Println("双精度浮点数表示正数的最大值是:", math.MaxFloat64) //1.7976931348623157e+308
}
即,双精度浮点数 float64
的取值范围是:
正
数
范
围
:
5
×
1
0
−
324
∼
1.7976931348623157
×
1
0
+
308
负
数
范
围
:
−
1.7976931348623157
×
1
0
308
∼
−
5
×
1
0
−
324
\begin{aligned} 正数范围 &: 5 \times 10^{-324} \sim 1.7976931348623157 \times 10^{+308} \\ 负数范围 &: -1.7976931348623157 \times 10^{308} \sim -5 \times 10^{-324} \end{aligned}
正数范围负数范围:5×10−324∼1.7976931348623157×10+308:−1.7976931348623157×10308∼−5×10−324
单精度浮点数 float32
的取值范围是:
正
数
范
围
:
1.401298464324817
×
1
0
−
45
∼
3.4028234663852886
×
1
0
38
负
数
范
围
:
−
3.4028234663852886
×
1
0
38
∼
−
1.401298464324817
×
1
0
−
45
\begin{aligned} 正数范围 &: 1.401298464324817\times 10^{-45} \sim 3.4028234663852886\times 10^{38} \\ 负数范围 &: -3.4028234663852886\times 10^{38} \sim -1.401298464324817\times 10^{-45} \end{aligned}
正数范围负数范围:1.401298464324817×10−45∼3.4028234663852886×1038:−3.4028234663852886×1038∼−1.401298464324817×10−45
由此,我们可以再次验证,计算机表示浮点数是这样的:
±
m
×
n
e
\pm m \times n^{e}
±m×ne
字符类型
字符串类似,即string
,其实是一个不可改变的字节序列。字符串可以包含任意的数据,包括byte值0,但是通常是用来包含人类可读的文本。文本字符串通常被解释为采用 UTF8 编码的Unicode码点(rune)序列,可以参考这个链接,查看字符编码对照。
所以,我们这里重点讨论 string
、byte
和 rune
三者的关系。更深入的讨论,我打算另外开一篇来重点讨论。
我们先看下面一段代码:
func main() {
var str string = "hello, world"
var strByte = [...]byte{
'h', 'e', 'l', 'l', 'o', ',', ' ', 'w', 'o', 'r', 'l', 'd',
}
var strRune = [...]rune{
'h', 'e', 'l', 'l', 'o', ',', ' ', 'w', 'o', 'r', 'l', 'd',
}
fmt.Println([]byte(str))
fmt.Println(strByte)
fmt.Println(strRune) //三者相等
// [104 101 108 108 111 44 32 119 111 114 108 100],这是Unicode编码10进制的表示
}
我们看到,string
是采用 UTF8 编码的,其实本质上还是一个数字。
布尔型
一个布尔类型的值只有两种:true
和 false
使用代码:
获知,占用了 1 个 byte
。
虚数
Go语言提供了两种精度的复数类型:complex64s和complex128,分别对应float32和float64两种浮点数精度。内置的complex函数用于构建复数,内建的real和imag函数分别返回复数的实部和虚部: