Skip to content

操作符与运算符

本文档详细介绍Go语言中的各种操作符和运算符,包括算术、比较、逻辑、位运算等操作符的使用方法和优先级。

📋 目录

运算符概述

Go语言提供了丰富的运算符来处理各种数据操作。运算符按功能可以分为以下几类:

类别运算符说明
算术运算符+, -, *, /, %基本数学运算
比较运算符==, !=, <, <=, >, >=值比较
逻辑运算符&&, ||, !布尔逻辑运算
位运算符&, |, ^, <<, >>, &^位级操作
赋值运算符=, +=, -=, *=, /=, %=赋值操作
其他运算符&, *, <-, .地址、指针、通道、选择器

算术运算符

基本算术运算

go
package main

import "fmt"

func main() {
    a, b := 10, 3
    
    fmt.Printf("a + b = %d\n", a+b)  // 加法:13
    fmt.Printf("a - b = %d\n", a-b)  // 减法:7
    fmt.Printf("a * b = %d\n", a*b)  // 乘法:30
    fmt.Printf("a / b = %d\n", a/b)  // 除法:3 (整数除法)
    fmt.Printf("a %% b = %d\n", a%b) // 取模:1
}

浮点数运算

go
func floatOperations() {
    x, y := 10.5, 3.2
    
    fmt.Printf("%.2f + %.2f = %.2f\n", x, y, x+y)  // 13.70
    fmt.Printf("%.2f - %.2f = %.2f\n", x, y, x-y)  // 7.30
    fmt.Printf("%.2f * %.2f = %.2f\n", x, y, x*y)  // 33.60
    fmt.Printf("%.2f / %.2f = %.2f\n", x, y, x/y)  // 3.28
    // 注意:浮点数不支持取模运算
}

字符串连接

go
func stringConcatenation() {
    str1 := "Hello"
    str2 := "World"
    
    result := str1 + " " + str2  // 字符串连接
    fmt.Println(result)          // 输出:Hello World
}

比较运算符

数值比较

go
func numericComparison() {
    a, b := 10, 20
    
    fmt.Printf("a == b: %t\n", a == b)  // false
    fmt.Printf("a != b: %t\n", a != b)  // true
    fmt.Printf("a < b: %t\n", a < b)    // true
    fmt.Printf("a <= b: %t\n", a <= b)  // true
    fmt.Printf("a > b: %t\n", a > b)    // false
    fmt.Printf("a >= b: %t\n", a >= b)  // false
}

字符串比较

go
func stringComparison() {
    str1, str2 := "apple", "banana"
    
    fmt.Printf("str1 == str2: %t\n", str1 == str2)  // false
    fmt.Printf("str1 < str2: %t\n", str1 < str2)    // true (字典序)
    fmt.Printf("str1 > str2: %t\n", str1 > str2)    // false
}

逻辑运算符

布尔逻辑

go
func logicalOperations() {
    a, b := true, false
    
    fmt.Printf("a && b: %t\n", a && b)  // 逻辑与:false
    fmt.Printf("a || b: %t\n", a || b)  // 逻辑或:true
    fmt.Printf("!a: %t\n", !a)          // 逻辑非:false
    fmt.Printf("!b: %t\n", !b)          // 逻辑非:true
}

短路求值

go
func shortCircuitEvaluation() {
    x := 0
    
    // 逻辑与的短路求值
    if x != 0 && 10/x > 1 {  // x != 0 为false,不会执行 10/x
        fmt.Println("This won't execute")
    }
    
    // 逻辑或的短路求值
    if x == 0 || 10/x > 1 {  // x == 0 为true,不会执行 10/x
        fmt.Println("This will execute")
    }
}

函数调用

基本函数调用

给定函数类型为 F 的表达式 f,可以使用参数列表调用函数:

go
f(a1, a2, ..., an)

函数调用示例:

go
import "math"

func callExamples() {
    // 标准库函数调用
    result := math.Atan2(1.0, 1.0)  // 函数调用
    fmt.Printf("atan2(1,1) = %.4f\n", result)
    
    // 方法调用
    var pt *Point
    pt.Scale(3.5)  // 方法调用,pt是接收者
}

