跳转至

运算符与控制结构:程序的「加减乘除」和「岔路口」

这两章管两件事: 值和值之间怎么运算(运算符),以及程序走到岔路口往哪走、 同一个动作重复多少次(控制结构)。读完你会拿到三个 Go 和别的语言不一样、 但天天会撞上的细节:整数相除砍掉小数、switch 默认只执行一个 case、 Go 没有 while 循环。

1. 运算符总览:六类,一张表

运算符是代表某种运算的符号,作用在一个或几个「操作数」(参与运算的值)上,产生一个结果1。 Go 的运算符分六类:

类别 成员 产出什么
算术 + - * / % ++ -- 数
关系 == != < > <= >= 布尔(true/false)
逻辑 && \|\| ! 布尔
位 & \| ^ << >> &^ 按二进制位逐位算的数
赋值 = += -= *= /= %= <<= >>= &= \|= ^= &^= 给变量换新值
杂项 &(取地址) *(按地址取值) <-(channel 收发) 指针与并发用,第 10、16 章细讲

主线例子:一次算完四则运算

照原书的例子走一遍(a = 12,b = 3)2:

  a+b   → 15      加法
  a-b   → 9       减法
  a*b   → 36      乘法
  a/b   → 4       除法——注意:12÷3 恰好整除,看不出门道
  a%b   → 0       取余:12 除以 3 余 0
  a++   → a 变 13 自增一
  b--   → b 变 2  自减一

图说:算术运算符分两种——二元(吃两个操作数,如 +)和一元(吃一个,如 ++)2。

细节一:整数相除,小数被直接砍掉

书里的例子恰好整除,看不出 Go 的一个硬规矩:两个整数相除,结果还是整数, 小数部分直接丢弃(不四舍五入)。12 / 5 的结果是 2,不是 2.4。 想要小数,至少一边得是浮点:12.0 / 5 得 2.4(补充,不在书里,来自通用知识—— 但这个行为由上一章「静态类型、不隐式转换」直接推得)。

细节二:++ 和 -- 是语句,不能塞进表达式

C/C++/Java 里可以写 a = b++;Go 里不行。++ 和 -- 只能独占一句, 它们没有「值」。这是 Go 刻意删掉的一类灵活性——少一种写法,少一类读码时的心智负担。

关系与逻辑:条件的原料

关系运算符比大小、判相等,产出布尔值,是 if 和循环的条件的原料3。 逻辑运算符把多个布尔条件粘起来:

符号 名字 什么时候为 true
&& 逻辑与 两边都为 true
\|\| 逻辑或 至少一边为 true
! 逻辑非 把 true 翻成 false,反之亦然

位运算:把整数当二进制看

位运算是把两个整数的二进制表示对齐,一位一位地算。 & 是两个位都为 1 才得 1;| 是有一个为 1 就得 1;^ 是两位不同得 1; << >> 是把整个二进制串往左/右推,空出来的补 04。

书里的演示:a 右移 5 位后打印 14。右移一位相当于除以 2(丢掉余数), 移 5 位相当于除 32——这个换算关系是位运算的全部实用价值所在 (标志位、权限、哈希(把任意数据算成一串固定长度的指纹)这类底层操作会用到, 日常业务代码极少碰)。

杂项三件套:提前混个脸熟

  • &a:取出变量 a 在内存里的地址(门牌号);
  • *p:按地址把值取回来(按门牌号进屋);
  • <-:channel 的收发,第 16 章专讲5。

书里还演示了怎么亲眼看一个变量的大小和地址—— unsafe.Sizeof(a) 告诉你变量占几个字节(int64 占 8 个), &a 打印地址,*(&a) 把值取回来6。「unsafe」这个名字是警告: 这个包能绕过 Go 的类型安全,非必要不用。

优先级与结合性:拿不准就加括号

优先级决定谁先算(乘除先于加减);结合性决定同级的从哪头开始算 (加减从左往右,赋值从右往左)7。原书给了一张完整优先级表, 从高到低的大致顺序:后缀 ++ -- > 一元 > 乘除取余 > 加减 > 移位 > 关系 > 相等 > 位与 > 位异或 > 位或 > 逻辑与 > 逻辑或 > 赋值7。

这张表不用背。工程上的通行答案是:复杂的表达式一律加括号—— 你省了记表的心,读你代码的人也省了。

