《C Primer Plus》第六章---C控制语句:循环(while for do while 嵌套循环 真值 逗号运算符 复习题编程题)

文章目录

  • C控制语句:循环
    • 本章内容
    • 再探while循环
      • 程序注释
      • C风格读取循环
    • while语句
      • 终止while循环
      • 何时终止循环
      • while:入口条件循环
      • 语法要点
    • 用关系运算符和表达式比较大小
      • 什么是真
      • 其他真值
      • 真值的问题
      • 优先级和关系运算符
    • 不确定循环和计数循环
    • for循环
      • 利用for的灵活性
    • 其他赋值运算符
      • +=、-=、*=、/=、%=
      • 逗号运算符
    • 出口条件循环:do while
    • 如何选择循环
    • 嵌套循环
      • 嵌套变式
    • 数组简介
      • 在for循环中使用数组
    • 使用函数返回值的循环示例
      • 程序分析
      • 使用带返回值的函数
    • 关键概念
    • 本章小结
    • 复习题
    • 编程练习

加粗与下面的点是用来标记我的知识盲区,无特殊含义。

  • 如果scanf转换值之前出问题的情况
  • 伪代码
  • 入口条件的概念
  • 整型溢出的情况复习
  • 跳过某类型输入的用法
  • 浮点数比较
  • 许多经验丰富的程序员在构建比较是否相等的表达式时,都习惯把常量放在左侧。
  • _BOOL类型
  • stdbool.h头文件
  • 不确定循环与计数循环的概念
  • for圆括号中的表达式也叫作控制表达式,它们都是完整表达式,所以每个表达式的副作用(如,递增变量)都发生在对下一个表达式求值之前。
  • for的灵活性
  • 逗号运算符
  • 入口条件循环与出口条件循环
  • 一些较好的编码风格

C控制语句:循环

本章内容

本章介绍以下内容:

  • 关键字——for、while、do while;
  • 运算符——<、>、>=、<=、!=、==、+=、*=、-=、/=、%=;
  • 函数——fabs();
  • C语言有3种循环——for、while、do while;
  • 使用关系运算符构建控制循环的表达式;
  • 其他运算符;
  • 循环常用的数组;
  • 编写有返回值的函数。

一门语言应该提供以下3种形式的程序流:

  • 执行语句序列;
  • 如果满足某些条件就重复执行语句序列(循环)
  • 通过测试选择执行哪一个语句序列(分支)

再探while循环

/* summing.c -- 根据用户键入的整数求和 */
#include 
int main(void)
{
    long num;
    long sum = 0L; /* 把sum初始化为0  */
    int status;
    printf("Please enter an integer to be summed ");
    printf("(q to quit): ");
    status = scanf("%ld", &num);
    while (status == 1) /* == 的意思是“等于”  */
    {
        sum = sum + num;
        printf("Please enter next integer (q to quit): ");
        status = scanf("%ld", &num);
    }
    printf("Those integers sum to %ld.\n", sum);
    return 0;
}

Please enter an integer to be summed (q to quit): 44

Please enter next integer (q to quit): 33

Please enter next integer (q to quit): 88

Please enter next integer (q to quit): 121

Please enter next integer (q to quit): q

Those integers sum to 286.

程序注释

  • ==运算符是C的相等运算符(equality operator),该表达式判断status是否等于1。

  • 不要把status == 1与status = 1混淆,后者是把1赋给status。根据测试条件status == 1,只要status等于1,循环就会重复。每次循环,num的当前值都被加到sum上,这样sum的值始终是当前整数之和。当status的值不为1时,循环结束。然后程序打印sum的最终值。

  • 要让程序正常运行,每次循环都要获取num的一个新值,并重置status。程序利用scanf()的两个不同的特性来完成。首先,使用scanf()读取num的一个新值;然后,检查scanf()的返回值判断是否成功获取值。

  • 第4章中介绍过,scanf()返回成功读取项的数量。如果scanf()成功读取一个整数,就把该数存入num并返回1,随后返回值将被赋给status

  • 如果用户输入的不是数字(如,q),scanf()会读取失败并返回0。此时,status的值就是0,循环结束。

  • 如果scanf()在转换值之前出了问题(例如,检测到文件结尾或遇到硬件问题),会返回一个特殊值EOF(其值通常被定义为-1)。这个值也会引起循环终止。

  • 我们来看看该程序的结构。总结如下:

    把sum初始化为0
    提示用户输入数据
    读取用户输入的数据
    当输入的数据为整数时,
    输入添加给sum,
    提示用户进行输入,
    然后读取下一个输入
    输入完成后,打印sum的值

  • 顺带一提,这叫作伪代码(pseudocode),是一种用简单的句子表示程序思路的方法,它与计算机语言的形式相对应。伪代码有助于设计程序的逻辑。确定程序的逻辑无误之后,再把伪代码翻译成实际的编程代码。使用伪代码的好处之一是,可以把注意力集中在程序的组织和逻辑上,不用在设计程序时还要分心如何用编程语言来表达自己的想法。

  • 总之,因为while循环是入口条件循环,程序在进入循环体之前必须获取输入的数据并检查status的值,所以在while前面要有一个scanf()。要让循环继续执行,在循环内需要一个读取数据的语句,这样程序才能获取下一个status的值,所以在while循环末尾还要有一个scanf(),它为下一次迭代做好了准备。可以把下面的伪代码作为while循环的标准格式:

    获得第1个用于测试的值
    当测试为真时
    处理值
    获取下一个值

C风格读取循环

status = scanf("%ld", &num);
while (status == 1)
{
    /* 循环行为 */  status = scanf("%ld", &num);
}
//可以用这些代码替换:
while (scanf("%ld", &num) == 1)
{ /*循环行为*/
}
  • 第二种形式同时使用scanf()的两种不同的特性。首先,如果函数调用成功,scanf()会把一个值存入num。然后,利用scanf()的返回值(0或1,不是num的值)控制while循环。

  • C的语法特性让你可以用下面的精简版本替换标准版本:

    当获取值和判断值都成功
    处理该值

while语句

  • while循环的通用形式如下:

    while ( expression )

    statement

  • statement部分可以是以分号结尾的简单语句,也可以是用花括号括起来的复合语句。

  • 到目前为止,程序示例中的expression部分都使用关系表达式。也就是说,expression是值之间的比较,可以使用任何表达式。如果expression为真(或者更一般地说,非零),执行statement部分一次,然后再次判断expression。在expression为假(0)之前,循环的判断和执行一直重复进行。每次循环都被称为一次迭代

终止while循环

  • while循环有一点非常重要:在构建while循环时,必须让测试表达式的值有变化,表达式最终要为假。否则,循环就不会终止

  • index = 1;while (index < 5)printf("Good morning!\n");
    
  • 上面的程序段将打印无数次Good morning!。为什么?因为循环中index的值一直都是原来的值1,不曾变过。现在,考虑下面的程序段:

  index = 1;while (--index < 5)printf("Good morning!\n");
  • 这段程序也好不到哪里去。虽然改变了index的值,但是改错了!不过,这个版本至少在index减少到其类型可容纳的最小负值并变成最大正值时会终止循环(第3章3.4.2节中的toobig.c程序解释过,最大正值加1一般会得到一个负值;类似地,最小负值减1一般会得到最大正值)。

何时终止循环

// when.c -- 何时退出循环
#include 
int main(void)
{
    int n = 5;
    while (n < 7) // 第7行
    {
        printf("n = %d\n", n);
        n++;
        // 第10行
        printf("Now n = %d\n", n); // 第11行
    }
    printf("The loop has finished.\n");
    return 0;
}
  • 运行程序清单6.2,输出如下:

    n = 5

    Now n = 6

    n = 6

    Now n = 7

    The loop has finished.

  • 在第2次循环时,变量n在第10行首次获得值7。但是,此时程序并未退出,它结束本次循环(第11行),并在对第7行的测试条件求值时才退出循环(变量n在第1次判断时为5,第2次判断时为6)。

while:入口条件循环

  • while循环是使用入口条件的有条件循环。所谓“有条件”指的是语句部分的执行取决于测试表达式描述的条件,如(index < 5)。该表达式是一个入口条件(entry condition),因为必须满足条件才能进入循环体。

  • 下面的情况中,就不会进入循环体,因为条件一开始就为假:

    index = 10;
    while (index++ < 5)printf("Have a fair day or better.\n");
    

