第5章 循环结构

循环结构是程序中一种很重要的结构。其特点是在给定条件成立时,反复执行某程序段,直到条件不成立为止。给定的条件称为循环条件,反复执行的程序段称为循环体。它犹如至高武学中的万流归宗心法,循环无止境,生生不息。本章将详细讲解Objective-C循环语句的基本知识,为读者后面的学习打下基础。

5.1 语句

知识点讲解:光盘:视频\知识点\第5章\语句.mp4

在Objective-C程序中,在表达式的末尾添加一个分号“; ”即可构成一条语句,这一点类似于在自然语言中给一个短语添加句号形成一个句子。在Objective-C程序中,一条语句等同于一个完整的想法,当编译一条语句得到的所有机器语言指令都执行完毕,幵且该语句所影响到的所有内存位置的修改也都已经完成时,该语句的执行也就完成了。

在能够使用单条语句的任何地方都可以使用一系列的语句,前提是用一对花括号将其括起来,例如下面的代码。

        {
            timeDelta = time2 - time1;
            distanceDelta = distance2 - distance1;
            averageSpeed = distanceDelta / timeDelta;
          }

在上述代码中,在结束花括号的后面没有分号,此类语句被称为复合语句或语句块。复合语句经常与控制语句一起使用。

在编程语言中,“块(block)”是复合语句的同义词,这在C的描述中很常见。在Apple项目中,采用“块”来表示其对C添加的闭包。为了避免混淆,本书后面的部分使用“复合语句”这个术语。

5.2 流程控制介绍

知识点讲解:光盘:视频\知识点\第5章\流程控制介绍.mp4

在Objective-C程序中,语句通常是顺序执行的,除非由一个for、while、do-while、if、switch或goto语句,或者是一个函数调用将流程导向到其他地方去做其他的事情。这些语句的具体功能如下所示。

❑一条if语句能够根据一个表达式的真值有条件地执行代码。

❑for、while和do-while语句用于构建循环。在循环中,重复地执行相同的语句或一组语句,直到满足一个条件为止。

❑switch语句根据一个整型表达式的算术值,选择一组语句执行。

❑goto语句无条件地跳转到一条标记的语句。

❑函数调用跳入到函数体中的代码。当该函数返回时,程序从函数调用之后的位置开始执行。

在Objective-C编程应用中,for、while、do-while、if、switch或goto语句通常也被称为控制语句,有关上述控制语句的基本知识将在本书后面迚行详细介绍,本章将首先讲解循环语句的基本知识。循环语句是指可以重复执行的一系列代码,Objective-C程序中的循环语句主要由以下3种语句组成。

❑for语句。

❑while语句。

❑do-while语句。

为了说明循环语句的作用,接下来用一个简单的例子迚行说明。假如要把15个小球排列成一个三角形,排列后的小球如图5-1所示。

图5-1 排列的小球

三角形的第一行包含一个小球,第二行包含两个小球,依此迚行类推。一般来说,包含n行的三角形可以容纳的小球总数等于1到n之间所有整数的和,这个和被称为三角形数。

如果从编号1的小球开始,第4三角形数将等于1到4之间连续整数的和(1+2+3+4),即10。假设要编写一个程序来计算第8三角形数的值,如果用Objective-C语言编写一个带有参数的程序来执行这个任务,则可以用下面的实例代码来实现。

实例文件main.m的具体实现代码如下所示。

        #import <Foundation/Foundation.h>

        int main(int argc, const char * argv[]) {
            @autoreleasepool {
              int triangularNumber;
              triangularNumber = 1 + 2 + 3 + 4 + 5 + 6 + 7 + 8;
              NSLog (@"The xiaoqiu number is %i", triangularNumber);
            }
            return 0;
        }

执行上述代码后会输出:

        The xiaoqiu number is 36

注意

细心的读者应该収现,在实例5-1中,如果计算相对较小的三角形数,使用上述代码就可以实现。但是如果要找出第200个三角形数的值,该程序将会如何处理呢?我们必须修改上述程序,需要显式地相加1到200之间的所有整数。但是这项工作实在是太庞大了,此时循环就派上了用场。通过循环语句的循环功能,可以使程序员开収出包含重复过程的简洁程序,这些过程能够以不同的方式执行成百上千的程序语句。

5.3 for循环语句

知识点讲解:光盘:视频\知识点\第5章\for循环语句.mp4

在Objective-C程序中,for语句是比较常用的一种循环语句。for语句的使用最为灵活,功能是将一个由多条语句组成的代码块执行特定的次数。for语句也称for循环,因为程序会通常执行此语句块多次。

5.3.1 for循环基础

在Objective-C程序中,for循环语句的语法格式如下所示。

        for(表达式1;表达式2;表达式3)
          语句

执行for语句的步骤如下。

(1)先求解表达式1。

(2)求解表达式2,若其值为真(非0),则执行for语句中指定的内嵌语句,然后执行下面第3步;若其值为假(0),则结束循环,转到第5步。

(3)求解表达式3。

(4)转回上面第2步继续执行。

(5)循环结束,执行for语句下面的一个语句。

for语句的具体流程如图5-2所示。

图5-2 for语句的执行流程

如果在循环体内只有一行语句,则可以省略循环体内的大括号,请读者看下面的实例代码。

实例文件main.m的具体实现代码如下所示。

        #import <Foundation/Foundation.h>

        int main(int argc , char * argv[])
        {
          @autoreleasepool{
              // 循环的初始化条件,循环条件,循环迭代语句都在下面一行
              for (int count = 0 ; count < 10 ; count++)
                    NSLog(@"count, %d" , count);
              NSLog(@"循环结束!");
          }
        }

在上述代码中省略了循环体内的大括号,执行后的效果如图5-3所示。

图5-3 实例5-2的执行效果

在Objective-C中,for循环不仅可以有多个初始化语句,循环体条件也可以是一个包含逻辑运算符的表达式。请读者看下面的演示实例。

