Go中表达式可置于赋值号右边、if条件或函数参数位置,如a + b、len(s);语句如for range则不可,因表达式求值而语句执行动作,二者不可混淆。

表达式必须能求出一个值,语句不能放在需要值的地方
Go里最直接的区分标准是:**能不能写在赋值号 = 右边、if 条件里、函数参数位置**。能放进去的,就是表达式;放不进去的,就是语句。
-
a + b、len(s)、strings.TrimSpace(str)都是表达式——它们求完值,立刻能用 -
if x > 0 { ... }、for i := 0; i 、return err全是语句——它们干完事就结束,不“产出”值 - 常见错误:
x := if true { 1 } else { 2 }直接报错,因为if是语句,不是表达式;想实现类似逻辑,得用三元替代写法(比如封装成函数或用短变量声明)
赋值语句左边必须可寻址,右边必须是表达式
Go对赋值有严格约束:左边得是个“能取地址”的东西,右边必须是个有结果的表达式。这决定了哪些写法合法、哪些看似合理却编译不过。
- 合法:
s[0] = 'a'(切片索引可寻址)、*p = 42(指针解引用可寻址)、x = 1 + 2(右边是表达式) - 非法:
1 + 2 = x(左边不是可寻址操作数)、f() = 10(函数调用不可寻址)、if true { 1 } = 5(if是语句,不能当左值) - 容易忽略的坑:
const pi = 3.14定义的是常量,pi = 3.1415会报cannot assign to pi,不是因为它是表达式,而是它不可寻址——常量名本身不是左值
函数调用是不是表达式,取决于有没有返回值
Go中函数调用是否算表达式,不看它“有没有副作用”,而只看它“有没有返回值”。这个细节影响 map 赋值、通道接收、错误处理等高频场景。
- 有返回值 → 表达式:
fmt.Sprintf("x=%d", x)、m["key"](map 访问,返回 value 和 bool)、(通道接收,返回接收到的值) - 无返回值 → 语句:
fmt.Println("hello")、ch (发送是语句)、time.Sleep(100 * time.Millisecond) - 典型误用:
err := os.Remove("file.txt")合法,因为os.Remove返回error;但v := fmt.Print("ok")报错,因为fmt.Print返回(int, error)?不对——它返回(int, error),所以其实是多值表达式,但你不能直接:=给单个变量,得写成n, err := fmt.Print("ok")
没有“表达式语句”这种东西,别套其他语言经验
Python 或 JavaScript 中,x = 1 既是语句又能当表达式(比如 if (x = 1) {...}),但 Go 彻底切断了这条路。这不是限制,而是防止隐式副作用和歧义。
立即学习“go语言免费学习笔记(深入)”;
- 所有控制结构都是纯语句:
if、for、switch、select、defer、go—— 它们永远不能嵌入表达式上下文 - 想让逻辑更紧凑?用立即执行函数:
res := func() int { if x > 0 { return 1 } else { return 0 } }(),这是合法的,因为函数字面量本身是表达式,调用它也是表达式 - 最容易栽跟头的地方:把
for range当成能返回值的结构,比如想写keys := for k := range m { ... }—— 不行,for就是语句,没得商量
Go 的设计选择很明确:表达式负责“算出什么”,语句负责“做什么”。混淆二者,往往不是语法记不清,而是试图把别的语言习惯强加给 Go。尤其在重构时,看到一堆 if 块想提成表达式,先停一下——它本就不该是表达式。