函数调用特性

  1. 参数求值顺序:函数参数按从左到右的顺序求值
  2. nil函数调用:调用值为nil的函数会导致运行时panic
  3. 多返回值传递:如果函数返回值个数等于下一个函数的参数个数,可以直接传递
go
func Split(s string, pos int) (string, string) {
    return s[0:pos], s[pos:]
}

func Join(s, t string) string {
    return s + t
}

// 多返回值直接传递
func example() {
    value := "hello"
    if Join(Split(value, len(value)/2)) != value {
	log.Panic("test fails")
}

如果 x 的方法集中包含 m 那么 x.m() 是合法的。并且参数列表和 m 的参数列表相同。如果x是可寻址的,那么那么x指针的方法集(&x).m()可以简写成x.m()

go
var p Point
p.Scale(3.5)

没有方法类型,也没有方法字面值。

通过 ... 来传递参数

如果 f 的最后一个参数 p 的类型是 ...T。那么在函数内部 p 参数的类型就是 []T。如果 f 调用时没有传入 p 对应的参数,那么p为 nil。否则这些参数会以切片方式传入,在新的底层切片中。切片中的类型都是能赋值给类型 T 的值。这个切片的长度和容量在不同的调用中有所不同。

给定函数调用:

go
func Greeting(prefix string, who ...string)
Greeting("nobody")
Greeting("hello:", "Joe", "Anna", "Eileen")

Greeting 中,第一次调用时,who是 nil 类型。而在第二次调用时是[]string{"Joe", "Anna", "Eileen"}

如果在调用的时候的最后一个参数是[]T,那么我们可以使用...来将切片中的值依次赋值给参数列表。

给定切片s并且调用:

go
s := []string{"James", "Jasmine"}
Greeting("goodbye:", s...)

z 在 Greeting。中 who 会和切片 s 共享相同的底层数组。

操作符

操作符用来连接运算元。

go
Expression = UnaryExpr | Expression binary_op Expression .
UnaryExpr  = PrimaryExpr | unary_op UnaryExpr .

binary_op  = "||" | "&&" | rel_op | add_op | mul_op .
rel_op     = "==" | "!=" | "<" | "<=" | ">" | ">=" .
add_op     = "+" | "-" | "|" | "^" .
mul_op     = "*" | "/" | "%" | "<<" | ">>" | "&" | "&^" .

unary_op   = "+" | "-" | "!" | "^" | "*" | "&" | "<-" .

比较运算符在此处讨论。对于其他二元操作符,两个操作元的类型必须是相同的,除了位移和无类型常量。针对常量的操作,请看常量表达式章节。

除了位移操作,如果其中一个操作符是无类型常量,而另个不是,那么无类型的常量会转换成另一个运算元的类型。

在右移表达式中的运算元必须是无符号的整数或者可以转换成 uint 的无类型的常量。如果左移一个无类型常量那么结果依然是无类型的。他首先会转换成指定类型。

go
var s uint = 33
var i = 1<<s           // 1 has type int
var j int32 = 1<<s     // 1 has type int32; j == 0
var k = uint64(1<<s)   // 1 has type uint64; k == 1<<33
var m int = 1.0<<s     // 1.0 has type int; m == 0 if ints are 32bits in size
var n = 1.0<<s == j    // 1.0 has type int32; n == true
var o = 1<<s == 2<<s   // 1 and 2 have type int; o == true if ints are 32bits in size
var p = 1<<s == 1<<33  // illegal if ints are 32bits in size: 1 has type int, but 1<<33 overflows int
var u = 1.0<<s         // illegal: 1.0 has type float64, cannot shift
var u1 = 1.0<<s != 0   // illegal: 1.0 has type float64, cannot shift
var u2 = 1<<s != 1.0   // illegal: 1 has type float64, cannot shift
var v float32 = 1<<s   // illegal: 1 has type float32, cannot shift
var w int64 = 1.0<<33  // 1.0<<33 is a constant shift expression
运算符优先级

一元运算符拥有最高优先级。++ 和 -- 是语句而不是表达式,他们在运算符的优先级之外。所以 (*p)++ 和 *p++ 是一样的。

二元运算符有 5 个优先级。乘法运算符在最高级,紧接着是加法运算符。比较运算符,&& 运算符,最后是 ||。

go
Precedence    Operator
    5             *  /  %  <<  >>  &  &^
    4             +  -  |  ^
    3             ==  !=  <  <=  >  >=
    2             &&
    1             ||

相同优先级的二元运算符的执行顺序是由左到右。例如 x/y*z(x/y)*z 是一样的。

go
+x
23 + 3*x[i]
x <= f()
^a >> b
f() || g()
x == y+1 && <-chanPtr > 0

算数运算符

算数运算符应用在 2 个数字值之间,别切生成一个相同类型的值作为第一个运算元。四种算数运算符(+,-,*,/)应用在数字,浮点,复合类型之中。+ 也可以用于字符串。位运算和位移运算只适用于整数。

go
+    sum                    integers, floats, complex values, strings
-    difference             integers, floats, complex values
*    product                integers, floats, complex values
/    quotient               integers, floats, complex values
%    remainder              integers

&    bitwise AND            integers
|    bitwise OR             integers
^    bitwise XOR            integers
&^   bit clear (AND NOT)    integers

<<   left shift             integer << unsigned integer
>>   right shift            integer >> unsigned integer
数字运算符

对于两个整数 x 和 y。整数商 q=x/y 和余数 r=x%y 遵循以下规律。

go
x = q*y + r  and  |r| < |y|

x/y 截断为 0。

go
x     y     x / y     x % y
 5     3       1         2
-5     3      -1        -2
 5    -3      -1         2
-5    -3       1        -2

作为这个规则的例外情况,如果 x 非常大,那么 q=x/-1 等于 x。

go
x, q
int8                     -128
int16                  -32768
int32             -2147483648
int64    -9223372036854775808

如果除数是一个常量。那么它不能是 0,如果除数在运行时为 0,会导致运行时恐慌。如果除数是负数并且除数是:

go
x     x / 4     x % 4     x >> 2     x & 3
 11      2         3         2          3
-11     -2        -3        -3          1

位移运算符移动左侧运算元右侧元算元指定的位数。如果左侧是有符号整型,那它就实现了位移运算,如果是无符号整数使用逻辑位移。位移运算没有上限,位移操作让左边运算元位移 n 个 1。x<<1x*2 是相等的。并且 x>>1x/2 是相同的。

对于整数运算元,一元运算符+-^定义如下:

go
+x                          is 0 + x
-x    negation              is 0 - x
^x    bitwise complement    is m ^ x  with m = "all bits set to 1" for unsigned x
                                      and  m = -1 for signed x
整型溢出

对于无符号的值,运算符+-*和<<都是2禁止运算。这里的n是无符号类型的宽度,无符号整型将会丢弃溢出的位,并且程序将会返回wrap around

对于有符号的整数,操作符+=*<<都会溢出并且值存在,并且代表相应的有符号的值。在运算时不会抛出异常。标一起不会报错。所以不是所有情况下x<x+1都成立。

浮点数运算符

对于浮点数和其他复杂数字,+x和x是一样的,-x是x的对立面。除了IEEE-754还没有指定浮点数除0或者复数的结果。是否抛出异常将会依赖其具体实现。

一种实现可以合并多个浮点操作进一个操作,有可能是夸语句的,并且他的结果可能和依次单独执行的结果不一样。1个浮点数类型将会转变成目标的精度,防止四舍五入的融合。

go
// FMA allowed for computing r, because x*y is not explicitly rounded:
r  = x*y + z
r  = z;   r += x*y
t  = x*y; r = t + z
*p = x*y; r = *p + z
r  = x*y + float64(z)

// FMA disallowed for computing r, because it would omit rounding of x*y:
r  = float64(x*y) + z
r  = z; r += float64(x*y)
t  = float64(x*y); r = t + z
字符串

字符串可以使用+和+=操作符。

go
s := "hi" + string(c)
s += " and good bye"

字符串想家将会创建一个新的字符串。

比较运算符

比较运算符比较连个运算元,并且生成一个无类型的布尔值。

go
==    equal
!=    not equal
<     less
<=    less or equal
>     greater
>=    greater or equal

在任何比较运算元中2种类型必须是可以分配的。

使用等于运算符==!=的运算元必须是可比较的。使用顺序运算符<,<=,>>=必须是可比较的。这些限制导致比较运算符被定义成以下的方式。

  • 布尔值是可比较的,两个布尔值当他们同为true或者false的使用是相等的

  • 整数值是可比较和排序的

  • 浮点数是可比较和排序的,具体定义在IEEE-754标准中。

  • 复数是可比较的,2个复数当实部和虚部都相等时就是相等的。

  • 字符串是可以比较和排序的。是按照字节顺序排序。

  • 指针式可以排序的,连个指针当指向相同变量时是相同的,或者他们2个都是nil。指向一个为非配的变量的结果是未定义的。

  • channel是可比较的。当两个管道是用同一个make出来的,或者都是nil时时相等的。

  • 接口值时可以比较的,2个接口值时相等的如果2个标识符的动态类型是一样的或者他们都是nil。

  • 一个非接口类型的值x和一个接口类型的值T在非接口类型是可以比较的并且非接口类型实现了接口是是可以比较的。当他们的动态类型类型相同时时相等的。

  • 当结构体内的所有字段都是可以比较的时候,他是可以比较的。连个结构体的值当非空字段都相等时他们是相等的。

  • 数组类型的值时可比较的,如果数组的原属时可以比较的,那么当数组的所有值是相等的时候他们就是相等的。

使用两个动态类型的标识符来比较接口的值。如果这个类型的值时不可比较的,那么将会引起一个panic。这个行为不仅仅时接口,数组结构体接口字段都有这个问题。

切片,map,和函数值都是不可比较的,然而,作为一个特殊的例子,切片,map和函数的值的nil时可以比较的,指针,channel和接口的值nil也是可以比较的。

go
const c = 3 < 4            // c is the untyped boolean constant true

type MyBool bool
var x, y int
var (
	// The result of a comparison is an untyped boolean.
	// The usual assignment rules apply.
	b3        = x == y // b3 has type bool
	b4 bool   = x == y // b4 has type bool
	b5 MyBool = x == y // b5 has type MyBool
)

逻辑操作符

逻辑运算符使用布尔值值,并且生成一个相同类型的结果值作为操作元。右面的操作元计算是有条件的。

go
&&    conditional AND    p && q  is  "if p then q else false"
||    conditional OR     p || q  is  "if p then true else q"
!     NOT                !p      is  "not p"

地址操作符

以类型 T 的 x 作为运算元,取址操作 &x 会生成一个类型为 *T 并指向 x 的指针。运算元必须是能够取址的,它可以是一个变量,指针,切片的取值操作;或是一个可取址结构体的字段选择器;或是对于可取址数组的索引取值操作。作为寻址能力的例外,x 可能是一个复合字面值。如果对 x 进行取址操作将会 panic,&x 也会 panic。

对于一个 *T 类型的运算元 x,指针解引用 *x 表示 x 指向的 T 类型。如果 x 为 nil,那么解引用 *x 会 panic。

go
&x
&a[f(2)]
&Point{2, 3}
*p
*pf(x)

var x *int = nil
*x   // causes a run-time panic
&*x  // causes a run-time panic

接收操作符

对于管道类型的运算元 ch,接收操作 <-ch 返回值是管道 ch 接收到的值。带方向的管道需要有接受权限,接收操作的类型也是通道的元素类型。表达式会一直阻塞直到接收到返回值。从 nil 通道接收值会一直阻塞。从一个已经关闭的通道接收数据会在其他数据都被接收以后生成该通道元素类型的零值。

go
v1 := <-ch
v2 = <-ch
f(<-ch)
<-strobe  // wait until clock pulse and discard received value

接收数据的表达式可以使用赋值表达式。

go
x, ok = <-ch
x, ok := <-ch
var x, ok = <-ch
var x, ok T = <-ch

它还可以生成一个额外的无类型布尔值来表示通道是否关闭。如果 ok 为 true 说明获取到的是发送到通道内的数据,而 false 它就返回一个零值因为通道内没有元素且已经关闭。

基于 MIT 许可发布