go无while/do-while和三元运算符;if与for可省括号但花括号不可省,且if支持初始化语句(如if err:=f(); err!=nil{…}),变量作用域限于对应分支;for是唯一循环结构,有类C、while、无限循环三种形式。

Go 里没有 while、do-while,也不支持三元运算符;if 和 for 的括号可以省,但花括号不能省——这是写错最多的地方。
Go 的 if 语句必须带花括号,且支持初始化语句
常见错误是照搬其他语言习惯,写成 if x > 0 { ... } else { ... } 看似正常,但一旦漏掉花括号(比如只有一行想省略),编译直接报错:syntax Error: unexpected else, expecting }。
Go 要求所有分支都必须用花括号包裹,哪怕只有一行。另外,if 后可紧跟初始化语句,变量作用域仅限该 if 块:
if err := someFunc(); err != nil { log.Fatal(err) } else if n := len(data); n == 0 { fmt.Println("empty") }
- 初始化语句和条件表达式之间用分号隔开,不是逗号
-
err和n在各自分支外不可访问,避免污染外层作用域 - 不支持
if x > 0 ? a : b这类三元写法,得用完整if/else
for 是 Go 唯一的循环结构,三种写法对应不同场景
Go 没有 while 或 do-while,所有循环都靠 for 实现。它有三种合法形式,本质都是同一语法糖的不同展开:
立即学习“go语言免费学习笔记(深入)”;
// 1. 类 C 风格(带初始化、条件、后置) for i := 0; i < 5; i++ { fmt.Println(i) } // 2. 类 while 风格(只有条件) for count < 10 { count++ } // 3. 无限循环(等价于 for ;;) for { select { case msg := <-ch: handle(msg) case <-done: break } }
- 第二种写法容易被误认为“缺少分号就报错”,其实只要条件表达式存在,就合法
- 第三种常用于 goroutine 中配合
select,但记得用break退出时要加标签才能跳出多层嵌套 - 遍历切片/映射/通道请用
range,不是for i = 0; i ——后者在并发中可能 panic,且无法感知底层变化
switch 默认自动 break,且支持类型断言和条件表达式
Go 的 switch 不会穿透(fallthrough 需显式写出),而且 case 可以是任意可比较类型或布尔表达式:
switch v := x.(type) { case int: fmt.Printf("int: %d", v) case string: fmt.Printf("string: %s", v) default: fmt.Printf("unknown type: %T", v) }
- 类型开关(
x.(type))只能用于接口类型,且v是具体值,不是类型名 - 普通 switch 也可以写条件:
switch { case x > 0 && y ,此时 case 后是布尔表达式,不需要switch后跟值 - 想穿透必须写
fallthrough,且它必须是 case 最后一条语句,后面不能有 return 或 break
控制流中的常见陷阱:作用域、空分支、defer 执行时机
这些细节不报错,但行为常和预期不符:
-
if或for内部定义的变量,在外部不可见;但若在顶层作用域多次用:=声明同名变量(如循环内),会报no new variables on left side of := - 空分支(如
if cond {} else {})合法,但容易掩盖逻辑缺失,建议用panic或日志占位 -
defer在函数返回前执行,不是块结束时——这意味着for循环里的defer会在整个函数结束才批量触发,不是每次迭代都执行
最易忽略的是 range 遍历时对切片元素取地址:所有迭代共享同一个变量地址,导致最后全指向最后一个元素。要存地址,得在循环内显式拷贝:val := val; ptr := &val。