1.操作符概览
Go 语言提供了丰富的操作符来操纵变量和执行程序逻辑。掌握这些操作符的语法和功能,是编写和理解 Go 代码的基础。
可以将 Go 语言的操作符主要分为以下几大类
分类 | 包含的操作符 |
算术操作符 | +、-、*、/、%、++、--等 |
关系操作符 | ==、!=、>、<、>=、<=等 |
逻辑操作符 | \&\&、||、!等 |
位操作符 | &、 |
赋值操作符 | =、+=、-=、&=、|=等 |
其他操作符 | &(取地址)、*(根据地址取值)、.(结构体字段访问)等 |
1.1 算术操作符
算术操作符用于执行基本的数学运算,例如加法、乘法等。
支持的算术操作符如下
操作符 | 描述 | 实例 |
+ | 加法 | x + y |
- | 减法 | x - y |
* | 乘法 | x * y |
/ | 除法 | x / y |
% | 求余 | x % y |
++ | 自增,变量值加 1 | x++ |
-- | 自减,变量值减 1 | x-- |
用一个简单的算术运算实例,来展示它们的基本用法
package main import "fmt" func main() { x := 10 y := 3 // 基本算数运算 fmt.Println(x + y) // 13 fmt.Println(x - y) // 7 fmt.Println(x * y) // 30 // 除法和取余 fmt.Println(x / y) // 3 fmt.Println(x % y) // 1 // 自增,自减 x++ fmt.Println(x) // 11 x-- fmt.Println(x) // 10 }
1.2 关系操作符
关系操作符用于比较两个值的大小关系,比较的结果为布尔类型,true 表示成立,false 表示不成立。
Go 语言支持的关系操作符如下
操作符 | 描述 | 实例 |
== | 等于,相同则为真 | x == y |
!= | 不等于,不同则为真 | x != y |
> | 大于,左操作数值>右操作数值则为真 | x > y |
< | 小于,左操作数值<右操作数值则为真 | x < y |
>= | 大于等于 | x >= y |
<= | 小于等于 | x <= y |
使用关系操作符进行比较运算的示例
package main import "fmt" func main() { x := 10 y := 3 // 相等与不等 fmt.Println(x == y) // false fmt.Println(x != y) // true // 大于与小于 fmt.Println(x > y) // true fmt.Println(x < y) // false // 大于等于与小于等于 fmt.Println(x >= y) // true fmt.Println(x <= y) // false}
1.3 逻辑操作符
逻辑操作符用于逻辑运算,最终结果是一个布尔值。
Go 语言支持以下逻辑操作符
操作符 | 描述 | 实例 |
&& | 逻辑 AND,两边都为真才为真 | x && y |
|| | 逻辑 OR,两边任一为真则为真 | x || y |
! | 逻辑 NOT,真为假,假为真 | !x |
使用示例:
package main import "fmt" func main() { x := true y := false // 逻辑与运算 fmt.Println(x && y) // false // 逻辑或运算 fmt.Println(x || y) // true // 逻辑非运算 fmt.Println(!x) // false}
2. 操作符使用细节
Go 语言的各类操作符及基本使用方法。
2.1 算术操作符类型支持
算术操作符可以处理多种类型的数据,同时也有一些使用上的限制。
1.整数
Go 语言中的整型可以进行所有算术及位运算。操作数为整数时,遵循数学运算的实数域规则:
package main import "fmt" func main() { var int1 int = 10 var int2 int32 = 3 var res int // 支持不同整型混合运算 res = int1 + int2 fmt.Println(res) // 支持取余运算 res = int1 % int2 fmt.Println(res) // 结果类型由操作数类型决定 fmt.Printf("%T\n", res) //int32}
需要注意的是,不同整数类型混合运算时,结果类型由操作数中的较大整数类型确定。
2.浮点数
Go 语言支持两种浮点数类型:float32 和 float64。浮点数运算遵循数学运算规则,但存在精度损失问题:
package main import ( "fmt" "math") func main() { // math包定义浮点数常量 var f1 = math.Pi var f2 = math.E // 支持基本算术运算 fmt.Println(f1 + f2) // 小数可能存在精度损失 fmt.Println(f1 - f2) }
3.复数
complex64 和 complex128 两种复数类型也可以进行基本的加减乘除运算:
package main import ( "fmt" "math/cmplx") func main() { // 定义两个复数 var c1 = 1 + 2i var c2 = 3 + 4i // 支持算术运算 fmt.Println(c1 + c2) fmt.Println(c1 - c2)}
需要注意复数同实数混合运算是不被支持的。
2.2 关系操作符的类型检查
关系操作符支持比较多种类型,但也存在一定的限制。
1. 基本数据类型
整数、浮点数、字符等基本类型直接使用关系操作符 执行 比较:
package main import "fmt" func main() { var int1 = 1 var int2 = 2 // 比较整数 fmt.Println(int1 > int2) // false var float1 = 1.2 var float2 = 2.3 // 比较浮点数 fmt.Println(float1 < float2) // true var char1 = 'a' var char2 = 'b' // 比较字符 fmt.Println(char1 != char2) // true}
2. 数组及切片
数组和切片不能直接比较,需要循环比较元素才能判断关系:
package main import "fmt" func main() { slice1 := []int{1, 2, 3} slice2 := []int{1, 2, 4} // 直接比较切片是非法的 // fmt.Println(slice1 == slice2) // 循环比较切片元素 for i := 0; i < len(slice1); i++ { if slice1[i] != slice2[i] { fmt.Println("slice1 != slice2") return } } fmt.Println("slice1 == slice2") }
3. 指针
指针之间可以使用 == 和 != 操作符进行比较,比较的是指针的值(内存地址):
package main import "fmt" func main() { var ptr1, ptr2 *int var num = 100 ptr1 = &num ptr2 = &num // 比较指针地址 if ptr1 == ptr2 { fmt.Println("Pointers are equal") }}
需要注意的是 Go 语言中没有指针运算,不能对指针进行算术比较。
2.3 逻辑操作符的短路效果
逻辑操作符 && 和 || 具有短路效果, 也就是说如果通过第一个操作数就能确定表达式的值,则不会再计算第二个操作数。
利用这个特点,可以简化代码:
package main func testShortCircuit() bool { println("testShortCircuit called") return true;} func main() { // 使用逻辑与运算短路 if testShortCircuit() && testShortCircuit() { println("do something") }}
在上例中,因为 && 是逻辑与,如果第一个操作数返回 false,则整个表达式结果一定为 false,不会再调用第二个操作数。
类似原理,也可利用逻辑或的短路效应简化代码。
2.4 利用位操作进行状态控制
Go 语言内建的位操作符使其可以轻松实现状态控制这样的操作:
package main const ( Open = 1 << iota Close Pending) var state = Open | Pending func main() { // 测试状态 if state&Open == Open { println("Open") } if state&Close == Close { println("Close") } // 修改状态 state &^= Pending println(state)}
用对比、与(&)、或(|)、异或(^)等位操作,可以有效控制程序状态。
2.5 赋值操作符的链式应用
Go 语言的赋值操作符比较特殊,它支持链式操作:
package main import "fmt" func main() { // 链式赋值操作 x, y := 1, 2 x, y = y, x+y fmt.Println(x, y)}
这样一行代码就交换了 x 与 y 的值。链式赋值顺序为从右到左。
利用这个特性,可以编写非常简洁的交换变量代码。同理,也可以实现链式增减等操作。
3. Go 语言操作符的运算顺序
在表达式中出现多个操作符时,运算的顺序会直接影响结果。Go 语言定义了一套完整的运算顺序规则。
3.1 操作符优先级表
当存在不同优先级的操作符组合时,Go 按照以下表中从上到下优先级顺序计算:
分类 | 操作符 |
一元操作符 | +、-、!、^等 |
乘除法 | *、/、%、<<、>>等 |
加减法 | +、- |
关系运算 | ==、<、<=等 |
逻辑与 | && |
逻辑或 | || |
在同一优先级中,按照操作符出现顺序从左至右计算。
看一个具体的运算顺序示例:
package main import "fmt" func main() { x := 6 + 3*4/2 - 1 fmt.Println(x) //11 y := true || false && true fmt.Println(y) //true}
示例按照上表优先级规则从高到低顺序计算,很好解释结果。
3.2 利用括号调整优先级
如果想调整某个子表达式的优先级,可利用括号来明确控制运算顺序:
package main import "fmt" func main() { x := (3 + 4) * 5 fmt.Println(x) //35 y := 3 + 4*5 fmt.Println(y) //23 z := true && (false || true) fmt.Println(z) //true}
类似数学约定,添加括号可以提高该组运算优先级,先行计算。
3.3 赋值运算通常最后运行
对于组合表达式,Go 语言保证所有的赋值运算通常都是最后运行:
package main func main() { x, y := 2, 3 // +会先运行,然后再赋值 a := x + y println(a) // /也先运行 b := x / y println(b)}
这符合预期表达式应该先运算而后取值的思维逻辑。
学会利用括号调整运算顺序,并注意每个操作符优先级,可以避免很多意外情况的出现。
4. 操作符实战示例
理解了基本操作符的用法之后,来看一些实际应用场景下的操作符代码示例,让这些抽象的概念具象化,加深理解。
这些示例覆盖字符串、位运算、复数等多个方面,都是可运行的完整代码。
4.1 利用关系运算符处理字符串
利用关系运算符,可以优雅实现字符串开头或结尾的判断:
package main import "fmt" func main(){ var str = "Hello World" // 判断以Hello开头 if "Hello" == str[0:5] { fmt.Println("string starts with Hello") } // 判断以orld结尾 if "orld" == str[len(str)-5:] { fmt.Println("string ends with orld") } }
再如判断字符串包含或不包含某子串也可以利用运算符实现:
package main import ( "fmt" "strings") func main() { var str = "Hello World" // 判断是否包含某子串 if strings.Contains(str, "World") { fmt.Println("string contains World") } // 判断是否不包含 if !strings.Contains(str, "foo") { fmt.Println("string does not contain foo") }}
通过合理灵活运用关系运算符,可以使代码更简洁易读。
4.2 位操作实现集合运算
Go 语言的位操作符可以实现高效的集合运算。可构建如下的使用示例
package main func main() { var A = 4 // 100 var B = 6 // 110 // 并集 var union = A | B println(union) // 110 // 交集 var intersect = A & B println(intersect) // 0 // 差集 var diff = A &^ B println(diff) // 100}
对比关系运算符,位操作符可以快速判断两个集合的包含和交叉情况。
再如根据特征位确定对象状态等也可以利用位操作实现。
4.3 复数的基本运算
Go 语言内置对复数的支持。可进行一些基本的复数运算
package main import ( "fmt" "math/cmplx") func main() { // 定义两个复数 var c1 = 1 + 2i var c2 = 3 + 4i // 复数支持加减乘除运算 fmt.Println(c1 + c2) fmt.Println(c1 - c2) fmt.Println(c1 * c2) // 取复数的modulus fmt.Println(cmplx.Abs(c1)) }
输出结果:
(4+6i)(-2-2i)(-5+10i)2.23606797749979
Go 语言内置的复数支持让开发者可以方便处理类似信号处理、图像处理等需要域运算的场景。
5. 操作符的最佳实践
通过上面内容的详细介绍,已全面了解了 Go 语言中操作符的种类、语法细节和实战应用等内容。
将尝试总结归纳操作符的最佳实践和使用经验。
5.1 采用简单明了的风格
虽然 Go 语言提供了大量操作符供选择,但在实践中, 仍要注意采用简单明了的风格:
优先考虑基本的算术、逻辑、关系运算符,避免使用过于花哨的位运算符
注意合理利用操作符的优先级和关联性,减少不必要的括号
多用常量,变量命名表达业务意图,不要依赖运算符实现业务
避免多行复杂的运算表达式,分解为简单的语句
比如实现条件判断,推荐:
if x > 0 && matchType(y) { ... }
不推荐:
if (0 < x) && (&TYP1 == (*int)(&y)) { ...}
针对不同场景采用不同的风格可以提高代码可读性。
5.2 注意操作符之间的交互作用
同时使用多个操作符时,要注意它们之间可能存在的交互作用:
比较运算与 nil 值的关系需要注意
位运算可以实现判断状态,但复杂时难懂
新旧变量值的交换,要留意计算顺序
譬如交换两个值需要注意赋值 优先级
x, y = y, x // 错误 tmp := x; x = y; y = tmp // 正确
5.3 合理利用操作符简化代码
熟练掌握操作符的相关技巧,可以合理简化代码:
& 取地址和 * 取值访问指针,可以避免 副本
关系运算符判断起始,可以减少重复代码
复合赋值运算简化重复计算
逻辑运算短路实现优雅分支
一元运算符结合 defer 优雅获取错误
比如 defer 方式获取错误信息:
func f() error { var err error defer func() { if r := recover(); r != nil { err = r.(error) // 将panic转换为错误 } }() //业务逻辑 ... return err }
6. 总结
运算是编程语言的重要组成部分。Go 语言提供全部的操作符对变量进行运算和操作。
这些操作符涵盖算术、逻辑、位运算、赋值等各个方面。熟练掌握这些操作符的语法及细节非常必要。
同时编写代码过程中,运用这些操作符解决实际问题可以大大提高效率。不过需要注意一些最佳实践,避免常见的陷阱。
希望这篇文章可以帮助读者全面了解 Go 语言操作符的世界,更好地编写灵活、高效的 Go 代码。