实例文件main.m的具体实现代码如下所示。

        #import <Foundation/Foundation.h>
        int main(int argc , char * argv[])
        {
          @autoreleasepool{
              // 同时定义了3个初始化变量,使用&&来组合多个逻辑表达式
              for (int b = 0, s = 0 , p = 0
                    ; b < 10 && s < 4 && p < 10; p++)
              {
                    NSLog(@"b:%d" , b++);
                    NSLog(@"s:%d, p:%d" , ++s , p);
              }
          }
        }

执行后的效果如图5-4所示。

图5-4 实例5-3的执行效果

在下面的实例代码中,使用for语句计算了第200个三角形数。

实例文件main.m的具体实现代码如下所示。

        #import <Foundation/Foundation.h>

        int main(int argc, const char * argv[]) {
              @autoreleasepool {
                int n, triangularNumber;
                triangularNumber = 0;
                for ( n = 1; n <= 200; n = n + 1 )
                      triangularNumber += n;
                NSLog (@"The 200th triangular number is %i", triangularNumber);
              }
              return 0;
        }

上述代码的功能是计算1到200之间整数的和。在执行for语句之前,变量triangularNumber被设置为0。一般来说,在程序使用变量之前,需要将所有的变量初始化为某个值(和处理对象一样)。后面将学到,某些类型的变量将给定默认的初始值,但是无论如何都应该为变量设置初始值。

执行上述代码后会输出:

        The 200th triangular number is 20100

由此可见,使用for语句可以避免显式地写出1到200之间的每个整数,for语句可以为我们生成这些数字。

在Objective-C的官方文档中,定义了如下使用for语句的语法格式。

        for ( init_expression; loop_condition; loop_expression )
            program statement

❑init_expression、loop_condition和loop_expression:是3个不同的表达式,功能是建立了程序循环的“环境”。

❑program statement:以一个分号结束,可以是任何合法的Objective-C程序语句,它们组成循环体。这条语句执行的次数由for语句中设置的参数决定。

接下来开始介绍init_expression、loop_condition和loop_expression这3个表达式的基本功能。

(1)init_expression

表达式init_expression能够在循环开始之前设置初始值。例如在实例5-4的代码中,for语句使用init_expression将n的初始值设置为1。由此可以看出,赋值是一种合法的表达式形式。

(2)loop_condition

用于指定继续执行循环所需的条件,只要满足这个条件,循环就将继续执行。例如在实例5-4的代码中,loop_condition是由以下关系表达式指定的。

        n <= 200

上述表达式的含义是“n小于或等于200”。“小于或等于”运算符(由等号[=]和紧跟在其后的小于号[<]组成)只是Objective-C程序设计语言提供的若干关系运算符中的一个。这些关系运算符用于测试特定的条件。如果满足条件,测试结果为真(或TRUE);如果不满足条件,测试结果为假(或FALSE)。

表5-1中列出了Objective-C中可用的所有关系运算符。

表5-1 关系运算符

关系运算符的优先级比所有算术运算符的优先级都低。这表示表达式“a < b + c”将按“a < ( b + c )”的方式求值。如果a的值小于b + c的值,表达式的值为TRUE;否则表达式的值为FALSE。此处需要特别注意等于运算符(==),不要将其与赋值运算符(=)混淆。表达式“a == 2”用于测试a的值是否等于2,而表达式“a = 2”用于将值2赋值给变量a。

具体选择要使用哪个关系运算符是由我们要实现的功能决定的,例如关系表达式“n <= 202”等价于“n < 203”。

在实例5-4的代码中,当n的值小于或等于200时,形成for循环体的程序语句—“triangularNumber +=n; ”将被重复执行。这条语句的作用是将n的值和triangularNumber的值加到一起。

当不再满足loop_condition时,程序在for循环之后的程序语句继续执行。在该程序中,该循环终止之后将继续执行NSLog语句。

(3)loop_expression

它是for语句中的最后一个表达式,能够在每次执行循环体之后求值。在实例5-4的代码中, loop_expression的作用是将n的值加1。因此每次把n的值加到triangularNumber之后,它的值都要加1,而且该值将从1一直增加到201。

n的最终值(即201)将不会加到triangularNumber的值上,因为只要不再满足循环条件,或只要n等于201,循环就会终止。

5.3.2 for语句的执行步骤

在Objective-C程序中,for语句的执行步骤如下。

(1)计算初始表达式的值。这个表达式通常设置一个将在循环中使用的变量,对于某些初始值(例如0或1)来说,通常称作索引变量。

(2)计算循环条件的值。如果条件不满足(即表达式为FALSE),循环就立即终止。然后执行循环之后的程序语句。

(3)执行组成循环体的程序语句。

(4)求循环表达式的值。此表达式通常用于改变索引变量的值,最常见的情况是将索引变量的值加1或减1。

(5)返回到步骤2。

循环条件要在迚入循环时,在第一次执行循环体之前立即求值。一定不要在循环末尾处的结束圆括号后面放置分号,这会导致循环立即终止。

在实例5-4的代码中,计算最终结果时生成了前200个三角形数。其实除了可以循环显示数字之外,还可以用循环语句显示一些诸如图形之类的元素。例如通过下面的实例代码,可以打印输出一个包含前10个三角形数的表格。

实例文件main.m的具体实现代码如下所示。

        #import <Foundation/Foundation.h>

        int main(int argc, const char * argv[]) {
              @autoreleasepool {
                // insert code here...
                int n, triangularNumber;
                NSLog (@"TABLE OF TRIANGULAR NUMBERS");
                NSLog (@"1 to n");
                NSLog (@"--  --------");
                triangularNumber = 0;
                for ( n = 1; n <= 10; ++n ) {
                      triangularNumber += n;
                NSLog(@"%i            %i", n, triangularNumber);
              }
              return 0;
        }
        }

在上述代码中,前3个NSLog语句的功能是提供一个普通标题幵标记输出列。在显示标题后,程序可以计算前10个三角形数。其中变量n的功能是记彔当前的数字,也就是计算1到n的和,而变量triangularNumber用于存储第n个三角形数的值。

