Go 语言基本数据类型

布尔类型 bool

一个简单的例子:var b bool = true。

布尔型的值只可以是常量 true 或者 false。

两个类型相同的值可以使用相等 == 或者不等 != 运算符来进行比较并获得一个布尔型的值。

当相等运算符两边的值是完全相同的值的时候会返回 true,否则返回 false,并且只有在两个的值的类型相同的情况下才可以使用。

  1. var aVar = 10
  2. aVar == 5 -> false
  3. aVar == 10 -> true

当不等运算符两边的值是不同的时候会返回 true,否则返回 false。

  1. var aVar = 10
  2. aVar != 5 -> true
  3. aVar != 10 -> false

Go语言 对于值之间的比较有非常严格的限制,只有两个类型相同的值才可以进行比较,如果值的类型是接口(interface),它们也必须都实现了相同的接口。如果其中一个值是常量,那么另外一个值的类型必须和该常量类型相兼容的。如果以上条件都不满足,则其中一个值的类型必须在被转换为和另外一个值的类型相同之后才可以进行比较。

布尔型的常量和变量也可以通过和逻辑运算符(非 !、和 &&、或 ||)结合来产生另外一个布尔值。

逻辑值可以被用于条件结构中的条件语句,以便判断某个条件是否满足。另外,和 &&、或 || 与相等 == 或不等 != 属于二元运算符,而非 ! 属于一元运算符。在接下来的内容中,我们会使用 T 来代表条件符合的语句,用 F 来代表条件不符合的语句。

Go 语言中包含以下逻辑运算符:

非运算符:!

  1. !T -> false
  2. !F -> true

非运算符用于取得和布尔值相反的结果。

与运算符:&&

  1. T && T -> true
  2. T && F -> false
  3. F && T -> false
  4. F && F -> false

只有当两边的值都为 true 的时候,和运算符的结果才是 true。

或运算符:||

  1. T || T -> true
  2. T || F -> true
  3. F || T -> true
  4. F || F -> false

只有当两边的值都为 false 的时候,或运算符的结果才是 false,其中任意一边的值为 true 就能够使得该表达式的结果为 true。

利用括号同样可以升级某个表达式的运算优先级。

在格式化输出时,你可以使用 %t 来表示你要输出的值为布尔型。

布尔值(以及任何结果为布尔值的表达式)最常用在条件结构的条件语句中,例如:ifforswitch 结构。

对于布尔值的好的命名能够很好地提升代码的可读性,例如以 is 或者 Is 开头的 isSortedisFinishedisVisible等,使用这样的命名能够在阅读代码的获得阅读正常语句一样的良好体验,例如标准库中的 unicode.IsDigit(ch)。

数字类型

Go 语言支持整型和浮点型数字,并且原生支持复数。Go 也有基于架构的类型,例如:int、uint 和 uintptr。

这些类型的长度都是根据运行程序所在的操作系统类型所决定的:

int 和 uint 在 32 位操作系统上,它们均使用 32 位(4 个字节),在 64 位操作系统上,它们均使用 64 位(8 个字节)。

uintptr 的长度被设定为足够存放一个指针即可。

Go 语言中没有 float 类型。(Go语言中只有 float32 和 float64)没有double类型。

与操作系统架构无关的类型都有固定的大小,并在类型的名称中就可以看出来:

整型

序号 类型 描述
1 uint8 无符号 8 位整型 (0 到 255)
2 uint16 无符号 16 位整型 (0 到 65535)
3 uint32 无符号 32 位整型 (0 到 4294967295)
4 uint64 无符号 64 位整型 (0 到 18446744073709551615)
5 int8 有符号 8 位整型 (-128 到 127)
6 int16 有符号 16 位整型 (-32768 到 32767)
7 int32 有符号 32 位整型 (-2147483648 到 2147483647)
8 int64 有符号 64 位整型 (-9223372036854775808 到 9223372036854775807)

浮点类型:IEEE-754标准

序号 类型 描述
1 float32 32位浮点型数
2 float64 64位浮点型数

