循环语句的操作,神奇的冒号

作者: 编程技术  发布:2019-09-26

Java中的标点符号主要有两类用途,一类是运算符,包括加号+、减号-、乘号*、除号/、取余号%、等号=、大于号>、小于号<、与号&、或号|、非号!、异或号^等等,另一类则是分隔符,包括区分代码块的花括号{}、容纳特定语句的圆括号()、标明数组元素的方括号[]、分隔长句的分号、分隔短句的逗号、分隔包名类名方法名的点号等等。当然还有几个特殊的分隔符,比如三元运算符“?:”,它的完整形式为“A?:B:C”,当式子A成立时,得到式子B的结果,不成立时得到式子C的结果。这些标点符号之中,尤以冒号最为特殊,之所以这么说,是因为Java编程一遇到特殊的分隔场景,基本都拿冒号这个万金油做标记。

上一篇文章介绍了简单分支与多路分支的实现,控制语句除了这两种条件分支之外,还有对循环处理的控制,那么本文接下来继续阐述Kotlin如何对循环语句进行操作。

冒号除了用在三元运算符“?:”以外,至少还有其它三种用法。第二种是在多路分支的时候,犹记得当年switch-case并肩作战,每个case带着数值再拖上具体的处理语句,case条件与处理语句之间可是以冒号分隔的,剩余的默认情况default也是通过冒号同处理语句区分开。譬如下面的多路分支代码,就能看到冒号的分隔作用:

 

// switch允许判断某个变量的多个取值,并分别进行单独处理switch  {case 1: // seq值为1时进入该分支System.out.println("凉风有信的谜底是“讽”");break; // 跳出多路分支。即跳到switch分支的右花括号之后case 2: // seq值为2时进入该分支System.out.println("秋月无边的谜底是“二”");break; // 跳出多路分支。即跳到switch分支的右花括号之后default: // seq值为其它时进入该分支System.out.println;break; // 跳出多路分支。即跳到switch分支的右花括号之后}

Koltin处理循环语句依旧采纳了for和while关键字,只是在具体用法上有所微调。首先来看for循环,Java遍历某个队列,可以通过“for (item : list)”形式的语句进行循环操作。同样Kotlin也能使用类似形式的循环,区别在于把冒号“:”换成了关键字“in”,具体语句形如“for (item in list)”。下面是Kotlin对数组进行循环处理的代码例子:

冒号的第三种用法,则跟数组的循环遍历有关。要想把某个数组里的所有元素数值都打印出来,就得通过for循环依次取出数组的每个元素,再打印该元素的数值。以整型数组为例,利用for语句遍历并打印元素的代码如下所示:

    val poemArray:Array<String> = arrayOf("朝辞白帝彩云间", "千里江陵一日还", "两岸猿声啼不住", "轻舟已过万重山")
    btn_repeat_item.setOnClickListener {
        var poem:String=""
        for (item in poemArray) {
            poem = "$poem$item,n"
        }
        tv_poem_content.text = poem
    }