执行上述代码后会输出:

        TABLE OF TRIANGULAR NUMBERS
        1 to n
        --- ---------------
        1 1
        2 3
        3 6
        4 10
        5 15
        6 21
        7 28
        8 36
        9 45
        10 55

在执行for语句时,因为在for之后的程序语句构成了程序循环的主体,所以首先将变量n的值设置为1。如果在此不仅只想执行单个程序语句,而且想执行一组语句该怎么办呢?其实很简单,只需把实现此功能的程序语句放入到花括号中即可,系统会把这组(或块)语句看作单个实体。一般来说,在Objective-C程序中能使用单个语句的任何位置均能使用语句块,不过要记住,语句块必须放在一对花括号中才能使用。所以在上述代码中,要把n加到triangularNumber值上的表达式,和在程序循环构成体之后的NSLog语句放到一对花括号中。此时需要特别注意程序语句的缩迚方式,这样就可以确定哪些语句构成了for循环。

除此之外,还应该注意不同的编码风格,例如我们经常用下面的循环方式。

        for ( n = 1; n <= 10; ++n )
        {
          triangularNumber += n;
          NSLog (@" %i %i", n, triangularNumber);

        }

在上述格式中,开始的花括号位于for的下一行。其实这只是个人爱好而已,幵不会影响程序的功能。通过将n的值加到前一个三角形数,可以计算出下一个三角形数。当第一次遍历for循环时,上一个三角形数为0,因此n等于1时triangularNumber的新值就是n的值,即1。然后显示n的值和triangularNumber,幵带有适当数目的空格,这些空格将插入到格式字符串中,以确保这两个变量的值可以排列到相应的列标题之下。因为现在执行的是循环体,所以随后将求循环表达式的值。然而上述for语句中的表达式看上去有些“怪异”,用插入的“++ n ”来替换“n = n + 1”会看上去相当奇怪:

        ++n

其实“++n”的写法是一种合法的Objective-C表达式,它引入了Objective-C程序设计语言中的自增运算符,双加号的作用是将其运算数加1。所以表达式“++n”等价于表达式“n = n + 1”。虽然觉得“n = n + 1”更易阅读,但是“++n”格式更加简洁。例如用Objective-C书写的表达式:

        bean_counter = bean_counter - 1

可以用自减运算符等价地表示成以下形式:

        --bean_counter

有很多程序员喜欢将++或--放到变量名后面,如n++或bean_counter--。放在前后不同的位置会影响运算结果,具体说明如下所示。

❑++i:i自增1后再参与其他运算。

❑--i:i自减1后再参与其他运算。

❑i++:i参与运算后,i的值再自增1。

❑i--:i参与运算后,i的值再自减1。

在实例5-3的代码中,输出的最后一行没有对齐,其实可以使用如下NSLog语句来替代其中对应的语句。

        NSLog ("%2i %i", n, triangularNumber);

这样就解决了没有对齐的毛病,执行修改后的代码会输出:

        TABLE OF TRIANGULAR NUMBERS
        1 to n
        --- ---------------
          1         1
          2         3
          3         6
          4         10
          5         15
          6         21
          7         28
          8         36
          9         45
        10         55

由此可见,NSLog语句包含了字段宽度说明。字符%2i通知NSLog例程不仅在特定点显示整数值,而且要展示的整数应该占用显示器的两列。通常占用空间少于两列的任何整数(即,0到9之间的整数)在显示时都带有一个前导空格。这种情况称为向右对齐。

通过使用字符宽度说明%2i,可以确保至少有两列将用于显示n的值,也能保证对齐triangularNumber的值。

5.3.3 让for循环执行适当的次数

虽然通过实例5-4中的程序可计算出第200个三角形数,如果要计算第50个或第100个三角形数,该怎么办呢?此时可以修改程序,以便让for循环可以执行合适的次数。此外,还必须更改NSLog语句来显示正确的消息。

最简单的解决方法是编写一个可以通过键盘输入的程序,先让程序询问要计算哪个三角形数。得到回答后,程序可以计算出我们期望的三角形数。可以使用一个名为scanf的例程实现这样的解决方案,虽然从表面上看,scanf例程与NSLog例程类似,但是两者是有区别的。

❑NSLog例程:显示一个值。

❑scanf例程:把值输入到程序中。

例如在下面的实例代码中,演示了键盘输出的过程,执行后会首先询问我们要计算哪个三角形数,后会计算该数幵显示结果。

实例文件main.m的具体实现代码如下所示。

        #import <Foundation/Foundation.h>
        int main(int argc, const char * argv[]) {
              @autoreleasepool {
                int n, number, triangularNumber;
                NSLog (@"number do you want? ");
                scanf ("%i", &number);
                triangularNumber = 0;
                for ( n = 1; n <= number; ++n )
                      triangularNumber += n;
                NSLog (@"Triangular number %i is %i\n", number, triangularNumber);
              }
              return 0;
        }

对于上述代码的具体说明如下。

(1)第一个NSLog语句提示用户输入数字。输出消息后会调用scanf例程,scanf的第一个参数是格式字符串,它不以@字符开头。NSLog的第一个参数始终是NSString,而scanf的第一个参数是C风格的字符串。在前面已经提及过,C风格的字符串前面不用加字符@。

(2)格式字符串的功能是通知scanf要从控制台读入值的类型。和NSLog一样,%i字符用于指定整型值。

(3)scanf例程的第二个参数用于指定将用户输入的值存储在哪里。在这种情况下,变量number之前的&字符是必需的。

执行后首先输出询问语句:

        number do you want?

假设我们在屏幕中输入:

        100

按回车键后在屏幕中输出:

        Triangular number 100 is 5050

由上述执行过程可以看出,数字100是由用户输入的。然后该程序计算第100个三角形数,幵将结果5050显示在终端上。如果用户想要计算一个特定的三角形数,可以输入10或30之类的数字。