语法要点

  • /* while1.c -- 注意花括号的使用 */
    /* 糟糕的代码创建了一个无限循环 */
    #include 
    int main(void)
    {
        int n = 0;
        while (n < 3)printf("n is %d\n", n);
        n++;
        printf("That's all this program does\n");
        return 0;
    }
    
  • 虽然程序中缩进了n++;这条语句,但是并未把它和上一条语句括在花括号内。因此,只有直接跟在测试条件后面的一条语句是循环的一部分。变量n的值不会改变,条件n < 3一直为真。该循环会一直打印n is 0,除非强行关闭程序。这是一个无限循环(infinite loop)的例子,没有外部干涉就不会退出。

  • 该语句从while开始执行,到第1个分号结束。在使用了复合语句的情况下,到右花括号结束。

  • /* while2.c -- 注意分号的位置 */
    #include 
    int main(void)
    {
        int n = 0;
        while (n++ < 3)
            ;                   /* 第7行 */
        printf("n is %d\n", n); /* 第8行 */
        printf("That's all this program does.\n");
        return 0;
    }
    

    n is 4

    That’s all this program does.

  • 如前所述,循环在执行完测试条件后面的第1条语句(简单语句或复合语句)后进入下一轮迭代,直到测试条件为假才会结束。该程序中第7行的测试条件后面直接跟着一个分号,循环在此进入下一轮迭代,因为单独一个分号被视为一条语句。虽然n的值在每次循环时都递增1,但是第8行的语句不是循环的一部分,因此只会打印一次循环结束后的n值。

  • 在该例中,测试条件后面的单独分号是空语句(null statement),它什么也不做。

  • 假设你想跳过输入到达第1个既不是空白字符也不是数字的位置,可以这样写:

  • while (scanf("%d", &num) == 1); /* 跳过整数输入 */
    
  • 注意,为了提高代码的可读性,应该让这个分号独占一行,不要直接把它放在测试表达式同行。这样做一方面让读者更容易看到空语句,一方面也提醒自己和读者空语句是有意而为之。

  • 处理这种情况更好的方法是使用下一章介绍的continue语句。

用关系运算符和表达式比较大小

  • while循环经常依赖测试表达式作比较,这样的表达式被称为关系表达式

  • 出现在关系表达式中间的运算符叫作关系运算符

  • 该表也涵盖了所有的数值关系(数字之间的关系再复杂也没有人与人之间的关系复杂)。

  • 《C Primer Plus》第六章---C控制语句:循环(while for do while 嵌套循环 真值 逗号运算符 复习题编程题)_第1张图片

  • while (number < 6)
    {
        printf("Your number is too small.\n");
        scanf("%d", &number);
    }
    while (ch != '$')
    {
        count++;
        scanf("%c", &ch);
    }
    while (scanf("%f", &num) == 1)
        sum = sum + num;
    
  • 第2个while语句的关系表达式还可用于比较字符。比较时使用的是机器字符码(假定为ASCII)。但是,不能用关系运算符比较字符串。

  • 虽然关系运算符也可用来比较浮点数,但是要注意:比较浮点数时,尽量只使用<和>。因为浮点数的舍入误差会导致在逻辑上应该相等的两数却不相等。

  • 使用fabs()函数(声明在math.h头文件中)可以方便地比较浮点数,该函数返回一个浮点值的绝对值(即,没有代数符号的值)。

  • // cmpflt.c -- 浮点数比较
    #include 
    #include 
    int main(void)
    {
        const double ANSWER = 3.14159;
        double response;
        printf("What is the value of pi?\n");
        scanf("%lf", &response);
        while (fabs(response - ANSWER) > 0.0001)
        {
            printf("Try again!\n");
    
            scanf("%lf", &response);
        }
        printf("Close enough!\n");
        return 0;
    }
    

    What is the value of pi?3.14

    Try again!3.1416

    Close enough!

  • 循环会一直提示用户继续输入,除非用户输入的值与正确值之间相差不大于0.0001:

什么是真

  • /* t_and_f.c -- C中的真和假的值 */
    #include 
    int main(void)
    {
        int true_val, false_val;
        true_val = (10 > 2);   // 关系为真的值
        false_val = (10 == 2); // 关系为假的值
        printf("true = %d; false = %d \n", true_val, false_val);
        return 0;
    }
    

    true = 1; false = 0

  • 对C而言,表达式为真的值是1,表达式为假的值是0。

其他真值

  • // truth.c -- 哪些值为真
    #include 
    int main(void)
    {
        int n = 3;
        while (n)
            printf("%2d is true\n", n--);
        printf("%2d is false\n", n);
        n = -3;
        while (n)
            printf("%2d is true\n", n++);
        printf("%2d is false\n", n);
        return 0;
    }
    

    3 is true

    2 is true

    1 is true

    0 is false

    -3 is true

    -2 is true

    -1 is true

    0 is false

  • 一般而言,所有的非零值都视为真,只有0被视为假。在C中,真的概念还真宽!

  • 许多C程序员都会很好地利用测试条件的这一特性。例如,用while (goats)替换while (goats != 0),因为表达式goats != 0和goats都只有在goats的值为0时才为0或假。第1种形式(while (goats != 0))对初学者而言可能比较清楚,但是第2种形式(while (goats))才是C程序员最常用的。要想成为一名C程序员,应该多熟悉while (goats)这种形式。

真值的问题

  • // trouble.c -- 误用=会导致无限循环
    #include 
    int main(void)
    {
        long num;
        long sum = 0L;
        int status;
        printf("Please enter an integer to be summed ");
        printf("(q to quit): ");
        status = scanf("%ld", &num);
        while (status = 1)
        {
            sum = sum + num;
            printf("Please enter next integer (q to quit): ");
            status = scanf("%ld", &num);
        }
        printf("Those integers sum to %ld.\n", sum);
        return 0;
    }
    
  • 这个麻烦的程序示例改动了while循环的测试条件,把status == 1替换成status = 1。后者是一个赋值表达式语句,所以status的值为1。

  • 这里,while (status = 1)实际上相当于while (1),也就是说,循环不会退出。虽然用户输入q,status被设置为0,但是循环的测试条件把status又重置为1,进入了下一次迭代。

《C Primer Plus》第六章---C控制语句:循环(while for do while 嵌套循环 真值 逗号运算符 复习题编程题)_第2张图片

  • 要注意使用正确的运算符。编译器不会检查出你使用了错误的形式,得出也不是预期的结果(误用=的人实在太多了,以至于现在大多数编译器都会给出警告,提醒用户是否要这样做)。如果待比较的一个值是常量,可以把该常量放在左侧有助于编译器捕获错误:

    5 = canoes ←语法错误

    5 == canoes ←检查canoes的值是否为5

  • 许多经验丰富的程序员在构建比较是否相等的表达式时,都习惯把常量放在左侧。

  • C语言中,一直用int类型的变量表示真/假值。C99专门针对这种类型的变量新增了_Bool类型。

  • _Bool是C语言中布尔变量的类型名。_Bool类型的变量只能存储1(真)或0(假)。如果把其他非零数值赋给_Bool类型的变量,该变量会被设置为1。

  • 把int类型的变量status替换为_Bool类型的变量input_is_good。给布尔变量取一个能表示真或假值的变量名是一种常见的做法。

  • // boolean.c -- 使用_Bool类型的变量 variable
    #include 
    int main(void)
    {
        long num;
        long sum = 0L;
    
        _Bool input_is_good;
        printf("Please enter an integer to be summed ");
    
        printf("(q to quit): ");
        input_is_good = (scanf("%ld", &num) == 1);
        while (input_is_good)
        {
            sum = sum + num;
    
            printf("Please enter next integer (q to quit): ");
    
            input_is_good = (scanf("%ld", &num) == 1);
        }
        printf("Those integers sum to %ld.\n", sum);
        return 0;
    }
    
  • 注意程序中把比较的结果赋值给_Bool类型的变量input_is_good:

    input_is_good = (scanf("%ld", &num) == 1);

  • 这样做没问题,因为==运算符返回的值不是1就是0。顺带一提,从优先级方面考虑的话,并不需要用圆括号把scanf("%ld", &num) == 1括起来。但是,这样做可以提高代码可读性。

  • C99提供了stdbool.h头文件,该头文件让bool成为_Bool的别名,而且还把true和false分别定义为1和0的符号常量。包含该头文件后,写出的代码可以与C++兼容,因为C++把bool、true和false定义为关键字。

