变量除了可以在全局声明中初始化,也可以在 init 函数中初始化。这是一类非常特殊的函数,它不能够被人为调用,而是在每个包完成初始化后自动执行,并且执行优先级比 main 函数高。
每个源文件都只能包含一个 init 函数。初始化总是以单线程执行,并且按照包的依赖关系顺序执行。
布尔型的值只可以是常量 true 或者 false。
两个类型相同的值可以使用相等 ==
或者不等 !=
运算符来进行比较并获得一个布尔型的值。
当相等运算符两边的值是完全相同的值的时候会返回 true,否则返回 false,并且只有在两个的值的类型相同的情况下才可以使用。
示例:
var aVar = 10
aVar == 5 -> false
aVar == 10 -> true
当不等运算符两边的值是不同的时候会返回 true,否则返回 false。
示例:
var aVar = 10
aVar != 5 -> true
aVar != 10 -> false
Go只有两个类型相同的值才可以进行比较,如果值的类型是接口(interface),它们也必须都实现了相同的接口。如果其中一个值是常量,那么另外一个值的类型必须和该常量类型相兼容的。如果以上条件都不满足,则其中一个值的类型必须在被转换为和另外一个值的类型相同之后才可以进行比较。
布尔型的常量和变量也可以通过和逻辑运算符(非 !
、和 &&
、或 ||
)结合来产生另外一个布尔值,这样的逻辑语句就其本身而言,并不是一个完整的 Go 语句。
逻辑值可以被用于条件结构中的条件语句,以便测试某个条件是否满足。另外,和 &&
、或 ||
与相等 ==
或不等!=
属于二元运算符,而非 !
属于一元运算符。我们使用 T 来代表条件符合的语句,用 F 来代表条件不符合的语句。
Go 语言中包含以下逻辑运算符:
非运算符:!
!T -> false
!F -> true
非运算符用于取得和布尔值相反的结果。(这个很好理解,负负得正)
和运算符:&&
T && T -> true
T && F -> false
F && T -> false
F && F -> false
只有当两边的值都为 true 的时候,和运算符的结果才是 true。
或运算符:||
T || T -> true
T || F -> true
F || T -> true
F || F -> false
只有当两边的值都为 false 的时候,或运算符的结果才是 false,其中任意一边的值为 true 就能够使得该表达式的结果为 true。
在 Go 语言中,&& 和 || 是具有快捷性质的运算符,当运算符左边表达式的值已经能够决定整个表达式的值的时候(&& 左边的值为 false,|| 左边的值为 true),运算符右边的表达式将不会被执行。利用这个性质,如果你有多个条件判断,应当将计算过程较为复杂的表达式放在运算符的右侧以减少不必要的运算。
利用括号同样可以升级某个表达式的运算优先级。
在格式化输出时,你可以使用 %t
来表示你要输出的值为布尔型。
布尔值(以及任何结果为布尔值的表达式)最常用在条件结构的条件语句中,例如:if、for 和 switch 结构
对于布尔值的好的命名能够很好地提升代码的可读性,例如以 is
或者 Is
开头的 isSorted
、isFinished
、isVisible
,使用这样的命名能够在阅读代码的获得阅读正常语句一样的良好体验。(可以让代码阅读者看见变量名就知道是布尔类型值)
Go 语言支持整型和浮点型数字,并且原生支持复数,其中位的运算采用补码
Go 也有基于架构的类型,例如:int、uint 和 uintptr。
这些类型的长度都是根据运行程序所在的操作系统类型所决定的:
int
和 uint
在 32 位操作系统上,它们均使用 32 位(4 个字节),在 64 位操作系统上,它们均使用 64 位(8 个字节)。uintptr
的长度被设定为足够存放一个指针即可。整数:
无符号整数:
浮点型(IEEE-754 标准):
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中不允许不同类型之间的混合使用,但是对于常量的类型限制非常少,因此允许常量之间的混合使用,下面这个程序很好地解释了这个现象(该程序无法通过编译):
示例 type_mixing.go
package main
func main() {
var a int
var b int32
a = 15
b = a + a // 编译错误,a,b值类型不同
b = b + 5 // 因为 5 是常量,所以可以通过编译
}
如果你尝试编译该程序,则将得到编译错误 cannot use a + a (type int) as type int32 in assignment
。
同样地,int16 也不能够被隐式转换为 int32。
通过显式转换来避免这个问题:
示例 casting.go
package main
import "fmt"
func main() {
var n int16 = 34
var m int32
// 编译报错: cannot use n (type int16) as type int32 in assignment
//m = n
m = int32(n)
fmt.Printf("32 bit int is: %d\n", m)
fmt.Printf("16 bit int is: %d\n", n)
}
输出:
32 bit int is: 34
16 bit int is: 34
2.3格式化说明符
在格式化字符串里,%d
用于格式化整数(%x
和 %X
用于格式化 16 进制表示的数字),%g
用于格式化浮点型(%f
输出浮点数,%e
输出科学计数表示法),%0d
用于规定输出定长的整数,其中开头的数字 0 是必须的。
%n.mg
用于表示数字 n 并精确到小数点后 m 位,除了使用 g 之外,还可以使用 e 或者 f,例如:使用格式化字符串 %5.2e
来输出 3.4 的结果为 3.40e+00
。
2.4数字值转换
当进行类似 a32bitInt = int32(a32Float)
的转换时,小数点后的数字将被丢弃。这种情况一般发生当从取值范围较大的类型转换为取值范围较小的类型时,或者你可以写一个专门用于处理类型转换的函数来确保没有发生精度的丢失。
位运算只能用于整数类型的变量,且需当它们拥有等长位模式时。
%b
是用于表示位的格式化标识符。
二元运算符
按位与 &
:
对应位置上的值经过和运算结果,并将 T(true)替换为 1,将 F(false)替换为 0
1 & 1 -> 1
1 & 0 -> 0
0 & 1 -> 0
0 & 0 -> 0
按位或 |
:
对应位置上的值经过或运算结果,并将 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
Go 中拥有以下逻辑运算符:==
、!=
、<
、<=
、>
、>=
。
它们之所以被称为逻辑运算符是因为它们的运算结果总是为布尔值 bool
。例如:
b3:= 10 > 5 // b3 is true
常见可用于整数和浮点数的二元运算符有 +
、-
、*
和 /
。
(相对于一般规则而言,Go 在进行字符串拼接时允许使用对运算符 +
的重载,但 Go 本身不允许开发者进行自定义的运算符重载)
/
对于整数运算而言,结果依旧为整数,例如:9 / 4 -> 2
。
取余运算符只能作用于整数:9 % 4 -> 1
。
整数除以 0 可能导致程序崩溃,将会导致运行时的恐慌状态(如果除以 0 的行为在编译时就能被捕捉到,则会引发编译错误);第 13 章将会详细讲解如何正确地处理此类情况。
浮点数除以 0.0 会返回一个无穷尽的结果,使用 +Inf
表示。
可以将语句 b = b + a
简写为 b+=a
,同样的写法也可用于 -=
、*=
、/=
、%=
。
对于整数和浮点数,你可以使用一元运算符 ++
(递增)和 --
(递减),但只能用于后缀:
i++ -> i += 1 -> i = i + 1
i-- -> i -= 1 -> i = i - 1
同时,带有 ++
和 --
的只能作为语句,而非表达式,因此 n = i++
这种写法是无效的,其它像 f(i++)
或者a[i]=b[i++]
这些可以用于 C、C++ 和 Java 中的写法在 Go 中也是不允许的。
在运算时 溢出 不会产生错误,(参考智能合约语言solidity中得溢出数字漏洞,一般会加入safemath函数进行校验,参考美链智能合约数字溢出漏洞凭空产生大量代币)Go会简单地将超出位数抛弃。如果你需要范围无限大的整数或者有理数(意味着只被限制于计算机内存),你可以使用标准库中的 big
包,该包提供了类似 big.Int
和 big.Rat
这样的类型。
一些像游戏或者统计学类的应用需要用到随机数。rand
包实现了伪随机数的生成。
示例 random.go 演示了如何生成 10 个非负随机数:
package main
import (
"fmt"
"math/rand"
"time"
)
func main() {
for i := 0; i < 10; i++ {
a := rand.Int()
fmt.Printf("%d / ", a)
}
for i := 0; i < 5; i++ {
r := rand.Intn(8)
fmt.Printf("%d / ", r)
}
fmt.Println()
timens := int64(time.Now().Nanosecond()) //直接用时间得纳秒级作为随机数来源得依据
rand.Seed(timens)
for i := 0; i < 10; i++ {
fmt.Printf("%2.2f / ", 100*rand.Float32())
}
}
可能的输出:
816681689 / 1325201247 / 623951027 / 478285186 / 1654146165 /
1951252986 / 2029250107 / 762911244 / 1372544545 / 591415086 / / 3 / 0 / 6 / 4 / 2 /22.10
/ 65.77 / 65.89 / 16.85 / 75.56 / 46.90 / 55.24 / 55.95 / 25.58 / 70.61 /
函数 rand.Float32
和 rand.Float64
返回介于 [0.0, 1.0) 之间的伪随机数,其中包括 0.0 但不包括 1.0。函数 rand.Intn
返回介于 [0, n) 之间的伪随机数。
可以使用 Seed(value)
函数来提供伪随机数的生成种子,一般情况下都会使用当前时间的纳秒级数字。
有些运算符拥有较高的优先级,二元运算符的运算方向均是从左至右。下表列出了所有运算符以及它们的优先级,由上至下代表优先级由高到低:
优先级 运算符
7 ^ !
6 * / % << >> & &^
5 + - | ^
4 == != < <= >= >
3 <-
2 &&
1 ||
可以通过使用括号来临时提升某个表达式的整体运算优先级。
字符只是整数的特殊用例。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 进制数。
例如 char.go
var ch int = '\u0041'
var ch2 int = '\u03B2'
var ch3 int = '\U00101234'
fmt.Printf("%d - %d - %d\n", ch, ch2, ch3) // integer
fmt.Printf("%c - %c - %c\n", ch, ch2, ch3) // character
fmt.Printf("%X - %X - %X\n", 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 的字符串
包 unicode
包含了一些针对测试字符的非常有用的函数(其中 ch
代表字符):
unicode.IsLetter(ch)
unicode.IsDigit(ch)
unicode.IsSpace(ch)
这些函数返回一个布尔值。包 utf8
拥有更多与 rune 相关的函数。