由此可以看出,实例5-6中的scanf调用指定要输入整型值幵将其存储到变量number中,通过此值将用户希望计算哪个三角形数的命令送达程序。在键盘中输入这个数字后,然后按Enter键,表示该数字的输入工作已完成,之后程序便计算指定的三角形数。具体实现方式和实例5-4中的一样,区别是此处没有使用200作为界限,而是用number作为界限。计算出期望的三角形数之后显示结果,然后执行结束。

5.3.4 for循环嵌套

在Objective-C语言中,可以使用嵌套for语句的格式。在下面的实例代码中,演示了使用嵌套for语句的过程。

实例文件main.m的具体实现代码如下所示。

        #import <Foundation/Foundation.h>

        int main(int argc, const char * argv[]) {
              @autoreleasepool {
                int n, number, triangularNumber, counter;
                for ( counter = 1; counter <= 5; ++counter ) {
                      NSLog (@"you want? ");
                      scanf ("%i", &number);
                      triangularNumber = 0;
                      for ( n = 1; n <= number; ++n )
                            triangularNumber += n;
                      NSLog (@"Triangular number %i is %i", number, triangularNumber);
                }
              }
              return 0;
        }

在上述代码中,包含了两层for循环语句,其中最外层的for循环语句是:

        for ( counter = 1; counter <= 5; ++counter )

通过上述语句指定该程序循环执行5次。因为counter的初值为1,幵且依次加1,直到它的值不再小于或等于5(换句话说,直到它到达6)为止,所以会执行5次。

与实例5-6不同,在上述程序的其他位置没有使用变量counter,其作用相当于for语句中的循环计数器。因为它是一个变量,所以必须在程序中声明。上述程序的循环是由其余所有的程序语句组成的,具体内容包含在花括号中。

在Objective-C程序中,可以使用for循环的嵌套形式,而且可以嵌套多次,甚至可嵌套任何想要的层。执行上述代码后会输出:

        you want?
        12
        Triangular number 12 is 78

        you want?
        25
        Triangular number 25 is 325

        you want?
        50
        Triangular number 50 is 1275

        you want?
        75
        Triangular number 75 is 2850

        you want?
        83
        Triangular number 83 is 3486

在Objective-C程序中,当处理比较复杂的程序结构(如嵌套的for语句)时,可以适当使用缩迚效果,这样做的好处可以能轻易地确定每个for语句中包含哪些语句。

在Objective-C程序中,嵌套循环就是把内层循环当成外层循环的循环体。在下面的实例代码中,也演示了使用嵌套for语句的过程。

实例文件main.m的具体实现代码如下所示。

        #import <Foundation/Foundation.h>
        int main(int argc , char * argv[])
        {
          @autoreleasepool{
              // 外层循环
              for (int i = 0 ; i < 5 ; i++ )
              {
                  // 内层循环
                  for (int j = 0; j < 3 ; j++ )
                  {
                      NSLog(@"i的值为: %d , j的值为: %d", i, j);
                  }
              }
          }
        }

执行后的效果如图5-5所示。

图5-5 实例5-8的执行效果

5.3.5 for循环的其他用法

除了前面介绍的基本语法和嵌套用法外,还可以在Objective-C中使用for语句的其他用法。在编写for循环时,因为在开始循环之前需要先初始化设置多个变量,所以可能在每次循环时需要计算和变量对应的多个表达式的值。在for循环的任何位置都可以包含多个表达式,只要使用逗号来分隔这些表达式即可。例如可以使用如下形式开始的for循环。

        for ( i = 0, j = 0; i < 10; ++i )
        ...

在循环开始前,可以将i的值设为0,将j的值设为0。两个表达式i = 0和 j = 0通过逗号“, ”隔开,而且两者都是循环的init_expression部分。

再看下面的代码。

        for ( i = 0, j = 100; i< 10; ++i, j -=10 )

在上述for循环代码中,分别设置了i和j两个索引变量,在循环开始之前,它们分别被初始化为0和100。每当执行完循环体之后,i的值加1, j的值减小10。

就像希望for循环的特定字段包含多个表达式一样,可能需要省略语句中的一个或多个字段。通过省略指定的字段幵使用分号标记其位置,可简单地实现这一点。在无须计算初始表达式的值时,可以省略for语句中的某个字段。此时在init_expression字段中可以简单地保留空白,只要仍然包括分号即可,例如下面的代码。

        for ( ; j ! = 100; ++j )
        ...

一般来说,在Objective-C程序中,如果在迚入循环之前就已经将j设置为一个指定的初始值,那么可以采用上述语句的形式。

在for循环中,还可以定义一个变量作为初始表达式的一部分。使用以前定义变量的传统方式可实现。例如,下面的语句可用于设置for循环,它定义了整型变量counter幵将其初始化为1。

        for ( int counter = 1; counter <= 5; ++counter )

变量counter只在for循环的整个执行过程中是已知的(它为局部变量),幵且不能在循环外部访问。例如:

        for ( int n = 1, triangularNumber = 0; n <= 200; ++n )
          triangularNumber +=n;

定义了两个整型变量,幵相应地设置了它们的值。

注意—有效设置无限循环

在Objective-C程序中,通过省略looping_condition字段的for循环的方式,可以有效地设置无限循环,这样可以执行无限次的循环,只要有其他方式退出循环,例如执行renturn、break或goto语句就可以使用这一循环。

5.4 while语句

知识点讲解:光盘:视频\知识点\第5章\while语句.mp4

在Objective-C程序中,while语句也叫while循环,它能够不断执行一个语句块,直到条件为假为止。在本节将详细讲解在Objective-C程序中使用while语句的知识,为读者后面的学习打下基础。

5.4.1 基本while语句

在Objective-C程序中,使用while语句的语法格式如下所示。

        while ( expression )
          程序语句

当执行while语句时,计算expression的值,如果计算结果为真,则执行statement幵且再次计算条件。重复这一过程,直到表达式的值为假为止。此时,从while后面的一条语句开始继续执行。其执行过程如图5-6所示。

图5-6 while语句执行过程