2. 控制结构:程序的三岔路口和跑道

控制结构决定源代码的执行流向:走到某个条件,该进哪个分支; 同一个代码块,该重复多少次8。Go 只有三类:分支(if/else、switch)、 循环(for)、跳转(break、continue,以及退出程序的 os.Exit)。

主线例子:一条 if-else 链从头走到尾

照原书的例子,a = 100,b = 209:

  第 1 个判断:if a == b          → 100 == 20?不成立,跳过
  第 2 个判断:else if a % b == 0 → 100 % 20 = 0,成立!
      → 执行:打印「a is divisible by b」
  else 分支:永远不会走到(前面已经有人接住了)

图说:if-else 链从上往下找第一个成立的条件,找到就执行、然后整条链结束; 全不成立才轮到 else。else 不是必须的9。

书里还有一个 Go 独有的洁癖:左花括号 { 不能单独换行。 你把 { 写到下一行开头,编译器会报错——Go 对「意料之外的换行」非常严格9。

switch:多个 if-else 的整齐写法

分支一多时,if-else 链很难看,switch 把它收拾整齐: 把要比较的值放在 switch 后面,下面一排 case,哪个 case 的值对上就执行哪个; 都对不上,走 default10。

主线例子继续(a = 100)10:

  switch a
    case 20  → 不匹配
    case 50  → 不匹配
    case 100 → 匹配!打印「value of a is 100」
    default  → 不执行(switch 到此为止)

Go 的 switch 有一个和 C/Java 相反的设计:匹配到一个 case、执行完, 就自动退出 switch,后面的 case 不执行11。C 里忘了写 break 导致「穿透」 到下一个 case 是经典 bug;Go 直接把默认行为反过来——想穿透,得显式写 fallthrough11:

switch x {
case 1:
    fmt.Println("Case 1")
    fallthrough   // 显式声明:接着往下走
case 2:
    fmt.Println("Case 2")   // 即使 x 不是 2 也会执行
}

这个设计是 Go 哲学的样本:把最常见的正确行为设为默认,把危险的用法变成显式声明。

for:Go 唯一的循环,包打天下

书里先把话说死:Go 没有传统的 while 和 do-while 循环——但一个 for 能覆盖所有循环需求12。

完整的 for 有三个部分,用分号隔开13:

for i := 1; i < 10; i++ {   // ① 初始化;② 条件;③ 每轮结束后的动作
    result *= i
}
fmt.Println(result)          // → 362880(即 9×8×…×1)

走一遍:① i 从 1 开始 → ② 检查 i < 10 → 成立,执行循环体(result 乘上 i)→ ③ i 加一 → 回到 ② ……直到 i = 10,条件不成立,循环结束13。 两个语法细节:条件不用加括号(和 if 一样,Go 要你把花括号写规范); 花括号必须有,哪怕循环体只有一句13。

for 的另外两个形态(书里散落在例子中):

形态 样子 干什么
只留条件 for a < 5 { ... } 就是别的语言的 while
三个全不要 for { ... } 死循环,靠体内的 break 出来

break、continue 和带标签的 break

  • break:立刻退出所在的那一层循环;
  • continue:跳过这一轮剩下的部分,直接进下一轮14。

主线例子的最后一站(照原书,a 从 0 数到 4,但跳过 3)15:

  a=0 → 打印 0 → a=1 → 打印 1 → a=2 → 打印 2
  a=3 → 撞上 if a == 3 → 先把 a 加一变成 4,再 continue → 打印被跳过
  a=4 → 打印 4 → a=5 → 条件 a < 5 不成立,循环结束
  输出:value of a: 0 / 1 / 2 / 4   ← 3 缺席

带标签的 break 是 Go 给多层循环的逃生门。普通 break 只跳一层; 想从两层循环的里层直接跳到最外面,先给外层循环起一个名字(标签), 然后 break 名字16:

OuterLoop:
for i := 1; i <= 3; i++ {
    for j := 1; j <= 3; j++ {
        if i == 2 && j == 2 {
            break OuterLoop   // 直接跳出外层循环,两层一起结束
        }
    }
}

图说:i=1 的内层走完;i=2、j=2 时触发,两层循环同时结束。 (书里这个例子的「输出」一节贴错了内容——印的是另一个例子的输出; 程序本身的逻辑如上,可自行跑验16。)

退出整个程序:os.Exit

想让程序在半路立即终止、把控制权还给操作系统,用 os.Exit(0)。 书里演示了它的一个特性:它后面的语句不会执行17——

fmt.Println("string value is: ", a)
os.Exit(0)                        // 程序在这里就结束了
a = a + " world"                  // 永远轮不到
fmt.Println("updated string value is: ", a)   // 同上

输出只有第一行的 hello17。注意 Go 语言里没有一个叫 exit 的关键字, 这是标准库 os 包里的一个函数;原书把它称作「exit 语句」,以函数理解更准。 顺带一个和第 6 章有关的伏笔:os.Exit 是「立刻走,什么都不管」, 另一种更文明的收尾方式叫 defer,第 6 章讲。

3. 作者的判断与证据

这两章几乎全是语言规则,没有推测。需要向读者说明的事实性问题有三处:

书里的内容 我们的核对
运算符六分类与优先级表 与 Go 语言一致,属实
带标签 break 的例子 程序正确,但「输出」一节贴错了,属排版错误
把 os.Exit 称作「exit 语句」 它是 os 包的函数,不是关键字;机制描述(后面语句不执行)是对的

4. 边界与局限

  • 位运算、杂项运算在本章只有定义和一行例子,实际用途(位标志、内存对齐) 要等到写系统级代码才碰得到,此处记住符号即可;
  • 短路求值(&& 左边为 false 就不算右边、|| 左边为 true 就不算右边) 原书没有讲——这在「右边有副作用」时是要紧行为(补充,不在书里,来自通用知识);
  • for 配合 range 遍历数组、切片、map 的写法,属于第 8、14 章的内容, 本书把它放在那里讲,此处不抢跑。

5. 可带走的

  1. 运算符六类:算术、关系、逻辑、位、赋值、杂项(& * <-);
  2. 整数相除砍小数:12/5 是 2;要小数就先换成浮点;
  3. ++/-- 只能独占一句,不能写进表达式;
  4. 关系/逻辑运算产出布尔值,是 if 和 for 条件的原料;
  5. 优先级表不用背,复杂表达式加括号;
  6. if-else 链找第一个成立的条件;{ 不能换行;
  7. switch 默认只走一个 case,想穿透必须显式写 fallthrough——和 C 恰好相反;
  8. Go 没有 while,一个 for 包打天下:for 条件 {} 就是 while,for {} 是死循环;
  9. break 跳一层;给外层起标签 + break 标签 可以一次跳出多层;
  10. os.Exit(0) 让程序立刻终止,后面的语句不再执行。

6. 原文地图

主题 原书章 原文位置
运算符的定义、二元/一元 Operators text/12-fm-learning-go-programming.txt:43(搜「specific mathematical」) · text/12-fm-learning-go-programming.txt:52(搜「Binary operators」)
四则运算例子 a=12 b=3 Operators text/12-fm-learning-go-programming.txt:182(搜「ex. of '+' operator」)
关系运算符 Operators text/12-fm-learning-go-programming.txt:209(搜「Boolean result」)
逻辑与/或/非 Operators text/12-fm-learning-go-programming.txt:389(搜「Logical AND」)
位运算与右移例子 Operators text/12-fm-learning-go-programming.txt:500(搜「Bitwise AND」) · text/12-fm-learning-go-programming.txt:601(搜「a>>b is」)
赋值与杂项运算符(& * <-) Operators text/12-fm-learning-go-programming.txt:901(搜「Address operator」) · text/12-fm-learning-go-programming.txt:930(搜「Receive operator」)
Sizeof / 地址 / 取值演示 Operators text/12-fm-learning-go-programming.txt:970(搜「unsafe.Sizeof」)
优先级与结合性 Operators text/12-fm-learning-go-programming.txt:997(搜「Operator precedence decides」)
控制结构定义 Control Structures text/13-fm-learning-go-programming.txt:46(搜「define the flow」)
if-else 与花括号规矩 Control Structures text/13-fm-learning-go-programming.txt:55(搜「strict with unexpected newlines」) · text/13-fm-learning-go-programming.txt:103(搜「divisible by b」)
switch 与 default Control Structures text/13-fm-learning-go-programming.txt:106(搜「optional default case」)
fallthrough、默认不穿透 Control Structures text/13-fm-learning-go-programming.txt:163(搜「exits the switch after the first match」)
没有 while,for 包打天下 Control Structures text/13-fm-learning-go-programming.txt:202(搜「does not feature conventional while」)
for 三段式与 9! 例子 Control Structures text/13-fm-learning-go-programming.txt:208(搜「three parts」) · text/13-fm-learning-go-programming.txt:244(搜「362880」)
break 与带标签 break Control Structures text/13-fm-learning-go-programming.txt:253(搜「immediate termination」) · text/13-fm-learning-go-programming.txt:291(搜「break OuterLoop」)
continue 跳过本轮 Control Structures text/13-fm-learning-go-programming.txt:327(搜「skips the current iteration」)
os.Exit 与「后面语句不执行」 Control Structures text/13-fm-learning-go-programming.txt:405(搜「os.Exit(0)」) · text/13-fm-learning-go-programming.txt:420(搜「string value is: hello」)

  1. 出处:「Operators」第 43 段(text/12-fm-learning-go-programming.txt:43,搜「specific mathematical」)。 ↩

  2. 出处:「Operators」第 52、176–206 段(text/12-fm-learning-go-programming.txt:52,搜「Binary operators」;text/12-fm-learning-go-programming.txt:182,搜「ex. of '+' operator」)。a=12、b=3 的全部运算结果照原书。 ↩↩

  3. 出处:「Operators」第 209 段(text/12-fm-learning-go-programming.txt:209,搜「Boolean result」)。 ↩

  4. 出处:「Operators」第 500、601 段(text/12-fm-learning-go-programming.txt:500,搜「Bitwise AND」;text/12-fm-learning-go-programming.txt:601,搜「a>>b is」)。 ↩↩

  5. 出处:「Operators」第 901、930 段(text/12-fm-learning-go-programming.txt:901,搜「Address operator」;text/12-fm-learning-go-programming.txt:930,搜「Receive operator」)。 ↩

  6. 出处:「Operators」第 966–979 段(text/12-fm-learning-go-programming.txt:970,搜「unsafe.Sizeof」)。 ↩

  7. 出处:「Operators」第 997 段(text/12-fm-learning-go-programming.txt:997,搜「Operator precedence decides」)及其后的优先级表(第 1012–1267 段)。 ↩↩

  8. 出处:「Control Structures」第 46 段(text/13-fm-learning-go-programming.txt:46,搜「define the flow」)。 ↩

  9. 出处:「Control Structures」第 55、103 段(text/13-fm-learning-go-programming.txt:55,搜「strict with unexpected newlines」;text/13-fm-learning-go-programming.txt:103,搜「divisible by b」)。 ↩↩↩

  10. 出处:「Control Structures」第 106、160 段(text/13-fm-learning-go-programming.txt:106,搜「optional default case」;text/13-fm-learning-go-programming.txt:160,搜「value of a is 100」)。 ↩↩

  11. 出处:「Control Structures」第 163 段(text/13-fm-learning-go-programming.txt:163,搜「exits the switch after the first match」)。 ↩↩

  12. 出处:「Control Structures」第 202 段(text/13-fm-learning-go-programming.txt:202,搜「does not feature conventional while」)。 ↩

  13. 出处:「Control Structures」第 208、244 段(text/13-fm-learning-go-programming.txt:208,搜「three parts」;text/13-fm-learning-go-programming.txt:244,搜「362880」)。 ↩↩↩

  14. 出处:「Control Structures」第 253、327 段(text/13-fm-learning-go-programming.txt:253,搜「immediate termination」;text/13-fm-learning-go-programming.txt:327,搜「skips the current iteration」)。 ↩

  15. 出处:「Control Structures」第 342–381 段(text/13-fm-learning-go-programming.txt:372,搜「value of a: 0」;text/13-fm-learning-go-programming.txt:381,搜「value of a: 4」)。跳过 3 的输出照原书。 ↩

  16. 出处:「Control Structures」第 271–303 段(text/13-fm-learning-go-programming.txt:291,搜「break OuterLoop」)。原书该例的「Output」一节(第 309–325 段)误贴了另一例的输出,程序逻辑以代码为准。 ↩↩

  17. 出处:「Control Structures」第 384–420 段(text/13-fm-learning-go-programming.txt:405,搜「os.Exit(0)」;text/13-fm-learning-go-programming.txt:420,搜「string value is: hello」)。 ↩↩