2.3 流程控制语句
流程控制语句是编程语言中的核心之一,可分为:
- 分支语句(if、when)
- 循环语句(for、while)
- 跳转语句(return、break、continue、throw)
2.3.1 if表达式
if…else语句是控制程序流程的最基本形式,其中else是可选的。在Kotlin中,if是一个表达式,即它会返回一个值(跟Scala一样)。代码示例如下:
另外,if的分支可以是代码块,最后的表达式作为该块的值:
if作为代码块时,最后一行为其返回值。另外,在Kotlin中没有类似true?1:0这样的三元表达式。对应的写法是使用if…else语句:
if(true) 1 else 0 //if…else实现三元表达式的逻辑
if…else语句规则:if后的括号不能省略,括号里表达式的值必须是布尔型。
代码反例:
如果条件体内只有一条语句需要执行,那么if后面的大括号可以省略。良好的编程风格是建议加上大括号。
编程实例:用if…else语句判断某年份是否是闰年。
2.3.2 when表达式
when表达式类似于switch…case表达式。when会对所有的分支进行检查直到有一个条件被满足。但相比switch而言,when语句的功能要更加强大、灵活。
Kotlin的极简语法表达风格,使得我们对分支检查的代码写起来更加简单、直接:
输出如下:
1 ===> 这是一个0-9之间的数字 hello ===> 这个是字符串hello X ===> 这是一个Char类型数据 null ===> else类似于Java中的case-switch中的default
像if语句一样,when语句的每一个分支也可以是一个代码块,它的值是块中最后的表达式的值。如果其他分支都不满足条件会到else分支(类似default)。
如果我们有很多分支需要用相同的处理方式,则可以把多个分支条件放在一起,用逗号分隔:
0,1,2,3,4,5,6,7,8,9 -> println("${obj} ===> 这是一个0-9之间的数字")
可以用任意表达式(而不只是常量)作为分支条件:
也可以检测一个值在in或者不在!in一个区间或者集合中:
编程实例:用when语句写一个阶乘函数。
2.3.3 for循环
for循环可以对任何提供迭代器(iterator)的对象进行遍历,语法如下:
如果想要通过索引遍历一个数组或者一个list,可以这么做:
for (i in array.indices) { //array.indices 存储了数组 array 的下标序列 print(array[i]) }
其中,array.indices持有数组的下标列表。我们也可以使用函数withIndex()来遍历下标与对应的元素:
另外,范围(Ranges)表达式也可用于循环中:
代码简写如下:
(1..10).forEach { print(it) }
其中的操作符形式的1..10等价于1.rangeTo(10)函数调用,由in和!in进行连接。
编程实例:编写一个Kotlin程序在屏幕上输出1!+2!+3!+…+10!的和。
我们使用上面的fact()函数,代码实现如下:
2.3.4 while循环
while和do…while循环语句的使用方式与C、Java语言基本一致。代码示例如下:
2.3.5 break和continue
break和continue语句都是用来控制循环结构的,主要用来停止循环(中断跳转),但是二者又有区别,下面分别介绍。
break语句用于完全结束一个循环,直接跳出循环体,然后执行循环后面的语句。
1. 问题场景:打印数字1~10,只要遇到偶数就结束打印
下面我们使用for循环打印1~10之间的数字,遇到偶数就使用break语句结束循环。代码示例如下:
输出如下:
1 2
continue语句是只终止本轮循环,但是还会继续下一轮循环。可以简单理解为直接在当前语句处中断,跳转到循环入口,执行下一轮循环。而break语句则是完全终止循环,跳转到循环出口。
2. 问题场景:打印数字1~10中的奇数
下面我们使用for循环来打印1~10中的数字,遇到偶数就使用continue语句跳过本轮循环,实现只打印奇数的效果。代码示例如下:
输出如下:
1 3 5 7 9
2.3.6 return返回
在Java和C语言中,return语句是很常见的了。虽然在Scala和Groovy类语言中,函数的返回值可以不需要显示用return语句来指定,但是我们仍然认为使用return语句的编码风格更容易阅读理解(尤其是在分支流代码块中)。
在Kotlin中,除了表达式的值,有返回值的函数都要求显式使用return语句返回其值。代码示例如下:
在Kotlin中可以直接使用“=”符号返回一个函数的值,这样的函数称为函数字面量。代码示例如下:
代码说明如下:
第(1)处使用表达式声明sum()函数。
第(2)处ifelse表达式返回的是一个值,我们直接用表达式来定义一个max()函数。
第(3)处使用fun关键字声明了一个匿名函数,并且直接使用表达式来实现函数。需要注意的是,后面的函数体语句中有没有大括号{}代表的意义完全不同。例如下面的代码:
代码说明如下:
第(4)处带上大括号{}的表达式返回的是一个Lambda表达式。
第(5)处{a+b}的类型是从(kotlin.Int, kotlin.Int)到()->kotlin.Int的映射函数。
第(6)处调用了sumf()函数。
第(7)处中sumf(1,1)的返回值是一个函数()->kotlin.Int,而不是具体的数值2。如何实现真正的函数调用呢?请看第(8)处。
第(8)处使用invoke()函数实现真正调用函数。
在Kotlin中,()操作符对应的是类的重载函数,如invoke()。我们使用()运算符来调用函数。也就是说第(8)处与第(9)处是等价的写法。
我们也可以使用fun关键字加上函数名来声明函数,下面同样是展示带上大括号{}的例子,代码示例如下:
可以看出,sumf和maxf()的返回值都是函数类型:
() -> kotlin.Int
这点与Scala是不同的。在Scala中,带不带大括号{}的意义是一样的:
可以看出,maxf: (x: Int,y:Int)Int与maxv: (x: Int, y: Int)Int的签名是一样的。在这里,Kotlin与Scala在大括号的使用上是完全不同的。调用函数方式是直接调用invoke()函数sumf(1,1).invoke()。
Kotlin中return语句会从最近的函数或匿名函数中返回,但是在Lambda表达式中遇到return语句时,则直接返回最近的外层函数。例如下面两个函数是不同的:
输出如下:
1 2
遇到3时会直接返回(类似于循环体中的break语句)。
而我们给forEach传入一个匿名函数fun(a:Int),这个匿名函数里的return语句不会跳出forEach循环,有点像continue语句的效果:
输出如下:
1 2 4 5
为了显式地指明return语句返回的地址,Kotlin还提供了@Label(标签)来控制返回语句,且看2.3.7节的讲解。
2.3.7 标签(label)
在Kotlin中任何表达式都可以用标签(label)来标记。标签的格式为标识符后跟@符号,如abc@、_isOK@都是有效的标签。我们可以用Label标签来控制return、break或continue语句的跳转(jump)行为。
代码示例如下:
输出如下:
1 2 4 5
我们在Lambda表达式开头处添加了标签here@,可以这么理解:该标签相当于记录了Lambda表达式的指令执行入口地址,然后在表达式内部使用return@here跳转至Lambda表达式中的该地址处。这样代码更加易懂。
另外,也可以使用隐式标签,更加方便。该标签与接收该Lambda的函数同名。
代码示例如下:
val intArray = intArrayOf(1, 2, 3, 4, 5) intArray.forEach { if (it == 3) return@forEach //返回@forEach 处继续下一个循环 println(it) }
输出如下:
1 2 4 5
接收该Lambda表达式的函数是forEach,所以我们可以直接使用return@forEach跳转到此处执行下一轮循环。
2.3.8 throw表达式
在Kotlin中throw是表达式,它的类型是特殊类型Nothing。该类型没有值,与C、Java语言中的void意思一样。
我们在代码中,用Nothing来标记无返回的函数:
另外,如果把一个throw表达式的值赋给一个变量,需要显式声明类型为Nothing,代码示例如下:
另外,因为ex变量是Nothing类型,没有任何值,所以无法作为参数传给函数。