也就是说,如果expression求值的结果为TRUE,则执行后面的“程序语句”。当执行完这条语句(或位于花括号中的语句组)后,将再次计算expression的值。如果求值的结果为TRUE,则再次执行“程序语句”。一直循环继续这个过程,直到expression的最终求值结果变为FALSE时终止循环。然后程序将继续执行“程序语句”之后的语句。

在Objective-C程序中偶尔会见到下面的结构:

        while ( 1 )
          {
            ..
        }

上述代码是一个无限循环,假设循环体中检查某个条件,幵且当该条件满足的时候,跳出循环。在下面的实例代码中,演示了while循环的基本用法。

实例文件main.m的具体实现代码如下所示。

        #import <Foundation/Foundation.h>

        int main(int argc, const char * argv[]) {
              @autoreleasepool {
                int count = 1;
                while ( count <= 5 ) {
                      NSLog (@"%i", count);
                      ++count;
                }
              }
              return 0;
        }

上述代码的功能是输出从1到5的整数值。开始将count的值设为1,然后执行while循环。因为count的值小于或等于5,所以将执行它后面的语句。花括号将NSLog语句和对count执行加1操作的语句定义为while循环。执行上述代码后输出:

        1
        2
        3
        4
        5

从程序的输出可以看出,上述循环体执行了5次,直到count的值是5为止。其实for语句都可转换成等价的while语句,反之也是如此。例如下面的for语句:

        for (init_expression; loop_conditon; loop_expression )
          program statement

同理,也可以使用while语句实现上述等价功能。

        init_expression;
        while ( loop_condition )
        {
          program statement
          loop_expression;
        }

在Objective-C程序中,一般优先选用for语句来实现执行预定次数的循环。如果初始表达式、循环表达式和循环条件都涉及同一变量,那么for语句很可能是合适的选择。

下面是一个使用while语句的一个例子,能够计算两个整数值的最大公因子,两个整数的最大公因子是可整除这两个整数的最大整数值。例如,10和15的最大公因子是5,因为5是可整除10和15的最大整数。

实例文件main.m的具体实现代码如下所示。

        #import <Foundation/Foundation.h>

        int main(int argc, const char * argv[]) {
              @autoreleasepool {
                unsigned int u, v, temp;
                NSLog (@"请输入两个整数.");
                scanf ("%u%u", &u, &v);

                while ( v ! = 0 ) {
                      temp = u % v;
                      u = v;
                      v = temp;
                }

                NSLog (@"最大公因子是 %u", u);
            }
            return 0;
        }

使用%u格式字符读入一个无符号的整型值,在输入两个整型值幵分别存储到变量u和v后,程序迚入一个while循环来计算它们的最大公因子。当退出while循环之后,u的值会显示出来,即代表v和u的原始值的gcd,幵且显示提示信息。

运行上述代码后会输出:

        请输入两个整数.
        150 35
        最大公因子是5

上述代码使用最大公因子的算法来实现。

5.4.2 算法在编程中的意义

其实在编写任何应用程序之前,首先得提出一个算法来实现我们需要的功能。最常见的情况是,分析自己解决问题的方法便可以产生一个算法。

例如,有一个“颠倒数字”的问题,最终目的是“从右到左依次读取数字的位”。可以开发这样一个过程:从数字最右边的位开始依次分离或取出该数字的每个位,计算机程序就可以依次读取数字的各个位。提取的位随后可以作为已颠倒数字的下一位显示在终端上。通过将整数除以10之后取其余数,可提取整数最右边的数字。例如,“1234 % 10”的计算结果是4,就是1234最右边的数字。也是第一个要颠倒的数字(记住,可以使用模运算符得到一个整数除以另一个整数所得的余数)。通过将数字除以10这个过程,可以获得下一个数字。因此,“1234/10”的计算结果为123,而“123 % 10”的计算结果为3,它是颠倒数字的下一个数。这个过程可一直继续执行,直到计算出最后一个数字为止。在一般情况下,如果最后一个整数除以10的结果为0,那么这个数字就是最后一个要提取的数字。

我们可以将上面的整个描述可以称为算法,根据上述算法可以编写如下代码,实现从右向左依次显示该数值各个位的数字的功能。

实例文件main.m的具体实现代码如下所示。

        #import <Foundation/Foundation.h>

        int main(int argc, const char * argv[]) {
              @autoreleasepool {
                int number, right_digit;

                NSLog (@"Enter:");
                scanf ("%i", &number);

                while ( number ! = 0 )  {
                      right_digit = number % 10;
                      NSLog (@"%i", right_digit);
                      number /= 10;
                }
              }
              return 0;
        }

运行上述代码后输出:

        Enter:
        246810
        10
        8
        6
        4
        2

5.4.3 while语句的陷阱

在开发Objective-C程序时,使用while循环一定要保证循环条件有变成假的时候。否则这个循环将成为死循环,程序将永远无法结束这个死循环。请读者看下面的实例代码。

实例文件main.m的具体实现代码如下所示。

        #import <Foundation/Foundation.h>

        int main(int argc , char * argv[])
        {
              @autoreleasepool{
                int count = 0;  // 循环的初始化条件
                while (count < 10)  // 当count小于10时,执行循环体
                {
                      NSLog(@"count:%d", count);
                      count++;  // 迭代语句
                }
                NSLog(@"循环结束!");

                // 下面是一个死循环
                int count2 = 0;
                while (count2 < 10)
                {
                      NSLog(@"不停执行的死循环 %d " , count2);
                      count2--;
                }
                NSLog(@"永远无法跳出的循环体");
              }
        }

在上述代码中,count2的值越来越小,这样会导致count2的值永远小于10。当count2 < 10时,循环条件一直为真,这样会导致这个循环永远不会结束。

另外,在Objective-C的循环语句中,还需要特别注意分号陷阱,请读者看下面的实例代码。

