1 布尔类型 bool
一个简单的例子:var b bool = true
。
布尔型的值只可以是常量 true 或者 false。
两个类型相同的值可以使用相等 ==
或者不等 !=
运算符来进行比较并获得一个布尔型的值。
当相等运算符两边的值是完全相同的值的时候会返回 true,否则返回 false,并且只有在两个的值的类型相同的情况下才可以使用。
示例:
var aVar = 10
aVar != 5 -> true
aVar != 10 -> false
Go 对于值之间的比较有非常严格的限制,只有两个类型相同的值才可以进行比较,如果值的类型是接口,它们也必须都实现了相同的接口。如果其中一个值是常量,那么另外一个值的类型必须和该常量类型相兼容的。如果以上条件都不满足,则其中一个值的类型必须在被转换为和另外一个值的类型相同之后才可以进行比较。
布尔型的常量和变量也可以通过和逻辑运算符(非 !
、和 &&
、或 ||
)结合来产生另外一个布尔值,这样的逻辑语句就其本身而言,并不是一个完整的 Go 语句。
逻辑值可以被用于条件结构中的条件语句,以便测试某个条件是否满足。另外,和 &&
、或 ||
与相等 ==
或不等 !=
属于二元运算符,而非 !
属于一元运算符。在接下来的内容中,我们会使用 T 来代表条件符合的语句,用 F 来代表条件不符合的语句。
非 !T -> false !F -> true 且 T && T -> true T && F -> false F && T -> false F && F -> false 或 T || T -> true T || F -> true F || T -> true F || F -> false
2 数字类型
2.1 整型 int 和浮点型 float
Go 语言支持整型和浮点型数字,并且原生支持复数,其中位的运算采用补码
Go 也有基于架构的类型,例如:int、uint 和 uintptr。
这些类型的长度都是根据运行程序所在的操作系统类型所决定的:
int
和uint
在 32 位操作系统上,它们均使用 32 位(4 个字节),在 64 位操作系统上,它们均使用 64 位(8 个字节)。uintptr
的长度被设定为足够存放一个指针即可。
Go 语言中没有 float 类型。(Go语言中只有 float32 和 float64)没有double类型。
与操作系统架构无关的类型都有固定的大小,并在类型的名称中就可以看出来:
整数:
- int8(-128 -> 127)
- int16(-32768 -> 32767)
- int32(-2,147,483,648 -> 2,147,483,647)
- int64(-9,223,372,036,854,775,808 -> 9,223,372,036,854,775,807)
无符号整数:
- uint8(0 -> 255)
- uint16(0 -> 65,535)
- uint32(0 -> 4,294,967,295)
- uint64(0 -> 18,446,744,073,709,551,615)
浮点型(IEEE-754 标准):
- float32(+- 1e-45 -> +- 3.4 * 1e38)
- float64(+- 5 * 1e-324 -> 107 * 1e308)
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 中不允许不同类型之间的混合使用,但是对于常量的类型限制非常少,因此允许常量之间的混合使用,下面这个程序很好地解释了这个现象(该程序无法通过编译):
package main
func main() {
var a int
var b int32
a = 15
b = a + a // 编译错误
b = b + 5 // 因为 5 是常量,所以可以通过编译
}
异常./data_types.go:66:4: cannot use a + a (type int) as type int32 in assignment
格式化说明符
在格式化字符串里,%d
用于格式化整数(%x
和 %X
用于格式化 16 进制表示的数字),%g
用于格式化浮点型(%f
输出浮点数,%e
输出科学计数表示法),%0nd
用于规定输出长度为n的整数,其中开头的数字 0 是必须的。%n.mg
用于表示数字 n 并精确到小数点后 m 位,除了使用 g 之外,还可以使用 e 或者 f,例如:使用格式化字符串 %5.2e
来输出 3.4 的结果为 3.40e+00
。
2.2 复数
complex64 (32 位实数和虚数) complex128 (64 位实数和虚数)
复数使用 re+imI
来表示,其中 re
代表实数部分,im
代表虚数部分,I 代表根号负 1。
示例:
var c1 complex64 = 5 + 10i
fmt.Printf("The value is: %v", c1)
// 输出: 5 + 10i
如果 re
和 im
的类型均为 float32,那么类型为 complex64 的复数 c 可以通过以下方式来获得:
c = complex(re, im)
函数 real(c)
和 imag(c)
可以分别获得相应的实数和虚数部分。
在使用格式化说明符时,可以使用 %v
来表示复数,但当你希望只表示其中的一个部分的时候需要使用 %f
。
复数支持和其它数字类型一样的运算。当你使用等号 ==
或者不等号 !=
对复数进行比较运算时,注意对精确度的把握。cmath
包中包含了一些操作复数的公共方法。如果你对内存的要求不是特别高,最好使用 complex128 作为计算类型,因为相关函数都使用这个类型的参数。
3 位运算
位运算只能用于整数类型的变量,且需当它们拥有等长位模式时。
%b
是用于表示位的格式化标识符。
二元运算符
-
按位与
&
:对应位置上的值经过和运算结果,具体参见和运算符,第 4.5.1 节,并将 T(true)替换为 1,将 F(false)替换为 0,并将 T(true)替换为 1,将 F(false)替换为 0
1 & 1 -> 1
1 & 0 -> 0
0 & 1 -> 0
0 & 0 -> 0
- 按位或
|
:
对应位置上的值经过或运算结果,具体参见或运算符,第 4.5.1 节,并将 T(true)替换为 1,将 F(false)替换为 0
1 | 1 -> 1
1 | 0 -> 1
0 | 1 -> 1
0 | 0 -> 0
- 按位异或
^
:
对应位置上的值根据以下规则组合:
1 ^ 1 -> 0
1 ^ 0 -> 1
0 ^ 1 -> 1
0 ^ 0 -> 0
位清除 &^
:将指定位置上的值设置为 0。
一元运算符
-
按位补足
^
:该运算符与异或运算符一同使用,即
m^x
,对于无符号 x 使用“全部位设置为 1”,对于有符号 x 时使用m=-1
。例如:^10 = -01 ^ 10 = -11
-
位左移
<<
:-
用法:
bitP << n
。 -
bitP
的位向左移动 n 位,右侧空白部分使用 0 填充;如果 n 等于 2,则结果是 2 的相应倍数,即 2 的 n 次方。例如:1 << 10 // 等于 1 KB 1 << 20 // 等于 1 MB 1 << 30 // 等于 1 GB
-
-
位右移
>>
:- 用法:
bitP >> n
。 bitP
的位向右移动 n 位,左侧空白部分使用 0 填充;如果 n 等于 2,则结果是当前值除以 2 的 n 次方。
- 用法:
当希望把结果赋值给第一个操作数时,可以简写为 a <<= 2
或者 b ^= a & 0xffffffff
。
位左移常见实现存储单位的用例
使用位左移与 iota 计数配合可优雅地实现存储单位的常量枚举:
type ByteSize float64
const (
_ = iota // 通过赋值给空白标识符来忽略值
KB ByteSize = 1<<(10*iota)
MB
GB
TB
PB
EB
ZB
YB
)
在通讯中使用位左移表示标识的用例
type BitFlag int
const (
Active BitFlag = 1 << iota // 1 << 0 == 1
Send // 1 << 1 == 2
Receive // 1 << 2 == 4
)
flag := Active | Send // == 3
2.4 逻辑运算符
Go 中拥有以下逻辑运算符:==
、!=
(第 4.5.1 节)、<
、<=
、>
、>=
。
它们之所以被称为逻辑运算符是因为它们的运算结果总是为布尔值 bool
。例如:
b3:= 10 > 5 // b3 is true
2.5 算术运算符
常见可用于整数和浮点数的二元运算符有 +
、-
、*
和 /
。
(相对于一般规则而言,Go 在进行字符串拼接时允许使用对运算符 +
的重载,但 Go 本身不允许开发者进行自定义的运算符重载)
/
对于整数运算而言,结果依旧为整数,例如:9 / 4 -> 2
。
取余运算符只能作用于整数:9 % 4 -> 1
。
整数除以 0 可能导致程序崩溃,将会导致运行时的恐慌状态(如果除以 0 的行为在编译时就能被捕捉到,则会引发编译错误);第 13 章将会详细讲解如何正确地处理此类情况。
浮点数除以 0.0 会返回一个无穷尽的结果,使用 +Inf
表示。
2.6 随机数
一些像游戏或者统计学类的应用需要用到随机数。rand
包实现了伪随机数的生成。
package main import ( "fmt" "math/rand" "time" ) func main() { // // 全局函数 // rand.Seed(time.Now().Unix()) fmt.Println(rand.Int()) // int随机值,返回值为int fmt.Println(rand.Intn(100)) // [0,100)的随机值,返回值为int fmt.Println(rand.Int31()) // 31位int随机值,返回值为int32 fmt.Println(rand.Int31n(100)) // [0,100)的随机值,返回值为int32 fmt.Println(rand.Float32()) // 32位float随机值,返回值为float32 fmt.Println(rand.Float64()) // 64位float随机值,返回值为float64 // 如果要产生负数到正数的随机值,只需要将生成的随机数减去相应数值即可 fmt.Println(rand.Intn(100) - 50) // [-50, 50)的随机值 // // Rand对象 // r := rand.New(rand.NewSource(time.Now().Unix())) fmt.Println(r.Int()) // int随机值,返回值为int fmt.Println(r.Intn(100)) // [0,100)的随机值,返回值为int fmt.Println(r.Int31()) // 31位int随机值,返回值为int32 fmt.Println(r.Int31n(100)) // [0,100)的随机值,返回值为int32 fmt.Println(r.Float32()) // 32位float随机值,返回值为float32 fmt.Println(r.Float64()) // 64位float随机值,返回值为float64 // 如果要产生负数到正数的随机值,只需要将生成的随机数减去相应数值即可 fmt.Println(r.Intn(100) - 50) // [-50, 50)的随机值 }
3 运算符与优先级
有些运算符拥有较高的优先级,二元运算符的运算方向均是从左至右。下表列出了所有运算符以及它们的优先级,由上至下代表优先级由高到低:
优先级 运算符
7 ^ !
6 * / % << >> & &^
5 + - | ^
4 == != < <= >= >
3 <-
2 &&
1 ||
当然,你可以通过使用括号来临时提升某个表达式的整体运算优先级。
4 类型别名
当你在使用某个类型时,你可以给它起另一个名字,然后你就可以在你的代码中使用新的名字(用于简化名称或解决名称冲突)。
在 type TZ int
中,TZ 就是 int 类型的新名称(用于表示程序中的时区),然后就可以使用 TZ 来操作 int 类型的数据。
package main import "fmt" type TZ int func main() { var a, b TZ = 3, 4 c := a + b fmt.Printf("c has the value: %d", c) // 输出:c has the value: 7 }
实际上,类型别名得到的新类型并非和原类型完全相同,新类型不会拥有原类型所附带的方法;TZ 可以自定义一个方法用来输出更加人性化的时区信息。
5 字符类型
严格来说,这并不是 Go 语言的一个类型,字符只是整数的特殊用例。byte
类型是 uint8
的别名,对于只占用 1 个字节的传统 ASCII 编码的字符来说,完全没有问题。例如:var ch byte = 'A'
;字符使用单引号括起来。
在 ASCII 码表中,A 的值是 65,而使用 16 进制表示则为 41,所以下面的写法是等效的:
var ch byte = 65 或 var ch byte = 'x41'
(x
总是紧跟着长度为 2 的 16 进制数)
另外一种可能的写法是 后面紧跟着长度为 3 的八进制数,例如:
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
则总是紧跟着长度为 4 的 16 进制数,前缀 U
紧跟着长度为 8 的 16 进制数。
var ch int = 'u0041'
var ch2 int = 'u03B2'
var ch3 int = 'U00101234'
fmt.Printf("%d - %d - %d
", ch, ch2, ch3) // integer
fmt.Printf("%c - %c - %c
", ch, ch2, ch3) // character
fmt.Printf("%X - %X - %X
", ch, ch2, ch3) // UTF-8 bytes
fmt.Printf("%U - %U - %U", ch, ch2, ch3) // UTF-8 code point
输出:
65 - 946 - 1053236
A - β - r
41 - 3B2 - 101234
U+0041 - U+03B2 - U+101234
格式化说明符 %c
用于表示字符;当和字符配合使用时,%v
或 %d
会输出用于表示该字符的整数;%U
输出格式为 U+hhhh 的字符串(另一个示例见第 5.4.4 节)。
包 unicode
包含了一些针对测试字符的非常有用的函数(其中 ch
代表字符):
- 判断是否为字母:
unicode.IsLetter(ch)
- 判断是否为数字:
unicode.IsDigit(ch)
- 判断是否为空白符号:
unicode.IsSpace(ch)
这些函数返回一个布尔值。包 utf8
拥有更多与 rune 相关的函数。