int 型是计算最快的一种类型。整型的零值为 0,浮点型的零值为 0.0。

float32 精确到小数点后 7 位,float64 精确到小数点后 15 位。由于精确度的缘故,你在使用 == 或者 != 来比较浮点数时应当非常小心。你最好在正式使用前测试对于精确度要求较高的运算。

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

你可以通过增加前缀 0 来表示 8 进制数(如:077),增加前缀 0x 来表示 16 进制数(如:0xFF),以及使用 e 来表示 10 的连乘(如: 1e3 = 1000,或者 6.022e23 = 6.022 x 1e23)。

你可以使用a := uint64(0) 来同时完成类型转换和赋值操作,这样 a 的类型就是 uint64

Go 中不允许不同类型之间的混合使用,但是对于常量的类型限制非常少,因此允许常量之间的混合使用,下面这个程序很好地解释了这个现象(该程序无法通过编译):

  1. package main
  2. func main() {
  3. var a int
  4. var b int32
  5. a = 15
  6. b = a + a // 编译错误
  7. b = b + 5 // 因为 5 是常量,所以可以通过编译
  8. }

如果你尝试编译该程序,则将得到编译错误 cannot use a + a (type int) as type int32 in assignment

同样地,int16 也不能够被隐式转换为 int32。

下面这个程序展示了通过显式转换来避免这个问题。

  1. package main
  2. import "fmt"
  3. func main() {
  4. var n int16 = 34
  5. var m int32
  6. // compiler error: cannot use n (type int16) as type int32 in assignment
  7. //m = n
  8. m = int32(n)
  9. fmt.Printf("32 bit int is: %d\n", m)
  10. fmt.Printf("16 bit int is: %d\n", n)
  11. }

输出:

  1. 32 bit int is: 34
  2. 16 bit int is: 34

复数类型

序号 类型 描述
1 complex64 32 位实数和虚数
2 complex128 64 位实数和虚数

复数使用 re+imI 来表示,其中 re 代表实数部分,im 代表虚数部分,I 代表根号负 1。

示例:

  1. var c1 complex64 = 5 + 10i
  2. fmt.Printf("The value is: %v", c1)
  3. // 输出: 5 + 10i

如果 re 和 im 的类型均为 float32,那么类型为 complex64 的复数 c 可以通过以下方式来获得:

c = complex(re, im)函数 real(c) 和 imag(c) 可以分别获得相应的实数和虚数部分。

在使用格式化说明符时,可以使用 %v 来表示复数,但当你希望只表示其中的一个部分的时候需要使用 %f。

复数支持和其它数字类型一样的运算。当你使用等号 == 或者不等号 != 对复数进行比较运算时,注意对精确度的把握。cmath 包中包含了一些操作复数的公共方法。如果你对内存的要求不是特别高,最好使用 complex128 作为计算类型,因为相关函数都使用这个类型的参数。

格式化说明符

示例:fmt.Printf("Path is %s\n", path)

  • %s 代表字符串标识符
  • %v 代表使用类型的默认输出格式的标识符
  • %t 代表使用布尔类型
  • %d 代表使用整数类型(%x 和 %X 用于格式化 16 进制表示的数字)
  • %g 代表使用浮点型(%f 输出浮点数,%e 输出科学计数表示法)
  • %0nd 用于规定输出长度为n的整数,其中开头的数字 0 是必须的
  • %n.mg 用于表示数字 n 并精确到小数点后 m 位,除了使用 g 之外,还可以使用 e 或者 f,例如:使用格式化字符串 %5.2e 来输出 3.4 的结果为 3.40e+00
  • %c 代表字符类型

数字值转换

当进行类似 a32bitInt = int32(a32Float) 的转换时,小数点后的数字将被丢弃。这种情况一般发生当从取值范围较大的类型转换为取值范围较小的类型时,或者你可以写一个专门用于处理类型转换的函数来确保没有发生精度的丢失。下面这个例子展示如何安全地从 int 型转换为 int8:

  1. func Uint8FromInt(n int) (uint8, error) {
  2. if 0 <= n && n <= math.MaxUint8 { // conversion is safe
  3. return uint8(n), nil
  4. }
  5. return 0, fmt.Errorf("%d is out of the uint8 range", n)
  6. }