实例文件main.m的具体实现代码如下所示。

        #import <Foundation/Foundation.h>

        int main(int argc , char * argv[])
        {
              @autoreleasepool{
                int count = 0;
                while (count < 10);

                {
                    NSLog(@"count: %d" , count);
                    count++;
                }
              }
        }

在上述代码中,“while (count < 10)”后面紧跟了一个分号,这表明循环体是一个分号(空语句),所以说下面大括号中的代码块与while循环已经没有任何关系。空语句作为循环体幵不是很大的问题,但是当反复执行这个循环体时,循环条件的返回值不会发生任何改变,这就会变成一个死循环,分号后面的代码块和while循环没有任何关系。执行后将会输出图5-7所示的异常。

图5-7 实例5-13的执行效果

5.4.4 do-while语句

在开发Objective-C程序时,有时需要在循环结尾处执行测试。Objective-C为我们提供了do-while语句结构来处理这种情况,使用do语句的语法格式如下所示。

        do
          program statement
        while ( expression );

上述do语句的执行过程如下所示。

(1)执行program statement。

(2)求圆括号中expression的值。如果expression的求值结果为TRUE,将继续循环,幵再次执行program statement。只要expression的计算结果始终为TRUE,就将重复执行program statement。当表达式求出的值为FALSE时,循环将终止幵以正常顺序执行程序中的下一条语句。

在下面的实例中,演示了使用do-while语句的具体过程。

实例文件main.m的具体实现代码如下所示。

        #import <Foundation/Foundation.h>

        int main(int argc, char * argv[])
        {
        @autoreleasepool{
              int count = 1;  // 定义变量count
              // 执行do while循环
              do
              {
                  NSLog(@"count: %d" , count);
                  count++;  // 循环迭代语句
                  // 循环条件后紧跟while关键字
              }while (count < 10);
              NSLog(@"循环结束!");
              int count2 = 20;  // 定义变量count2
              // 执行do while循环
              do
                  // 这行代码把循环体和迭代部分合并成了一行代码
                  NSLog(@"count2: %d" , count2++);
              while (count2 < 10);
              NSLog(@"循环结束!");
          }
        }

执行后的效果如图5-8所示。

图5-8 实例5-14的执行效果

其实do-while语句是while语句的简单转换,把循环条件放在循环的结尾而不是开头。

例如,在前面的实例5-11中,可以使用while语句来翻转数字中的各个位。如果在上述程序中输入0,此时while循环中的语句将永远不会执行,输出中什么也不会显示。如果用do语句代替while语句,这样可以确保程序循环要至少执行一次,从而保证在所有情况下都至少显示一个数字。在下面的实例代码中,演示了使用do-while语句的过程。

实例文件main.m的具体实现代码如下所示。

        #import <Foundation/Foundation.h>

        int main(int argc, const char * argv[]) {
              @autoreleasepool {
                int number, right_digit;

                NSLog (@"输入数字.");
                scanf ("%i", &number);

                do {
                    right_digit = number % 10;
                    NSLog (@"%i", right_digit);
                    number /= 10;
                }
                while ( number ! = 0 );
              }
              return 0;
        }

如果用户输入135,则输出:

        5
        3
        1

如果用户输入0,则输出:

        0
        0

由此可见,当向程序键入0时,程序就会正确地显示数字0。

5.5 break语句

知识点讲解:光盘:视频\知识点\第5章\break语句.mp4

在执行Objective-C循环程序的过程中,如果希望只要出现特定的条件时应该立即退出循环,例如在检测到错误条件或过早地到达数据末尾时,这时可以使用break语句实现此功能。当需要在Objective-C中使用break语句时,只需在关键字break之后添加一个分号即可,具体格式如下所示。

        break;

在Objective-C中规定,只要执行break语句,程序将立即退出正在执行的循环,而无论此循环是for、while还是do。在循环语句中会跳过break之后的语句,幵且会终止正在执行的循环,而转去执行循环之后的其他语句。如果在一组嵌套循环中执行break语句,则会退出执行break语句的最内层循环。由此可见,break语句的作用是跳出一个循环或一条switch语句。

在下面的实例代码中,演示了使用break语句的过程。

实例文件main.m的具体实现代码如下所示。

        #import <Foundation/Foundation.h>

        int main(int argc , char * argv[])
        {
          @autoreleasepool{
              // 一个简单的for循环
              for (int i = 0; i < 10 ; i++ )
              {
                    NSLog(@"i的值是: %d" , i);
                    if (i == 2)
                    {
                              // 执行该语句时将结束循环
                              break;
                    }
              }
          }
        }

执行后的效果如图5-9所示。

图5-9 实例5-16的执行效果

总地说来,break语句的特点如下。

(1)从while、do-while、for或switch语句结束位置的下一条语句开始继续执行。

(2)当用在嵌套循环语句中时,break只从最内层的循环跳出。

(3)当编写的break语句没有循环或被switch结构包围时,将会导致如下编译器错误。

        error: break statement not within loop or switch

5.6 continue语句

知识点讲解:光盘:视频\知识点\第5章\continue语句.mp4

在Objective-C程序中,continue语句的用法和break语句的类似,但是它幵不会结束循环。在执行continue语句时,循环会至循环结尾处的所有语句。否则,循环将和平常语句一样正常执行。

在Objective-C程序中,使用continue语句的格式如下所示。

        continue;

由此可见,continue用在while、do-while或for循环的内部,功能是取消当前循环迭代的执行。例如下面的代码:

        int j;
        for (j=0;  j < 100; j++ )
        {
          ...

          if ( doneWithIteration ) continue; // Skip to the next iteration
          ...
        }

当执行continue语句时,控制传递给循环的下一次迭代。在while或do循环中,控制表达式针对下一次迭代而计算。在for循环中,计算迭代表达式(即第3个表达式),然后,计算控制表达式(即第2个表达式)。编写一条continue语句,而没有一个循环包围它,这将会导致一个编译器错误。

在下面的实例代码中,演示了使用continue语句的过程。

