运算符与控制结构:程序的「加减乘除」和「岔路口」¶
这两章管两件事: 值和值之间怎么运算(运算符),以及程序走到岔路口往哪走、 同一个动作重复多少次(控制结构)。读完你会拿到三个 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:
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. 可带走的¶
- 运算符六类:算术、关系、逻辑、位、赋值、杂项(& * <-);
- 整数相除砍小数:
12/5是2;要小数就先换成浮点; - ++/-- 只能独占一句,不能写进表达式;
- 关系/逻辑运算产出布尔值,是 if 和 for 条件的原料;
- 优先级表不用背,复杂表达式加括号;
- if-else 链找第一个成立的条件;
{不能换行; - switch 默认只走一个 case,想穿透必须显式写 fallthrough——和 C 恰好相反;
- Go 没有 while,一个 for 包打天下:
for 条件 {}就是 while,for {}是死循环; - break 跳一层;给外层起标签 +
break 标签可以一次跳出多层; 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」) |
-
出处:「Operators」第 43 段(
text/12-fm-learning-go-programming.txt:43,搜「specific mathematical」)。 ↩ -
出处:「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 的全部运算结果照原书。 ↩↩ -
出处:「Operators」第 209 段(
text/12-fm-learning-go-programming.txt:209,搜「Boolean result」)。 ↩ -
出处:「Operators」第 500、601 段(
text/12-fm-learning-go-programming.txt:500,搜「Bitwise AND」;text/12-fm-learning-go-programming.txt:601,搜「a>>b is」)。 ↩↩ -
出处:「Operators」第 901、930 段(
text/12-fm-learning-go-programming.txt:901,搜「Address operator」;text/12-fm-learning-go-programming.txt:930,搜「Receive operator」)。 ↩ -
出处:「Operators」第 966–979 段(
text/12-fm-learning-go-programming.txt:970,搜「unsafe.Sizeof」)。 ↩ -
出处:「Operators」第 997 段(
text/12-fm-learning-go-programming.txt:997,搜「Operator precedence decides」)及其后的优先级表(第 1012–1267 段)。 ↩↩ -
出处:「Control Structures」第 46 段(
text/13-fm-learning-go-programming.txt:46,搜「define the flow」)。 ↩ -
出处:「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」)。 ↩↩↩ -
出处:「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」)。 ↩↩ -
出处:「Control Structures」第 163 段(
text/13-fm-learning-go-programming.txt:163,搜「exits the switch after the first match」)。 ↩↩ -
出处:「Control Structures」第 202 段(
text/13-fm-learning-go-programming.txt:202,搜「does not feature conventional while」)。 ↩ -
出处:「Control Structures」第 208、244 段(
text/13-fm-learning-go-programming.txt:208,搜「three parts」;text/13-fm-learning-go-programming.txt:244,搜「362880」)。 ↩↩↩ -
出处:「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」)。 ↩ -
出处:「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 的输出照原书。 ↩ -
出处:「Control Structures」第 271–303 段(
text/13-fm-learning-go-programming.txt:291,搜「break OuterLoop」)。原书该例的「Output」一节(第 309–325 段)误贴了另一例的输出,程序逻辑以代码为准。 ↩↩ -
出处:「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」)。 ↩↩