int[] primeNumbers = {2, 3, 5, 7};for (int i = 0; i < primeNumbers.length; i++) {int number = primeNumbers[i]; // 获取下标为i的元素,并赋值给名为number的变量System.out.println("prime number = " + number);}

上述代码的目的是将一个诗句数组用逗号与换行符拼接起来,以便在界面上展示完整的诗歌内容。拼接后的诗歌显示界面如下图所示:

上面的循环语句很常规,用法形式也很常见,无非是依次取出数组里的每个元素罢了。倘若此时不修改元素数值,仅仅是读取数值的话,那么可以简化成一套通用的循环模板,就像下述的循环语句那样:

图片 1

int[] primeNumbers = {2, 3, 5, 7};// 在for循环中,可以利用“变量类型 变量名称 : 数组名称”的形式,直接把数组元素赋值给该变量for (int number : primeNumbers) {System.out.println("number = "+number);}

注意到截图中每行诗句都以逗号结尾,这有个句点问题,因为每首绝句的第一、三行末尾才是逗号,第二、四行的末尾应该是句号;所以这个循环代码得加以改进,补充对数组下标的判断,如果当前是奇数行,则末尾加逗号;如果当前是偶数行,则末尾加句号。倘若使用Java编码,要是涉及到下标的循环,基本采取“for (初始的赋值语句; 满足循环的条件判断; 每次循环之后的增减语句)”这般形式,具体实现可参考以下的示例代码:

上述的Java代码,把原循环内部的变量number提前放到for后面的圆括号当中,并且number与数组primeNumbers之间用冒号分开,表示每次循环处理之前,都把数组元素逐个赋值给number变量,然后循环内部即可直接处理该变量。如此这般便优化了先前的for循环代码,免去了冗余的数组下标、判断条件以及自增操作。

    for (int i=0; i<array.length; i++) {
        ...
    }

冒号的第四种用法也与循环语句有关,但不限于for循环,而是与for和while都有关联。前述的循环处理,基本都只有一层循环,然而实际开发常常会遇到多层循环,也就是一个循环内部嵌套了另一个循环,看起来像是层峦叠嶂、反复重叠,故又被称作多重循环。例如有个二维数组,要把它里面的所有元素都打印出来,这便需要两层循环才能搞定,第一层循环负责遍历第一个维度的下标,而第二层循环负责遍历第二个维度的下标,编码上则需一个for循环嵌套另一个for循环,具体的Java实现代码如下所示:

出人意料的是,Kotlin废除了“for (初始; 条件; 增减)”这个规则;若想实现上述功能,取而代之的,则为“for (i in array.indices)”语句,其中indices表示该数组对象的下标数组,每次循环都从下标数组依次取出当前元素的下标。根据该规则判断下标的数值,再分别在句尾添加逗号与句号,改造后的Kotlin代码如下所示:

double[][] triangle = { {-2.0, 0.0}, {0.0, -1.0}, {2.0, 1.0} };// 下面通过多重循环依次打印二维数组里面的所有元素for (int i=0; i<triangle.length; i++) {for (int j=0; j<triangle[i].length; j++) {System.out.println("value = "+triangle[i][j]);}}
    btn_repeat_subscript.setOnClickListener {
        var poem:String=""
        for (i in poemArray.indices) {
            if (i%2 == 0) {
                poem = "$poem${poemArray[i]},n"
            } else {
                poem = "$poem${poemArray[i]}。n"
            }
        }
        tv_poem_content.text = poem
    }

可见以上的多重循环代码还是挺简单的,并不涉及到复杂的break和continue操作。即使用到break和continue,处理逻辑也没有什么特别之处,因为break语句想当然就是只能跳出当前层次的循环,不能跳出上个层次的循环,continue语句同理。所以要想从内层循环跳出外层循环,就得设置一个标记,从内层循环跳到外层循环时,通过判断该标记再决定是否立刻跳出外层循环。仍以前面的二维数组为例,假设在内层循环找到某个元素为0.0,则立即结束全部循环(包括外层循环和内层循环),按此思路编写的代码例子见下:

正确补充标点的诗歌显示界面如下图所示:

// 下面的循环要求:一旦发现数组元素等于0.0,就立即从第二层循环跳出第一层循环for (int i=0; i<triangle.length; i++) {boolean isFound = false; // 该布尔变量用来标记是否找到0.0for (int j=0; j<triangle[i].length; j++) {if (triangle[i][j] == 0.0) {isFound = true; // 找到了0.0System.out.println("simple found 0.0");break; // 跳出第二层循环}}if  {break; // 跳出第一层循环}}

图片 2

以上代码固然实现了功能要求,但是两个break的写法着实令人憋屈,而且布尔变量isFound纯粹是到此一游。有没有一种写法允许让代码直接从内层循环跳出外层循环呢?与其让布尔变量做标记,不如给外层循环加个记号,然后内层循环就能告诉编译器,接下来的break语句要跳出指定标记的循环。这时冒号便派上用场了,通过形如“标记名称 : for或者while”的表达式,即可给指定循环起个外号,于是语句“break 标记名称”便实现了跳出指定循环的需求。那么使用新写法改造前面的循环跳出代码,修改之后的代码如下所示:

 

// 下面的loop1是一个记号,连同后面的冒号加在for前面,表示它指代这个for循环loop1 : for (int i=0; i<triangle.length; i++) {for (int j=0; j<triangle[i].length; j++) {if (triangle[i][j] == 0.0) {System.out.println("loop1 found 0.0");break loop1; // 跳出loop1代表的循环,也就是跳出第一层循环}}}

然而取消“for (初始; 条件; 增减)”这个规则是有代价的,因为实际开发中往往存在非同一般的需求,比如以下几种情况,Kotlin的“for (i in array.indices)”语句就无法很好地处理:
1、如何设定条件判断的起始值和终止值?
2、每次循环之后的递增值不是1的时候要怎么办?
3、循环方向不是递增而是递减,又如何是好?
4、与条件判断有关的变量不止一个,咋整?
5、循环过程中的变量,在循环结束后还能不能使用?
针对以上情况,其实Kotlin也给出了几个解决办法,代价是多了几个诸如until、step、downTo这样的关键字,具体用法见下列代码:

如上代码先在外层的for循环之前添加“loop1 : ”,表明外层循环的绰号叫loop1,然后内层循环的break语句改成“break loop1;”,表示跳出loop1这个外层循环,这样只需一个break语句就跳出多重循环了。除了break语句,continue也允许带上标记名称,比如“continue loop1”表示继续loop1这个外层循环的下一次循环处理,并且while循环也同样认可在break和continue后面添加标记。当然,利用前面介绍的冒号第三种用法,上面的多重循环还能简化成下述这般代码:

    // 左闭右开区间,合法值包括11,但不包括66
    for (i in 11 until 66) { ... }
    // 每次默认递增1,这里改为每次递增4
    for (i in 23..89 step 4) { ... }
    // for循环默认递增,这里使用downTo表示递减
    for (i in 50 downTo 7) { ... } 
// 下面用到了两种冒号,一种用来标记循环,另一种用来简化数组遍历loop2 : for (double[] dot : triangle) { // dot等价于前面的triangle[i]for (double coordinate : dot) { // coordinate等价于前面的triangle[i][j]if (coordinate == 0.0) {System.out.println("loop2 found 0.0");break loop2; // 跳出loop2代表的循环}}}

可是这些解决办法并不完美,因为业务需求是千变万化的,并非限定在几种固定模式。同时,以上规则容易使人混淆,一旦没搞清楚until和downTo的开闭区间,在判断边界值时会产生问题。所以更灵活的解决方案是,起止数值、条件判断、循环方向与递增值都应当在代码中明确指定,“for (初始; 条件; 增减)”这个规则固然废除了,但开发者依旧能够使用while语句实现相关功能,Kotlin的while循环与Java的处理是一致的,下面是使用while进行循环判断的代码例子:

如此一来,上述的循环代码联合应用了冒号的两种用法,整个代码也变得更加精炼了。

    btn_repeat_begin.setOnClickListener {
        var poem:String=""
        var i:Int = 0
        while (i < poemArray.size) {
            if (i%2 ==0) {
                poem = "$poem${poemArray[i]},n"
            } else {
                poem = "$poem${poemArray[i]}。n"
            }
            i++
        }
        poem = "${poem}该诗歌一共有${i}句。"
        tv_poem_content.text = poem
    }

既然while语句保留了下来,do/while语句继续保留,写法也没什么变化,采用do/while写法的代码如下所示:

    btn_repeat_end.setOnClickListener {
        var poem:String=""
        var i:Int = 0
        do {
            if (i%2 ==0) {
                poem = "$poem${poemArray[i]},n"
            } else {
                poem = "$poem${poemArray[i]}。n"
            }
            i++
        } while (i < poemArray.size)
        poem = "${poem}该诗歌一共有${i}句。"
        tv_poem_content.text = poem
    }

 

前面的循环处理其实都还中规中矩,只有内忧没有外患,但要是数组里的诗句本身就不完善,比如有空指针、有空串、有空格串、有多余串等等,此时就得进行诗句的合法性判断,如此方可输出正常的诗歌文字。合法性判断主要由两块代码组成:
1、如果发现有空指针、有空串、有空格串,则忽略此行,即使用关键字continue继续下个循环;
2、如果合法诗句达到四句,则不管是否遍历完成,直接拼好绝句并结束循环,即使用关键字break跳出循环;
加入了合法性判断的代码见下,主要演示了continue和break:

    val poem2Array:Array<String?> = arrayOf("朝辞白帝彩云间", null, "千里江陵一日还", "", "两岸猿声啼不住", "   ", "轻舟已过万重山", "送孟浩然之广陵")
    btn_repeat_continue.setOnClickListener {
        var poem:String=""
        var pos:Int=-1
        var count:Int=0
        while (pos <= poem2Array.size) {
            pos++
            if (poem2Array[pos].isNullOrBlank())
                continue
            if (count%2 ==0) {
                poem = "$poem${poem2Array[pos]},n"
            } else {
                poem = "$poem${poem2Array[pos]}。n"
            }
            count++
            if (count == 4)
                break
        }
        tv_poem_content.text = poem
    }

看来合法性判断用到的continue和break,Kotlin并没有做什么改进呀?这是真的吗?如果是真的,那真是图样图森破。以往我们操作多层循环的时候,有时在内层循环发现某种状况,就得跳出包括外层循环在内的整个循环。例如遍历诗歌数组,一旦在某个诗句中找到“一”字,便迅速告知外界“我中奖啦”之类的欢呼。可是这里有两层循环,如果使用Java编码,只能先跳出内层循环,然后外层循环通过判断标志位再决定是否跳出,而不能从内层循环直接跳出外层循环。现在Kotlin大笔一挥,干嘛这么麻烦,咱想跳到哪里就跳到哪里,只消给外层循环加个@标记,接着遇到情况便直接跳出到这个标记,犹如孙悟空蹦上筋斗云,想去哪就去哪,多方便。这个创意真好,省事省力省心,赶紧看看下面代码是怎么实现的:

    btn_repeat_break.setOnClickListener {
        var i:Int = 0
        var is_found = false
        outside@ while (i < poemArray.size) {
            var j:Int = 0
            var item = poemArray[i];
            while ( j < item.length) {
                if (item[j] == '一') {
                    is_found = true
                    break@outside
                }
                j++
            }
//            //如果内层循环直接跳出两层循环,那么下面的判断语句就不需要了
//            if (is_found)
//                break
            i++
        }
        tv_poem_content.text = if (is_found) "我找到'一'字啦" else "没有找到'一'字呀"
    }

 

总结一下,对于循环语句的操作,Kotlin仍然保留了for和while两种循环,主要区别在于:Kotlin取消了“for (初始; 条件; 增减)”这个规则,不过新增了对跳出多重循环的支持(通过“break@标记位”实现)。

__________________________________________________________________________
本文现已同步发布到微信公众号“老欧说安卓”,打开微信扫一扫下面的二维码,或者直接搜索公众号“老欧说安卓”添加关注,更快更方便地阅读技术干货。

图片 3

 

本文由贝博体育app发布于编程技术,转载请注明出处:循环语句的操作,神奇的冒号

关键词: