Go 数据类型篇(二):布尔类型、整型、浮点型和复数类型

2023-03-03 13:45:40 浏览数 (1)

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,可赋值且只可以赋值为预定义常量 truefalse。示例代码如下:

代码语言:javascript复制
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 语言中则不然,不同类型的值不能使用 ==!= 运算符进行比较,在编译期就会报错,比如下面这段代码:

代码语言:javascript复制
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 支持的整型类型非常丰富,你可以根据需要设置合适的整型类型,以节省内存空间,此外 intint32 在 Go 语言里被认为是两种不同的类型(同理,intint64 也是不同的类型),编译器也不会帮你自动做类型转换,比如以下的例子会有编译错误:

代码语言:javascript复制
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 语言中,不同类型的整型值不能直接进行算术运算,比如下面这样计算就会报编译错误:

代码语言:javascript复制
intValue3 := intValue1   intValue2

编译错误信息如下:

代码语言:javascript复制
invalid operation: intValue1   intValue2 (mismatched types int8 and int)

类型转化之后就好了:

代码语言:javascript复制
intValue3 := intValue1   int8(intValue2)

如果你是从动态语言转过来学习 Go,在刚开始写代码时尤其要注意这些因为类型问题产生的 bug。

在 Go 语言中,也支持自增/自减运算符,即 /--,但是只能作为语句,不能作为表达式,且只能用作后缀,不能放到变量前面:

代码语言:javascript复制
intValue1    // 有效,intValue1 的值变成 9
intValue1 = intValue1   // 无效,编译报错
--intValue1  // 无效,编译报错

还支持 =-=*=/=%= 这种快捷写法:

代码语言:javascript复制
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 &lt; 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 &lt;&lt; 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 &lt; intValue3 &amp;&amp; intValue1 == 8 {
    fmt.Println("条件为真")
}

运算符优先级

上面介绍的 Go 语言运算符优先级如下所示(由上到下表示优先级从高到低,或者数字越大,优先级越高):

代码语言:javascript复制
6      ^(按位取反) !
5      *  /  %  <<  >>  &  &^
4         -  |  ^(按位异或)
3      ==  !=  <  <=  >  >=
2      &&
1      ||

-- 只能出现在语句中,不能用于表达式,故不参与优先级判断。

浮点型

浮点型也叫浮点数,用于表示包含小数点的数据,比如 3.141.00 都是浮点型数据。

浮点数的表示

Go 语言中的浮点数采用IEEE-754 标准的表达方式,定义了两个类型:float32float64,其中 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 位长度表示的。因此,对于以上的例子,下面的赋值将导致编译错误:

代码语言:javascript复制
floatValue1 = floatValue2  // floatValue2 是 float64 类型

编译错误信息如下:

代码语言:javascript复制
cannot use floatValue2 (type float64) as type float32 in assignment

必须使用这样的强制类型转换才可以:

代码语言:javascript复制
floatValue1 = float32(floatValue2)

在实际开发中,应该尽可能地使用 float64 类型,因为 math 包中所有有关数学运算的函数都会要求接收这个类型。

浮点数的精度

浮点数不是一种精确的表达方式,因为二进制无法精确表示所有十进制小数,比如 0.10.7 这种,下面我们通过一个示例来给大家直观演示下:

代码语言:javascript复制
floatValue4 := 0.1
floatValue5 := 0.7
floatValue6 := floatValue4   floatValue5

注:浮点数的运算和整型一样,也要保证操作数的类型一致,float32float64 类型数据不能混合运算,需要手动进行强制转化才可以,这一点和动态语言不同。

你觉得上面计算结果 floatValue6 的值是多少?0.8?不,它的结果是 0.7999999999999999,这是因为计算机底层将十进制的 0.10.7 转化为二进制表示时,会丢失精度,所以永远不要相信浮点数结果精确到了最后一位,也永远不要比较两个浮点数是否相等。

浮点数的比较

浮点数支持通过算术运算符进行四则运算,也支持通过比较运算符进行比较(前提是运算符两边的操作数类型一致),但是涉及到相等的比较除外,因为我们上面提到,看起来相等的两个十进制浮点数,在底层转化为二进制时会丢失精度,因此不能被表象蒙蔽。

如果一定要判断相等,下面是一种替代的解决方案:

代码语言:javascript复制
p := 0.00001
// 判断 floatValue1 与 floatValue2 是否相等
if math.Dim(float64(floatValue1), floatValue2) &lt; 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 位实部与虚部),对应的表示示例如下,和数学概念中的复数表示形式一致:

代码语言:javascript复制
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 作为计算类型,因为相关函数大都使用这个类型的参数。

(本文完)

0 人点赞