优先级和关系运算符

  • 关系运算符的优先级比算术运算符(包括+和-)低,比赋值运算符高。
  • 关系运算符之间有两种不同的优先级。
    高优先级组: < <= > >=
    低优先级组: == !=

《C Primer Plus》第六章---C控制语句:循环(while for do while 嵌套循环 真值 逗号运算符 复习题编程题)_第3张图片

不确定循环和计数循环

  • 另外,还有一类是计数循环(counting loop)。这类循环在执行循环之前就知道要重复执行多少次。

  • 一些while循环是不确定循环(indefinite loop)。所谓不确定循环,指在测试表达式为假之前,预先不知道要执行多少次循环。

  • 在创建一个重复执行固定次数的循环中涉及了3个行为:

    • 必须初始化计数器;
    • 计数器与有限的值作比较;
    • 每次循环时递增计数器。
  • // sweetie1.c -- 一个计数循环
    #include 
    int main(void)
    {
        const int NUMBER = 22;
        int count = 1;          // 初始化
        while (count <= NUMBER) // 测试
        {
            printf("Be my Valentine!\n"); // 行为
            count++;
            // 更新计数
        }
        return 0;
    }
    
  • while循环的测试条件执行比较,递增运算符执行递增。程序清单6.10中,递增发生在循环的末尾,这可以防止不小心漏掉递增。因此,这样做比将测试和更新组合放在一起(即使用count++ <= NUMBER)要好,但是计数器的初始化放在循环外,就有可能忘记初始化。实践告诉我们可能会发生的事情终究会发生,所以我们来学习另一种控制语句,可以避免这些问题。

for循环

  • // sweetie2.c -- 使用for循环的计数循环
    #include 
    int main(void)
    {
        const int NUMBER = 22;
        int count;
        for (count = 1; count <= NUMBER; count++)
            printf("Be my Valentine!\n");
        return 0;
    }
    
  • 关键字for后面的圆括号中有3个表达式,分别用两个分号隔开。

  • 第1个表达式是初始化,只会在for循环开始时执行一次。

  • 第2个表达式是测试条件,在执行循环之前对表达式求值。如果表达式为假(本例中,count大于NUMBER时),循环结束。

  • 第3个表达式执行更新,在每次循环结束时求值。

  • 程序清单6.10用这个表达式递增count的值,更新计数。完整的for语句还包括后面的简单语句或复合语句。

  • for圆括号中的表达式也叫作控制表达式,它们都是完整表达式,所以每个表达式的副作用(如,递增变量)都发生在对下一个表达式求值之前。

  • /* for_cube.c -- 使用for循环创建一个立方表 */
    #include 
    int main(void)
    {
        int num;
        printf("  n  n cubed\n");
        for (num = 1; num <= 6; num++)
            printf("%5d %5d\n", num, num * num * num);
        return 0;
    }
    

    n n cubed

    1 1

    2 8

    3 27

    4 64

    5 125

    6 216

利用for的灵活性

  • 可以使用递减运算符来递减计数器:

  • 可以让计数器递增2、10等:

  • /* for_13s.c */
    #include 
    int main(void)
    {
        int n; // 从2开始,每次递增13
        for (n = 2; n < 60; n = n + 13)
            printf("%d \n", n);
        return 0;
    }
    
  • 可以用字符代替数字计数:

  • /* for_char.c */
    #include 
    int main(void)
    {
        char ch;
        for (ch = 'a'; ch <= 'z'; ch++)
            printf("The ASCII value for %c is %d.\n", ch, ch);
        return 0;
    }
    
  • 除了测试迭代次数外,还可以测试其他条件。在for_cube程序中,可以把:

    for (num = 1; num <= 6; num++)
    替换成:
    for (num = 1; num*num*num <= 216; num++)

  • 可以让递增的量几何增长,而不是算术增长。也就是说,每次都乘上而不是加上一个固定的量:

  • /* for_geo.c */
    #include 
    int main(void)
    {
        double debt;
        for (debt = 100.0; debt < 150.0; debt = debt * 1.1)
            printf("Your debt is now $%.2f.\n", debt);
        return 0;
    }
    
  • 第3个表达式可以使用任意合法的表达式。无论是什么表达式,每次迭代都会更新该表达式的值。

  • /* for_wild.c */
    #include 
    int main(void)
    {
        int x;
        int y = 55;
        for (x = 1; y <= 75; y = (++x * 5) + 50)
            printf("%10d %10d\n", x, y);
        return 0;
    }
    
  • 注意,虽然该例可以正常运行,但是编程风格不太好。如果不在更新部分加入代数计算,程序会更加清楚

  • 可以省略一个或多个表达式(但是不能省略分号),只要在循环中包含能结束循环的语句即可。

  • /* for_none.c */
    #include 
    int main(void)
    {
        int ans, n;
        ans = 2;
        for (n = 3; ans <= 25;)
            ans = ans * n;
        printf("n = %d; ans = %d.\n", n, ans);
        return 0;
    }
    
  • 顺带一提,省略第2个表达式被视为真

  • 第1个表达式不一定是给变量赋初值,也可以使用printf()。记住,在执行循环的其他部分之前,只对第1个表达式求值一次或执行一次。

  • /* for_show.c */
    #include 
    int main(void)
    {
        int num = 0;
        for (printf("Keep entering numbers!\n"); num != 6;)
            scanf("%d", &num);
        printf("That's the one I want!\n");
        return 0;
    }
    
  • 循环体中的行为可以改变循环头中的表达式。例如,假设创建了下面的循环:

  • for (n = 1; n < 10000; n = n + delta)
    如果程序经过几次迭代后发现delta太小或太大,循环中的if语句(详见第7章)可以改变delta的大小。在交互式程序中,用户可以在循环运行时才改变delta的值。这样做也有危险的一面,例如,把delta设置为0就没用了。
    总而言之,可以自己决定如何使用for循环头中的表达式,这使得在执行固定次数的循环外,还可以做更多的事情。

  • for ( initialize; test; update )
    statement

  • for语句使用3个表达式控制循环过程,分别用分号隔开。initialize表达式在执行for语句之前只执行一次;然后对test表达式求值,如果表达式为真(或非零),执行循环一次;接着对update表达式求值,并再次检查test表达式。for语句是一种入口条件循环,即在执行循环之前就决定了是否执行循环。因此,for循环可能一次都不执行。statement部分可以是一条简单语句或复合语句。

其他赋值运算符

+=、-=、*=、/=、%=

  • scores += 20 与 scores = scores + 20 相同

    dimes -= 2 与 dimes = dimes - 2 相同

    bunnies *= 2 与 bunnies = bunnies * 2 相同

    time /= 2.73 与 time = time / 2.73 相同

    reduce %= 3 与 reduce = reduce % 3 相同

    x *= 3 * y + 12 与 x = x * (3 * y + 12) 相同

  • 以上提到的赋值运算符与=的优先级相同
  • 它们让代码更紧凑,而且与一般形式相比,组合形式的赋值运算符生成的机器代码更高效。当需要在for循环中塞进一些复杂的表达式时,这些组合的赋值运算符特别有用。

