控制流
通常,计算机程序编写出来,就是为了让计算机理解而解决某问题。按照预先设定的顺序执行的指令序列,也就显得尤为重要。程序员除了学会告诉计算机需要处理的那些数据以外,还需要学会指定这些指令的执行顺序,在程序机构中,我们称之为控制流。
控制流,也成为控制结构,通常包括:顺序结构、条件结构、循环结构、转向结构。
顺序结构是组成计算机程序的基本结构,它依据指令序列的先后顺序,从上到下依次执行。
顺序结构是组成计算机程序的基本机构,它依据指令序列的先后顺序,从上到下依次执行。
下面我们先来了解条件结构,条件结构又称分之结构,在这种结构中包含一个条件判断,根据条件是否成立来确定是执行A分支还是执行B分支。
条件结构(if、when)
Kotlin语言提供两种分支语句,“if” 和 “when”。通常,当条件判断简单且可能的情况很少时,使用if语句。而条件分支较多的情况时,建议搭建使用when语句。
if 语句
作为程序语句最基本的控制结构,if语句分为三种基本结构:单分支结构、双分支结构、多分支结构。
- if单分支结构
if的单分支结构是最简单的if结构,只包含一个条件选择,当且仅当条件表达式为真时,才执行相关代码,且流程图和标准格式如下:
if <条件表达式> {
语句体
}
举个例子:
val intA : Int = 3
// 如果intA的值大于1,则执行下面的语句
if (intA > 1) {
println("intA is " + intA)
}
- if双分支选择结构
双分支选择结构是单分支结构的升级版,我们可以使用if-else构成双分支结构,它和但分支结构的区别在于条件表达式为假时,会执行else分支的语句体,其流程如下:
if <条件表达式> {
// 如果条件表达式为真则执行语句体1
语句体1
} else {
// 如果条件表达式为假则执行语句体2
语句体2
}
举个例子:
val intB : Int = 3
if (intB > 1) {
// 如果intB的值大于1,则执行下面的语句
println("intB is bigger than 1")
} else {
// 如果intB的值小于1,则执行下面的语句
println("intB is smaller than 1")
}
在简单的双分支结构中,确保了至少一种分支会被执行,就在二选一的场景中是非常实用的。
- if多分支选择结构
多分支选择结构用于条件分支大于两个的情况,我们也可以把多个if语句连载一起,从而构成if的多分支结构,形成下面这样的形式:
if <条件表达式1> {
// 如果条件表达式1为真,则执行语句体1
语句体1
} else if <条件表达式2>{
// 如果条件表达式1为假,且表达式2位真,则执行语句体2
语句体2
} else {
// 如果条件表达式1为假,且表达式2位假,则执行语句体3
语句体3
}
当条件表达式1的值为真的时候,将执行语句体1,。当它为家的时候,则转向判断条件表达式2的值是否为真。如果条件表达式2为真,则执行语句体2,它不为真则转向下一个判断分之。如此循环进行判断,如果所有的分之都没有满足条件,则执行最后一个else块内的语句体。如果最后没有else,则不执行任何分之语句。
when 语句
虽然if-else可以构成多分支结构,但是需要判断的条件分支过多的时候,继续使用if-else使得代码读起来冗长而逻辑层次过多,所有我们建议大家在类似的这种情况下使用when语句。
在Kotlin语言之中,没有switch语句,取而代之的是when语句。其逻辑结构如下:
when <value> {
<表达式1> -> 语句体1 // 表达式1为真,则执行语句体1
<表达式2> -> 语句体2 // 表达式2为真,则执行语句体2
<表达式3> -> 语句体3 // 表达式3为真,则执行语句体3
<表达式4> -> 语句体4 // 表达式4为真,则执行语句体4
......
else -> 默认语句体 // else可以不写。如果存在else,所有条件均为假,则执行默认语句体
}
值得注意的是:Kotlin语言中when语句并不仅仅是针对Java语言switch语句的替代,when语句在使用上还进行了扩展升级。常用的形式有三种:常量结构、in结构、表达式结构。
when语句与switch语句的区别,如果符合判断中的任意一条表达式,则在执行表达式对应的语句体之后,直接跳出整个when逻辑,我们不需要手动的break。
>
- 常量结构
常量结构与我们熟知的switch使用方式很类似,之间判断value的值是否是在所列举的表达式之中,如果存在,则执行对应的语句体。
val intA : Int = 1
when (intA) {
1 -> println("intA == 1") // 打印这句话
2 -> println("intA == 2")
else -> {
println("intA is neither 1 nor 2")
}
}
when 将它的参数和所有的分支条件,从上到下顺序比较,直到某个分支满足条件为止。遇到满足条件的分支,则执行对应的语句。如,这里intA的值就是1,满足了第一个分支逻辑,所以执行了 println(“intA == 1”) 语句。
- in结构
这里再次强调一下,针对when而言,它没有类似switch的break操作。如果我们希望一个逻辑值value能够对应多个常量值,则我们就必须使用in结构。其操作,与常量结构的when语句类似,即将满足一个条件的多个常量值使用 “,” 隔开,如:
val intB : Int = 3
when (intB) {
1,2,3,4 -> println("intB in 1,2,3,4") // 仅执行这一句
3,4,5 -> println("intB in 3,4,5") // 这句话不会被打印
else -> {
println("intB is not in 1,2,3,4,5")
}
}
- 表达式结构
常量的限制还是比较多,when还给提供用任意表达式(而不只是常量)作为分支条件。
when (obj) {
1 -> {
println("obj is a number , value = 1")
}
"hello" -> {
println("obj is a String 'hello'")
}
is Long -> {
println("obj's type is Long")
}
else -> println("Unknown")
}
当然,我们也可以将多个表达式对一个判断同时使用,只需要在两个表达式之间增加“,”符号。如,这里我们有一个需求,判断一个数字是整数还是浮点数,使用when语句的操作就如下所示:
when (obj) {
is Int, is Long, is Short, is Byte -> {
// obj 是一个整数型数字
println("obj is a integer number")
}
is Float, is Double -> {
// obj 是一个浮点型数字
println("obj is a float number")
}
else -> {
// obj 不是数字
print("obj is not a number")
}
}
Kotlin语言中的is运算符,是用来在运行时指出对象是否是特定类的一个实例,类似Java语言中的instaceof。
对于when而言,在其语句体之中,它本身是包含值返回的。即,我们书写在语句中的内容,能够直接作为when的返回值。如:
fun hasPrefix(x: Any) = when(x) {
is String -> x.startsWith("prefix")
else -> false
}
when 也可以用来取代 if-else if链。 如果不提供参数,所有的分支条件都是简单的布尔表达式,而当一个分支的条件为真时则执行该分支:
when {
x.isOdd() -> print("x is odd")
x.isEven() -> print("x is even")
else -> print("x is funny")
}
循环机构(for-in、while)
循环结构可以理解为重复迭代结构,在条件满足的情况下, 会重复执行循环体中的代码指令,知道条件不满足位置。在Kotlin中循环结构使用关键字for-in、while、do-while来构成。
- for-in循环
用来遍历一个范围(range)、队列(sequence)、集合(collection)、数组(array)里面的所有元素,并且执行一段语句体代码。其中,被反复执行的程序段称之为循环体,用来控制循环进行迭代的变量成为循环变量。在for-in循环中,循环变量可以使一个被隐式声明的,只需要在每次循环遍历开始时被自动复制的变量,它包含在循环的声明中,而不需要特意使用显示的声明。for-in循环的标准格式和流程图如下:
for (<循环变量> in <范围、集合、数组...>) {
循环体...
}
循环变量只存在当前循环的声明周期之中,如果你想在循环完成后访问循环变量,你必须在循环声明之前单独进行声明。
如这里我们声明了一个员工的array,employees,我们希望遍历所有的员工姓名,我们就可以这样做:
val employees = arrayOf("david", "mike", "jason")
for (emp in employees) {
println("employee name = $emp")
}
输出:
employee name = david
employee name = mike
employee name = jason
for-in语法逻辑类似于Java语言之中的foreach。语句用于循环访问集合以获取所需信息,但不应用于更改集合内容以避免产生不可预知的副作用。
当然,在Kotlin语言中也提供了在withIndex的函数,让我们方便去拿到每个对象的下标。如:
// 使用array 的 withIndex , 可以获取数组之中的下标index
for ((index, emp) in employees.withIndex()) {
println("the $index employee name is $emp")
}
如果只想打印下标,则直接使用indices属性即可。
// 仅打印下标
for (index in employees.indices) {
println("$index")
}
对于字符串而言,字符串本身就是一个字符数组,我们同样可以使用for-in的方式,将其中每一个字符打印出来。
// 循环打印显示每一个字母
for (word in "hello world") {
print(" $word")
}
即得到结果:
h e l l o w o r l d
- while循环
while循环用于实现“当型”循环结构,即只有当条件满足时才执行循环体,Kotlin语言中提供两种while的循环方式:while、do-while。两种方式很类似,这里我们先介绍while循环。
每次在循环开始时计算表达式,如果表达式值为true,会执行循环体。执行完循环体之后再进行表达式的判断,知道条件表位false才终止循环。while循环比较适合在循环开始前并不知道循环次数的情况。while循环的流程图和标准结构如下:
while (<表达式>) {
循环体
}
如这里我们做一个从10到1的倒数计时,使用while循环我们就可以这么干:
var countDown = 10
// 当countDown大于0的时候,执行循环体中的内容
while (countDown > 0) {
println("current number $countDown")
countDown--
}
while循环特别容易造成死循环,必须记住判断每次循环体的条件改变,造成表达式在一定的情况下能够为假,终止while循环。
- do-while循环
do-while循环和while循环极为类似,它和while循环的区别在于,当执行到这一个循环结构时,会限制性循环体的代码,在判断循环条件,确定是否继续循环执行。知道条件表达式为false。也就是说循环体逻辑至少会被执行一次。do-while的循环流程如下:
do {
循环体
} while (<条件表达式>)
这里我们将上述while的逻辑做一个简单的修改,如下所示:
countDown = 0
do {
// 此处逻辑必定会执行一次
println("current number $countDown")
countDown--
} while (countDown > 0)
我们会发现,无论countDown > 0 条件成立与否,循环体之中的内容都会执行一次,即先执行后判断。
控制转向语句(continue、break、return)
控制专项语句是指当程序执行到改语句时,立即转移到程序的其他位置执行。也就是改变代码的执行顺序,通过它可以实现代码的跳转,Kotlin有三种控制转向语句,continue、break、return:
continue
continue,即继续的意思。continue会通知一个循环体立即停止本次循环,直到回到循环判断条件,重新开始下次循环。我们这里我们来举一个例子:
// continue
val words = "abcdefg"
for (w in words) {
// 遇到c字符,直接执行下一次循环条件判断
if (w == 'c') {
continue
}
print("$w-")
}
运行后我们得到数据:
a-b-d-e-f-g-
我们能够看到,当for循环答应字符串”abcdefg” 的时候,遇到’c’字符,我们就有选择性的跳过了。当然,continue不仅仅能够与for循环配合,它与while、do-while循环配合能够产生一样的效果,如:
var countDownC = 10
do {
countDownC--
// 遇到数字5,直接执行下一次循环条件判断
if (countDownC == 5) {
continue
}
print("$countDownC-")
} while (countDownC > 0)
运行后得到:
10-9-8-7-6-
从上述例子我们可以看得出来,continue的主要作用是与循环逻辑配合来使用。很多情况下,我们在一些循环逻辑之中需要批量处理一系列数据,其中又想忽略某些数据不出来,continue的优势就显而易见了。
break
break语句会强制结束整个控制结构的执行,你可以在循环体之中使用它。当一个循环体之执行到break时,会立即终端该循环体,然后跳转到表示循环体结束的位置,跳出循环层。
如,这里我们有一个需求,需要从所有的学生之中寻找到一个名叫“jason”的学生,并返回其所在的位置。我们就可以这样写:
val students = arrayOf("mike", "david", "jason", "green", "eva")
for ((index, stu) in students.withIndex()) {
// 寻找jason的下标
if (stu == "jason") {
println("find jason,his index is $index")
break // 跳出for
} else {
println("find student $stu , find next")
}
}
使用break,我们能够在找到jason之后跳出整个for循环,程序不会继续对students进行遍历,从而一定上节约了计算开销。
如同continue一样,break同样能够在while与do-while循环中使用,直接结束整个循环过程,如:
var countDownD = 10
while (countDownD > 0) {
countDownD--
println("countDownD is $countDownD")
if (countDownD == 5) {
break // countDownD为5时,跳出while
}
}
这里需要注意的是,break只能够写在循环体之中,如果存在多重嵌套循环,break结束的循环是最近的循环。如,这里我们有一个需求,为了查询班里是否存在有男学生的名字与女学生的名字相同,操作如下:
// 判断是否存在同名的男女学生
// 男学生
val maleStudent = arrayOf("david","jason","mike")
// 女学生
val femaleStudent = arrayOf("marry","mike","eva")
// 标记是否找到
var hasFind = false
for (male in maleStudent) {
for (female in femaleStudent) {
if(male == female) {
hasFind = true // 做个标记符,不然外层循环不知道内层循环是否找到,无法判断是否需要break
println("find the same name student!")
break // 跳出女学生for循环
}
}
if (hasFind) {
break // 跳出男学生for循环
}
}
通常,多重循环希望使用break作为跳出处理,都会增加一个标识符,如上述例子标识符就是hasFind。在外层循环的判断跳出逻辑则是根据标识符来做break与否的操作。
当然,对于跳出多重循环的方式,不一定都需要加标志,我们还可以使用标签的方式解决。后面,我们会跟大家介绍。
return
return意为返回,return关键字是配合着函数(fun)而是用的,我们尝试用的main就是一个主入口函数,函数(fun)的后面会有具体说明,在Kotlin语言中,针对函数的返回值而言是非常灵活的,在需要的时候,你可以定义一个或者选择性省略返回值。
在定义一个函数的返回值的时候,我们可以使用我们常见的基础类型,当然如果该函数没有任何的返回,也可以不写,即认为是无返回值。
/**
* 返回a+b的值
*/
fun add(a: Int, b: Int): Int {
return a + b // 向函数体返回a+b的值
}
/**
* 查找x字符串
*/
fun findX(xArray : Array<String>) {
for (word in xArray) {
if (word == "x") {
println("find the 'x' word!")
return // 中断整个循环执行,退出函数
}
}
println("Can not find 'x' !")
}
fun main(args: Array<String>) {
val result = add(1,2)
println("a + b = $result")
val xArray = arrayOf("a","b","x","c","d")
findX(xArray)
}
标签(@)
在Java语言之中,你可以调用break和continue跳出本次循环或者本层循环,这种控制方法在循环嵌套场景中非常使用。不过,Kotlin语言提供了更强大的跳出机制,你可以显示地跳出需要跳出的是哪一层循环。
为了实现这么一个目的,我们可以使用标签来,为循环体或者代码块打上标记,需要使用break或者continue时,带上这个标签就可以控制该标签代表处的对象的中断或者跳出。
标签必须是要伴随着continue、break、return三个控制转向语句一起使用的,
<标签名>@ (循环体|函数)
(continue|break|return)@<标签名>
如上述我们查找同名男女学生的例子,我们使用标签之后,我们的break就可以写成如下所示:
- 配合break使用
// 男学生
val maleStudent = arrayOf("david","jason","mike")
// 女学生
val femaleStudent = arrayOf("marry","mike","eva")
// 标签配合break使用
formale@ for (male in maleStudent) {
for (female in femaleStudent) {
if(male == female) {
println("find the same name student!")
break@formale // 从标签@formale处跳出
}
}
}
- 配合continue使用
// 标签配合continue使用
formale@ for (male in maleStudent) {
for (female in femaleStudent) {
if(male == female) {
println("find the same name student!")
continue@formale // 继续查找下一个
}
}
}
- 配合return使用
正如我们上述所说,return是配合着函数(fun)而使用的。而对于Kotlin语言来说,单纯的fun是无法完成嵌套的。所以,很多情况下标签配合着return使用都是在lambda 表达式和匿名内部类中完成。
/**
* return 标签测试
*/
fun foo() {
val ints = arrayOf(1,2,0,3,5)
ints.forEach lit@ {
if (it == 0) return@lit
print(it)
}
}
/**
* 匿名内部类 return 标签配合测试
*/
fun foo2() {
val ints = arrayOf(1,2,0,3,5)
ints.forEach(fun(value: Int) {
if (value == 0) return
print(value)
})
}