Go 精简(合并)了流控制语句,虽然某些时候不够便捷,但够用。

if...else...

条件表达式值必须是布尔类型,可省略括号,且左花括号不能另起一行。

func main()  {x := 3if x > 5 {println("a")} else if x < 5 && x > 0 {println("b")} else {println("z")}
}

比较特别的是对初始化语句的支持,可定义块局部变量或执行初始化函数。

func main()  {x := 10if xinit(); x == 0 { // 优先执行 xinit 函数println("a")}if a, b := x + 1, x + 10; a < b { // 定义一个或多个局部变量(也可以是函数返回值)println(a)} else {println(b)}
}

局部变量的有效范围包含整个 if/else 块。

尽可能减少代码块嵌套,让正常逻辑处于相同层次。

import ("errors""log"
)func check(x int) error {if x <= 0 {return errors.New("x <= 0")}return nil
}func main()  {x := 10if err := check(x); err == nil {x++println(x)} else {log.Fatal(err)}
}

该示例中,if 块虽然承担了 2种 逻辑:错误处理 和 后续正常操作。基于重构原则,我们应该保持代码块功能的单一性。

func check(x int) error {if x <= 0 {return errors.New("x <= 0")}return nil
}func main() {x := 10if err := check(x); err != nil {log.Fatalln(err)}x++println(x)
}

如此,if 块仅完成条件检查 和 错误处理,相关正常逻辑保持在同一层次。当有人视图通过阅读这段代码来获知逻辑流程时,完全可忽略 if 块细节。同时,单一功能可提升代码可维护性,更利于拆分重构。

当然,如须在多个条件块中使用局部变量,那么只能保留层次,或直接使用外部变量。

func main() {s := "9"n, err := strconv.ParseInt(s, 10, 64) // 使用外部变量if err != nil {log.Fatalln(err)} else if n < 0 || n > 10 { // 也可以考虑拆分成另一个独立 if 块log.Fatalln("invalid number")}println(n) // 避免 if 局部变量将该逻辑放到 else 块
}

对应某些过于复杂的组合条件,建议将其重构为函数。

func main() {s := "9"if n, err := strconv.ParseInt(s, 10, 64); err != nil || n < 0 || n > 10 || n % 2 != 0 {log.Fatalln("invalid number")}println("ok")
}

函数调用虽然有一些性能损失,可却让主流程序变得更加清爽。况且,条件语句独立之后,更易于测试,同样会改善代码可维护性。

func check(s string) error {n, err := strconv.ParseInt(s, 10, 64)if err != nil || n < 0 || n > 10 || n%2 != 0 {return errors.New("invalid number")}return nil
}func main() {s := "9"if err := check(s); err != nil {log.Fatalln(err)}println("ok")
}

将 流程 和 布局 细节分离是很常见的做法,不同的变化因素被分隔在各自独立单元(函数或模块)内,可避免修改时造成关联错误,减少患“肥胖症”的函数数量。当然,代码单元测试也是主要原因之一。另一方面,该示例中的函数 check 仅被 if 块调用,也可将其作为局部函数,以避免扩大作用域,只是对测试的友好度会差一些。

当前编译器只能说够用,须优化的地方太多,其中内联处理做得也差强人意,所以代码维护性 和 性能平衡需要投入更多心力。

语言方面,最遗憾的是没有条件运算符 “a > b ? a : b”。有没有 lambda 无所谓,但没有这个却少份优雅。加上一大堆 err != nil 判断语句,对于有完美主义倾向的代码洁癖患者来说是种折磨。

switch

与 if 类似,switch 语句也用于选择执行,但具体使用场景会有所不同。

1、表达式 switch 语句

func main() {a, b, c, x := 1, 2, 3, 2switch x {             // 将 x 与 case 条件匹配case a, b:                // 多个匹配条件命中其一即可(OR),变量println("a | b")case c:                    // 单个匹配条件println("c")case 4:                  // 常量println("d")default:println("z")}
}

输出:

a | b

条件表达式支持非常量值,这要比 C 更加灵活。相比 if 表达式,switch 值列表要更加简洁。编译器对 if、switch 生成的机器指令可能完全相同,所谓谁性能更好须看具体情况,不能作为主观判断条件。

switch 同样支持初始化语句,按从上到下、从左到右顺序匹配 case 执行。只有全部匹配失败时,才会执行 default 块。

func main() {switch x := 5; x {default:          // 编译器确保不会先执行 default 块x += 100println(x)case 5:x += 50println(x)}
}

输出:

55

考虑到 default 作用类似 else,建议将其放置在 switch 末尾。

相邻的空 case 不构成多条件匹配。

switch x {      // 单条件,内容为空。隐式 "case a: break;"
case a:
case b:println("b")
}

不能出现重复的 case 常量值。

func main() {switch x := 5; x {case 5:println("a")case 6, 5:       // 错误:duplicate case 5 in switchprintln("b")}
}

无须显式执行 break 语句,case 执行完毕后自动中断。如须贯通后续 case (源码顺序),须执行 fallthrough,但不再匹配后续条件表达式。

func main() {switch x := 5; x {default:println(x)case 5:x += 10println(x)fallthrough      // 继续执行下一个 case,但不再匹配条件表达式case 6:x += 20println(x)//fallthrough    // 如果在此继续 fallthrough,不会执行 default,完全按照源码顺序// 导致 "cannot fallthrough final case in switch" 错误}
}

输出:

15
35

注意:fallthrough 必须放在 case 块结尾,可使用 break 语句阻止。

func main() {switch x := 5; x {case 5:x += 10println(x)if x >= 15 {break        // 终止,不再执行后续语句}fallthrough       // 必须是 case 块的最后一条语句case 6:x += 20println(x)}
}

输出:

15

某些时候,switch 还被用来替换 if 语句。被省略的 switch 条件表达式默认值为 true,继而与 case 比较表达式结果匹配。

func main() {switch x := 5; { // 相当于 "switch x :=5; true { ... }"case x > 5:println("a")case x > 0 && x <= 5: // 不能写成 "case x > 0, x <= 5",因为多条件是 OR 关系println("b")default:println("c")}
}

输出:

b

2、类型 switch 语句

类型 switch 语句 将对类型进行判定,而不是值。下面是一个简单的例子:

var v interface{}
// 省略了部分代码
// v = 8
// v = "wenjianbao"switch v.(type) {
case string:fmt.Printf("The string is '%s'\n", v.(string))
case int, uint, int8, uint8, int16, uint16, int32, uint32, int64, uint64:fmt.Printf("The interger is %d\n", v)
default:fmt.Printf("Unsupporte value.(type=%T)\n", v)
}

类型 switch 语句的 switch 表达式会包含一个特殊的类型断言,例如 v.(type)。它虽然特殊,但是也要遵循类型断言的规则。其次,每个 case 表达式中包含的都是 类型字面量,而不是表达式。最后,fallthrough 语句不允许出现在类型 switch 语句中。

类型 switch 语句的 switch 表达式还有一种变形写法,如下:

var v interface{}
// 省略了部分代码
// v = 8
// v = "wenjianbao"switch i := v.(type) {
case string:fmt.Printf("The string is '%s'\n", i)
case int, uint, int8, uint8, int16, uint16, int32, uint32, int64, uint64:fmt.Printf("The interger is %d\n", i)
default:fmt.Printf("Unsupporte value.(type=%T)\n", i)
}

这里的 i := v.(type) 使经类型转换后的值得以保存。i 的类型一定会是 v 的值的实际类型。

for

仅有 for 一种循环语句,但常用方式都能支持。

for i := 0; i < 3; i++ { // 初始化表达式支持函数调用或定义局部变量
}
for x < 10 { // 类似 "while x < 10 {}" 或 "for ; x < 10; {}"x++
}
for {      // 类似 "while true {}" 或 "for true {}"break
}

初始化语句仅被执行一次。条件表达式中如有函数调用,须确认是否会重复执行。可能会被编译器优化掉,也可能是动态结果,须每次执行确认。

func count() int {print("count.")return 3
}func main() {for i, c := 0, count(); i < c; i++ { // 初始化语句的 count() 函数仅执行一次println("a", i)}c := 0for c < count() { // 条件表达式中的 count 重复执行println("b", c)c++}
}

输出:

count.a 0
a 1
a 2
count.b 0
count.b 1
count.b 2
count.
Process finished with exit code 0

规避方式 就是在初始化表达式中定义局部变量保存 count 结果。

可用 for ... range 完成数据迭代,支持 字符串、数组、数组指针、切片、字典、通道类型,返回 索引、键值 数据。

data teyp 1st value 2nd value  
string index s[index] unicode, rune
array/slice index v[index]  
map key value  
channel element    

# 迭代字符串:

func main() {str := "hello world"for index, ch := range str {fmt.Printf("%d -- %c\n", index, ch)}
}

输出:

0 -- h
1 -- e
2 -- l
3 -- l
4 -- o
5 --
6 -- w
7 -- o
8 -- r
9 -- l
10 -- d

# 迭代数组

func main() {data := [3]string{"a", "b", "c"}for i, s := range data {println(i, s)}
}

输出:

0 a
1 b
2 c

没有相关接口实现自定义类型迭代,除非基础类型是上述类型之一。

允许返回单值,或用 “_” 忽略。

func main() {data := [3]string{"a", "b", "c"}for i := range data { // 只返回 1st valueprintln(i, data[i])}for _, s := range data { // 忽略 1st valueprintln(s)}for range data { // 仅迭代,不返回。可用来执行清空 channel 等操作}
}

无论普通 for 循环,还是 range 迭代,其定义的局部变量都会 重复使用。

func main() {data := [3]string{"a", "b", "c"}for i, s := range data {println(&i, &s)}
}

输出:

0xc42003bef0 0xc42003bf08
0xc42003bef0 0xc42003bf08
0xc42003bef0 0xc42003bf08

这对 闭包 存在一些影响,相关详情,请阅读后续章节。

注意,range 会 复制 目标数据。受直接影响的是 数组,可改用 数组指针 或 切片 类型。

func main() {data := [3]int{10, 20, 30}for i, x := range data { // 从 data 复制品中取值if i == 0 {data[0] += 100data[1] += 200data[2] += 300}fmt.Printf("x: %d, data: %d\n", x, data[i])}for i, x := range data[:] { // 仅复制 slice,不包括 底层 arrayif i == 0 {data[0] += 100data[1] += 200data[2] += 300}fmt.Printf("x: %d, data: %d\n", x , data[i])}
}

输出:

x: 10, data: 110
x: 20, data: 220    // range 返回的依旧是复制值
x: 30, data: 330x: 110, data: 210   // 当 i == 0 修改 data 时,x 已经取值,所以是 110
x: 420, data: 420   // 复制的仅是 slice 自身,底层 array 依旧是原对象
x: 630, data: 630

相关数据类型中,字符串、切片基本结构是个很小的结构体,而 字典、通道 本身是指针封装,复制成本都很小,无须专门优化。

如果 range 目标表达式是函数调用,也仅被执行一次。

func data() []int {println("origin data.")return []int{10, 20, 30}
}func main() {for i, x := range data() {println(i, x)}
}

输出:

origin data.
0 10
1 20
2 30

建议嵌套循环不要超过 2 层,否则会难以维护。必要时可剥离,重构为函数。

使用 range 子句,有 3 点需要注意,如下:

  • 若对数组、切片 或 字符串值进行迭代,且 := 左边只有一个迭代变量时,一定要小心。这时只会得到其中元素的索引,而不是元素本身;这很可能并不是你想要的。
  • 迭代没有任何元素的数组值、为 nil 的切片值、为 nil 的字典值 或 为 "" 的字符串值,并不会执行 for 语句中的代码。for 语句在一开始就会直接结束执行。因为这些值的长度都为 0。
  • 迭代为 nil 的通道值 会让当前流程永远阻塞在 for 语句上!

goto,continue,break

对于 goto 的讨伐由来已久,仿佛它是“笨蛋”标签一般。可事实上,能在很多场合见到

它的身影,就连 Go 源码里都有很多。

$ cd go/src
$ grep -r -n "goto"  *

单就 Go 1.6 的源码统计结果,goto 语句就超过 1000 条有余。很惊讶,不是吗?虽然某些设计模式可用来消除 goto 语句,但在性能优先的场合,它能发挥积极作用。

使用 goto 前,须先定义标签。标签区分大小写,且未使用的标签会引发编译错误。

func main() {
start:                          // 错误:label start defined and note usedfor i := 0; i < 3; i++ {println(i)if i > 1 {goto exit}}
exit:println("exit.")
}

不能跳转到其他函数,或内层代码块内。

func test() {
test:println("test")println("test exit.")
}func main() {for i := 0; i < 3; i++ {loop:println(i)}goto test       // 错误:label test not definedgoto loop        // 错误:goto loop jumps into block
}

和 goto 定义跳转不同,break、continue 用于中断代码执行。

  • break:用于 switch、for、select 语句,终止整个语句块执行。
  • continue:仅用于 for 循环,终止后续逻辑,立即进入下一轮循环。
func main() {for i := 0; i < 10; i++ {if i%2 == 0 {continue        // 立即进入下一轮循环}if i > 5 {break         // 立即终止整个 for 循环}println(i)}
}

输出:

1
3
5

配合标签,break 和 continue 可在多层嵌套中指定目标层级。

func main() {
outer:for x := 0; x < 5; x++ {for y := 0; y < 10; y++ {if y > 2 {println()continue outer}if x > 2 {break outer}print(x, ":", y, " ")}}
}

输出:

0:0 0:1 0:2
1:0 1:1 1:2
2:0 2:1 2:2

转载于:https://www.cnblogs.com/52php/p/6391537.html

《Go学习笔记 . 雨痕》流程控制(if、switch、for range、goto、continue、break)相关推荐

  1. html 流程控制,HTML5独家分享:原生JS学习笔记2——程序流程控制

    当当当当 .....楼主又来了!新一期的js学习笔记2--程序流程控制更新了! 想一键获取全部js学习笔记的可以给楼主留言哦! js中的程序控制语句 常见的程序有三种执行结构: 1.顺序结构 2.分支 ...

  2. shell脚本编程学习笔记8(XDL)——流程控制和循环

    shell脚本编程学习笔记8--流程控制和循环 1,if语句 1,框架 1,单分支:if [条件判断式] ;thenprogramfiif [条件判断式]thenprogramfi注意:if语句使用f ...

  3. php学习笔记02:流程控制if、switch、循环、系统函数、文件路径

    流程控制:三大结构即顺序结构.分支结构.循环结构. 流程控制参考 一.顺序结构: 基本结构.代码依次顺序执行. 二.分支结构: 含if分支.switch分支 1.if分支: if分支基本语法: ①最简 ...

  4. MATLAB学习笔记:程序流程控制

    这里介绍一下如何利用matlab语言来编写程序,也就是程序控制. 一. 顺序结构程序 1,程序是用某种计算机能够理解并且能够执行的语言来描述的解决问题的方法和步骤.程序设计并不是简单的编写代码,而是反 ...

  5. 《Go学习笔记 . 雨痕》方法

    一.定义 方法 是与对象实例绑定的特殊函数. 方法 是面向对象编程的基本概念,用于维护和展示对象的自身状态.对象是内敛的,每个实例都有各自不同的独立特征,以 属性 和 方法 来暴露对外通信接口.普通函 ...

  6. python3学习笔记 雨痕_Python 3 学习笔记:数字和布尔

    数字 基本类型 整数 在 Python 编程中,整数就是数学意义上的整数,包括正整数.负整数和零,且它的位数是任意的.根据表示方法的不同,可以分为: 二进制整数 八进制整数 十进制整数 十六进制整数 ...

  7. 《Go学习笔记 . 雨痕》类型

    一.基本类型 清晰完备的预定义基础类型,使得开发跨平台应用时无须过多考虑符合和长度差异. 类型 长度 默认值 说明 bool 1 false   byte 1 0 uint8 int, uint 4, ...

  8. 《Go学习笔记 . 雨痕》反射

    一.类型(Type) 反射(reflect)让我们能在运行期探知对象的类型信息和内存结构,这从一定程度上弥(mi)补了静态语言在动态行为上的不足.同时,反射还是实现元编程的重要手段. 和 C 数据结构 ...

  9. LIteOS学习笔记-7LiteOS启动流程与编译流程

    LIteOS学习笔记-7LiteOS启动流程与编译流程 LiteOS启动流程 1. 启动方式 2. 启动流程 硬件初始化 内核初始化 调试串口初始化 尝试进行网络连接 启动任务调度 LiteOS编译流 ...

最新文章

  1. css 选择器 伪元素_CSS伪元素-解释选择器之前和之后
  2. C# Delegate(委托)与多线程
  3. NEO从源码分析看网络通信
  4. EF 如何更新多对多关系的实体
  5. 多继承中构造器和析构器的调用顺序
  6. ARM和NEON指令 very gooooooood.............
  7. Weblogic负载均衡/Session复制之集群架构续
  8. UnityShader4:UnityShader的形式
  9. mybatis 存储过程 tmp_count_mysql存储过程(一)-navicat与mybatis
  10. 【Spring 核心】装配Bean(一) 自动化装配
  11. VMPlayer中Ubuntu 20.04鼠标在移动过程中消失的一种处理方法
  12. 收集常用汉字6725个
  13. 三菱PLC安装报错“工程初始化失败”处理方法
  14. Atom处理器喜迎周岁生日 主频达2GHz
  15. 【笔记】初读《SICP》:递归和迭代
  16. 2020年5个最佳免费WordPress托管提供商
  17. docker: Error response from daemon: Conflict. The container name “/mysql“ is already in use by conta
  18. centos如何安装软件
  19. 用dango框架搭建博客网站
  20. Visual Studio 好用的插件

热门文章

  1. 选下拉框的的值对应上传相应的图片_vue.js如何拿到多种类型表单值提交到后台,包含上传图片、单选、复选、文本框、下拉列表框...
  2. FastDFS安装脚本
  3. 第10讲 | 深入区块链技术(二):P2P网络
  4. SQLPrompt 安装后sql上看不到菜单
  5. 一句话总结.Net下struct和class内存分配方面的区别
  6. nginx的反向代理、负载均衡、页面缓存、URL重写及读写分离
  7. 获取Linux命令源代码的方法
  8. 一步步Netty的基石 - Reactor模式
  9. 项目范围变更管理方法研究
  10. 【工程项目经验】函数编译可见性