逗号运算符

  • 逗号运算符扩展了for循环的灵活性,以便在循环头中包含更多的表达式。

  • // postage.c -- 一类邮资
    #include 
    int main(void)
    {
        const int FIRST_OZ = 46; // 2013邮资
        const int NEXT_OZ = 20;  // 2013邮资
        int ounces, cost;
        printf(" ounces cost\n");
        for (ounces = 1, cost = FIRST_OZ; ounces <= 16; ounces++, cost += NEXT_OZ)
            printf("%5d  $%4.2f\n", ounces, cost / 100.0);
        return 0;
    }
    
    
  • 该程序在初始化表达式和更新表达式中使用了逗号运算符。初始化表达式中的逗号使ounces和cost都进行了初始化,更新表达式中的逗号使每次迭代ounces递增1、cost递增20(NEXT_OZ的值是20)。绝大多数计算都在for循环头中进行

  • 逗号运算符并不局限于在for循环中使用,但是这是它最常用的地方。

  • 逗号运算符有两个其他性质。首先,它保证了被它分隔的表达式从左往右求值(换言之,逗号是一个序列点,所以逗号左侧项的所有副作用都在程序执行逗号右侧项之前发生)。因此,ounces在cost之前被初始化。在该例中,顺序并不重要,但是如果cost的表达式中包含了ounces时,顺序就很重要。

  • ounces++, cost = ounces * FIRST_OZ

  • 在该表达式中,先递增ounce,然后在第2个子表达式中使用ounce的新值。作为序列点的逗号保证了左侧子表达式的副作用在对右侧子表达式求值之前发生。

  • 其次,整个逗号表达式的值是右侧项的值。例如,下面语句

    x = (y = 3, (z = ++y + 2) + 5);

  • 的效果是:先把3赋给y,递增y为4,然后把4加2之和(6)赋给z,接着加上5,最后把结果11赋给x。 至于为什么有人编写这样的代码,在此不做评价。

  • 另一方面,假设在写数字时不小心输入了逗号:

    houseprice = 249,500;

  • 这不是语法错误,C编译器会将其解释为一个逗号表达式,即houseprice = 249是逗号左侧的子表达式,500是右侧的子表达式。因此,整个逗号表达式的值是逗号右侧表达式的值,而且左侧的赋值表达式把249赋给变量houseprice。

  • 因此,这与下面代码的效果相同:

    houseprice = 249;

    500;

  • 记住,任何表达式后面加上一个分号就成了表达式语句。所以,500;也是一条语句,但是什么也不做。
    另外,下面的语句

    houseprice = (249,500);

  • 赋给houseprice的值是逗号右侧子表达式的值,即500。

  • 逗号也可用作分隔符。在下面语句中的逗号都是分隔符,不是逗号运算符:

    char ch, date;

    printf("%d %d\n", chimps, chumps);

出口条件循环:do while

  • while循环和for循环都是入口条件循环,即在循环的每次迭代之前检查测试条件,所以有可能根本不执行循环体中的内容。C语言还有出口条件循环(exit-condition loop),即在循环的每次迭代之后检查测试条件,这保证了至少执行循环体中的内容一次。这种循环被称为do while循环。

  • /* do_while.c -- 出口条件循环 */
    // 在用户输入13之前不断提示用户输入数字。
    #include 
    int main(void)
    {
        const int secret_code = 13;
        int code_entered;
        do
        {
            printf("To enter the triskaidekaphobia therapy club,\n");
            printf("please enter the secret code number: ");
            scanf("%d", &code_entered);
    
        } while (code_entered != secret_code);
        printf("Congratulations! You are cured!\n");
        return 0;
    }
    
    /* entry.c -- 出口条件循环 */
    //等价于
    #include 
    int main(void)
    {
        const int secret_code = 13;
        int code_entered;
        printf("To enter the triskaidekaphobia therapy club,\n");
        printf("please enter the secret code number: ");
        scanf("%d", &code_entered);
        while (code_entered != secret_code)
        {
            printf("To enter the triskaidekaphobia therapy club,\n");
            printf("please enter the secret code number: ");
            scanf("%d", &code_entered);
        }
        printf("Congratulations! You are cured!\n");
        return 0;
    }
    
  • do statement
    while ( expression );
    statement可以是一条简单语句或复合语句。注意,do while循环以分号结尾

  • do while循环适用于那些至少要迭代一次的循环。例如,下面是一个包含do while循环的密码程序伪代码:

    do

    {

    提示用户输入密码

    读取用户输入的密码

    } while (用户输入的密码不等于密码);

如何选择循环

  • 如何选择使用哪一种循环?首先,确定是需要入口条件循环还是出口条件循环。通常,入口条件循环用得比较多,有几个原因。其一,一般原则是在执行循环之前测试条件比较好。其二,测试放在循环的开头,程序的可读性更高。另外,在许多应用中,要求在一开始不满足测试条件时就直接跳过整个循环。

  • 那么,假设需要一个入口条件循环,用for循环还是while循环?这取决于个人喜好,因为二者皆可。要让for循环看起来像while循环,可以省略第1个和第3个表达式。例如:

    for ( ; test ; )

    与下面的while效果相同:

    while ( test )

    要让while循环看起来像for循环,可以在while循环的前面初始化变量,并在while循环体中包含更新语句。例如:

    初始化;

    while ( 测试 )

    {

    其他语句

    更新语句

    }

    与下面的for循环效果相同:

    for ( 初始化 ;测试 ; 更新 )

    其他语句

    一般而言,当循环涉及初始化和更新变量时,用for循环比较合适,而在其他情况下用while循环更好。对于下面这种条件,用while循环就很合适:

    while (scanf("%ld", &num) == 1)

    对于涉及索引计数的循环,用for循环更适合。例如:

    for (count = 1; count <= 100; count++)

嵌套循环

  • 嵌套循环(nested loop)指在一个循环内包含另一个循环。嵌套循环常用于按行和列显示数据,也就是说,一个循环处理一行中的所有列,另一个循环处理所有的行。

  • /* rows1.c -- 使用嵌套循环 */
    #include 
    #define ROWS 6
    #define CHARS 10
    int main(void)
    {
        int row;
        char ch;
        for (row = 0; row < ROWS; row++) /* 第10行 */
    
        {
            for (ch = 'A'; ch < ('A' + CHARS); ch++) /* 第12行 */
    
                printf("%c", ch);
            printf("\n");
        }
        return 0;
    }
    
  • 第10行开始的for循环被称为外层循环(outer loop),

  • 第12行开始的for循环被称为内层循环(inner loop)。

  • 外层循环从row为0开始循环,到row为6时结束。因此,外层循环要执行6次,row的值从0变为5。每次迭代要执行的第1条语句是内层的for循环,该循环要执行10次,在同一行打印字符A~J;

  • 第2条语句是外层循环的printf("\n");,该语句的效果是另起一行,这样在下一次运行内层循环时,将在下一行打印的字符。

嵌套变式

  • 上一个实例中,内层循环和外层循环所做的事情相同。可以通过外层循环控制内层循环,在每次外层循环迭代时内层循环完成不同的任务。

  • // rows2.c -- 依赖外部循环的嵌套循环
    #include int main(void)
    {
        const int ROWS = 6;
        const int CHARS = 6;
        int row;
        char ch;
        for (row = 0; row < ROWS; row++)
        {
            for (ch = ('A' + row); ch < ('A' + CHARS); ch++)
                printf("%c", ch);
            printf("\n");
        }
        return 0;
    }
    

    ABCDEF

    BCDEF

    CDEF

    DEF

    EF

    F

数组简介

  • 数组(array)是按顺序存储的一系列类型相同的值,如10个char类型的字符或15个int类型的值。整个数组有一个数组名,通过整数下标访问数组中单独的项或元素(element)。例如,以下声明:

    float debts[20];

  • 注意,数组元素的编号从0开始,不是从1开始。

  • 实际上,使用数组元素和使用同类型的变量一样。例如,可以这样把值读入指定的元素中:

    scanf("%f", &debts[4]); // 把一个值读入数组的第5个元素

  • 这里要注意一个潜在的陷阱:考虑到影响执行的速度,C编译器不会检查数组的下标是否正确。 下面的代码,都不正确:

    debts[20] = 88.32; // 该数组元素不存在

    debts[33] = 828.12; // 该数组元素不存在

  • 编译器不会查找这样的错误。当运行程序时,这会导致数据被放置在已被其他数据占用的地方,可能会破坏程序的结果甚至导致程序异常中断。

    int nannies[22];  /* 可存储22个int类型整数的数组 */char actors[26];  /* 可存储26个字符的数组 */long big[500];   /* 可存储500个long类型整数的数组 */
    
  • 用于识别数组元素的数字被称为下标(subscript)、索引(indice)或偏移量(offset)。下标必须是整数,而且要从0开始计数。数组的元素被依次存储在内存中相邻的位置

在for循环中使用数组

  • // scores_in.c -- 使用循环处理数组
    #include 
    #define SIZE 10
    #define PAR 72
    int main(void)
    {
        int index, score[SIZE];
        int sum = 0;
        float average;
        printf("Enter %d golf scores:\n", SIZE);
        for (index = 0; index < SIZE; index++)
            scanf("%d", &score[index]); // 读取10个分数
        printf("The scores read in are as follows:\n");
    
        for (index = 0; index < SIZE; index++)
            printf("%5d", score[index]); // 验证输入
        printf("\n");
        for (index = 0; index < SIZE; index++)
            sum += score[index];     // 求总分数
        average = (float)sum / SIZE; // 求平均分
        printf("Sum of scores = %d, average = %.2f\n", sum, average);
    
        printf("That's a handicap of %.0f.\n", average - PAR);
        return 0;
    }
    

    Enter 10 golf scores:99 95 109 105 10096 98 93 99 97 98

    The scores read in are as follows:

    99 95 109 105 100 96 98 93 99 97

    Sum of scores = 991, average = 99.10

    That’s a handicap of 27.

  • 首先,注意程序示例虽然输入了11个数字,但是只读入了10个数字,因为循环只读了10个值。由于scanf()会跳过空白字符,所以可以在一行输入10个数字,也可以每行只输入一个数字,或者像本例这样混合使用空格和换行符隔开每个数字

  • for循环提供了一个简单直接的方法来使用数组下标。注意,int类型数组元素的用法与int类型变量的用法类似。要读取int类型变量fue,应这样写:scanf("%d", &fue)。程序清单6.19中要读取int类型的元素score[index],所以这样写scanf("%d", &score[index])。

  • 该程序示例演示了一些较好的编程风格。

  • 第一,用#define指令创建的明示常量(SIZE)来指定数组的大小。这样就可以在定义数组和设置循环边界时使用该明示常量。如果以后要扩展程序处理20个分数,只需简单地把SIZE重新定义为20即可,不用逐一修改程序中使用了数组大小的每一处。

  • 第二,下面的代码可以很方便地处理一个大小为SIZE的数组:
    for (index = 0; index < SIZE; index++)

  • 设置正确的数组边界很重要。第1个元素的下标是0,因此循环开始时把index设置为0。因为从0开始编号,所以数组中最后一个元素的下标是SIZE - 1。也就是说,第10个元素是score[9]。通过测试条件index < SIZE来控制循环中使用的最后一个index的值是SIZE - 1。

  • 第三,程序能重复显示刚读入的数据。这是很好的编程习惯,有助于确保程序处理的数据与期望相符。

  • 最后,注意该程序使用了3个独立的for循环。这是否必要?是否可以将其合并成一个循环?当然可以,读者可以动手试试,合并后的程序显得更加紧凑。

  • 但是,调整时要注意遵循模块化(modularity)的原则。模块化隐含的思想是:应该把程序划分为一些独立的单元,每个单元执行一个任务。这样做提高了程序的可读性。也许更重要的是,模块化使程序的不同部分彼此独立,方便后续更新或修改程序。在掌握如何使用函数后,可以把每个执行任务的单元放进函数中,提高程序的模块化。

使用函数返回值的循环示例

  • 本章最后一个程序示例要用一个函数计算数的整数次幂(math.h库提供了一个更强大幂函数pow(),可以使用浮点指数)。该示例有3个主要任务:设计算法、在函数中表示算法并返回计算结果、提供一个测试函数的便利方法。

  • 首先分析算法。为简化函数,我们规定该函数只处理正整数的幂。这样,把n与n相乘p次便可计算n的p次幂。这里自然会用到循环。先把变量pow设置为1,然后将其反复乘以n:

    for(i = 1; i <= p; i++)

    pow *= n;

  • 回忆一下,*=运算符把左侧的项乘以右侧的项,再把乘积赋给左侧的项。第1次循环后,pow的值是1乘以n,即n;第2次循环后,pow的值是上一次的值(n)乘以n,即n的平方;以此类推。这种情况使用for循环很合适,因为在执行循环之前已预先知道了迭代的次数

  • 现在算法已确定,接下来要决定使用何种数据类型。指数p是整数,其类型应该是int。为了扩大n及其幂的范围,n和pow的类型都是double。

  • 接下来,考虑如何把以上内容用函数来实现。要使用两个参数(分别是double类型和int类型)才能把所需的信息传递给函数,并指定求哪个数的多少次幂。而且,函数要返回一个值。如何把函数的返回值返回给主调函数?编写一个有返回值的函数,要完成以下内容:

    • 定义函数时,确定函数的返回类型;
    • 使用关键字return表明待返回的值。
  • 要声明函数的返回类型,在函数名前写出类型即可,就像声明一个变量那样。关键字return表明该函数将把它后面的值返回给主调函数。

  • 主调函数中,可以把返回值赋给另一个变量、作为表达式中的值、作为另一个函数的参数(如,printf("%f", power(6.28, 3)),或者忽略它。

  • // power.c -- 计算数的整数幂
    #include 
    double power(double n, int p); // ANSI函数原型
    int main(void)
    {
        double x, xpow;
        int exp;
        printf("Enter a number and the positive integer power");
        printf(" to which\nthe number will be raised. Enter q");
        printf(" to quit.\n");
        while (scanf("%lf%d", &x, &exp) == 2)
        {
            xpow = power(x, exp); // 函数调用
            printf("%.3g to the power %d is %.5g\n", x, exp, xpow);
    
            printf("Enter next pair of numbers or q to quit.\n");
        }
        printf("Hope you enjoyed this power trip -- bye!\n");
        return 0;
    }
    double power(double n, int p) // 函数定义
    {
        double pow = 1;
        int i;
        for (i = 1; i <= p; i++)
            pow *= n;
        return pow; // 返回pow的值
    }
    

    Enter a number and the positive integer power to which

    the number will be raised. Enter q to quit.1.2 12

    1.2 to the power 12 is 8.9161

    Enter next pair of numbers or q to quit.216

    2 to the power 16 is 65536

    Enter next pair of numbers or q to quit.q

    Hope you enjoyed this power trip – bye!

程序分析

  • 该程序示例中的main()是一个驱动程序(driver),即被设计用来测试函数的小程序。

  • power()函数在程序中出现了3次。首次出现是:

    double power(double n, int p); // ANSI函数原型

  • 这是power()函数的原型,它声明程序将使用一个名为power()的函数。开头的关键字double表明power()函数返回一个double类型的值。编译器要知道power()函数返回值的类型,才能知道有多少字节的数据,以及如何解释它们。这就是为什么必须声明函数的原因。圆括号中的double n, int p表示power()函数的两个参数。第1个参数应该是double类型的值,第2个参数应该是int类型的值。

  • 第2次出现是:

    xpow = power(x,exp); // 函数调用

  • 程序调用power(),把两个值传递给它。该函数计算x的exp次幂,并把计算结果返回给主调函数。在主调函数中,返回值将被赋给变量xpow。

  • 第3次出现是:

    double power(double n, int p) // 函数定义

  • 这里,power()有两个形参,一个是double类型,一个是int类型,分别由变量n和变量p表示。注意,函数定义的末尾没有分号,而函数原型的末尾有分号。在函数头后面花括号中的内容,就是power()完成任务的代码。

使用带返回值的函数

  • 声明函数、调用函数、定义函数、使用关键字return,都是定义和使用带返回值函数的基本要素。
  • 编译器在程序中首次遇到power()时,需要知道power()的返回类型。此时,编译器尚未执行到power()的定义,并不知道函数定义中的返回类型是double。因此,必须通过前置声明(forward declaration)预先说明函数的返回类型。前置声明告诉编译器,power()定义在别处,其返回类型为double。如果把power()函数的定义置于main()的文件顶部,就可以省略前置声明,因为编译器在执行到main()之前已经知道power()的所有信息。但是,这不是C的标准风格。因为main()通常只提供整个程序的框架,最好把main()放在所有函数定义的前面。另外,通常把函数放在其他文件中,所以前置声明必不可少。
  • 为什么不用声明scanf()函数就可以使用它?其实,你已经声明了。stdio.h头文件中包含了scanf()、printf()和其他I/O函数的原型。scanf()函数的原型表明,它返回的类型是int。

关键概念

  • 创建循环时,要特别注意以下3个方面:

    • 注意循环的测试条件要能使循环结束;
    • 确保循环测试中的值在首次使用之前已初始化;
    • 确保循环在每次迭代都更新测试的值。
  • C通过求值来处理测试条件,结果为0表示假,非0表示真。带关系运算符的表达式常用于循环测试,它们有些特殊。如果关系表达式为真,其值为1;如果为假,其值为0。这与新类型_Bool的值保持一致。

  • 数组由相邻的内存位置组成,只存储相同类型的数据。记住,数组元素的编号从0开始,所有数组最后一个元素的下标一定比元素数目少1。C编译器不会检查数组下标值是否有效,自己要多留心。

  • 使用函数涉及3个步骤:

    • 通过函数原型声明函数;
    • 在程序中通过函数调用使用函数;
    • 定义函数。
  • 函数原型是为了方便编译器查看程序中使用的函数是否正确,函数定义描述了函数如何工作。现代的编程习惯是把程序要素分为接口部分和实现部分,例如函数原型和函数定义。接口部分描述了如何使用一个特性,也就是函数原型所做的;实现部分描述了具体的行为,这正是函数定义所做的。

本章小结

  • 本章的主题是程序控制。C语言为实现结构化的程序提供了许多工具。while语句和for语句提供了入口条件循环。for语句特别适用于需要初始化和更新的循环。使用逗号运算符可以在for循环中初始化和更新多个变量。有些场合也需要使用出口条件循环,C为此提供了do while语句。
  • 典型的while循环设计的伪代码如下:
  • 获得初值
    while (值满足测试条件)
    {
    处理该值
    获取下一个值
    }
    for循环也可以完成相同的任务
  • for (获得初值; 值满足测试条件; 获得下一个值)
    处理该值
  • 这些循环都使用测试条件来判断是否继续执行下一次迭代。一般而言,如果对测试表达式求值为非0,则继续执行循环;否则,结束循环。
  • 通常,测试条件都是关系表达式(由关系运算符和表达式构成)。表达式的关系为真,则表达式的值为1;如果关系为假,则表达式的值为0。C99新增了_Bool类型,该类型的变量只能存储1或0,分别表示真或假。
  • 除了关系运算符,本章还介绍了其他的组合赋值运算符,如+=或*=。这些运算符通过对其左侧运算对象执行算术运算来修改它的值。
  • 接下来还简单地介绍了数组。声明数组时,方括号中的值指明了该数组的元素个数。数组的第1个元素编号为0,第2个元素编号为1,以此类推。例如,以下声明:
  • double hippos[20];
  • 创建了一个有20个元素的数组hippos,其元素从hippos[0]~hippos[19]。利用循环可以很方便地操控数组的下标。
  • 最后,本章演示了如何编写和使用带返回值的函数。

复习题

  1. 写出执行完下列各行后quack的值是多少。后5行中使用的是前一行生成的quack的值。

    int quack = 2;

    quack += 5;

    quack *= 10;

    quack -= 6;

    quack /= 8;

    quack %= 3;

  2. 假设value是int类型,下面循环的输出是什么?

    for ( value = 36; value > 0; value /= 2)

    ​ printf("%3d", value);

    如果value是double类型,会出现什么问题?

  3. 用代码表示以下测试条件:

    a.x大于5
    b.scanf()读取一个名为x的double类型值且失败
    c.x 的值等于5

  4. 用代码表示以下测试条件:

    a.scanf()成功读入一个整数
    b.x 不等于5
    c.x 大于或等于20

  5. 下面的程序有点问题,请找出问题所在。

    #include 
    int main(void)
    {                                     /* 第3行 */
        int i, j, list(10);               /* 第4行 */
        for (i = 1, i <= 10, i++)         /* 第6行 */
        {                                 /* 第7行 */
            list[i] = 2 * i + 3;          /* 第8行 */
            for (j = 1, j > = i, j++)     /* 第9行 */printf(" %d", list[j]); /* 第10行 */
            printf("\n");                 /* 第11行 */
    
        } /* 第12行 */
    }
    
  6. 编写一个程序打印下面的图案,要求使用嵌套循环:

    $$$$$$$$

    $$$$$$$$

    $$$$$$$$

    $$$$$$$$

  7. 下面的程序各打印什么内容?

    a.
    #include

    int main(void)

    {

    int i = 0;

    while (++i < 4)

    ​ printf("Hi! ");

    do

    ​ printf("Bye! ");

    while (i++ < 8);

    return 0;

    }
    b.
    #include

    int main(void)

    {

    int i;

    char ch;

    for (i = 0, ch = ‘A’; i < 4; i++, ch += 2 * i)

    ​ printf("%c", ch);

    return 0;

    }

  8. 假设用户输入的是Go west, young man!,下面各程序的输出是什么?(在ASCII码中,!紧跟在空格字符后面)

    a.#include

    int main(void)

    {

    char ch;

    scanf("%c", &ch);

    while (ch != ‘g’)

    {

    ​ printf("%c", ch);

    ​ scanf("%c", &ch);

    }

    return 0;

    }

    b.#include

    int main(void)

    {

    char ch;

    scanf("%c", &ch);

    while (ch != ‘g’)

    {

    ​ printf("%c", ++ch);

    ​ scanf("%c", &ch);

    }

    return 0;

    }

    c.#include

    int main(void)

    {

    char ch;

    do

    {

    ​ scanf("%c", &ch);

    ​ printf("%c", ch);

    } while (ch != ‘g’);

    return 0;

    }

    d.#include

    int main(void)

    {

    char ch;

    scanf("%c", &ch);

    for (ch = ‘$’; ch != ‘g’;

    ​ scanf("%c", &ch))

    ​ printf("%c", ch);

    return 0;

    }

  9. 下面的程序打印什么内容?

    #include 
    int main(void)
    {
        int n, m;
        n = 30;
        while (++n <= 33)
            printf("%d|", n);
        n = 30;
        do
            printf("%d|", n);
        while (++n <= 33);
        printf("\n***\n");
        for (n = 1; n * n < 200; n += 4)
            printf("%d\n", n);
        printf("\n***\n");
        for (n = 2, m = 6; n < m; n *= 2, m += 2)
            printf("%d %d\n", n, m);
        printf("\n***\n");
        for (n = 5; n > 0; n--)
        {
            for (m = 0; m <= n; m++)
                printf("=");
            printf("\n");
        }
        return 0;
    }
    
  10. 考虑下面的声明:

double mint[10];
a.数组名是什么?
b.该数组有多少个元素?
c.每个元素可以存储什么类型的值?
d.下面的哪一个scanf()的用法正确?
i.scanf("%lf", mint[2])
ii.scanf("%lf", &mint[2])
iii.scanf("%lf", &mint)

  1. Noah先生喜欢以2计数,所以编写了下面的程序,创建了一个存储2、4、6、8等数字的数组。这个程序是否有错误之处?如果有,请指出。

    #include 
    #define SIZE 8
    int main(void)
    {
        int by_twos[SIZE];
        int index;
        for (index = 1; index <= SIZE; index++)
            by_twos[index] = 2 * index;
        for (index = 1; index <= SIZE; index++)
            printf("%d ", by_twos);
        printf("\n");
        return 0;
    }
    
  2. 假设要编写一个返回long类型值的函数,函数定义中应包含什么?

  3. 定义一个函数,接受一个int类型的参数,并以long类型返回参数的平方值。

  4. 下面的程序打印什么内容?
    #include
    int main(void)
    {
    int k;
    for (k = 1, printf("%d: Hi!\n", k); printf(“k = %d\n”, k),

    k*k < 26; k += 2, printf(“Now k is %d\n”, k))
    printf(“k is %d in the loop\n”, k);
    return 0;
    }


  1. 2,7,70,64,8,2。

  2. 该循环的输出是:
    36 18 9 4 2 1
    如果value是double类型,即使value小于1,循环的测试条件仍然为真。循环将一直执行,直到浮点数下溢生成0为止。另外,value是double类型时,%3d转换说明也不正确。

  3. a.x > 5
    b.scanf("%lf",&x) != 1
    c.x == 5

  4. a.scanf("%d", &x) == 1
    b.x != 5
    c.x >= 20

  5. 第4行:应该是list[10]。
    第6行:逗号改为分号。i的范围应该是0~9,不是1~10。
    第9行:逗号改为分号。>=改成<=,否则,当i等于1时,该循环将成为无限循环。
    第10行:在第10行和第11行之间少了一个右花括号。该右花括号与第7行的左花括号配对,形成一个for循环块。然后在这个右花括号与最后一个右花括号之间,少了一行return 0;。
    下面是一个正确的版本:

    #include 
    int main(void)
    {                             /* 第3行 */
        int i, j, list(10);       /* 第4行 */
        for (i = 1, i <= 10, i++) /* 第6行 */
    
        {                               /* 第7行 */
            list[i] = 2 * i + 3;        /* 第8行 */
            for (j = 1, j > = i, j++)   /* 第9行 */
                printf(" %d", list[j]); /* 第10行 */
            printf("\n");               /* 第11行 */
        }
        return 0;
    }
    
  6. 下面是一种方法:

    #include 
    int main(void)
    {
        int col, row;
        for (row = 1; row <= 4; row++)
        {
            for (col = 1; col <= 8; col++)
                printf("$");
            printf("\n");
        }
        return 0;
    }
    
  7. a.Hi! Hi! Hi! Bye! Bye! Bye! Bye! Bye!
    b.ACGM(因为代码中把int类型值与char类型值相加,编译器可能警告会损失有效数字)

  8. a.Go west, youn
    b.Hp!xftu-!zpvo
    c.Go west, young
    d.$o west, youn

  9. 其输入如下:

    31|32|33|30|31|32|33|
        ***
        1
        5
        9
        13
        ***
        2 6
        4 8
        8 10
        ***
        ======
        =====
        ====
        ===
        ==
    
  10. a.mint
    b.10个元素
    c.double 类型的值
    d.第ii行正确,mint[2]是double类型的值,&mingt[2]是它在内存中的位置。

  11. 因为第1个元素的索引是0,所以循环的范围应该是0~SIZE - 1,而不是1~SIZE。但是,如果只是这样更改会导致赋给第1个元素的值是0,不是2。所以,应重写这个循环:
    for (index = 0; index < SIZE; index++)
    by_twos[index] = 2 * (index + 1);
    与此类似,第2个循环的范围也要更改。另外,应该在数组名后面使用数组索引:
    for( index = 0; index < SIZE; index++)
    printf("%d ", by_twos[index]);错误的循环条件会成为程序的定时炸弹。程序可能开始运行良好,但是由于数据被放在错误的位置,可能在某一时刻导致程序不能正常工作。

  12. 该函数应声明为返回类型为long,并包含一个返回long类型值的return语句。

  13. 把num的类型强制转换成long类型,确保计算使用long类型而不是int类型。在int为16位的系统中,两个int类型值的乘积在返回之前会被截断为一个int类型的值,这可能会丢失数据。

    long square(int num)

    {

    return ((long) num) * num;

    }

  14. 输出如下:

    1: Hi!

    k = 1

    k is 1 in the loop

    Now k is 3

    k = 3

    k is 3 in the loop

    Now k is 5

    k = 5

    k is 5 in the loop

    Now k is 7

    k = 7

编程练习

  1. 编写一个程序,创建一个包含26个元素的数组,并在其中存储26个小写字母。然后打印数组的所有内容。
  2. 使用嵌套循环,按下面的格式打印字符:

$
$$
$$$
$$$$
$$$$$

  1. 使用嵌套循环,按下面的格式打印字母:
    F
    FE
    FED
    FEDC
    FEDCB
    FEDCBA
    注意:如果你的系统不使用ASCII或其他以数字顺序编码的代码,可以把字符数组初始化为字母表中的字母:
    char lets[27] = “ABCDEFGHIJKLMNOPQRSTUVWXYZ”;
    然后用数组下标选择单独的字母,例如lets[0]是’A’,等等。
  2. 使用嵌套循环,按下面的格式打印字母:
    A
    BC
    DEF
    GHIJ
    KLMNO
    PQRSTU
    如果你的系统不使用以数字顺序编码的代码,请参照练习3的方案解决。
  3. 编写一个程序,提示用户输入大写字母。使用嵌套循环以下面金字塔型的格式打印字母:
    A
   ABA    
  ABCBA 
 ABCDCBA 
ABCDEDCBA

打印这样的图形,要根据用户输入的字母来决定。例如,上面的图形是在用户输入E后的打印结果。提示:用外层循环处理行,每行使用3个内层循环,分别处理空格、以升序打印字母、以降序打印字母。如果系统不使用ASCII或其他以数字顺序编码的代码,请参照练习3的解决方案。
6. 编写一个程序打印一个表格,每一行打印一个整数、该数的平方、该数的立方。要求用户输入表格的上下限。使用一个for循环。
7. 编写一个程序把一个单词读入一个字符数组中,然后倒序打印这个单词。提示:strlen()函数(第4章介绍过)可用于计算数组最后一个字符的下标。
8. 编写一个程序,要求用户输入两个浮点数,并打印两数之差除以两数乘积的结果。在用户输入非数字之前,程序应循环处理用户输入的每对值。
9. 修改练习8,使用一个函数返回计算的结果。
10. 编写一个程序,要求用户输入一个上限整数和一个下限整数,计算从上限到下限范围内所有整数的平方和,并显示计算结果。然后程序继续提示用户输入上限和下限整数,并显示结果,直到用户输入的上限整数等于或小于下限整数为止。程序的运行示例如下:
Enter lower and upper integer limits: 5 9
The sums of the squares from 25 to 81 is 255
Enter next set of limits: 3 25
The sums of the squares from 9 to 625 is 5520
Enter next set of limits: 5 5
Done
11. 编写一个程序,在数组中读入8个整数,然后按倒序打印这8个整数。
12. 考虑下面两个无限序列:
1.0 + 1.0/2.0 + 1.0/3.0 + 1.0/4.0 + …
1.0 - 1.0/2.0 + 1.0/3.0 - 1.0/4.0 + …编写一个程序计算这两个无限序列的总和,直到到达某次数。提示:奇数个-1相乘得-1,偶数个-1相乘得1。让用户交互地输入指定的次数,当用户输入0或负值时结束输入。查看运行100项、1000项、10000项后的总和,是否发现每个序列都收敛于某值?
13. 编写一个程序,创建一个包含8个元素的int类型数组,分别把数组元素设置为2的前8次幂。使用for循环设置数组元素的值,使用do while循环显示数组元素的值。
14. 编写一个程序,创建两个包含8个元素的double类型数组,使用循环提示用户为第一个数组输入8个值。第二个数组元素的值设置为第一个数组对应元素的累积之和。例如,第二个数组的第4个元素的值是第一个数组前4个元素之和,第二个数组的第5个元素的值是第一个数组前5个元素之和(用嵌套循环可以完成,但是利用第二个数组的第5个元素是第二个数组的第4个元素与第一个数组的第5个元素之和,只用一个循环就能完成任务,不需要使用嵌套循环)。最后,使用循环显示两个数组的内容,第一个数组显示成一行,第二个数组显示在第一个数组的下一行,而且每个元素都与第一个数组各元素相对应。
15. 编写一个程序,读取一行输入,然后把输入的内容倒序打印出来。可以把输入存储在char类型的数组中,假设每行字符不超过255。回忆一下,根据%c转换说明,scanf()函数一次只能从输入中读取一个字符,而且在用户按下Enter键时scanf()函数会生成一个换行字符(\n)。
16. Daphne以10%的单利息投资了100美元(也就是说,每年投资获利相当于原始投资的10%)。Deirdre以5%的复合利息投资了100美元(也就是说,利息是当前余额的5%,包含之前的利息)。编写一个程序,计算需要多少年Deirdre的投资额才会超过Daphne,并显示那时两人的投资额。
17. Chuckie Lucky赢得了100万美元(税后),他把奖金存入年利率8%的账户。在每年的最后一天,Chuckie取出10万美元。编写一个程序,计算多少年后Chuckie会取完账户的钱?
18. Rabnud博士加入了一个社交圈。起初他有5个朋友。他注意到他的朋友数量以下面的方式增长。第1周少了1个朋友,剩下的朋友数量翻倍;第2周少了2个朋友,剩下的朋友数量翻倍。一般而言,第N周少了N个朋友,剩下的朋友数量翻倍。编写一个程序,计算并显示Rabnud博士每周的朋友数量。该程序一直运行,直到超过邓巴数(Dunbar’s number)。邓巴数是粗略估算一个人在社交圈中有稳定关系的成员的最大值,该值大约是150。


  1. #include 
    int main(void)
    {
        char charset[26];
        for (int i = 0; i < 26; i++)
        {
            charset[i] = 'a' + i;
        }
        for (int i = 0; i < 26; i++)
        {
            printf("charset[%d] is %c.\n", i, charset[i]);
        }
        return 0;
    }
    
  2. #include 
    int main(void)
    {
        for (int i = 0; i < 5; i++)
        {
            for (int j = 0; j < i + 1; j++)
                printf("$");
            printf("\n");
        }
        return 0;
    }
    
  3. #include 
    int main(void)
    {
        char charset[26];
        int begin = 5;
        for (int i = 0; i < 26; i++)
        {
            charset[i] = 'A' + i;
        }
    
        for (int i = 0; i < 6; i++)
        {
            for (int j = 0; j < i + 1; j++)
            {
                printf("%c", charset[begin - j]);
            }
            printf("\n");
        }
        return 0;
    }
    
  4. #include 
    int main(void)
    {
        char charset[26];
        int begin = 0;
        for (int i = 0; i < 26; i++)
        {
            charset[i] = 'A' + i;
        }
        for (int i = 0; i < 6; i++)
        {
            begin += i;
            for (int j = 0; j < i + 1; j++)
            {
                printf("%c", charset[begin + j]);
            }
            printf("\n");
        }
        return 0;
    }
    
  5. #define _CRT_SECURE_NO_WARNINGS
    #include 
    int main(void)
    {
    	char end = 'E';
    	printf("Please enter a upper character: \n");
    	scanf("%c", &end);
    	if (!(end >= 'A' && end <= 'Z'))
    	{
    		printf("Error!");
    		return 0;
    	}
    	for (int row = 0; row <= end - 'A'; row++)
    	{
    		char middle = 'A' + row;
    		for (int space = 0; space < end - middle; space++)
    		{
    			printf(" ");
    		}
    		for (int zhengxu = 0; zhengxu < row + 1; zhengxu++)
    		{
    			printf("%c", zhengxu + 'A');
    		}
    		for (int nixu = middle - 1; nixu >= 'A'; nixu--)
    		{
    			printf("%c", nixu);
    		}
    		printf("\n");
    	}
    	return 0;
    }
    
  6. #include
    #include
    #include
    int main(void)
    {
    	int begin, end;
    	printf("Please enter the numer of begin: ");
    	scanf("%d", &begin);
    	printf("Please enter the numer of end: ");
    	scanf("%d", &end);
    	if (begin > end)
    	{
    		printf("Error!\n");
    		return 0;
    	}
    	if (pow(end, 3) > INT_MAX)
    	{
    		printf("too big\n");
    		return 0;
    	}
    	printf("num\tsquare\tcube\n");
    	for (int i = begin; i <= end; i++)
    	{
    		printf("%d\t%d\t%d\n", i, (int)pow(i, 2), (int)pow(i, 3));
    	}
    	return 0;
    }
    
  7. #include
    #include
    int main(void)
    {
        char word[30];
        printf("Please enter a word: ");
        scanf("%s",word);
        unsigned length = strlen(word);
        for(int i=length-1;i>=0;i--)
        {
            printf("%c",word[i]);
        }
        printf("\n");
        return 0;
    }
    
  8. #include 
    #include 
    int main(void)
    {
        float num1, num2;
        while (true)
        {
            printf("Please enter the first number: ");
            bool judge1 = scanf("%f", &num1);
            printf("Please enter the second number: ");
            bool judge2 = scanf("%f", &num2);
            if (!(judge1 && judge2))
            {
                printf("Exit!\n");
                return 0;
            }
            printf("%g - %g = %g , %g * %g = %g\n", num1, num2, num1 - num2, num1, num2, num1 * num2);
        }
        return 0;
    }
    
  9. #include 
    #include 
    float fun(float num1, float num2);
    int main(void)
    {
        float num1, num2;
        while (true)
        {
            printf("Please enter the first number: ");
            bool judge1 = scanf("%f", &num1);
            printf("Please enter the second number: ");
            bool judge2 = scanf("%f", &num2);
            if (!(judge1 && judge2))
            {
                printf("Exit!\n");
                return 0;
            }
            printf("(%g - %g) / (%g * %g) = %g\n", num1, num2, num1, num2, fun(num1, num2));
        }
        return 0;
    }
    float fun(float num1, float num2)
    {
        return (num1 - num2) / (num1 * num2);
    }
    
  10. #include 
    int main(void)
    {
        int begin, end;
        while (1)
        {
            printf("Enter lower and upper integer limits: ");
            scanf("%d %d", &begin, &end);
            if (begin >= end)
            {
                printf("Done\n");
                return 0;
            }
            long sum = 0L;
            for (int i = begin; i <= end; i++)
            {
                sum += (long)i * i;
            }
            printf("The sums of the squares from %d to %d is %ld\n", begin * begin, end * end, sum);
        }
        return 0;
    }
    
  11. #include 
    #define SIZE 8
    int main(void)
    {
        int num[SIZE];
        for (int i = 0; i < SIZE; i++)
        {
            scanf("%d", &num[i]);
        }
        for (int i = SIZE - 1; i >= 0; i--)
        {
            printf("%d ", num[i]);
        }
    }
    
  12. #include 
    #include 
    int main(void)
    {
        int ci;
        while (1)
        {
            /* code */
            printf("enter a number: ");
            scanf("%d", &ci);
            if (ci <= 0)
            {
                printf("Done.\n");
                return 0;
            }
            int sign = -1;
            double term;
            double sum1 = 0.0, sum2 = 0.0;
            for (int i = 1; i <= ci; i++)
            {
                sign = pow(sign, i + 1);
                term = 1.0 / i;
                sum1 += term;
                sum2 += term * sign;
            }
            printf("%f %f\n", sum1, sum2);
        }
        return 0;
    }
    
  13. #include 
    #include 
    #define SIZE 8
    int main(void)
    {
        int num[SIZE];
        for (int i = 0; i < SIZE; i++)
        {
            num[i] = (int)pow(2, i + 1);
        }
        int i = 0;
        do
        {
            /* code */
            printf("%d ", num[i]);
            i++;
        } while (i < SIZE);
        return 0;
    }
    
  14. #include 
    #define SIZE 8
    int main(void)
    {
        double num1[SIZE], num2[SIZE];
        for (int i = 0; i < SIZE; i++)
        {
            scanf("%lf", &num1[i]);
        }
        num2[0] = num1[0];
        for (int i = 1; i < SIZE; i++)
        {
            num2[i] = num2[i - 1] + num1[i];
        }
        for (int i = 0; i < SIZE; i++)
        {
            printf("%g ",num1[i]);
        }
        printf("\n");
        for (int i = 0; i < SIZE; i++)
        {
            printf("%g ",num2[i]);
        }
        return 0;
    }
    
  15. #include 
    #include 
    int main(void)
    {
        char str[256];
        scanf("%s", str);
        for (int i = strlen(str); i >= 0; i--)
        {
            printf("%c", str[i]);
        }
        printf("\n");
        return 0;
    }
    
  16. #include 
    int main(void)
    {
        float num1 = 100.0f, num2 = 100.0f;
        float sum1 = num1, sum2 = num2;
        while (1)
        {
            /* code */
            sum1 += num1 * 0.1f;
            sum2 += num2 * 0.05f;
            // printf("%g %g\n%g %g\n---\n", num1, num2, sum1, sum2);
            num2 = sum2;
            if (sum2 > sum1)
                break;
        }
        printf("%g %g\n", sum1, sum2);
        return 0;
    }
    
  17. #include 
    #define RATE .08
    int main(void)
    {
        const float qu = 1.E+5;
        float sum = 1.E+6;
        int count = 0;
        while (1)
        {
            /* code */
            sum += sum * RATE;
            sum -= qu;
            count++;
            if (sum <= 0)
            {
                break;
            }
        }
        printf("%d\n",count);
        return 0;
    }
    
  18. #include 
    #define D_B_NUM 150
    int main(void)
    {
        int sum = 5;
        for (int i = 1;; i++)
        {
            sum--;
            sum *= 2;
            printf("%d : %d\n", i, sum);
            if (sum > D_B_NUM)
                break;
        }
        return 0;
    }
    

你可能感兴趣的:(C,Primer,Plus,c语言)