- Kotlin从入门到进阶实战
- 陈光剑
- 2514字
- 2021-04-02 03:46:21
2.3 流程控制语句
流程控制语句是编程语言中的核心之一,可分为:
分支语句(if、when)
循环语句(for、while)
跳转语句(return、break、continue、throw)
2.3.1 if表达式
if…else语句是控制程序流程的最基本形式,其中else是可选的。在Kotlin中,if是一个表达式,即它会返回一个值(跟Scala一样)。代码示例如下:
![](https://epubservercos.yuewen.com/317ECE/15253386404112206/epubprivate/OEBPS/Images/Figure-P34_40827.jpg?sign=1739325387-UBijoSYeBxd416ORw4IvazraxgIYsiAr-0-00fa4dcefe8f5b1c9ddd4be084c6d79f)
另外,if的分支可以是代码块,最后的表达式作为该块的值:
![](https://epubservercos.yuewen.com/317ECE/15253386404112206/epubprivate/OEBPS/Images/Figure-P34_40828.jpg?sign=1739325387-WpBpPyBZ2YV6foXxPHjdA9doqvoNOYzf-0-8331a3328e38dc762682cdc8c90ef10f)
if作为代码块时,最后一行为其返回值。另外,在Kotlin中没有类似true?1:0这样的三元表达式。对应的写法是使用if…else语句:
if(true) 1 else 0 //if…else实现三元表达式的逻辑
if…else语句规则:if后的括号不能省略,括号里表达式的值必须是布尔型。
代码反例:
![](https://epubservercos.yuewen.com/317ECE/15253386404112206/epubprivate/OEBPS/Images/Figure-P34_40829.jpg?sign=1739325387-IErESysJpbMy3felTGKAvxx5vtTODMI5-0-c7e8d66de4a2580e210ed1e42c8e1503)
如果条件体内只有一条语句需要执行,那么if后面的大括号可以省略。良好的编程风格是建议加上大括号。
![](https://epubservercos.yuewen.com/317ECE/15253386404112206/epubprivate/OEBPS/Images/Figure-P34_40830.jpg?sign=1739325387-pFDFjdCY42n4Ut63rTKhH5BchCDaPaVj-0-90828a5440bdca3577ca1e5d1f454d52)
编程实例:用if…else语句判断某年份是否是闰年。
![](https://epubservercos.yuewen.com/317ECE/15253386404112206/epubprivate/OEBPS/Images/Figure-P35_41073.jpg?sign=1739325387-cQ4F8ck69odYjTwAQf5aojaQSfbNAl0H-0-4560f9e096dbd44da6d95b3e81df97c7)
2.3.2 when表达式
when表达式类似于switch…case表达式。when会对所有的分支进行检查直到有一个条件被满足。但相比switch而言,when语句的功能要更加强大、灵活。
Kotlin的极简语法表达风格,使得我们对分支检查的代码写起来更加简单、直接:
![](https://epubservercos.yuewen.com/317ECE/15253386404112206/epubprivate/OEBPS/Images/Figure-P35_41074.jpg?sign=1739325387-tqTcxLFhsevZcpxOcNgzutT185Td0zVR-0-b360c7e781c80d4f78959b72d84c2c35)
输出如下:
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之间的数字")
可以用任意表达式(而不只是常量)作为分支条件:
![](https://epubservercos.yuewen.com/317ECE/15253386404112206/epubprivate/OEBPS/Images/Figure-P36_41309.jpg?sign=1739325387-G2gChUrwxXgfUzZRL6sKv2X0M1KKyTBc-0-c1eb27915fc6a504c54942333fc6c3ab)
也可以检测一个值在in或者不在!in一个区间或者集合中:
![](https://epubservercos.yuewen.com/317ECE/15253386404112206/epubprivate/OEBPS/Images/Figure-P36_41310.jpg?sign=1739325387-zDPfPEAbL8czNLtIIDEqGadCBHD0dIxv-0-237aae90cf40f61193e19b57f557a682)
编程实例:用when语句写一个阶乘函数。
![](https://epubservercos.yuewen.com/317ECE/15253386404112206/epubprivate/OEBPS/Images/Figure-P36_41311.jpg?sign=1739325387-hFZ4bvIMVfLTX5HJfoElRHWqeoXYKPaS-0-0389855d45fbc52691cd94925e069653)
2.3.3 for循环
for循环可以对任何提供迭代器(iterator)的对象进行遍历,语法如下:
![](https://epubservercos.yuewen.com/317ECE/15253386404112206/epubprivate/OEBPS/Images/Figure-P36_41312.jpg?sign=1739325387-ucDtgF6h7PcMjpAc1G4BO6hJss3Ue6nD-0-629e85f60f7baf55b12278ba9dfbf612)
如果想要通过索引遍历一个数组或者一个list,可以这么做:
for (i in array.indices) { //array.indices 存储了数组 array 的下标序列 print(array[i]) }
其中,array.indices持有数组的下标列表。我们也可以使用函数withIndex()来遍历下标与对应的元素:
![](https://epubservercos.yuewen.com/317ECE/15253386404112206/epubprivate/OEBPS/Images/Figure-P37_41315.jpg?sign=1739325387-uN8PMofdsgylhpGoIQZsMnx32W6SKUa6-0-d16ad62c272650fd7d4a815bc0b6d63b)
另外,范围(Ranges)表达式也可用于循环中:
![](https://epubservercos.yuewen.com/317ECE/15253386404112206/epubprivate/OEBPS/Images/Figure-P37_41316.jpg?sign=1739325387-EaSba8UQvSUhOrhFcC2LSbBZfH5RYPBe-0-77d3525fb30c881fd15de6580bce6423)
代码简写如下:
(1..10).forEach { print(it) }
其中的操作符形式的1..10等价于1.rangeTo(10)函数调用,由in和!in进行连接。
编程实例:编写一个Kotlin程序在屏幕上输出1!+2!+3!+…+10!的和。
我们使用上面的fact()函数,代码实现如下:
![](https://epubservercos.yuewen.com/317ECE/15253386404112206/epubprivate/OEBPS/Images/Figure-P37_41317.jpg?sign=1739325387-6MBOLXjdFPOqrtIh8l8xuv70qUAJdHw2-0-e8c8747161b7ed07de458e09f9678fb9)
2.3.4 while循环
while和do…while循环语句的使用方式与C、Java语言基本一致。代码示例如下:
![](https://epubservercos.yuewen.com/317ECE/15253386404112206/epubprivate/OEBPS/Images/Figure-P37_41318.jpg?sign=1739325387-pHp33khY0bWUcc8oRLP9gC8S86aTUt01-0-0bac4e91d5f5f625de4e52b14bc38109)
2.3.5 break和continue
break和continue语句都是用来控制循环结构的,主要用来停止循环(中断跳转),但是二者又有区别,下面分别介绍。
break语句用于完全结束一个循环,直接跳出循环体,然后执行循环后面的语句。
1. 问题场景:打印数字1~10,只要遇到偶数就结束打印
下面我们使用for循环打印1~10之间的数字,遇到偶数就使用break语句结束循环。代码示例如下:
![](https://epubservercos.yuewen.com/317ECE/15253386404112206/epubprivate/OEBPS/Images/Figure-P38_41319.jpg?sign=1739325387-cwdLhG2ZhgvX0Ipf8eD4i5qp3Zkg1U7w-0-6d6cee958d5c66746fbcf928c7550a40)
输出如下:
1 2
continue语句是只终止本轮循环,但是还会继续下一轮循环。可以简单理解为直接在当前语句处中断,跳转到循环入口,执行下一轮循环。而break语句则是完全终止循环,跳转到循环出口。
2. 问题场景:打印数字1~10中的奇数
下面我们使用for循环来打印1~10中的数字,遇到偶数就使用continue语句跳过本轮循环,实现只打印奇数的效果。代码示例如下:
![](https://epubservercos.yuewen.com/317ECE/15253386404112206/epubprivate/OEBPS/Images/Figure-P38_41320.jpg?sign=1739325387-0OrdM9o2qC277Lxv88WkyrSAzHJNqa1j-0-0c4827b46c89a8a354aeb3bda0ba944b)
输出如下:
1 3 5 7 9
2.3.6 return返回
在Java和C语言中,return语句是很常见的了。虽然在Scala和Groovy类语言中,函数的返回值可以不需要显示用return语句来指定,但是我们仍然认为使用return语句的编码风格更容易阅读理解(尤其是在分支流代码块中)。
在Kotlin中,除了表达式的值,有返回值的函数都要求显式使用return语句返回其值。代码示例如下:
![](https://epubservercos.yuewen.com/317ECE/15253386404112206/epubprivate/OEBPS/Images/Figure-P39_41324.jpg?sign=1739325387-ISIr030m9gq55rFZTWPLsai8njKF2zIG-0-b48e1eb8213f6d4748350467b7291e44)
在Kotlin中可以直接使用“=”符号返回一个函数的值,这样的函数称为函数字面量。代码示例如下:
![](https://epubservercos.yuewen.com/317ECE/15253386404112206/epubprivate/OEBPS/Images/Figure-P39_41325.jpg?sign=1739325387-4NJYmnouWZff0PdCOiNjzU19rkx3Wzal-0-889d7041784ac11b5dd3bbd9424fb724)
代码说明如下:
第(1)处使用表达式声明sum()函数。
第(2)处ifelse表达式返回的是一个值,我们直接用表达式来定义一个max()函数。
![](https://epubservercos.yuewen.com/317ECE/15253386404112206/epubprivate/OEBPS/Images/Figure-P39_41326.jpg?sign=1739325387-xLZ7k1ofpWtEEHLVElCZNi1R2xPTTrRC-0-dac51f369a1a2d1d8308099261f16f45)
第(3)处使用fun关键字声明了一个匿名函数,并且直接使用表达式来实现函数。需要注意的是,后面的函数体语句中有没有大括号{}代表的意义完全不同。例如下面的代码:
![](https://epubservercos.yuewen.com/317ECE/15253386404112206/epubprivate/OEBPS/Images/Figure-P39_41327.jpg?sign=1739325387-l2xXUQBs3MJDaktIw6OUNarFeAxEhMq6-0-1edd263a71818b3e0932a771a11992ce)
代码说明如下:
第(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关键字加上函数名来声明函数,下面同样是展示带上大括号{}的例子,代码示例如下:
![](https://epubservercos.yuewen.com/317ECE/15253386404112206/epubprivate/OEBPS/Images/Figure-P40_1251.jpg?sign=1739325387-Jcwlu7tamuVtXrZlk9OUYQp088fwUKr9-0-0db4c4c54164a4bce3d34cc659add7ec)
可以看出,sumf和maxf()的返回值都是函数类型:
() -> kotlin.Int
这点与Scala是不同的。在Scala中,带不带大括号{}的意义是一样的:
![](https://epubservercos.yuewen.com/317ECE/15253386404112206/epubprivate/OEBPS/Images/Figure-P40_41329.jpg?sign=1739325387-lrGZZKbuzyYsrlkvzXEAY7GWr6LnuMIX-0-a28ec9aeebe5700cfd8b7da83f61e9d1)
可以看出,maxf: (x: Int,y:Int)Int与maxv: (x: Int, y: Int)Int的签名是一样的。在这里,Kotlin与Scala在大括号的使用上是完全不同的。调用函数方式是直接调用invoke()函数sumf(1,1).invoke()。
Kotlin中return语句会从最近的函数或匿名函数中返回,但是在Lambda表达式中遇到return语句时,则直接返回最近的外层函数。例如下面两个函数是不同的:
![](https://epubservercos.yuewen.com/317ECE/15253386404112206/epubprivate/OEBPS/Images/Figure-P40_41330.jpg?sign=1739325387-aqf9cOgaUe67HqB7TC0Z1k0wOO46vwZV-0-c9b19b11050d149beb4911e829f64628)
输出如下:
1 2
遇到3时会直接返回(类似于循环体中的break语句)。
而我们给forEach传入一个匿名函数fun(a:Int),这个匿名函数里的return语句不会跳出forEach循环,有点像continue语句的效果:
![](https://epubservercos.yuewen.com/317ECE/15253386404112206/epubprivate/OEBPS/Images/Figure-P41_41333.jpg?sign=1739325387-zmEyQ1sJq3cPcxk68QY6Qzvk3Mpu0soD-0-53af1d8405ef4f7aa708505bddc941b4)
输出如下:
1 2 4 5
为了显式地指明return语句返回的地址,Kotlin还提供了@Label(标签)来控制返回语句,且看2.3.7节的讲解。
2.3.7 标签(label)
在Kotlin中任何表达式都可以用标签(label)来标记。标签的格式为标识符后跟@符号,如abc@、_isOK@都是有效的标签。我们可以用Label标签来控制return、break或continue语句的跳转(jump)行为。
代码示例如下:
![](https://epubservercos.yuewen.com/317ECE/15253386404112206/epubprivate/OEBPS/Images/Figure-P41_41336.jpg?sign=1739325387-mjDyRo2NUpr0ldfojDz5MzJYs1q1SNmY-0-17f19aa05979ac9c0fcfaecaf86cd117)
输出如下:
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意思一样。
![](https://epubservercos.yuewen.com/317ECE/15253386404112206/epubprivate/OEBPS/Images/Figure-P42_41339.jpg?sign=1739325387-t7HUel1ZyXqYnkbV50kPpmaf8ckW6vG8-0-b74fd337da6ac386d5aab17d8bacb2ed)
我们在代码中,用Nothing来标记无返回的函数:
![](https://epubservercos.yuewen.com/317ECE/15253386404112206/epubprivate/OEBPS/Images/Figure-P42_41340.jpg?sign=1739325387-3NrxiCPaWxB1GHBRAkoTeGAoApprDMcl-0-8fd24ea2b364ffda07ea4fe4091f3649)
另外,如果把一个throw表达式的值赋给一个变量,需要显式声明类型为Nothing,代码示例如下:
![](https://epubservercos.yuewen.com/317ECE/15253386404112206/epubprivate/OEBPS/Images/Figure-P42_41341.jpg?sign=1739325387-4z6r8oQKRhCGfQ3NHp1BMzIlK3gPUnTO-0-928d928a543cfb4d8471e173aa3d50e8)
另外,因为ex变量是Nothing类型,没有任何值,所以无法作为参数传给函数。