第3章程序流程控制 视频讲解 3.1代码块与作用域 3.1.1代码块 从宏观上来说,一个项目程序由多个包构成,每个包由若干个函数构成,函数又由若干个代码块形成,当然也可以把整个函数看成一个代码块。代码块是一个相对独立的执行单元,通常用大括号“{}”括起来(或者由两个case关键字分隔)。例如,下述函数体是一个代码块: func sum(a int,b int)int{ return a+b } 以下循环体也属于一个代码块: var sum=0 for i :=0;i5;i++{ sum +=i fmt.Println("sum=",sum) } 以下条件分支的大括号内也属于一个代码块: if ab{ c=a a=b b=c } 在多分支switch及select中的两个case之间的语句也构成一个代码块,但不含大括号。上述代码块的特点是定义在大括号内,大括号内的所有语句成为一个整体。当然,大括号内的语句块还可以嵌套,包含子语句块,子语句块又成为一个独立的语句块,嵌套的数量不受限制。 可见,代码块就是相对独立的一段程序,有一定的边界。 3.1.2作用域 一般而言,代码块内部定义的变量具有private属性,只在这个代码块内可用,称为在代码块内可见,这种可见性称为变量的作用域。Go语言规定任何对象只有在其作用域内才可被访问,或者说它们的名字才可以被使用。 在代码块内定义的变量其作用域为整个代码块内; 函数体内定义的变量其作用域为整个函数体内; 而主函数体外定义的变量其作用域为整个包,称为全局变量,在整个包内可见。根据Go语言的命名规则,如果全局变量或函数名字的首字母为大写,那么这样的变量或函数具有外部可见性,其作用域超出本包,可为其他包所引用。以下例子演示各种变量在不同的作用域内的使用情况: Go语言程序设计微课视频版 第 3 章 程序流程控制 package main import ( "fmt" ) var StuId=0//作用域为包内外,即包外可见 var a=10//作用域为包内 func main() { a++ //取全局变量的值 StuId++//取全局变量的值 fmt.Println("函数体内StuId=", StuId) var x=1 for i :=0; i 3; i++ { a++//取全局变量的值 x++//取局部变量的值 StuId++ //取全局变量的值 fmt.Println("循环体内StuId=", StuId) fmt.Println("循环体内全局a=", a, "局部x=", x, "块内i=", i) var a, x=10, 20 // 块内变量覆盖块外变量 fmt.Println("内部声明a=", a, "x=", x) x=Add(a, x) fmt.Println("求和后a=", a, "x=", x, "i=", i) a++ //取内部变量的值 x++ //取内部变量的值 fmt.Println("循环体内求和自增后a=", a, "局部x=", x, "块内i=", i) } a++ //取全局变量的值 x++ //取局部变量的值 StuId++//取全局变量的值 fmt.Println("循环体外StuId=", StuId) fmt.Println("循环体外全局a=", a, "局部x=", x) //fmt.Println("循环体外i=",i) 语法错误,块内定义的变量块外不可见 } func Add(x int, y int) int { return x+y } 输出结果: 函数体内StuId=1 循环体内StuId=2 循环体内全局a=12局部x=2块内i=0 内部声明a=10 x=20 求和后a=10 x=30 i=0 循环体内求和自增后a=11局部x=31块内i=0 循环体内StuId=3 循环体内全局a=13局部x=3块内i=1 内部声明a=10x=20 求和后a=10 x=30 i=1 循环体内求和自增后a=11局部x=31块内i=1 循环体内StuId=4 循环体内全局a=14局部x=4块内i=2 内部声明a=10 x=20 求和后a=10 x=30 i=2 循环体内求和自增后a=11局部x=31块内i=2 循环体外StuId=5 循环体外全局a=15局部x=5 上述例子中,声明了一个公共变量StuId,为全局变量,包内外均可见,即可以被别的包引用。在函数体内或循环体内均可以修改全局变量的值。 此外,还声明了一个全局变量a、局部变量x以及三个块内变量i,a,x。i为块内定义的循环计数变量,在关键字for后面用短变量声明操作符“:=”声明并赋值,称为for的子句。for子句仅允许声明一个变量,其作用域仅在循环体内,循环结束即被注销。在循环体内声明的a,x为内部变量,与外部变量同名,是合法的,本质上是不同的变量,在内存中处于不同的位置。内部变量的值会屏蔽外部同名变量的值,但不会真实覆盖。循环程序运行时使用的是内部变量的值,直至当前循环结束。整个循环结束后,变量a,x又恢复了被覆盖的值。 函数Add首字母大写,表明为一个全局性函数,可以被别的包引用。 循环和函数等知识将在后面相关章节中叙述,有兴趣的读者可以提前查阅。 3.1.3变量的可见性 变量在其作用域内是可见的,一个可见的变量可以被访问及赋值。3.1.2节的程序例子看起来有点复杂,不细心分析比较,很难看出其表达的意思。这里就变量的可见性总结以下几点: (1) 全局变量StuId在函数体内外,及函数体内嵌的程序块内均可见; (2) 函数体内局部变量在函数体及其内嵌的程序块内均可见; (3) 函数体内嵌程序块定义的变量仅在该块内可见; (4) 程序块定义的变量可与外部变量同名、同类型或不同类型; (5) 同名的内部变量会屏蔽外部变量,但不会改变外部变量的值; (6) 在循环体内定义的变量仅在当前循环内起作用,对下一轮次循环无效; (7) 循环体一次循环就是一个作用域,第二次循环为新的作用域。 变量必须可见才可以操作,使用不可见的变量会引起编译错误。从安全的角度来说,应该尽量缩小变量的可见性,仅为需要的地方提供可见性即可。 视频讲解 因此,谨慎定义全局变量,因为为其可见性覆盖整个包的所有函数,任何一个函数内对该变量的不正确引用都可能带来灾难性后果。 3.2if语句 if语句称为条件语句,通常用于选择“yes”和“no”的逻辑判断场合。if语句是Go语言基本的条件分支语句,使用的时候必须带有逻辑表达式。 3.2.1基本语法 if语句为条件分支语句,用于条件判断后至少有两种可能的程序执行流的情况。在实际编程中有时候需要面对各种不同的情况,针对不同的情况采取不同的措施,这时就要用到条件分支语句。最常用到的条件分支语句是if…else语句,该语句有多种语法格式,其典型的语法格式如下: if 条件表达式{ 语句块1 }else{ 语句块2 } 条件表达式为任意的逻辑表达式,其计算结果必须为布尔值。左大括号必须紧接着条件表达式,不得另起一行,否则将导致编译错误。条件表达式可以用小括号“()”括起来,也可以不用。如果条件表达式的值为真true,则执行语句块1,否则执行语句块2。 关键字else必须紧接在语句块1的右大括号后面与其同行,同时语句块2的左大括号必须与else同行,不允许另起一行。 语句块1和语句块2可以有多条语句,也可以没有语句,但是,大括号是必须有的,例如: if ab{ fmt.Println("ab") }else{} 或 if ab{ }else{ fmt.Println("ab") } 都是合法的。 但是,上述情况中如果语句块2为空,则else可以省略。 3.2.2省略else 如果只有两种情况,也就是说只有二分支,而第二个分支没有需要执行的语句,此时可以忽略else,例如: if i100{ i++ } 这时就成了简单的if语句,在实际编程中很常见。实际上,有多种情况需要判断也可以使用简单的if语句,例如学生成绩等级划分就可以使用简单的if语句。假如学生成绩为x,等级划分程序如下所示: package main import "fmt" func main() { var x float64 fmt.Println("请输入学生的成绩: ") fmt.Scanf("%f", &x) if x =90 { fmt.Println("学生成绩等级为: 优秀") } if x =80 && x 90 { fmt.Println("学生成绩等级为: 良好") } if x =70 && x 80 { fmt.Println("学生成绩等级为: 中等") } if x =60 && x 70 { fmt.Println("学生成绩等级为: 及格") } if x 60 { fmt.Println("学生成绩等级为: 不及格") } } 运行结果: 请输入学生的成绩: 45 学生成绩等级为: 不及格 //再次运行: 请输入学生的成绩: 85 学生成绩等级为: 良好 //再次运行: 请输入学生的成绩: 95 学生成绩等级为: 优秀 上述程序中,每一个if语句只考虑自己的条件,满足就执行语句块,不满足就跳过。多种条件分别用不同的if语句判断,这样使用简单的if语句可以使程序的逻辑性变得清晰,让人易读易懂。 3.2.3带子句的if Go语言允许在if关键字之后,条件表达式之前插入一个简单语句,称为if语句的子句,与条件表达式之间用分号隔开。if子句是可选的,一旦有,必须为简单语句(区别于由多条语句构成的复合语句)。带子句的if语句其语法格式如下: if可选子句 ; 条件表达式{ 语句块 } 以下子句写法都是正确的: if i :=0; x 5{ i++ } 或 var x,y int x=10 if y=10+x; x 5{ x++ } 显然,上述子句都是简单语句,包括表达式、通道操作、增减值语句、赋值语句或短变量声明等语句均可。 上述变量i定义在if的子句里面,属于if语句块的局部变量,其可见性仅在当前if语句块及其块内嵌套的其他块内可见,结束if语句块后i不可用。如果有多层if嵌套,i的作用域包括所有else if及else分支。但变量x,y在if语句块外定义,结束if语句块后仍然有效。 可选子句的执行可为其后续的逻辑表达式提前准备条件,如变量初始化赋值等。当然,可选子句也可以与条件表达式无关,仅仅用来声明及初始化一个变量,而且仅能声明一个变量,为后续程序块做准备。可选子句也可以是函数调用,如下所示: package main import "fmt" func main() { UU :=1 vv :=2 if i :=add(UU, vv); i 8 { fmt.Println("UU=", UU) var UU int=10 UU++ fmt.Println("UU=", UU, "i=", i) } fmt.Println("UU=", UU, "vv=", vv) } func add(x int, y int) int { return x+y } 运行结果: UU=1 UU=11 i=3 UU=1 vv=2 程序分析: 上述程序的if子句是一个函数调用,先执行函数计算,获得其计算结果的返回值赋给i,然后i作为条件表达式的参数,计算条件表达式的值。如果该值为true,则执行if语句块; 如果该值为false,则跳过if语句块。add为一个独立的加法子函数,可被主函数调用。 上述程序中主函数main中有一个变量UU,在if语句块中又声明了一个变量UU。主函数内的UU在if语句块内可见,因此,第一个打印语句的输出是main函数内UU的值。第二个打印语句打印的UU是if语句块内自己定义的UU,它会覆盖外部UU的值,因此,打印结果是内部UU的值,同时,i是调用函数的返回值。第三个打印语句已经退出了if语句块,这时打印的UU又恢复了外部UU的值。显然,内部UU只是遮挡了外部UU,并不是真正地改变了外部UU的值。 3.2.4if语句的嵌套 if…else语句还允许嵌套,在其语句块1和语句块2内部还可以有if…else语句,形成语句块3和语句块4; 语句块3和语句块4还可以嵌套if…else形成语句块5和语句块6。这样一层套一层,层数没有限制,只需要保持大括号配对即可。实际编程中,最好不要超过三层,例如: package main import "fmt" func main() { var a, b, c, d, e, f=11, 2, 1, 4, 15, 6 if a b { if c d { if e f { fmt.Println("ab and cd and ef") } else { fmt.Println("ab and cd and ef") } } else { fmt.Println("ab and cd") } } else { fmt.Println("ab ") } } 运行结果: ab and cd and ef 注意: if嵌套会占用大量系统资源,极大地影响系统性能,应尽量避免使用。 上述主要采用两分支的情况进行嵌套判断,很容易导致逻辑复杂混乱,程序也难以看懂。如果分支不多,可以采用3.2.5节介绍的if…else if语句,逻辑会更清晰一些。 3.2.5if…else if语句 对于多分支的情况,如果采用if…else嵌套,则很容易导致逻辑复杂、混乱且易出错,程序难懂,这时可以考虑用if…else if语句替代。该语句格式是专门用来处理if语句多分支的情况。例如,上述为学生成绩分档的示例,可以用if…else if格式改写如下: package main import "fmt" func main() { var x float64 fmt.Println("请输入学生的成绩: ") fmt.Scanf("%f%", &x) if x =90 { fmt.Println("学生成绩等级为: 优秀") } else if x =80 { fmt.Println("学生成绩等级为: 良好") } else if x =70 { fmt.Println("学生成绩等级为: 中等") } else if x =60 { fmt.Println("学生成绩等级为: 及格") } else { fmt.Println("学生成绩等级为: 不及格") } } 运行结果: 请输入学生的成绩: 56 学生成绩等级为: 不及格 // 再次运行: 请输入学生的成绩: 78 学生成绩等级为: 中等 // 再次运行: 请输入学生的成绩: 98 学生成绩等级为: 优秀 程序分析: 通过上述if…else if 改写后,条件表达式变得更加简洁,程序更容易为人读懂,逻辑思路也清晰。因此,对于多分支的if语句建议采用这种处理方式。但是,这种方式也有不足,当分支过多的时候,大括号的配对比较麻烦,容易出错。其实,对于多分支条件表达式有更好的语句,这就是3.3节要介绍的switch…case 语句。 视频讲解 3.3switch语句 switch语句称为开关语句,是Go语言专门为多分支选择执行而设计的语句。switch作为多分支开关,后接一个开关表达式,而用case表示多分支出口。每个case后面也要带同类型的表达式,case后边还可以带多个表达式。case表达式的计算顺序是从上到下,从左到右,计算结果会与switch表达式的计算结果相比较,结果相同的case分支将得到执行。如果所有case表达式的值都与switch表达式的值不同,则执行default case分支; 如果没有该分支,则直接结束switch语句。 Go语言提供的switch分为两种类型: 表达式switch(expression switch)和类型switch(type switch)。表达式switch就是常用的多分支选择语句,类型switch主要用于类型判断,使用类型断言表达式,根据不同的数据类型执行不同的动作。 3.3.1表达式switch 表达式switch是常用的格式,适用于大多数的多分支选择情况,其语法格式如下: switch 可选子句; 可选表达式 { case 表达式列表1: 语句块1 case 表达式列表2: 语句块2 ... case 表达式列表n: 语句块n default: 语句块d } 可选子句: 为Go语言的简单语句,与if子句相同。如果有可选子句出现,则必须用分号与后面的可选表达式隔开,即使后面的可选表达式没有,分号也不能省略。如果变量是在可选子句里用短变量声明的,则其作用域为从声明处到switch结尾,包括所有case分支和default分支。如果各个case包含了嵌套的子语句块,则在该子语句块也可见。 可选表达式: 可以是算术表达式,也可以是逻辑表达式,甚至可以没有。如果没有可选表达式,则编译系统会默认为逻辑表达式,其值为true,这种情况下要求所有case表达式为逻辑表达式。 case: 后面必须有表达式列表,其类型必须与switch表达式匹配,表达式数量不限,必须用逗号隔开。多个表达式对应同一个分支,执行相同动作。 default: 为缺省分支,不是必须的,如果有的话,位置不限,可放置在任何地方。若没有任何case匹配,流程控制将执行default分支,执行结束退出switch语句。如果没有任何case匹配,也没有default分支,则直接退出switch语句。 语句块: 可以含有0到多条语句,甚至还可以嵌套其他的语句块,但是不用大括号括起来,两个case之间就等同于大括号。语句块内的语句顺序执行,执行完毕自动退出switch语句。 下面,仍然以上述成绩等级划分为例,看看用switch多分支语句,如何改写该程序。 package main import "fmt" func main() { var x float64 fmt.Println("请输入学生的成绩: ") fmt.Scanf("%f%", &x) switch int(x / 10) { case 9, 10: fmt.Println("学生成绩等级为: 优秀") var x, y=10, 20 // x,y为局部变量,会屏蔽外部变量x x=x+y fmt.Println("x+y=", x) case 8: fmt.Println("学生成绩等级为: 良好") case 7: fmt.Println("学生成绩等级为: 中等") case 6: fmt.Println("学生成绩等级为: 及格") default: fmt.Println("学生成绩等级为: 不及格") } fmt.Println("x+y=", x) // y变量不可用,x为switch外部变量 } 运行结果: 请输入学生的成绩: 95 学生成绩等级为: 优秀 x+y=30 x+y=95 程序分析: 从上面的例子可以看出,case表达式的值类型必须与switch表达式的值类型相匹配。一个case后可以有多个值,每个值匹配成功后均执行相同的语句块。语句块为一个独立的作用域,其内部定义的变量(如x,y)只在该块内有效,退出switch后该变量不可用。 如果忽略switch表达式,则其默认为true,其后的所有case表达式必须为逻辑表达式,其值为布尔型,这样才能匹配switch。可将上述成绩分级程序改写如下: package main import "fmt" func main() { var x float64 fmt.Println("请输入学生的成绩: ") fmt.Scanf("%f%", &x) var y=int(x / 10) switch { case y =9: fmt.Println("学生成绩等级为: 优秀") case y =8: fmt.Println("学生成绩等级为: 良好") case y =7: fmt.Println("学生成绩等级为: 中等") case y =6: fmt.Println("学生成绩等级为: 及格") default: fmt.Println("学生成绩等级为: 不及格") } } 运行结果: 请输入学生的成绩: 52 学生成绩等级为: 不及格 可见,两种形式的switch本质上没什么不同,完全可以互换使用。第一种形式是经典形式,与其他编程语言一样。第二种形式逻辑更清晰,程序更容易读懂,感觉更符合人类的思维习惯,实际使用时可以依据个人习惯来选择。 3.3.2类型switch 类型switch本质上与表达式switch差不多,都是switch后面带表达式,根据表达式的计算结果来匹配每个case。不同的地方是,类型switch的表达式为类型断言表达式,用关键字“type”代替实际类型,其表达式的计算结果值是变量的类型(Type)字面量。而其每个case 表达式是各种数据类型的字面量,为常数。语法格式如下: switch 可选子句; 类型开关守护 { case 类型列表1: 语句块1 case 类型列表2: 语句块2 ... case 类型列表n: 语句块n default: 语句块d } 可选子句与表达式switch语句及if语句中可选子句一样,为简单语句或短变量声明等。 类型开关守护(type switch guard)是一个结果为类型的表达式x.(type),如果其采用短变量声明的方式赋值给一个变量,则变量的值即为该表达式的值。该变量的类型将与所有case子句的类型表达式匹配,匹配成功将执行该分支的语句块。default分支也不是必须的,有的话其位置是任意的。 参看以下的例子: package main import "fmt" func main() { var x interface{} var ( chrune ='a' ctint=12 cffloat32=12.34 cff float64 =34.56 cbbool=true csstring ="asdfgh" i =0 ) fmt.Println("请输入数字1--6!") fmt.Scanln(&i) switch i { case 1: x=ch case 2: x=ct case 3: x=cf case 4: x=cff case 5: x=cb case 6: x=cs default: x=i } switch z :=x.(type) { case rune: fmt.Printf("你输入字符a(ASCII 97)!%T,%v\n", z, z) case int,int8,int16,int64: fmt.Printf("你输入的是整型数字!%T,%v\n", z, z) case float32: fmt.Printf("你输入的是浮点数!%T,%v\n", z, z) case float64: fmt.Printf("你输入的是浮点数!%T,%v\n", z, z) case string: fmt.Printf("你输入的是字符串!%T,%v\n", z, z) case bool: fmt.Printf("你输入的是布尔型!%T,%v\n", z, z) default: fmt.Printf("未知类型! %T,%v\n", z, z) } } 运行结果: 请输入数字1--6! 6 你输入的是字符串!string,asdfgh 上述例子中,首先定义了一个空接口类型的变量x,它可以接受任意类型的赋值,类型守护开关就是常用接口类型。然后声明了6种数据类型(rune,int,float32,float64,bool,string)的6个变量,rune是int32的别名。要求用户从键盘上读入一个1~6的数字,从而确定让哪一个变量赋给x,然后执行类型断言表达式x.(type)获得接口变量的类型及值,由switch…case来判断输出该类型的名字。 实际工作中,很少用到type switch,只有牵涉外部数据源需要判断数据类型的时候才用到type switch。 3.3.3switch的嵌套 switch语句和if语句一样,允许嵌套使用。在每个case后面的语句块中如果又包含了多种可能条件需要判断执行,就可以继续使用switch…case进行多分支选择,从而形成了switch的嵌套。例如: package main import "fmt" func main() { var ( cff float64 i int chrune ) fmt.Println("请输入参数: ") fmt.Scanln(&cff) i=int(cff / 10) ch='c' switch i { case 0: switch ch { case 'a': fmt.Printf("i=0,ch=a\n") case 'b': fmt.Printf("i=0,ch=b\n") case 'c': fmt.Printf("i=0,ch=c\n") default: fmt.Printf("i=0,ch=_\n") } case 1: switch ch { case 'd': fmt.Printf("i=1,ch=d\n") case 'e': fmt.Printf("i=1,ch=e\n") case 'f': fmt.Printf("i=1,ch=f\n") default: fmt.Printf("i=1,ch=_\n") } case 2: switch ch { case 'g': fmt.Printf("i=2,ch=g\n") case 'h': fmt.Printf("i=2,ch=h\n") case 'i': fmt.Printf("i=2,ch=i\n") default: fmt.Printf("i=2,ch=_\n") } default: fmt.Printf("输入超范围!\n") } } 运行结果: 请输入参数i: 21 i=2,ch=_ 程序分析: 上述外层switch根据变量i的取值来选择分支,而内层switch根据字符变量ch的取值来选择分支。switch嵌套层数是没有限制的,建议不要嵌套太深,以免造成阅读、调试困难。 3.3.4break语句 与C语言不同,Go语言不需要显式地在每一个case分支最后加一个break语句。因为系统默认在每一个case分支最后隐含一个break。如果要在case分支语句块中提前退出程序的执行,则必须人为地插入一个break语句,退出当前的switch语句。 例如: var x,a,b,c=0,1,2,3 switch x{ case 0: if a==0{ break } c=b/a case 1: if b==0{ break } c=a/b } 在任何一个case语句块中,如果不需要程序继续执行,就可以插入一个break,退出当前switch语句。需要注意的是,break仅中断当前所在的switch语句。如果break是在子语句块的switch语句里面,则仅退出子语句块所在的switch,其外层switch的语句仍在执行。 break语句也可以一次退出多层switch,只需在break后面加一个标签即可。要退出哪一层switch,就跳转到该switch前面的标签处。标签是一个带有冒号结束的标识符,只可以放置在for、switch和select前面,如下所示: package main import "fmt" func main() { var ( x=0 a, b, c=1, 2, 3.14 ) NEXT: switch x { case 0: if a 0 { switch y :=0; y { case 0: c=float64(b) / float64(a) fmt.Println("y=0") break // No.1 case 1: c=float64(5*b) / float64(a) fmt.Println("y=1") break NEXT // No.2 } } a++ case 1: if b==0 { break// No.3 } c=float64(a) / float64(b) fmt.Printf("c=%f\n", c) default: break // No.4 } fmt.Printf("a=%d,c=%f\n", a, c) } 运行结果: (我们改变x,y的值观察不同的运行结果) x=0,y=0 的输出: y=0 a=2,c=2.000000 x=0,y=1 的输出: y=1 a=1,c=10.000000 x=1,y=1 的输出: c=0.500000 a=1,c=0.500000 程序分析: 上述程序中,在最外层switch前面加了一个标签NEXT,在内层switch中有两个break: 第一个break不带标签,执行后将退出内层子switch,外层switch仍在运行; 第二个break带了标签NEXT,一旦执行该break语句,程序流程将直接跳转到NEXT标签处。由于该标签在外层switch的前面,意味着退出该层switch,执行该switch语句下面一条语句。 第三条和第四条break语句处于外层switch,如果执行的话直接退出外层switch。 3.3.5fallthrough语句 switch语句中在每个case语句块最后隐含了一条break语句,语句块执行结束后直接退出switch。而不是像C语言那样,会穿越下一个case继续执行后续分支的语句块。这对大多数实际编程应用来说是有利的,减少了程序员敲键盘的数量,减轻了工作量。 但是,在某些情况下,还是有需要穿越case,执行后续的语句。Go语言专门提供了一条语句: fallthrough,用来穿越case,去执行下一个case分支的语句块。一条fallthrough语句,仅穿越一个case,如果需要连续穿越多个case,则需要多条fallthrough语句,在每个需要被穿越的case前面放置一条fallthrough。fallthrough语句穿越case后直接执行该case后第一条语句。如果没有语句,即该case语句块为空,则直接退出switch。 需要注意的是,fallthrough仅适用于表达式switch,对类型switch及select语句无效,且非法。 举个例子,假如需要对某类果汁饮料浓度进行分等级,原汁含量超过80%的为上等,50%~80%的为中等,低于50%的为下等,则可以编制以下程序实现。 package main import "fmt" func main() { var ( cff float64 i int ) fmt.Println("请输入原汁含量百分比: ") fmt.Scanln(&cff) i=int(cff / 10) switch i { case 0: fallthrough case 1: fallthrough case 2: fallthrough case 3: fallthrough case 4: fmt.Printf("该饮料品质等级为: 下等\n") case 5: fallthrough case 6: fallthrough case 7: fmt.Printf("该饮料品质等级为: 中等\n") default: fmt.Printf("该饮料品质等级为: 上等\n") } } 运行结果: 请输入原汁含量百分比: 75 该饮料品质等级为: 中等 程序分析: 从上述例子可以看出,需要在每个被穿越的case前面加一个fallthrough语句,这样重复劳动太多,显得烦琐。Go语言允许一个case有多个值,可以省略写多个case的情况。例如上述程序可以改写如下: func main() { var cff float64 fmt.Println("请输入原汁含量百分比: ") fmt.Scanln(&cff) switch int(cff / 10) { case 0,1,2,3,4: fmt.Printf("该饮料品质等级为: 下等\n") case 5,6,7: fmt.Printf("该饮料品质等级为: 中等\n") default: fmt.Printf("该饮料品质等级为: 上等\n") } } 一个case含多个值,等同于多个case的串联,同时也隐含了多个fallthrough,就像本例一样,把多个值写在一个case后面,就省略了给每个case写fallthrough,这样程序就显得简洁多了。 对于需要共同执行同一动作的多种情况,使用这种方式最适宜。 视频讲解 3.4select语句 Go语言是原生支持并发编程的语言,Go语言的goroutine是其专有的执行单元,是非常轻量级的线程(也称为协程),程序执行期间可以创建成千上万个goroutine。所有goroutine共享相同进程的内存空间,而各goroutine之间使用channel进行同步与通信。select语句就是用来随机选择一个channel通信的。有关并发及通信的相关知识将在第10章介绍,这里仅仅说明select语句的一些语法知识。 从形式上来说,select语句与switch语句类似,都是根据表达式的值来匹配多分支case,匹配成功则执行该case分支; 匹配不成功则执行default分支。但是,它们计算case表达式的顺序是不同的,switch采用从上到下,从左到右的顺序,一旦匹配成功马上执行该case分支。而select不同,select语句的case表达式只有通道操作语句,select语句从上到下,从左到右扫描计算所有case,寻找所有非阻塞的case,然后随机选择一个非阻塞的case执行。下面通过一个例子来看看select语句的执行情况。 package main import ( "fmt" "math/rand" ) func main() { a :=make(chan int, 10) b :=make(chan int, 10) c :=make(chan int, 10) d :=make(chan int, 10) for i :=0; i 10; i++ { q :=rand.Intn(6) fmt.Printf("第%d次读取,rand=%d ", i, q) switch q { case 0: a - 1 case 1: b - 1 case 2: c - 1 case 3: d - 1 default: a - 1 } select { case -a: fmt.Printf("通道为: %c\n", 'a') case -b: fmt.Printf("通道为: %c\n", 'b') case -c: fmt.Printf("通道为: %c\n", 'c') case -d: fmt.Printf("通道为: %c\n", 'd') default: -a fmt.Printf("通道为: %c\n", 'a') } } } 运行结果: 第0次读取,rand=5 通道为: a 第1次读取,rand=3 通道为: d 第2次读取,rand=5 通道为: a 第3次读取,rand=5 通道为: a 第4次读取,rand=1 通道为: b 第5次读取,rand=0 通道为: a 第6次读取,rand=1 通道为: b 第7次读取,rand=2 通道为: c 第8次读取,rand=4 通道为: a 第9次读取,rand=0 通道为: a 程序分析: 上述程序首先定义了4个通道,然后通过一个随机数来控制通道赋值(往通道里写入1),接着让select语句随机选择一个有数据的通道进行读取,并打印通道号。如果不用随机给通道赋值,可以每次循环都给全部通道赋值。尽管每个case都有数据,但select语句仍然是每次随机选择一个有数据的通道读取。 同样,select也支持break语句,在任意case分支上,如果提前结束程序流程,可以插入一条break语句。如果有多层嵌套的select语句,需要一次性退出多层select,可以加标签,将程序流程直接退出标签标注的select语句。参看以下例子: package main import "fmt" func main() { a :=make(chan int, 10) b :=make(chan int, 10) c :=make(chan int, 10) d :=make(chan int, 10) for i :=0; i 10; i++ { fmt.Printf("第%d次读取,", i) a - 1 b - 1 c - 1 d - 1 out: select { case -a: fmt.Printf("通道为: %c\n", 'a') case -b: fmt.Printf("外层退出,放弃b!\n") break fmt.Printf("通道为: %c\n", 'b') case -c: fmt.Printf("通道为: %c\n", 'c') case -d: select { case -a: fmt.Printf("内层退出至外层,放弃a:\n") break out // out 指向外层select语句 fmt.Printf("通道为: %c\n", 'a') case -b: fmt.Printf("内层通道为: %c\n", 'b') } default: fmt.Printf("退出通道!\n") } } } 运行结果: 第0次读取,通道为: c 第1次读取,内层退出至外层,放弃a: 第2次读取,外层退出,放弃b! 第3次读取,外层退出,放弃b! 第4次读取,通道为: c 第5次读取,通道为: a 第6次读取,外层退出,放弃b! 第7次读取,内层退出至外层,放弃a: 第8次读取,通道为: a 第9次读取,通道为: c 程序分析: 仔细分析上述程序的运行结果,就能看清楚什么时候执行的是外层break,什么时候执行的是内层break。执行内层break的时候才能退出标签所在的switch。 但是,fallthrough语句不适用于select语句,加入了该语句,编译系统会报错(错误提示: fallthrough statement out of place)。 视频讲解 3.5for语句 循环是编程实践中使用最多的语法结构,重复执行的动作都需要用循环完成。Go语言不同于其他语言,它仅有for循环,而没有while和do…while循环。 3.5.1基本语法 for循环的基本语法格式如下: for 可选前置子句; 布尔表达式; 可选后置子句{ 循环体 } for关键字后面由三部分构成: 可选前置子句、布尔表达式、可选后置子句。各部分之间必须用分号“;”隔开。事实上,可选子句及表达式都是可以省略的,省略子句后分隔符“;”也要跟着省略。 如果可选子句及布尔表达式均省略,编译系统默认为逻辑真true,则for循环就成了以下格式: for{ 循环体 } 这是一个无限循环,如果需要退出循环,必须在循环体内设置退出条件,比如加上一条break语句。 大多数情况下的循环语句是以下格式的: for布尔表达式{ 循环体 } 当布尔表达式的值为true时,执行循环体。循环体执行结束之后程序流程将返回布尔表达式,再次计算布尔表达式的值,如果其值为false,则退出循环; 为真就继续执行循环体。 前置子句: 必须为Go语言的简单语句,多用于变量的初始化,通常是短变量声明语句。需要注意的是,用短变量操作符“:=”声明的变量,其作用域仅为循环体内,循环体外不可见。大多数情况下,子句中声明的变量都是作为循环控制变量,用来控制循环次数。 布尔表达式: 也称逻辑表达式,是循环的条件,可省略。省略的逻辑表达式其结果默认为true。 后置子句: 通常用来控制循环的次数,通过修改循环控制变量的值来影响逻辑表达式的值,目标是使得循环控制变量逐渐脱离循环条件,最终结束循环。 后置子句是可选的,如果没有,则控制循环的表达式放在循环体内,在循环体内控制循环结束的条件。 for循环程序的执行顺序是: ① 执行前置子句; ② 执行条件表达式,如果为假,则直接退出循环; 为真,则执行循环体; ③ 执行循环体; ④ 执行后置子句; ⑤ 返回②继续执行条件表达式。 以下程序利用循环实现输出20以内的奇数。 package main import ( "fmt" ) func main() { fmt.Printf("20以内的奇数是: \n") for i :=1; i =20; i++ { if i%2 !=0 { fmt.Printf("%3d ", i) } } fmt.Printf("\n") } 运行结果: 20以内的奇数是: 1 3 5 7 9 11 13 15 17 19 程序分析: 上述程序中利用模2运算,也就是求余运算,从1开始,能被2整除的数就是偶数,不能被2整除的数就是奇数。因此,通过2的求余运算,只要余数为0就抛弃,不为0就打印输出。奇数打印的时候是连续打印,不换行(不加转义字符\n),待全部打印完毕后打印一个“\n”,表示换行。 3.5.2for子句 for子句主要是指前置子句和后置子句,它们都是可选的。所有子句必须为简单语句。前置子句多用来声明循环变量,后置子句多用来修改循环变量。仅当需要明确控制变量的作用域时,才把控制循环次数的变量放在前置子句中定义,一旦循环结束,该变量将不可见。这样做既可以节省内存,还可以提高变量使用的安全性。3.5.1节例子就是在前置子句中声明的循环变量。当然也可以把变量声明及初始化表达式放在循环语句前面,变成以下形式: func main() { fmt.Printf("20以内的奇数是: \n") i :=1 for ; i =20; i=i+1{ if i%2 !=0 { fmt.Printf("%d ", i) } } fmt.Println() } 上述fmt.Println()语句起到换行的作用。 如果有必要,还可以把后置子句省略掉,在循环体内修改循环条件,如下所示: func main() { fmt.Printf("20以内的奇数是: \n") i :=1 for i =20{ if i%2 !=0 { fmt.Printf("%d ", i) } i++ } fmt.Printf("\n") } 效果是一样的,这种方式更符合一般的逻辑。 将变量声明及初始化放在for前面,把循环控制语句放在循环体内最后一句,逻辑更加清晰,程序也更容易让人读懂。 for关键字后省略掉前置和后置两个子句后,实际上变成以下形式: for ; i=20; { 循环体 } 可选子句省略了,但保留了两个分号,不算错,编译系统会自动把它去掉。但是位置不能出错,如果是下面的形式: for ; ; i=20{ 循环体 } 或者 for i=20 ; ; { 循环体 } 则编译系统会报错,程序无法执行。如果只是省略前置子句或后置子句其中一个,则分号不能省略,必须是如下形式: for ; i=20 ; i++{ 循环体 } 或者 for i:=0; i=20 ; { 循环体 } 这是合法的,可以正常编译通过。 3.5.3range子句 通过循环来遍历一棵树的全部元素,或者一个数组的全部元素等,类似的应用在编程实践中经常遇到,这种一遍一遍的重复动作称为迭代。Go语言专门提供了一个迭代关键字range,用来完成迭代操作。range配合for在循环中使用,其语法格式如下: for 变量1,变量2 :=range 迭代对象{ 循环体 } 迭代对象: 可以是数组(array)、切片(slice)、字符串(string)、映射(map)及通道(channel)。不同的迭代对象,其返回的值也不同,具体见表31。 表31range返回值 变量1(key)变量2(value)迭代对象(object) indexarrayName[index]array indexsliceName[index]slice indexstringName[index]string keymapName[key]map elementchan 循环次数取决于迭代对象的内容长度,其长度可用内部函数len求得,如遇到空的字符串等会直接退出,不会遍历。 从表中可以看出,数组、切片、字符串、映射等都有两个返回值,变量1为索引值,其值从0开始,直至其长度len-1; 变量2为迭代对象的内容值。映射没有索引值,返回的是映射项中的键,第二个返回映射项中的值。而通道仅返回一个值,就是通道里的元素值。 变量1和变量2不需要预先定义,可以在for子句里采用短变量声明操作符声明并赋值。当然,这也不是必须的,在for子句里给已声明过的变量赋值也是允许的。关于数组、切片、字符串等知识尚未讲到,后续章节中会陆续说明,目前我们暂时用字符串来说明range子句的用法。以下例子是从键盘输入一个字符串,然后遍历每一个字符并输出。 package main import ( "fmt" ) func main() { fmt.Printf("请输入一个给定的字符串: \n") var str string fmt.Scanf("%s", &str) for i, ch :=range str { fmt.Printf("i=%d ch=%c\n", i, ch) } fmt.Printf("\n") } 运行结果: 请输入一个给定的字符串: abcde i=0 ch=a i=1 ch=b i=2 ch=c i=3 ch=d i=4 ch=e 程序分析: 使用range子句遍历字符串的时候,其遍历的第一个变量是其索引值,为int型,序号从0开始,是其UTF8字节编码的下标。遍历的第二个变量为rune类型的字符的Unicode码值。如果遇到非法的UTF8顺序,则第二个遍历的值为0xFFED,并从下一个字节再次遍历。 上述变量i和ch在for子句里声明,其作用域仅在循环体内,遍历的结果也仅在循环体内有效,结束循环后i及ch均不可见。为了保存遍历结果,可以定义一个外部的字符串数组,将遍历出来的每一个字符存放数组里。 如果只是提取字符串中的每一个字符,也可以使用标准库函数来获取每一个字符,如下所示。 package main import ( "fmt" "unicode/utf8" ) func main() { fmt.Printf("给定字符串: xαyβzγ \n") var str string="xαyβzγ" for i :=range str { char, size :=utf8.DecodeRuneInString(str[i:]) fmt.Printf("i=%d char=%c size=%d \n", i, char, size) } fmt.Printf("\n") } 运行结果: 给定字符串: xαyβzγ i=0char=x size=1 i=1char=α size=2 i=3char=y size=1 i=4char=β size=2 i=6char=z size=1 i=7char=γ size=2 程序分析: 上述程序使用了库函数,没有前一种方法方便直接,因此,推荐使用前一种方法。 需要注意的是,range迭代字符串给出的index是字节偏移值,如果是纯ASCII码字符串,如“abcde”,则index的值为0,1,2,3,4。如果是UTF8字符串,如“xαyβzγ”这样的字符串,则index的值为0,1,3,4,6,7。UTF8字符使用Unicode码点值,占两个字节。所以,遍历字符串字符用UTF8包的库函数比较安全可靠。 从上述程序中可以看出,在range中迭代出索引值,然后在循环体里根据索引获得元素值,这种用法在数组、切片的编程实践中经常使用。 在实际编程实践中,变量1和变量2有时可能只需要其中一个,这个时候可以用一个空字符“_”代替,作用是忽略该变量,如下所示。 _, ch=range str 或 i, _=range str 甚至一个变量都不要,也是允许的,如下所示。 package main import ( "fmt" ) func main() { var str="astring" i :=1 for range str { fmt.Printf("str[%d]=%c \n", i, str[i]) if i++; i==len(str) { break } } } 运行结果: str[1]=s str[2]=t str[3]=r str[4]=i str[5]=n str[6]=g 程序分析: 上述索引 i从1开始,因此str[0]的字符a并没有输出,这进一步说明了,用下标方式索引字符串、数组、切片等,下标值一定要从0开始。 如果赋值号左边只写一个变量,默认是变量1(index),如果需要获取数组或切片元素的值及其索引,则必须有第二个变量。 for …range遍历语句中的for后面不再支持加入子句,如果加入了子句会编译出错,如下所示。 func main() { var y=[]int{1,2,3,4,5,6} for sum :=0; _, value :=range y { sum +=value } fmt.Println("sum=", sum) } 上述程序中把变量sum的声明及初始化放在了for后面的子句里,导致编译出错,syntax error: unexpected range, expecting expression。 如果把初始化语句提到for前面一行,编译就通过了,如下所示。 func main() { var y=[]int{1, 2, 3, 4, 5, 6} sum :=0 for _, value :=range y { sum +=value } fmt.Println("sum=", sum) } 运行结果: sum=21 3.5.4break语句 for循环的结束由其逻辑表达式的值决定,当出现逻辑假时,循环结束。如果有必要,可以人为地结束循环,这就是break语句的使用。上述例子中就是使用break结束循环的。break结束的是离它所在位置最近的最内层循环,如果需要退出到最外层循环,或者是某一层循环,则可以在break后面加标签,指定退出的是哪一层循环。想要结束哪一层循环,就在该层循环前面加一个标签,如下述例子所示。 假设在一个有i行j列的二维矩阵Matrix中搜索一个特定值x,搜索成功就将标志符flag置1,失败就将flag清零,程序实现如下。 package main import ( "fmt" ) func main() { var Matrix=[][]int{{12, 25, 14, 29}, {32, 58, 41, 42}, {24, 31, 56, 63}} flag :=0 x :=42 Found: for i :=range Matrix { for j :=range Matrix[i] { if Matrix[i][j]==x { flag=1 fmt.Printf("\n搜索成功!flag=%d\n", flag) fmt.Printf("Matrix[%d][%d]=%d \n", i, j, Matrix[i][j]) break Found } } } } 运行结果: 搜索成功!flag=1 Matrix[1][3]=42 程序分析: 上述例子中首先定义了一个3行4列的数组切片,假设一个被搜索数据42,通过两层循环来扫描所有数据。 搜索成功后置flag为1,并打印标志位及该矩阵位置的值。break 后加了标签Found,指明是外层循环,break直接退出外层循环。如果不加标签,则退出的是内层循环,内层循环之外还得加一个break才能退出外层循环。 3.5.5continue 语句 在循环编程实践中,常常会碰到这种情况: 循环体程序运行到某一处,已经获得了期望的结果,不需要继续执行后续的语句了,这时该怎么做呢?这就要用到continue语句了。 continue语句的执行逻辑是中断本轮循环,将程序控制流程返回到for后置子句,重新计算循环次数,接着计算逻辑表达式的值。如果为true,则进入下一轮循环; 如果为false,则直接退出循环,等同执行break语句。 需要注意的是,如果for语句省略了后置子句,而将循环控制变量的计算放在循环体末尾,这时执行continue中断循环,并不会执行循环控制变量的运算,而是直接返回执行逻辑表达式计算。由于循环变量没改变,逻辑表达式的值也就不会改变,从而又重复执行本次循环,反复执行同一轮循环将导致程序死锁。 另外,continue语句只适合于for循环,不适合于switch和select语句。 以下程序实现求一个最小的四位整数,要求该整数的个位为2,十位是4,且减去3后能被3整除,减去7后能被7整除。 package main import ( "fmt" ) func main() { for i :=1000; i =9999; i++ { if i%10 !=2 { continue } else if i/10%10 !=4 { continue } else if (i-3)%3 !=0 { continue } else if (i-7)%7 !=0 { fmt.Printf("\n满足条件的数是: %d\n", i) break } } } 运行结果: 满足条件的数是: 1242 程序分析: 上述程序多次使用continue中断当前循环轮次,进入下一轮次循环,当所有条件都满足之后,由break退出循环体。 上述程序的执行逻辑是: 只要第一个条件不满足,则其余条件就不用检查了,直接用continue中断当前循环轮次,进入下一轮次循环,继续比对下一个数据。 3.5.6goto语句 goto语句是程序流程跳转语句,改变程序流程,直接跳转到同一函数内某个标签所在的位置。goto语句不允许跨越函数,否则编译系统会报错。 在很多编程语言中goto语句已经被取消,人为控制程序流程极容易导致程序逻辑混乱,程序难以移植,难以读懂,不易维护。Go语言保留了goto语句,主要是为了编程的灵活性,适应某些需求。但是,编程实践中建议尽量少使用goto语句。goto语句的使用非常简单,在函数体内有需要的地方插入标签,在goto语句后边加上标签即可。 参看下例,寻找10以内减3且能被3整除的数: package main import ( "fmt" ) func main() { i :=10 for i 0 { i-- if i==0 { break } if (i-3)%3==0 { goto cont } else { continue } cont: fmt.Printf("10以内减3且能被3整除的数是: %d\n", i) } } 运行结果: 10以内减3且能被3整除的数是: 9 10以内减3且能被3整除的数是: 6 10以内减3且能被3整除的数是: 3 程序分析: 上述程序算法比较简单,仅用求余运算就可以解决问题。程序中加入了goto语句,goto语句后必须带标签,表示跳转的目的地。 上 机 训 练 一、 训练内容 1. 请用循环语句实现以下圣诞树。 * *** ***** ******* ********* ***** ******* ********* *********** ************* ********* *********** ************* *************** ***************** ***** ***** ***** 2. 请编程实现从键盘输入一门课程成绩,为其分级,等级为优秀、良好、中等、及格和不及格,要求用两种方式实现: if…else if和switch case。 二、 参考程序 1. 圣诞树参考程序。 参考程序 package main import ( "fmt" ) func main() { mid :=30 // 控制树顶的打印位置 续 参考程序 for a :=0; a 5; a +=2 {// 控制圣诞树层数 // 打印树叶 for i :=0; i 5; i++ { // 打印行数 for j :=0; j mid-i-a; j++ { // 打印空格数 fmt.Printf(" ") } for j :=-(i+a); j =i+a; j++ { // 打印*数量 fmt.Printf("*") } fmt.Printf("\n") } } for i :=0; i 3; i++ { // 打印树干 for j :=0; j =mid-3; j++ { fmt.Printf(" ") } fmt.Printf("*****\n") } } 运行结果 * *** ***** ******* ********* ***** ******* ********* *********** ************* ********* *********** ************* *************** ***************** ***** ***** ***** 2. 成绩等级划分参考程序。 (1) if…else if 方式。 参考程序 package main import ( "fmt" ) func main() { fmt.Println("请输入一门课程的成绩: ") var score int 续 参考程序 fmt.Scanf("%d", &score) if score =90 && score 100 { fmt.Println("成绩等级为: 优秀") } else if score =80 && score 90 { fmt.Println("成绩等级为: 良好") } else if score =70 && score 80 { fmt.Println("成绩等级为: 中等") } else if score =60 && score 70 { fmt.Println("成绩等级为: 及格") } else if score =0 && score 60 { fmt.Println("成绩等级为: 不及格") } } 运行结果 请输入一门课程的成绩: 89 成绩等级为: 良好 (2) switch case方式。 参考程序 package main import ( "fmt" ) func main() { fmt.Println("请输入一门课程的成绩: ") var score int fmt.Scanf("%d", &score) switch score / 10 { case 9, 10: fmt.Println("成绩等级为: 优秀") case 8: fmt.Println("成绩等级为: 良好") case 7: fmt.Println("成绩等级为: 中等") case 6: fmt.Println("成绩等级为: 及格") default: fmt.Println("成绩等级为: 不及格") } } 运行结果 请输入一门课程的成绩: 76 成绩等级为: 中等 习题 一、 单项选择题 1. 以下全局变量定义正确的是()。 A. var X int=10B. X :=10C. int X=10D. var X=10 2. 以下全局可导出变量的定义正确的是()。 A. set NameB. int NameC. var Name intD. var name int 3. 以下局部变量定义正确的是()。 A. int x :=10B. var x :=10C. x :=10D. int x=10 4. 已知: a=10,b=3.14,c=20,以下表达式正确的是()。 A. a=100*b-cB. a+=bC. c=10%3+aD. c=10*a/b 5. 已知: a=1,b=3.2,c=20,以下表达式正确的是()。 A. a=++b-cB. a++=b C. c=10/3+a++D. c=10*a/c 6. 已定义c为字符型变量,则下列语句中正确的是()。 A. c='97'B. c="97"C. c=97D. c="a" 7. 以下if语句格式正确的是()。 A. if (x>y && y+1) {block}B. if x+y {block} C. if x>0 && y>1 {block}D. if x=5 {block} 8. 以下if语句格式正确的是()。 A. if x:=0>y {block}B. if x=x+y; x>y {block} C. if x>0||y=1 {block}D. if !( x=5){block} 9. 以下if语句格式正确的是()。 A. if x:=0; y<=10 {block} B. if x>y; x=x-1 {block} C. if x>0;; {block} D. if x>=5; x=x-1 {block} 10. 以下switch语句格式正确的是()。 A. switch x :=0; y<=10{ case 0: block0 case 1: block1 default: blockd } B. switch x :=0; y=10 { case 0: block0 case 1: block1 default: blockd } C. switch y <=10 { case 0: block0 case 1: block1 default: blockd } D. switch x :=0; y { case 0: block0 case 1: block1 default: blockd } 二、 判断题 1. if语句的表达式必须为算术表达式。() 2. if语句的表达式必须为逻辑表达式。() 3. if语句中的表达式子句是必须的。() 4. if语句中的表达式是可选的。() 5. if语句中的前置子句是可选的。() 6. for语句中的后置子句是可选的。() 7. 当for语句中的前置及后置子句其中之一缺省时,缺省子句前面或后面的分号不可省略。() 8. 在外层if前置子句声明的变量在嵌入的内层if表达式中是可见的。() 9. 当switch语句不带表达式时,其case子句的表达式必须为逻辑表达式。() 10. 当switch语句带逻辑表达式时,其case子句的表达式必须为逻辑表达式。() 11. 当switch语句带逻辑表达式时,其case子句的表达式可以是算术表达式。() 12. 当switch语句带算术表达式时,其case子句的表达式必须为逻辑表达式。() 13. 当switch语句带算术表达式时,其case子句的表达式必须为算术表达式。() 14. fallthrough语句可以穿越“表达式switch”的case分支。() 15. fallthrough语句可以穿越“类型switch”的case分支。() 16. fallthrough语句可以穿越select语句的case分支。() 17. break语句可以退出for循环语句。() 18. break语句可以退出switch…case语句。() 19. break语句可以退出if…else语句。() 20. continue语句可以用于switch…case语句。() 21. continue语句只能用于for循环语句。() 22. 标签lable只能用于break语句或goto语句。() 23. 标签lable也可以用于continue语句或fallthrough语句。() 24. break语句一次性退出多层循环必须使用标签lable。() 三、 分析题 1. 分析以下程序,写出运行结果,并分析其实现的功能。 package main import ( "fmt" ) func main() { UU :=0 for i :=0; i 2; i++ { fmt.Printf("这是外部变量UU的值: %d循环变量i=%d\n", UU, i) var UU int=1 UU++ fmt.Printf("这是内部变量UU的值: %d循环变量i=%d\n", UU, i) } fmt.Println("这是退出循环后外部变量UU的值: ", UU) } 2. 分析以下程序,写出运行结果,并分析其实现的功能。 package main import ( "fmt" ) func main() { var i=10 switch i++; { case i =10: i++ case i =11: i++ case i =12: i++ default: i++ } fmt.Println("i=", i) } 3. 分析以下程序,写出运行结果。 package main import "fmt" func main() { var a, b='a', 'b' a++ fmt.Printf("%c,", a) a=b +10 b=a fmt.Printf("%c\n", b) } 4. 分析以下程序,写出运行结果。 package main import "fmt" func main() { var i int for i=0; i 3; i=i+1 { switch i { case 1: fmt.Printf("%d,", i) case 2: fmt.Printf("%d\n", i) } } } 5. 分析以下程序,写出运行结果。 package main import "fmt" func main() { var ch='W' if ch 'A' && ch 'Z' { ch=ch+32 } else { ch=ch-32 } fmt.Printf("%c\n", ch) } 6. 分析以下程序,写出运行结果。 package main import "fmt" func main() { var a, c, b, d, x=2, 6, 5, 4, 10 if a b { if c d { x=1 } else if a c { if b d { x=2 } else { x=3 } } else { x=6 } } else { x=7 } fmt.Printf("%d\n", x) } 7. 分析以下程序,写出运行结果。 package main import "fmt" func main() { var a, c, b, x=5, 2, 3, 35 x-- if !(a b) && ^c a { if c b { x=1 } else if a c { x=2 } else { x=3 } } else { x=6 } fmt.Printf("%d\n", x) } 8. 分析以下程序,写出运行结果。 package main import "fmt" func main() { var x, y, z=16, 21, 0 switch x % 3 { case 0: z++ case 1: z++ switch y % 2 { case 0: z++ break default: z++ } } fmt.Printf("%d\n", z) } 9. 若x=-4,y=8,z=1,m=-13,计算下列各表达式的值。 A. x+y<z+m%4 B. x+3*y+z >=2*x-m C. 4*y/x-m>y%2-z D. !(x>2*y)||( z+m)<x E. x<3*m/4&&y%5<z 10. 下列代码执行后,x,y,z的值各是多少? package main import "fmt" func main() { var x, y, z=0, 0, 1 switch x { case 0: x=2 y=3 case 1: x=4 default: x=1 y=3 } fmt.Printf("x=%d, y=%d, z=%d\n", x, y, z) } 11. 下列代码执行后,x,y,z的值各是多少? package main import "fmt" func main() { var x, y, z=2, 1, 1 switch x % 2 { case 0: x=2 y=3 case 1: x=4 default: x=1 y=3 } fmt.Printf("x=%d, y=%d, z=%d\n", x, y, z) } 12. 下列代码执行后,x,y,z的值各是多少? package main import "fmt" func main() { var x, y, z=1, 3, 0 switch x+y%2 { case 0: x=5 y=7 case 1: x=4 default: x=9 y=8 } fmt.Printf("x=%d, y=%d, z=%d\n", x, y, z) } 四、 简答题 1. Go语言流程控制语句主要包括哪些? 2. if语句的子句有什么限制条件? 3. if语句的语法格式有什么样的规定? 4. 什么情况下可以省略else子句? 5. if语句中子句声明的变量其作用域到哪里结束? 6. 什么叫代码块? 7. 什么是变量的可见性? 8. 什么叫变量的作用域? 9. 什么是全局变量? 10. 什么叫局部变量? 11. 内部变量对外部变量的覆盖是什么意思? 12. 如何快速退出嵌套的switch语句? 13. 什么是标签?有什么作用? 14. switch有几种类型?各有什么特点? 15. 表达式switch…case和select…case有什么区别? 16. switch语句的执行流程是什么样的? 17. select语句的执行流程是什么样的? 18. switch子句声明的变量作用域在哪里? 19. break语句的适用范围是什么? 20. break语句的标签应该放在程序中什么位置? 21. fallthrough语句的功能和适用范围是什么? 22. for的前置子句省略后,其后面的分号也要省略吗? 23. range子句迭代数组输出两个变量,分别是什么? 24. continue语句的适用范围是什么? 五、 编程题 1. 请编程实现从键盘输入一个任意位数的整数,按逆序打印输出每一位数据。 2. 请编程实现从键盘输入三个整数,按从小到大顺序打印输出。 3. 判断100~200有多少个素数,并输出所有素数。 4. 编程实现从键盘输入两个正整数m和n,求其最大公约数和最小公倍数。 5. 请编程从键盘输入一行字符,分别统计出其中大写英文字母、小写英文字母、数字和其他字符的个数。 6. 请编程实现从键盘输入一个任意整数,求该数的阶乘并输出。 7. 请编程实现求1+2+3+…+100的和并打印输出。 8. 请编程实现从键盘输入四门课程的成绩,求其最高分、最低分及平均分并打印输出。 9. 请编程输出9×9乘法口诀表(分别用两种形式输出: 正方形和三角形)。 10. 一球从100米高度自由落下,每次落地后反跳回原高度的一半; 再落下,求它在第10次落地时,共经过多少米?第10次反弹多高?