Go 支持的数据类型
Go 语言内置对以下这些基本数据类型的支持:
- 布尔类型:bool
- 整型:int8、byte、int16、int、uint、uintptr 等
- 浮点类型:float32、float64
- 复数类型:complex64、complex128
- 字符串:string
- 字符类型:rune
- 错误类型:error
此外,Go 语言还支持以下这些复合类型:
- 指针(pointer)
- 数组(array)
- 切片(slice)
- 字典(map)
- 通道(chan)
- 结构体(struct)
- 接口(interface)
与其他静态语言不同的是,Go 新增了一个通道类型,该类型主要用于并发编程时不同协程之间的通信,后面介绍 Go 语言并发编程的时候会详细介绍它。
结构体类似于面向对象编程语言中的类(class),Go 沿用了 C 语言的这一复合类型,而没有像传统面向对象编程那样引入单独的类概念,Go 语言还把接口单独作为一个类型提出来,后面介绍 Go 语言面向对象编程的时候会详细介绍这两个类型的使用。
下面我们就来逐一介绍这些数据类型。
布尔类型
Go 语言中的布尔类型与其他主流编程语言差不多,类型关键字为 bool
,可赋值且只可以赋值为预定义常量 true
和 false
。示例代码如下:
var v1 bool
v1 = true
v2 := (1 == 2) // v2 也会被推导为 bool 类型
Go 是强类型语言,变量类型一旦确定,就不能将其他类型的值赋值给该变量,因此,布尔类型不能接受其他类型的赋值,也不支持自动或强制的类型转换。以下的示例是一些错误的用法,会导致编译错误:
代码语言:javascript复制var b bool
b = 1 // 编译错误
b = bool(1) // 编译错误
不过通过表达式计算得到的布尔类型结果可以赋值给 Go 布尔类型变量:
代码语言:javascript复制var b bool
b = (1!=0) // 编译正确
fmt.Println("Result:", b) // 打印结果为Result: true
此外,由于强类型的缘故,Go 语言在进行布尔值真假判断时,对值的类型有严格限制,在 PHP 这种弱类型语言中,以下这些值在进行布尔值判断的时候(使用非严格的 ==
比较符)都会被认为是 false
(JavaScript、Python 也类似):
- 布尔值
FALSE
本身 - 整型值
0
(零) - 浮点型值
0.0
(零) - 空字符串,以及字符串 "0"
- 不包括任何元素的数组
- 特殊类型 NULL(包括尚未赋值的变量)
- 从空标记生成的 SimpleXML 对象
而在 Go 语言中则不然,不同类型的值不能使用 ==
或 !=
运算符进行比较,在编译期就会报错,比如下面这段代码:
b := (false == 0);
在编译的时候就会报如下错误:
代码语言:javascript复制cannot convert 0 (type untyped number) to type bool
invalid operation: false == 0 (mismatched types bool and int)
同样,!
运算符也不能作用于非布尔类型值。
整型
整型是所有编程语言里最基础的数据类型,Go 语言默认支持如下这些整型类型:
类型 | 长度(单位:字节) | 说明 | 值范围 | 默认值 |
---|---|---|---|---|
int8 | 1 | 带符号8位整型 | -128~127 | 0 |
uint8 | 1 | 无符号8位整型,与 byte 类型等价 | 0~255 | 0 |
int16 | 2 | 带符号16位整型 | -32768~32767 | 0 |
uint16 | 2 | 无符号16位整型 | 0~65535 | 0 |
int32 | 4 | 带符号32位整型,与 rune 类型等价 | -2147483648~2147483647 | 0 |
uint32 | 4 | 无符号32位整型 | 0~4294967295 | 0 |
int64 | 8 | 带符号64位整型 | -9223372036854775808~9223372036854775807 | 0 |
uint64 | 8 | 无符号64位整型 | 0~18446744073709551615 | 0 |
int | 32位或64位 | 与具体平台相关 | 与具体平台相关 | 0 |
uint | 32位或64位 | 与具体平台相关 | 与具体平台相关 | 0 |
uintptr | 与对应指针相同 | 无符号整型,足以存储指针值的未解释位 | 32位平台下为4字节,64位平台下为8字节 | 0 |
Go 支持的整型类型非常丰富,你可以根据需要设置合适的整型类型,以节省内存空间,此外 int
和 int32
在 Go 语言里被认为是两种不同的类型(同理,int
和 int64
也是不同的类型),编译器也不会帮你自动做类型转换,比如以下的例子会有编译错误:
var intValue1 int8
intValue2 := 8 // intValue2 将会被自动推导为 int 类型
intValue1 = intValue2 // 编译错误
编译错误类似于:
代码语言:javascript复制cannot use intValue2 (type int) as type int8 in assignment
使用强制类型转换可以解决这个编译错误:
代码语言:javascript复制intValue1 = int8(intValue2)) // 编译通过
注:关于类型转换我们在后面介绍完所有数据类型后会单独介绍。
我们还可以通过 intValue := uint8(intValue2)
这种方式同时完成类型转化和赋值操作。
此外,和其他编程语言一样,可以通过增加前缀 0
来表示八进制数(如:077),增加前缀 0x
来表示十六进制数(如:0xFF),以及使用 E
来表示 10 的连乘(如:1E3 = 1000
)。
运算符
算术运算符
Go 语言支持所有常规的整数四则运算:
、-
、*
、/
和 %
(取余运算只能用于整数),不过由于强类型的关系,在 Go 语言中,不同类型的整型值不能直接进行算术运算,比如下面这样计算就会报编译错误:
intValue3 := intValue1 intValue2
编译错误信息如下:
代码语言:javascript复制invalid operation: intValue1 intValue2 (mismatched types int8 and int)
类型转化之后就好了:
代码语言:javascript复制intValue3 := intValue1 int8(intValue2)
如果你是从动态语言转过来学习 Go,在刚开始写代码时尤其要注意这些因为类型问题产生的 bug。
在 Go 语言中,也支持自增/自减运算符,即
/--
,但是只能作为语句,不能作为表达式,且只能用作后缀,不能放到变量前面:
intValue1 // 有效,intValue1 的值变成 9
intValue1 = intValue1 // 无效,编译报错
--intValue1 // 无效,编译报错
还支持 =
、-=
、*=
、/=
、%=
这种快捷写法:
intValue1 = intValue1 // 18
intValue1 -= intValue1 // 0
intValue1 *= intValue1 // 81
intValue1 /= intValue1 // 1
intValue1 %= intValue1 // 0
比较运算符
Go 语言支持以下几种常见的比较运算符:>
、<
、==
、>=
、<=
和 !=
,比较运算符运行的结果是布尔值。
如前面所说,Go 是强类型语言,不同类型的值不能放在一起比较,否则会报编译错处:
代码语言:javascript复制if intValue1 == intValue2 {
fmt.Println("intValue1 和 intValue2 相等")
}
相同类型的值才可以:
代码语言:javascript复制if intValue1 < intValue3 {
fmt.Println("intValue1 比 intValue3 小")
}
由此可见,所有比较运算符在比较的时候都会考虑进数据类型的因素,所以不需要额外引入类似 PHP 等动态语言中的 ===
和 !==
这种严格比较运算符。
不过,各种类型的整型变量都可以直接与字面常量进行比较,比如:
代码语言:javascript复制if intValue1 == 8 {
fmt.Println("intValue1 = 8")
}
位运算符
位运算符以二进制的方式对数值进行运算,效率更高,性能更好,Go 语言支持以下这几种位运算符:
运算符 | 含义 | 结果 |
---|---|---|
x & y | 按位与 | 把 x 和 y 都为 1 的位设为 1 |
x | y | 按位或 | 把 x 或 y 为 1 的位设为 1 |
x ^ y | 按位异或 | 把 x 和 y 一个为 1 一个为 0 的位设为 1 |
^x | 按位取反 | 把 x 中为 0 的位设为 1,为 1 的位设为 0 |
x << y | 左移 | 把 x 中的位向左移动 y 次,每次移动相当于乘以 2 |
x >> y | 右移 | 把 x 中的位向右移动 y 次,每次移动相当于除以 2 |
我们可以做一些简单的测试:
代码语言:javascript复制var intValueBit uint8
intValueBit = 255
intValueBit = ^intValueBit // 按位取反
fmt.Println(intValueBit) // 0
intValueBit = 1
intValueBit = intValueBit << 3 // 左移 3 位,相当于乘以 2^3 = 8
fmt.Println(intValueBit) // 8
逻辑运算符
Go 语言支持以下逻辑运算符:
运算符 | 含义 | 结果 |
---|---|---|
x && y | 逻辑与运算符(AND) | 如果 x 和 y 都是 true,则结果为 true,否则结果为 false |
x || y | 逻辑或运算符(OR) | 如果 x 或 y 是 true,则结果为 true,否则结果为 false |
!x | 逻辑非运算符(NOT) | 如果 x 为 true,则结果为 false,否则结果为 true |
逻辑运算符计算的结果也是布尔值,通常我们可以组合使用逻辑运算符和比较运算符:
代码语言:javascript复制if intValue1 < intValue3 && intValue1 == 8 {
fmt.Println("条件为真")
}
运算符优先级
上面介绍的 Go 语言运算符优先级如下所示(由上到下表示优先级从高到低,或者数字越大,优先级越高):
代码语言:javascript复制6 ^(按位取反) !
5 * / % << >> & &^
4 - | ^(按位异或)
3 == != < <= > >=
2 &&
1 ||
或 --
只能出现在语句中,不能用于表达式,故不参与优先级判断。
浮点型
浮点型也叫浮点数,用于表示包含小数点的数据,比如 3.14
、1.00
都是浮点型数据。
浮点数的表示
Go 语言中的浮点数采用IEEE-754 标准的表达方式,定义了两个类型:float32
和 float64
,其中 float32
是单精度浮点数,可以精确到小数点后 7 位(类似 PHP、Java 等语言的 float
类型),float64
是双精度浮点数,可以精确到小数点后 15 位(类似 PHP、Java 等语言的 double
类型)。
在 Go 语言里,定义一个浮点型变量的代码如下:
代码语言:javascript复制var floatValue1 float32
floatValue1 = 10
floatValue2 := 10.0 // 如果不加小数点,floatValue2 会被推导为整型而不是浮点型
floatValue3 := 1.1E-10
对于浮点类型需要被自动推导的变量,其类型将被自动设置为 float64
,而不管赋值给它的数字是否是用 32 位长度表示的。因此,对于以上的例子,下面的赋值将导致编译错误:
floatValue1 = floatValue2 // floatValue2 是 float64 类型
编译错误信息如下:
代码语言:javascript复制cannot use floatValue2 (type float64) as type float32 in assignment
必须使用这样的强制类型转换才可以:
代码语言:javascript复制floatValue1 = float32(floatValue2)
在实际开发中,应该尽可能地使用 float64
类型,因为 math 包中所有有关数学运算的函数都会要求接收这个类型。
浮点数的精度
浮点数不是一种精确的表达方式,因为二进制无法精确表示所有十进制小数,比如 0.1
、0.7
这种,下面我们通过一个示例来给大家直观演示下:
floatValue4 := 0.1
floatValue5 := 0.7
floatValue6 := floatValue4 floatValue5
注:浮点数的运算和整型一样,也要保证操作数的类型一致,
float32
和float64
类型数据不能混合运算,需要手动进行强制转化才可以,这一点和动态语言不同。
你觉得上面计算结果 floatValue6
的值是多少?0.8?不,它的结果是 0.7999999999999999
,这是因为计算机底层将十进制的 0.1
和 0.7
转化为二进制表示时,会丢失精度,所以永远不要相信浮点数结果精确到了最后一位,也永远不要比较两个浮点数是否相等。
浮点数的比较
浮点数支持通过算术运算符进行四则运算,也支持通过比较运算符进行比较(前提是运算符两边的操作数类型一致),但是涉及到相等的比较除外,因为我们上面提到,看起来相等的两个十进制浮点数,在底层转化为二进制时会丢失精度,因此不能被表象蒙蔽。
如果一定要判断相等,下面是一种替代的解决方案:
代码语言:javascript复制p := 0.00001
// 判断 floatValue1 与 floatValue2 是否相等
if math.Dim(float64(floatValue1), floatValue2) < p {
fmt.Println("floatValue1 和 floatValue2 相等")
}
可以看到,我们的解决方案是一种近似判断,通过一个可以接受的最小误差值 p
,约定如果两个浮点数的差值在此精度的误差范围之内,则判定这两个浮点数相等。这个解决方案也是其他语言判断浮点数相等所采用的通用方案。
复数类型
除了整型和浮点型之外,Go 语言还支持复数类型,与复数相对,我们可以把整型和浮点型这种日常比较常见的数字称为实数,复数是实数的延伸,可以通过两个实数(在计算机中用浮点数表示)构成,一个表示实部(real),一个表示虚部(imag),常见的表达形式如下:
代码语言:javascript复制z = a bi
其中 a、b 均为实数,i 称为虚数单位,当 b = 0 时,z 就是常见的实数,当 a = 0 而 b ≠ 0 时,将 z 称之为纯虚数,如果你理解数学概念中的复数概念,这些都很好理解,下面我们来看下复数在 Go 语言中的表示和使用。
在 Go 语言中,复数支持两种类型:complex64
(32 位实部和虚部) 和 complex128
(64 位实部与虚部),对应的表示示例如下,和数学概念中的复数表示形式一致:
var complexValue1 complex64
complexValue1 = 1.10 10i // 由两个 float32 实数构成的复数类型
complexValue2 := 1.10 10i // 和浮点型一样,默认自动推导的实数类型是 float64,所以 complexValue2 是 complex128 类型
complexValue3 := complex(1.10, 10) // 与 complexValue2 等价
对于一个复数 z = complex(x, y)
,就可以通过 Go 语言内置函数 real(z)
获得该复数的实部,也就是 x
,通过 imag(z)
获得该复数的虚部,也就是 y
。
复数支持和其它数字类型一样的算术运算符。当你使用 ==
或者 !=
对复数进行比较运算时,由于构成复数的实数部分也是浮点型,需要注意对精度的把握。
更多关于复数的函数,请查阅 math/cmplx 标准库的文档。如果你对内存的要求不是特别高,最好使用 complex128
作为计算类型,因为相关函数大都使用这个类型的参数。
(本文完)