或者安全地从 float64 转换为 int:

  1. func IntFromFloat64(x float64) int {
  2. if math.MinInt32 <= x && x <= math.MaxInt32 { // x lies in the integer range
  3. whole, fraction := math.Modf(x)
  4. if fraction >= 0.5 {
  5. whole++
  6. }
  7. return int(whole)
  8. }
  9. panic(fmt.Sprintf("%g is out of the int32 range", x))
  10. }

字符类型

严格来说,字符类型并不是 Go 语言的一个类型,字符只是整数的特殊用例。byte 类型是 uint8 的别名,对于只占用 1 个字节的传统 ASCII 编码的字符来说,完全没有问题。例如:var ch byte = ‘A’;字符使用单引号括起来。

在 ASCII 码表中,A 的值是 65,而使用 16 进制表示则为 41,所以下面的写法是等效的:

  1. var ch byte = 65 var ch byte = '\x41'
  2. \x 总是紧跟着长度为 2 16 进制数)

另外一种可能的写法是 \ 后面紧跟着长度为 38 进制数,例如:\377

不过 Go 同样支持 Unicode(UTF-8),因此字符同样称为 Unicode 代码点或者 runes,并在内存中使用 int 来表示。在文档中,一般使用格式 U+hhhh 来表示,其中 h 表示一个 16 进制数。其实 rune 也是 Go 当中的一个类型,并且是 int32 的别名。

在书写 Unicode 字符时,需要在 16 进制数之前加上前缀 \u 或者 \U

因为 Unicode 至少占用 2 个字节,所以我们使用 int16 或者 int 类型来表示。如果需要使用到 4 字节,则会加上 \U 前缀;前缀 \u 则总是紧跟着长度为 416 进制数,前缀 \U 紧跟着长度为 816 进制数。

示例:

  1. var ch int = '\u0041'
  2. var ch2 int = '\u03B2'
  3. var ch3 int = '\U00101234'
  4. fmt.Printf("%d - %d - %d\n", ch, ch2, ch3) // integer
  5. fmt.Printf("%c - %c - %c\n", ch, ch2, ch3) // character
  6. fmt.Printf("%X - %X - %X\n", ch, ch2, ch3) // UTF-8 bytes
  7. fmt.Printf("%U - %U - %U", ch, ch2, ch3) // UTF-8 code point
  8. 输出:
  9. 65 - 946 - 1053236
  10. A - β - r
  11. 41 - 3B2 - 101234
  12. U+0041 - U+03B2 - U+101234

格式化说明符 %c 用于表示字符;当和字符配合使用时,%v%d 会输出用于表示该字符的整数;%U 输出格式为 U+hhhh 的字符串。

基础包 unicode 种包含了一些针对测试字符的非常有用的函数(其中 ch 代表字符):

  1. 判断是否为字母:unicode.IsLetter(ch)
  2. 判断是否为数字:unicode.IsDigit(ch)
  3. 判断是否为空白符号:unicode.IsSpace(ch)

这些函数返回一个布尔值。包 utf8 拥有更多与 rune 类型相关的函数。

类型别名

当你在使用某个类型时,为了简化名称或解决名称冲突,你可以给它起另一个名字,然后你就可以在你的代码中使用新的名字。

type TZ int 中,TZ 就是 int 类型的新名称,然后就可以使用 TZ 来操作 int 类型的数据。

  1. package main
  2. import "fmt"
  3. type TZ int
  4. func main() {
  5. var a, b TZ = 3, 4
  6. c := a + b
  7. fmt.Printf("c has the value: %d", c) // 输出:c has the value: 7
  8. }

实际上,类型别名得到的新类型并非和原类型完全相同,新类型不会拥有原类型所附带的方法;TZ 可以自定义一个方法用来输出更加人性化的时区信息。