07.Go 流程控制

流程控制是Go语言中必不可少的一部分,也是整个编程基础的重要一环。Go语言的流程控制语句和其他编程语言的流程控制语句有些不同,主要体现在Go语言没有do-while语句。Go语言常用的流程控制包括if语句、switch语句、for语句及goto语句等,switch语句和goto语句主要是为了简化代码、降低代码重复率,属于扩展类的流程控制语句。

1、条件判断

在Go语言中,if语句主要用于条件判断。if语句还有两个分支结构:if-else语句和else-if语句

a. if 单分支

在Go语言中,关键字if是用于测试某个条件(布尔型或逻辑型)的语句,如果该条件成立,则会执行if后由大括号“{}”括起来的代码块,否则就忽略该代码块继续执行后续的代码。

if 条件表达式 {    
    代码块
}

使用if语句判断一个变量的大小:

func main() {
	/*定义局部变量*/
	var a int = 20
	if a < 30 {
		fmt.Printf("a 小于 30\n")
	}
	fmt.Printf("a 的值为:%d\n", a)
}

b. if-else 双分支

if语句后可以使用可选的else语句,else语句中的表达式在条件表达式为false时执行。if和else后的两个代码块是相互独立的分支,只能执行其中一个。

if 条件表达式 {    
    代码块1
} else { 
    代码块2
}
func main() {
	/*定义局部变量*/
	var a int = 50
	if a < 30 {
		fmt.Printf("a 小于 30\n")
	} else {
		fmt.Printf("a 不小于 30\n")
	}
	fmt.Printf("a 的值为:%d\n", a)
}

c. if-else-if 多分支
if 条件表达式 {    
    代码块1
} else if 条件表达式  { 
    代码块2
} else {
    代码块3
}
func main() {
	/*定义局部变量*/
	var a int = 15
	if a < 10 {
		fmt.Printf("a 小于 10\n")
	} else if a > 20 {
		fmt.Printf("a 大于 20\n")
	} else {
		fmt.Printf("a 大于 10\n")
		fmt.Printf("a 小于 20\n")
	}
	fmt.Printf("a 的值为:%d\n", a)
}

d. if 语句的注意事项

(1)if后面的条件判断子句不需要使用小括号括起来,例如,if a > 30。

