运算符


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= yop是指算术运算符,例如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 / number2number2等于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++ 那样对指针做运算,之后有机会用到指针时,会再做相关说明。


展开阅读全文