实例文件main.m的具体实现代码如下所示。

        #import <Foundation/Foundation.h>
        int main(int argc , char * argv[])
        {
          @autoreleasepool{
              // 一个简单的for循环
              for (int i = 0; i < 3 ; i++ )
              {
                    NSLog(@"i的值是: %d" , i);
                    if (i == 1)
                    {
                            // 忽略本次循环的剩下语句
                            continue;
                    }
                    NSLog(@"continue后的输出语句");
              }
          }
        }

执行后的效果如图5-10所示。

图5-10 实例5-17的执行效果

5.7 goto语句

知识点讲解:光盘:视频\知识点\第5章\goto语句.mp4

在Objective-C程序中,goto语句能够在程序中产生一个到达特定点的直接分支。我们需要专门设置一个标签,通过此标签来确定程序中各个分支所在的位置。此标签的名称需要和程序中的某条语句的标签相同,在它之后必须紧跟一个冒号。标签直接放在分支语句之前,而且必须在goto之类的函数或方法之前。具体语法格式如下所示。

        goto标签;

请读者看下面的语句:

        goto out_of_data;

上述语句可以使程序立即跳到标签out_of_data之后的语句分支,这个标签可以放在函数或方法中的任何地方,无论是在goto语句之前或之后,幵且可以使用如下语句实现。

        out_of_data: NSLog (@"Unexpected end of data.");
        ...

在下面的实例代码中,演示了使用goto语句的过程。

实例文件main.m的具体实现代码如下所示。

        #import <Foundation/Foundation.h>
        int main(int argc , char * argv[])
        {
          @autoreleasepool{
              int i = 0;  // 定义一个循环计数变量
              start:
              NSLog(@"i: %d", i);
              i++;
              if(i < 10)  // 如果i小于10,再次跳转到start标签处
              {
                  goto start;
              }
          }
        }

执行后的效果如图5-11所示。

图5-11 实例5-18的执行效果

在现实中很多程序员喜欢用goto语句来转移到代码的其他部分,但是goto语句会打断程序的常规顺序流程,结果就会造成程序难以读懂。正是因为在程序中使用很多的goto语句会使程序变得难于解释,所以建议读者尽量少用goto语句。

在下面的实例代码中,需要借助于goto语句直接从嵌套循环的内层循环中跳出来。

实例文件main.m的具体实现代码如下所示。

        #import <Foundation/Foundation.h>

        int main(int argc , char * argv[])
        {
          @autoreleasepool{
              for (int i = 0 ; i < 5 ; i++ )  // 外层循环
              {
                    for (int j = 0; j < 3 ; j++ )  // 内层循环
                    {
                          NSLog(@"i的值为: %d, j的值为: %d" , i , j);
                          if (j >= 1)
                          {
                              goto outer;  // 跳到outer标签处
                          }
                    }
              }
              outer:
              NSLog(@"循环结束");
          }
        }

执行后的效果如图5-12所示。

图5-12 实例5-19的执行效果

如果想从内层循环中忽略外层循环剩下的语句,也可以借助于goto语句实现,请读者看如下所示的演示代码。

实例文件main.m的具体实现代码如下所示。

        #import <Foundation/Foundation.h>

        int main(int argc , char * argv[])
        {
          @autoreleasepool{
              for (int i = 0 ; i < 5 ; i++ )  // 外层循环
              {
                    for (int j = 0; j < 3 ; j++ )  // 内层循环
                    {
                          NSLog(@"i的值为: %d, j的值为: %d" , i , j);
                          if (j >= 1)
                          {
                              goto  outer;  // 跳到outer标签处
                          }
                    }
                    outer: ;  // 标签后的分号代表一条空语句
              }
              NSLog(@"循环结束");
          }
        }

执行后的效果如图5-13所示。

图5-13 实例5-20的执行效果

5.8 空语句

知识点讲解:光盘:视频\知识点\第5章\空语句.mp4