(2)Go语言规定,与if匹配的“{”必须与if和表达式放在同一行,如果尝试将“{”放在其他位置,将会出发编译错误。与else匹配的“{”也必须与else在同一行,else也必须与上一个if或else if的右边的大括号在一行。

(3)if后面可以带一个简单的初始化语句,并以分号进行分隔,该简单语句声明的变量的作用域是整个if语句块,包括后面的else if和else分支。

(4)Go语言没有条件运算符(a>b?a:b),符合Go语言的设计理念,只提供一种方法做事情。

(5)if分支语句如果遇到return,则直接返回

2、选择结构

switch语句和select语句在Go语言中主要用于条件的选择。相比C语言,Go语言中的switch语句在结构上更加灵活,语法设计尽量以使用方便为主。

a. switch 语句

在Go语言中,switch表示选择语句的关键字,switch语句会根据初始化表达式得出一个值,然后根据case语句的条件,执行相应的代码块,最终返回特定内容。每个case被称为一种情况,只有当初始化语句的值符合case的条件语句时,case才会被执行。

如果没有遇到符合的case,则可以使用默认的case (default case),如果已经遇到了符合的case,那么后面的case都不会被执行。

Go语言改进了switch的语法设计,case与case之间是独立的代码块,不需要通过break语句跳出当前case代码块以避免执行到下一行

switch (表达式) {
    case常数1:
        代码块1;
    case常数2:
        代码块2;
        ……
    default:
        代码块n+1;
}
func main() {
	/*定义局部变量*/
	a := "hello"
	switch a {
	case "hello":
		fmt.Println("hello")
	case "world":
		fmt.Println("world")
	default:
		fmt.Println("hello world!!!")
	}
}

与其他编程语言不同的是,在Go语言编程中,switch有两种类型。

(1)表达式switch:在表达式switch中,case包含与switch表达式的值进行比较的表达式。

(2)类型switch:在类型switch中,case包含与特殊注释的switch表达式的类型进行比较的类型。

表达式switch:

func main() {
	grade := "E"
	marks := 95
	switch {
	case marks >= 90:
		grade = "A"
	case marks >= 80:
		grade = "B"
	case marks >= 70:
		grade = "C"
	case marks >= 60:
		grade = "D"
	default:
		grade = "E"
	}
	switch {
	case grade == "A":
		fmt.Println("成绩优秀!")
	case grade == "B":
		fmt.Println("表现良好!")
	case grade == "C", grade == "D":
		fmt.Println("再接再厉!")
	default:
		fmt.Println("成绩不合格!")
	}
	fmt.Println("你的成绩为:", grade)
}

类型switch:

类型switch语句针对变量的类型判断该执行哪个case代码块

func main() {
	x = 1
	switch i := x.(type) {
	case nil:
		fmt.Println("这里是nil,x的类型是%T", i)
	case int:
		fmt.Println("这里是int,x的类型是%T", i)
	case float64:
		fmt.Println("这里是float64,x的类型是%T", i)
	case bool:
		fmt.Println("这里是bool,x的类型是%T", i)
	case string:
		fmt.Println("这里是string,x的类型是%T", i)
	default:
		fmt.Println("未知类型!")
	}
}

switch的特点如下:

  • (1)switch和if语句一样,switch后面可以带一个可选的简单的初始化语句。
  • (2)switch后面的表达式也是可选的,如果没有表达式,则case子句是一个布尔表达式,而不是一个值,此时就相当于多重if-else语句。
  • (3)switch条件表达式的值不像C语言那样必须限制为整数,可以是任意支持相等比较运算的类型变量。
  • (4)通过fallthough语句来强制执行下一个case子句(不再判断下一个case子句的条件是否满足)。
  • (5)switch支持default语句,当所有的case分支都不符合时,执行default语句,并且default语句可以放到任意位置,并不影响switch的判断逻辑。
  • (6)switch和.(type)结合可以进行类型的查询。
b. select 语句

在Go语言中,除了switch语句外,还有一种选择结构——select。select语句可以用于配合通道(channel)的读/写操作,用于多个channel的并发读/写操作。

select语句类似于switch语句,switch语句是按照顺序从上到下依次执行,而select是随机选择一个case执行。如果没有case可运行,它将阻塞,直到有case可运行。

select {
    case:       
    	代码块1;    
    case:       
    	代码块2;     
    default : /*可选*/       
    	代码块n;
}
  • 每个case都必须是一个通信。
  • 所有channel表达式都会被求值。
  • 所有被发送的表达式都会被求值。
  • 如果任意某个通信可以进行,它就执行,其他被忽略。
  • 如果有多个case都可以运行,select会随机公平地选出一个执行,其他不会执行。
  • 如果没有可执行的case,但是有default子句,则执行该语句。
  • 如果没有可执行的case,并且也没有default子句,select将阻塞,直到某个通信可以运行;Go不会重新对channel或值进行求值。
func main() {
	a := make(chan int, 1024)
	b := make(chan int, 1024)
	for i := 0; i < 10; i++ {
		fmt.Printf("第%d次,", i)
		a <- 1
		b <- 1
		select {
		case <-a:
			fmt.Println("from a")
		case <-b:
			fmt.Println("from b")
		default:
			fmt.Println("from c")
		}
	}
}

在以上代码中,同时在a和b中进行选择,哪个有内容就从哪个读,由于channel的读/写操作是阻塞操作,使用select语句可以避免单个channel的阻塞。此外,select同样可以使用default代码块,避免所有channel同时阻塞。

3、循环结构

在Go语言中,循环语句的关键字是for,没有while关键字。for语句可以根据指定的条件重复执行其内部的代码块,这个判断条件一般是由for关键字后面的子语句给出的。

a. for 语句

for循环是一个循环控制结构,可以执行指定次数的循环。循环体不停地进行循环,直到循环终止条件返回false时自动退出循环,执行for的“}”之后的语句。

for 循环控制变量初始化; 循环终止条件; 循环控制变量增量 {    
    循环体
}

初始语句是在第一次循环前执行的语句,一般使用初始语句执行变量初始化,如果变量在此处被声明,其作用域将被局限在这个for的范围内。

初始语句可以被忽略,但是初始语句之后的分号必须写,例如:

step := 2
for ; step > 0; step-- {    
    fmt.Println(step)
}

注意事项

  • 左花括号“{”必须与for处于同一行。
  • Go语言中的for循环与C语言一样,都允许在循环条件中定义和初始化变量,唯一的区别是,Go语言不支持以逗号为间隔的多个赋值语句,必须使用平行赋值的方式来初始化多个变量。
  • Go语言的for循环同样支持continue和break来控制循环,但是它提供了一个更高级的break,可以选择中断具体的哪一个循环。
b. range 语句

每一个for语句都可以使用一个特殊的range子语句,其作用类似于迭代器,用于轮询数组或者切片值中的每一个元素,也可以用于轮询字符串的每一个字符,以及字典值中的每个键值对,甚至还可以持续读取一个通道类型值中的元素。

range关键字的右边是range表达式,表达式一般写在for语句的前面,以便提高代码的易读性。

range关键字的左边表示的是一对索引-值对,根据不同的表达式,返回不同的结果,range右边表达式返回的类型如下图:

range右边表达式返回的类型,除了轮询字符串外,还包括数组、切片、字典及通道等。

func main() {
	numbers := [5]int{1, 2, 3, 4}
	for i, x := range numbers {
		fmt.Printf("第%d次,x的值为%d\n", i, x)
	}
}

在以上代码中,定义了numbers的长度为5,但numbers中只有4个值,因此最后一个为空值,从for循环返回的信息可以看到第5次x的值为0,代码块的确执行了5次。

4、defer 延迟

Go语言除了传统的流程控制语句外,还有一些特殊的控制语句,defer就是其中之一。defer主要用于延迟调用指定的函数,defer关键字只能出现在函数的内部:

func main() {
	defer fmt.Printf("world!")
	fmt.Printf("Hello ")
}

在以上代码中会首先打印hello,然后打印world,因为第一句使用了defer关键字,defer语句会在函数最后执行,defer后面的表达式必须是外部函数的调用,上面的例子就是针对fmt.Println函数的延迟调用。

defer有如下两大特点:

(1)只有当defer语句全部执行,defer所在函数才算真正结束执行。

(2)当函数中有defer语句时,需要等待所有defer语句执行完毕,才会执行return语句。

因为defer的延迟特点,可以把defer语句用于回收资源、清理收尾等工作。使用defer语句之后,不用纠结回收代码放在哪里,反正都是最后执行。

需要注意defer的执行时机:

var i = 0

func print() {
	fmt.Println(i)
}

func main() {
	for ; i < 5; i++ {
		defer print()
	}
}

  • 在以上代码中,返回了5个5,这是因为每个defer都是在函数轮询之后才执行,此时i的值为5。

var i = 0

func print(i int) {
    fmt.Println(i)
}

func main() {
    for ; i < 5; i++ {
        defer print(i)
    }
}

  • 当i等于0时,defer语句第一次被压栈,此时defer后面的函数返回0;i不断自增,一直到i等于4时,defer语句第5次入栈,defer后的函数返回4。
  • Go语言会根据defer后进先出原则逐条打印栈内的数值,因此就看到了现在的结果。
5、标签

在Go语言中,有一个特殊的概念就是标签,可以给for、switch或select等流程控制代码块打上一个标签,配合标签标识符可以方便跳转到某一个地方继续执行,有助于提高编程效率。

标签的名称区分大小写,为了提高代码的易读性,建议标签名称使用大写字母和数字。标签可以标记任何语句,并不限定于流程控制语句,未使用的标签会引发错误。

a. break 语句

Go语言中的break语句主要用于以下两方面:

  • 用于循环语句中跳出循环,并开始执行循环之后的语句。
  • break可以使switch语句执行一条case后跳出语句。

在多重循环中,可以用标号label标出想跳出的指定循环:

func main() {
    OuterLoop:
    for i := 0; i < 2; i++ {
        for j := 0; j < 5; j++ {
            switch j {
                case 2:
                fmt.Println(i, j)
                break OuterLoop
                case 3:
                fmt.Println(i, j)
                break OuterLoop
            }
        }
    }
    fmt.Println("调出循环")
}
  • 退出OuterLoop对应的循环之外,也就是跳转到最后。

b. continue 语句

Go语言中的continue语句可以结束当前循环,开始下一次的循环迭代过程,仅限在for循环内使用,在continue语句后添加标签时,表示开始标签对应的循环:

func main() {
OuterLoop:
	for i := 0; i < 2; i++ {
		for j := 0; j < 5; j++ {
			switch j {
			case 2:
				fmt.Println(i, j)
				continue OuterLoop
			case 3:
				fmt.Println(i, j)
				continue OuterLoop
			}
		}
	}
	fmt.Println("调出循环")
}
  • 第8行将结束当前循环,开启下一次外层循环,而不是第4行的循环。与break不同的是,continue表示跳转后继续执行操作。

c. goto 语句

Go语言中的goto语句通过标签进行代码间的无条件跳转,同时goto语句在快速跳出循环、避免重复退出上也有一定的作用,使用goto语句能简化一些代码的实现过程。

goto语句通常与条件语句配合使用,可用来实现条件转移、构成循环、跳出循环体等功能。但是,在结构化程序设计中一般不主张使用goto语句,以免造成程序流程的混乱,使理解和调试程序都产生困难。

goto语句有以下几个特点:

  • goto语句只能在函数内跳转。
  • goto语句不能跳过内部变量声明语句,这些变量在goto语句的标签语句处又是可见的。
  • goto语句只能跳到同级作用域或者上层作用域内,不能跳到内部作用域内。

使用goto退出多层循环:

func main() {
    for x := 0; x < 10; x++ {
        for y := 0; y < 10; y++ {
            if y == 2 {
                // 跳转到标签
                goto breakHere
            }
        }
    }
    // 手动返回,避免执行进入标签
    return
    // 标签
    breakHere:
    fmt.Println("done")
}
  • 第11行,标签只能被goto使用,但不影响代码执行流程,此处如果不手动返回,在不满足条件时,也会执行第14行代码。

  • 32
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值