Go 语言中的运算符,大致上与 C 语系的语言中提供的运算符差不多,其中&
、*
也用来作为指针(Pointer)运算符。
算术运算符
算术运算符作用于数值,产生与第一个运算数相同类型的结果。+
、-
、*
、/
四个运算符,可用于整数、浮点数与复数;+
也用于字符串连接;%
余除运算符,只用于整数,&
、|
、^
、&^
位运算符只用于整数,<<
、>>
位移运算符只用于整数。
+
、-
、*
、/
使用上应该没什么问题,主要就是注意运算的顺序是先乘除后加减,必要时使用括号让顺序清楚,例如:
package main
import "fmt"
func main() {
fmt.Println(1 + 2*3) // 7
fmt.Println(2 + 2 + 8/4) // 6
fmt.Println((2 + 2 + 8) / 4) // 3
fmt.Println(10 % 3) // 1
}
%
运算符计算的结果是除法后的余数,例如上头10 % 3
会得到余数 1。
对于递增与递减 1 的操作,Go 可以使用++
与--
的操作,不过,++
与--
只能置于变量后方,而且是个陈述,因此,对于i := 1
,你可以在一行陈述中写i++
或i--
,不过,不能写fmt.Println(i++)
,这样就能避免是要先返回i
值再递增i
,还是先递增i
再返回 1 的问题。
在二进制运算上有 AND、OR、XOR 等运算,底下是 Go 中的一些例子:
package main
import "fmt"
func main() {
fmt.Println("AND运算:")
fmt.Printf("0 AND 0 %5d\n", 0&1)
fmt.Printf("0 AND 1 %5d\n", 0&1)
fmt.Printf("1 AND 0 %5d\n", 1&0)
fmt.Printf("1 AND 1 %5d\n", 1&1)
fmt.Println("\nOR运算:")
fmt.Printf("0 OR 0 %6d\n", 0|0)
fmt.Printf("0 OR 1 %6d\n", 0|1)
fmt.Printf("1 OR 0 %6d\n", 1|0)
fmt.Printf("1 OR 1 %6d\n", 1|1)
fmt.Println("\nXOR运算:")
fmt.Printf("0 XOR 0 %5d\n", 0^0)
fmt.Printf("0 XOR 1 %5d\n", 0^1)
fmt.Printf("1 XOR 0 %5d\n", 1^0)
fmt.Printf("1 XOR 1 %5d\n", 1^1)
fmt.Println("\nAND NOT运算:")
fmt.Printf("0 AND NOT 0 %5d\n", 0&^0)
fmt.Printf("0 AND NOT 1 %5d\n", 0&^1)
fmt.Printf("1 AND NOT 0 %5d\n", 1&^0)
fmt.Printf("1 AND NOT 1 %5d\n", 1&^1)
}
执行结果如下:
AND运算:
0 AND 0 0
0 AND 1 0
1 AND 0 0
1 AND 1 1
OR运算:
0 OR 0 0
0 OR 1 1
1 OR 0 1
1 OR 1 1
XOR运算:
0 XOR 0 0
0 XOR 1 1
1 XOR 0 1
1 XOR 1 0
AND NOT运算:
0 AND NOT 0 0
0 AND NOT 1 0
1 AND NOT 0 1
1 AND NOT 1 0
位运算是逐位运算,例如 10010001 与 01000001 作 AND 运算,是一个一个位对应运算,答案就是 00000001。补数运算是将所有位 0 变 1,1 变 0。例如 00000001 经补数运算就会变为 11111110。Go 的补数运算符是^
,例如:
package main
import "fmt"
func main() {
number := 0
fmt.Println(^number) // -1
}
上面的程序片段会显示 -1,因为 number 在内存中全部位都是 0,经补数运算全部位就都变成 1,这个数在电脑中用整数表示则是 -1。
<<
左移运算符会将所有位往左移指定位数,左边被挤出去的位会被丢弃,而右边补上 0;>>
右移运算则是相反,会将所有位往右移指定位数,右边被挤出去的位会被丢弃,至于最左边补上原来的位,如果左边原来是 0 就补0,1 就补 1。
package main
import "fmt"
func main() {
number := 1
fmt.Printf("2 的 0 次方: %d\n", number) // 1
fmt.Printf("2 的 1 次方: %d\n", number << 1) // 2
fmt.Printf("2 的 2 次方: %d\n", number << 2) // 4
fmt.Printf("2 的 3 次方: %d\n", number << 3) // 8
}
实际来左移看看就知道为何可以如此作次方运算了:
00000001 -> 1
00000010 -> 2
00000100 -> 4
00001000 -> 8
对于一个算术运算x = x op y
,可以写成x op= y
,op
是指算术运算符,例如x = x + y
,可以写成x += y
,这也就是所谓的指定运算符。
比较运算
数学上有大于、等于、小于的比较运算,Go 中也提供了这些运算符,它们有大于(>
)、不小于(>=
)、小于(<
)、不大于(<=
)、等于(==
)以及不等于(!=
),比较条件成立时用true
表示,比较条件不成立用false
表示。以下程序片段示范了几个比较运算的使用:
package main
import "fmt"
func main() {
fmt.Printf("10 > 5 结果 %t\n", 10 > 5) // true
fmt.Printf("10 >= 5 结果 %t\n", 10 >= 5) // true
fmt.Printf("10 < 5 结果 %t\n", 10 < 5) // false
fmt.Printf("10 <= 5 结果 %t\n", 10 <= 5) // false
fmt.Printf("10 == 5 结果 %t\n", 10 == 5) // false
fmt.Printf("10 != 5 结果 %t\n", 10 != 5) // true
}
==
与!=
只能用在 comparable 的运算数上,这有一套严格规则,Go 语言中哪些值是可以比较的,可以参考规格书中〈Comparison operators〉的说明。
Go 中没有?:
三元条件运算符。
逻辑运算
在逻辑上有所谓的「且」、「或」与「反相」,在 Go 中提供对应的逻辑运算符(Logical operator),分别为&&
、||
及!
。看看以下的程序片段会输出什么结果?
package main
import "fmt"
func main() {
number := 75
fmt.Println(number > 70 && number < 80) // true
fmt.Println(number > 80 || number < 75) // false
fmt.Println(!(number > 80 || number < 75)) // true
}
&&
与||
有短路运算(Short-Circuit Evaluation)。因为&&
只要其中一个为假,就可以判定结果为假,所以只要左运算数评估为false
,就会直接返回false
,不会再去运算右运算数。因为||
只要其中一个为真,就可以判定结果为真,所以只要左运算数评估为true
,就会直接返回true
,就不会再去运算右运算数。
来举个运用短路运算的例子,在 Go 中两个整数相除,若除数为 0 会发生 integer divide by zero 的错误,以下运用&&
短路运算避免了这个问题:
if(number2 != 0 && number1 / number2 > 1) {
fmt.Println(number1 / number2)
}
在这个程序片段中,变量 number1 与 number2 都是int
类型,如果number2
为 0 的话,&&
左边运算数结果就是false
,直接判断整个&&
的结果应是false
,不用再去评估右运算数,从而避免了number1 / number2
而number2
等于0
时的除零错误。
指针
Go 语言中有指针(Pointer),你可以在定义变量时于类型前加上*
,这表示创建一个指针,例如:
var i *int
这时i
是个空指针,也就是值为nil
,上头等同于var i *int = nil
,目前并没有存储任何地址,如果想让它存储另一个变量的内存地址,可以使用&
获取变量地址并指定给i
,例如:
package main
import "fmt"
func main() {
var i *int
j := 1
i = &j
fmt.Println(i) // 0x104382e0 之类的值
fmt.Println(*i) // 1
j = 10
fmt.Println(*i) // 10
*i = 20
fmt.Println(j) // 20
}
j
的位置存储了 1,那么具体来说,j
的位置到底是在哪?这就是&
取址运算的目的,&j
具体获取了j
的位置,然后指定给i
。
如上所示,如果想访问指针地址处的变量存储的值,可以使用*
,因而,你改变j
的值,*i
获取的就是改变后的值,透过*i
改变值,从j
获取的也会是改变后的值。
其应用的实例之一是使用fmt.Scanf
获取标准输入时,例如:
package main
import "fmt"
func main() {
var input int
fmt.Printf("输入数字")
fmt.Scanf("%d", &input)
fmt.Println(input)
}
这边使用&input
取出input
的内存地址值,并传入fmt.Scanf
函数,函数中会获取使用者的标准输入,并存储至input
变量的内存地址,因而,再度获取input
的值时,就会是使用者输入的值。
Go 虽然有指针,不过不能如同 C/C++ 那样对指针做运算,之后有机会用到指针时,会再做相关说明。