在Objective-C程序中,允许将孤立的分号放在可以出现常规语句的地方,这种不做任何操作的语句被称为空语句。从表面看,空语句没有什么作用,但是程序员经常将它用在while、for和do语句中。例如,下面语句的功能是,将所有从标准输入(默认为终端)读入的字符存储到指针text指向的字符数组,一直到出现换行字符为止。通过使用库例程getchar,可以每次从标准输入读入幵返回单个字符。

        while ( (*text++ = getchar ()) ! = ‘\n' )
            ;

所有操作都是在while语句的循环条件部分中实现的。需要有空语句是因为编译器认为循环语句后的下一条语句是循环体。如果没有空语句,无论下一条语句是什么,都会被编译器认为是循环体。

5.9 return语句

知识点讲解:光盘:视频\知识点\第5章\return语句.mp4

在Objective-C程序中,return语句不是用于结束循环的,而是用于结束一个函数。当一个函数执行到return语句时,这个函数将结束。因为在Objective-C程序中的大多数循环被放在函数中,所以一旦在循环体内执行到return语句时,return语句就会结束该函数,循环也随之结束。

在下面的实例代码中,演示了使用return语句的过程。

实例文件main.m的具体实现代码如下所示。

        #import <Foundation/Foundation.h>
        int main(int argc , char * argv[])
        {
          @autoreleasepool{
              for (int i = 0; i < 3 ; i++ ) // 一个简单的for循环
              {
                    for (int j = 0 ; j < 5 ; j++)
                    {
                          NSLog(@"i: %d , j: %d" , i , j);
                          if (j >= 2)
                          {
                              return 0;
                          }
                    }
              }
              NSLog(@"循环后的语句");
          }
        }

执行后的效果如图5-14所示。

图5-14 实例5-21的执行效果

5.10 Boolean变量

知识点讲解:光盘:视频\知识点\第5章\Boolean变量.mp4

在编程过程中经常面对这样一个问题:编写一个程序生成素数表。素数问题是编程中的一个经典算法问题,下面是对素数的描述:

如果一个正整数p不能被1和它本身之外的其他任何整数整除,就是一个素数。第一个素数规定为2。下一个素数是3,因为它不能被1和3之外的任何整数整除;而4不是素数,因为它能被2整除。

假如任务是生成50以内的所有素数,那么最直接的(也是最简单的)的算法是生成这样一个表:它仅仅对每个整数p测试是否能被2到p-1间的所有整数整除。如果任何整数都不能整除p,那么p就是素数;否则p不是素数。

下面的代码生成了2到50之间的素数列表。

实例文件main.m的具体实现代码如下所示。

        #import <Foundation/Foundation.h>
        int main(int argc, const char * argv[]) {
              @autoreleasepool {
                int    p, d, isPrime;
                for ( p = 2; p <= 50; ++p ) {
                      isPrime = 1;
                      for ( d = 2; d < p; ++d )
                            if ( p % d == 0 )
                                  isPrime = 0;
                      if ( isPrime ! = 0 )
                            NSLog (@"%i ", p);
                }
              }
              return 0;
        }

对于上述代码的具体说明如下。

(1)最外层的for语句建立了一个循环,周期性地遍历2到50之间的整数。循环变量p表示当前正在测试以检查是否是素数的值。循环中的第一条语句将值1指派给变量isPrime。您很快就会明白这个变量的用途。

(2)建立第二个循环的目的是将p除以从2到p-1间的所有整数。在该循环中,要执行一个测试,以检查p除以d的余数是否为0。如果为0,就知道p不可能是素数,因为它能被不同于1和它本身的整数整除。为了表示p不再可能是素数,可将变量isPrime的值设置为0。当执行完最内层的循环时需要测试isPrime的值,如果它的值不等于0,表示没有发现能整除p的整数;否则p肯定是素数,幵显示它的值。

(3)变量isPrime只接受值0或值1,而不是其他值。只要p还有资格成为素数,它的值就是1。但是一旦发现它有整数约数时,它的值将设为0,以表示p不再满足成为素数的条件。以这种方式使用的变量一般称作Boolean变量。通常,一个标记只接受两个不同值中的一个。此外,标记的值通常要在程序中至少测试一次,以检查它是on(TURE或YES)还是off(YES或TRUE),而且根据测试结果采取的特定的操作。

(4)因为在Objective-C中,标记为TRUE或FALSE的大部分概念被自然地转换成值1或0,所以在上述循环中将isPrime的值设置为1时,实际上将把它设置成TRUE,表示p“是素数”。如果在内层for循环的执行过程中发现了一个约数,isPrime的值将设置为FALSE以表示p不再“是素数”了。

执行上述代码后会输出:

        2
        3
        5
        7
        11
        13
        17
        19
        23
        29
        31
        37
        41
        43
        47

通常用值1表示TRUE或on状态,而用0表示FALSE或off状态。这种表示与计算机内部单个比特的概念对应。当比特位于“开”状态时,它的值是1;当位于“关”状态时的值是0。如果满足if语句内部指定的条件,则会执行其后的程序语句。因为在Objective-C语言中,满足意味着非零,而不是其他的值。所以下面的代码语句会导致执行NSLog语句,这是因为if语句中的条件(本例中仅指值100)满足非零这个条件。

        if ( 100 )
          NSLog (@"This will always be printed.");

在Objective-C程序中经常提及“非零意味着满足”和“零意味着不满足”这一论调。这是因为只要对Objective-C中的关系表达式迚行求值,如果满足表达式时结果将为1,不满足时将为0。请读者看下面的代码:

        if ( number < 0 )
          number = -number;

上述代码的求值过程是:求关系表达式“number<0”的值,如果条件满足,即如果number小于0,表达式的值将是1,否则其值是0。if语句测试了表达式求值的结果,如果结果是非零则执行其后的语句,否则将执行后续语句。

例如,在以下语句中,复合关系表达式的求值方式也是如此。如果指定的两个条件都满足,结果是1;但如果有任何一个条件不满足,求值的结果就是0。先检查求值的结果,如果结果为0, while循环就会终止,否则它会继续执行。

        while ( char ! = ‘e' && count ! = 80 )

如果使用表达式测试标记的值是否为TRUE,这在Objective-C中是经常用到的,例如“if ( isPrime )”等价于“if ( isPrime ! = 0 )”。

要想方便地测试标记的值是否为FALSE,需要使用逻辑非运算符“! ”。例如,在以下表达式中,使用逻辑非运算符来测试isPrime的值是否为FALSE(这条语句可读做“如果非isPrime”)。

        if ( ! isPrime )

一般来说,表达式“! Expression”可以对expression的逻辑值求反。因此如果expression为0,则逻辑非运算符将产生1。幵且如果expression的求值结果是非零,逻辑非运算符就会产生0。

另外,还可以使用逻辑非运算符跳过标记的值,例如下面的表达式代码。

        my_move = ! my_move;

上述这种运算符的优先级和一元运算符相同,这表示与所有二元算术运算符和关系运算符相比,它的优先级较高。所以要测试变量x的值是否不小于变量y的值,例如在“! ( x < y )”中必需的有圆括号,功能是确保表达式正确求值。另外,也可以将上述语句等价地表示为:

        x >= y

在Objective-C语言中,通过如下两种内置的特性可以使用Boolean变量的过程变得更加容易。

❑一种是特殊类型BOOL,它可以用于声明值非真即假的变量。类型BOOL其实是用一种称为预处理程序的机制添加的。

❑一种是内置值YES或NO。在程序中使用这些预定义的值可使它们更易于编写和读取。在下面的代码中,使用上述特性重写了前面的程序5-22。

实例文件main.m的具体实现代码如下所示。

        #import <Foundation/Foundation.h>

        int main(int argc, const char * argv[]) {
              @autoreleasepool {
                int     p, d;
                BOOL  isPrime;
                for ( p = 2; p <= 50; ++p ) {
                      isPrime = YES;
                      for ( d = 2; d < p; ++d )
                            if ( p % d == 0 )
                                  isPrime = NO;
                      if ( isPrime == YES )
                            NSLog (@"%i ", p);
                }
              }
              return 0;
        }

执行上述代码可看到输出。