![Kotlin从入门到进阶实战](https://wfqqreader-1252317822.image.myqcloud.com/cover/633/26793633/b_26793633.jpg)
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=1739350369-lm25RHMX9WEsXjAB20bNbFGxiSqh63zM-0-c1bb72df3366765d0ab0a4661769707a)
另外,if的分支可以是代码块,最后的表达式作为该块的值:
![](https://epubservercos.yuewen.com/317ECE/15253386404112206/epubprivate/OEBPS/Images/Figure-P34_40828.jpg?sign=1739350369-qoPkBEHZ7U84VsOue0glYxtj1PX3y03B-0-3703b8fc3443fec889be829d3cb97177)
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=1739350369-hYVNd1dqGYx4NjRcbjlPmH3ZzX2UV1Ss-0-3f25556f3b1d056da42fabb00273de54)
如果条件体内只有一条语句需要执行,那么if后面的大括号可以省略。良好的编程风格是建议加上大括号。
![](https://epubservercos.yuewen.com/317ECE/15253386404112206/epubprivate/OEBPS/Images/Figure-P34_40830.jpg?sign=1739350369-VwCBG7BxvrTj5ViXJqkcsRLXhhYRqvZA-0-519403823b295cb792650fb33e7e4b10)
编程实例:用if…else语句判断某年份是否是闰年。
![](https://epubservercos.yuewen.com/317ECE/15253386404112206/epubprivate/OEBPS/Images/Figure-P35_41073.jpg?sign=1739350369-jDMvJu4RSFYOBfeqc5XWyK07qTLYkfNL-0-10994036cd4d5a946ee9ce2786c4cd1c)
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=1739350369-rV8bhVGCVegA8MuCL9IkiUidnxerHTwE-0-e239bf1ede025ed942ce937fdc7aa74a)
输出如下:
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=1739350369-wI5JIGjunp9TIR7LoTMOzOeAt0ZfH6kY-0-896294fa43c8bb2c195635f07180d859)
也可以检测一个值在in或者不在!in一个区间或者集合中:
![](https://epubservercos.yuewen.com/317ECE/15253386404112206/epubprivate/OEBPS/Images/Figure-P36_41310.jpg?sign=1739350369-1pOlKB0RTC0H5u6ycuyZfKC5Vuh9nJTy-0-73d20da4cb9ae1361b081f2d3e41bee6)
编程实例:用when语句写一个阶乘函数。
![](https://epubservercos.yuewen.com/317ECE/15253386404112206/epubprivate/OEBPS/Images/Figure-P36_41311.jpg?sign=1739350369-yiD0blqvSeK7rmkKuxMUoYZo8HWYUdSQ-0-5720bcdfc5219f9233bf060281133f22)
2.3.3 for循环
for循环可以对任何提供迭代器(iterator)的对象进行遍历,语法如下:
![](https://epubservercos.yuewen.com/317ECE/15253386404112206/epubprivate/OEBPS/Images/Figure-P36_41312.jpg?sign=1739350369-tOZg84nhrK4xoVqZD3ahkOE22Yg1Fm1r-0-dc3c0ec58749696aa3ae2c5e254a8adf)
如果想要通过索引遍历一个数组或者一个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=1739350369-pgXOSdwt3HPT5MdL2eIIJsvtBPpHtCUl-0-24143882d51f63f6fd311f4c728467be)
另外,范围(Ranges)表达式也可用于循环中:
![](https://epubservercos.yuewen.com/317ECE/15253386404112206/epubprivate/OEBPS/Images/Figure-P37_41316.jpg?sign=1739350369-4DxlMCbFyZsrxVKQQGaVs4CHHnVLMfiA-0-7939756b054615a41ec272d4b3e1f67b)
代码简写如下:
(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=1739350369-IFXVDtVdxnyx69TcIRJF7FhpD9vI5I9D-0-d6b225f33fe157dbde3eed007f716cf8)
2.3.4 while循环
while和do…while循环语句的使用方式与C、Java语言基本一致。代码示例如下:
![](https://epubservercos.yuewen.com/317ECE/15253386404112206/epubprivate/OEBPS/Images/Figure-P37_41318.jpg?sign=1739350369-dmp4PvTjNbOwThEzkBVE72RKuyYsqsZZ-0-ad1ca22183e608844be899d89865a297)
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=1739350369-xCFwQvCynt12sB3LalkdhyGtxwOjIC5l-0-109f48ece5a03a5e8f8ff34d39b8fada)
输出如下:
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=1739350369-WiXy6oXFkojUpEbY9ORxn941ndIP5Li3-0-4929b32ef4d6213b113da47616f3caff)
输出如下:
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=1739350369-vFRiHC1nZtul16YC284kqdCXQzvNPZUD-0-3bcfaf43f8145d32570f68fd11027abd)
在Kotlin中可以直接使用“=”符号返回一个函数的值,这样的函数称为函数字面量。代码示例如下:
![](https://epubservercos.yuewen.com/317ECE/15253386404112206/epubprivate/OEBPS/Images/Figure-P39_41325.jpg?sign=1739350369-LnMrzdXIgEpdZ166ijYYxg0v5SbJsoRQ-0-896a57280e4424169c947f3cc948cc15)
代码说明如下:
第(1)处使用表达式声明sum()函数。
第(2)处ifelse表达式返回的是一个值,我们直接用表达式来定义一个max()函数。
![](https://epubservercos.yuewen.com/317ECE/15253386404112206/epubprivate/OEBPS/Images/Figure-P39_41326.jpg?sign=1739350369-1fekugxPot8PVZuxhOFPe6N5B11DoOtN-0-8a3920df1231259b4360eb30daa39fcb)
第(3)处使用fun关键字声明了一个匿名函数,并且直接使用表达式来实现函数。需要注意的是,后面的函数体语句中有没有大括号{}代表的意义完全不同。例如下面的代码:
![](https://epubservercos.yuewen.com/317ECE/15253386404112206/epubprivate/OEBPS/Images/Figure-P39_41327.jpg?sign=1739350369-RNNDhRZpStzYYVnuOkCQfjlMZjqd1EIF-0-a151522f10d41c1fda3d036ff530c666)
代码说明如下:
第(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=1739350369-rL0jBDPvOKE5yoo3SCzb2OYJ1bGxpO9V-0-793683e3cf0c27c0277a3f515489be99)
可以看出,sumf和maxf()的返回值都是函数类型:
() -> kotlin.Int
这点与Scala是不同的。在Scala中,带不带大括号{}的意义是一样的:
![](https://epubservercos.yuewen.com/317ECE/15253386404112206/epubprivate/OEBPS/Images/Figure-P40_41329.jpg?sign=1739350369-DRvbmDpLJQ8sMPaxdd0WeMwlPOfr1xJ3-0-ead2480247ab0adc53e3e92c3b5de82c)
可以看出,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=1739350369-cYVsFQ74Cp1YsOk5FGi2Ib9W7ZDvcxDT-0-84b419e5ae18cf915e5227b6e949ef05)
输出如下:
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=1739350369-yw5sx2qkEl6VOoPURHZWUPAzOrA6GHj3-0-7a8ed2aa37995d34073b6505abdb4551)
输出如下:
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=1739350369-X2mvUOn1VYhLolKQhG5YIAzcCRgG7WuR-0-ada005e196af518f85a60144a3a7727b)
输出如下:
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=1739350369-ZZrbUFIkBymSeCHxJPqTzuC3EjhC4H6O-0-86fe8072859a9d98b9c3b4c6d29e1a85)
我们在代码中,用Nothing来标记无返回的函数:
![](https://epubservercos.yuewen.com/317ECE/15253386404112206/epubprivate/OEBPS/Images/Figure-P42_41340.jpg?sign=1739350369-dOGIVjg1aMGP6pZXAbvA5JJRxcnZK9dJ-0-9f5448b71cb378548f9f94a543533eb6)
另外,如果把一个throw表达式的值赋给一个变量,需要显式声明类型为Nothing,代码示例如下:
![](https://epubservercos.yuewen.com/317ECE/15253386404112206/epubprivate/OEBPS/Images/Figure-P42_41341.jpg?sign=1739350369-lLivxp8J97pcP9wQZ9cptZRoYwuar2fs-0-f6fe1f33bf48771305782db2e6f33280)
另外,因为ex变量是Nothing类型,没有任何值,所以无法作为参数传给函数。