6.0 前言

循环(loop)是重复执行其他语句的一种语句 (本质是语句, 类似于if)

学习三类循环语句:

  • while
  • do-while
  • for

学习循环特性语句:

  • break
  • continue
  • goto语句

6.1 while语句

基本使用

while (控制表达式)
    单语句;

while (控制表达式) { // 复合语句
    语句...
}

6.1.0 while无限循环

惯用写法

while (1) {
    语句...
}

(程序) 平方表

square.c

#include <stdio.h>

int main(void) {
    int i, n;

    printf("This program prints a table of squares.\n");
    printf("Enter number of entries in table: ");
    scanf("%d", &n);

    i = 1;

    while (i < n) {
        printf("%10d%10d\n", i, i * i);
        i++;
    }

    return 0;
}

(程序) 数列求和

sum.c

#include <stdio.h>

int main(void) {
    int n, sum = 0;

    printf("This program sums a series of integers.\n");
    printf("Enter integers (0 to terminate): ");

    scanf("%d", &n);
    while (n != 0) {
        sum += n;
        scanf("%d", &n);
    }
    printf("\nThe sum is: %d\n", sum);

    return 0;
}

输出

This program sums a series of integers.
Enter integers (0 to terminate): 8 23 71 5 0

The sum is: 107

6.2 do语句

使用场景:

  1. 至少需要执行一次的情况
  2. 用户输入
do {
    语句...
} while (条件表达式); // 注意结尾带有分号

(程序) 计算整数的位数

numdigit.c

#include <stdio.h>

int main(void) {
    int digits = 0, n;

    printf("Enter a nonnegative integer: ");
    scanf("%d", &n);

    do {
        n /= 10;
        digits++;
    } while (n > 0);

    printf("The number has %d digit(s).\n", digits);

    return 0;
}

输出

Enter a nonnegative integer: 0
The number has 1 digit(s).

6.3 for语句

6.3.0 简介

for执行流程:

  • 初始化: 执行表达式1

  • 检查: 执行表达式2, 非零为真

  • 若为真: 执行循环体

  • 循环体完成: 执行表达式3

  • 检查: 表达式2

  • 若为真: 循环体

  • ...

    for (初始化表达式1; 表达式2; 表达式3) 单语句

    for (表达式1; 表达式2; 表达式3) { // 复合语句 语句 }

6.3.1 for语句的惯用法

// 0~(n - 1)
for (i = 0; i < n; i++)
// 1~n
for (i = 1; i <= n; i++)
// (n - 1)~0
for (i = n - 1; i >= 0; i--)
// n~1
for (i = n; i > 0; i--)

6.3.2 for省略表达式

int i = 0;
// 省略表达式1
for (; i > 0; i--)
    printf("hello");

// 省略表达式3
for (i = 10; i > 0; )
    i--;

6.3.3 C99中的for语句 (覆盖特性)

  • C99允许在其他为值声明变量
  • 覆盖特性: 如果在for中声明同名变量, for作用域中将覆盖外部i
#include <stdio.h>

int main(void) {
    int i = 5;

    for (int i = 0; i < 10; i++) { // 覆盖原本的i
        printf("%d ", i); // for作用域中的i
    }

    printf("\n\n%d\n", i); // main作用域中的i

    return 0;
}

6.3.4 逗号运算符

使用场景:

  • for循环
  • 宏定义

具体过程:

  1. 计算表达式1, 并丢弃表达式值
  2. 计算表达式2, 作为整个表达式的值
表达式1, 表达式2

(程序) 平方表

square2.c

#include <stdio.h>

int mian(void) {
    int i, n;

    printf("This program prints a table of squares.\n");
    printf("Enter number of entries in table: ");
    scanf("%d", &n);

    for (i = 1; i <= n; i++) {
        printf("%10d%10d\n", i, i * i);
    }

    return 0;
}

6.4 退出循环

6.4.1 break语句

特性:

  • break只能跳出单层嵌套
#include <stdio.h>

int main(void) {
    int d;
    int n = 7;

    for (d = 2; d < n; d++)
        if (n % d == 0)
            break;

    if (d < n)
        printf("%d is divisible by %d\n", n, d);
    else
        printf("%d is prime\n", n);

    return 0;
}```

## 6.4.2 continue语句

continue仅跳过单轮的循环, 不会跳出循环

```c
n = 0; 
sum = 0; 
while (n < 10) { 
    scanf("%d", &i); 
    if (i == 0) 
        continue; 
    sum += i; 
    n++; /* continue jumps to here */ 
}

6.4.3 goto语句

使用场景:

  • 多层嵌套跳出break无法实现, 可以使用goto实现.

break和continue是受限跳转, goto则不受限制 (C99限制: goto语句不可绕过边长数组)

// [标号语句]
标识符 : 语句

// [跳转语句]
goto 标识符

实例

#include <stdio.h>

int main(void) {
    int d;
    int n = 7;

    for (d = 2; d < n; d++) {
        if (n % d == 0)
            goto done;
    }
    done:
    if (d < n)
        printf("%d is divisible by %d\n", n, d);
    else
        printf("%d is prime\n", n);

    return 0;
}

(程序) 账簿结算

#include <stdio.h>

int main(void) {
    int cmd;
    float balance = 0.0f, credit, debit;

    printf("*** ACME checkbook-balancing program ***\n");
    printf("Commands: 0=clear, 1=credit, 2=debit, ");
    printf("3=balance, 4=exit\n\n");

    for (;;) {
        printf("Enter command: ");
        scanf("%d", &cmd);

        switch (cmd) {
            case 0:
                balance = 0.0f;
                break;
            case 1:
                printf("Enter amount of credit: ");
                scanf("%f", &credit);
                balance += credit;
                break;
            case 2:
                printf("Enter amount of debit: ");
                scanf("%f", &debit);
                balance -= debit;
                break;
            case 3: 
                printf("Current balance: $%.2f\n", balance);
                break;
            case 4:
                return 0;
            default: 
                printf("Commands: 0=clear, 1=credit, 2=debit, ");
                printf("3=balance, 4=exit\n\n");
                break;
        }
    }

    return 0;
}

6.5 空语句

空语句: 除了分号什么都没有

使用场景:

  • 编写空循环体的循环 (优点: 简洁, 某些情况下更加高效)
i = 0; ; j = 1; // 第二条语句就是空语句

for (int i = 0; i < 10; i++) ;// 后续的分号就是空语句

练习题

答案链接:

  • http://knking.com/books/c2/answers/c6.html
  • https://github.com/williamgherman/c-solutions/tree/master/06

记号说明:

  • 加粗(错误): 表示错误的答案或者遗漏的知识点
  • 在代码块中(错误): 表示错误的答案

Practice1

下列程序片段的输出是什么?

i = 1; 
while (i <= 128) { 
    printf("%d ", i); 
    i *= 2;
}

回答:

1 2 4 8 16 32 64 128

Practice2

下列程序片段的输出是什么?

i = 9384;
do {
    printf("%d ", i);
    i /= 10;
} while (i > 0);

回答:

9384 938 93 9

Practice3 *

下面这条for 语句的输出是什么?

for (i = 5, j = i - 1; i > 0, j > 0; --i , j = i - 1)
    printf("%d ", i);

回答:

5 4 3 2

Practice4

下列哪条语句和其他两条语句不等价(假设循环体都是一样的)?

  1. for (i = 0; i < 10; i++)...
  2. for (i = 0; i < 10; ++i)...
  3. for (i = 0; i++ < 10; )...

回答: 3, for循环的控制表达式只执行一次

Practice5

下列哪条语句和其他两条语句不等价(假设循环体都是一样的)?

  1. while (i < 10) {...}
  2. for (; i < 10;) {...}
  3. do {...} while (i < 10);

回答: 3, do-while至少执行一次, 1和2是否执行取决于i的初值

Practice6

把练习题1中的程序片段改写为一条for语句。

回答:

// 源
i = 1; 
while (i <= 128) { 
    printf("%d ", i); 
    i *= 2;
}

// for语句
for (i = 1; i <= 128; i *= 2)
    printf("%d ", i);

Practice7

把练习题2中的程序片段改写为一条for语句。

回答:

// 源
i = 9384;
do {
    printf("%d ", i);
    i /= 10;
} while (i > 0);

// for语句
for (i = 9384; i > 0; i /= 10)
    printf("%d ", i);

Practice8

下面这条for语句的输出是什么?

for (i = 10; i >= 1; i /= 2)
    printf("%d ", i++);

回答:

10 5 3 2 1 1 1 ...

Practice9

把练习题8中的for语句改写为一条等价的while语句。除了while循环本身之外,还需要一条语句

回答:

i = 10
while (i >= 1) {
    printf("%d ", i++);
    i /= 2;
}

Practice10

说明如何用等价的goto语句替换continue语句。

回答: continue的功能是跳过当前轮次, goto到循环体结尾前即可

Practice11

下列程序片段的输出是什么?

sum = 0;
for (i = 0; i < 10; i++) {
    if (i % 2)
        continue;
    sum += i;
}
printf("%d\n", sum);

回答:

2 + 4 + 6 + 8 = 20

Practice12

下面的"素数判定"循环作为示例出现在6.4节中:

这个循环不是很高效。没有必要用n除以2~n-1的所有数来判断它是否为素数。事实上,只需要检查不大于n的平方根的除数。利用这一点来修改循环。提示:不要试图计算出n的平方根,用d*d和n进行比较。

for (d = 2; d < n; d++)
    if (n % d == 0)
        break;

回答:

for (d = 2; d * d <= n; d++)
    if (n % d == 0)
        break;

Practice13

重写下面的循环,使其循环体为空。

for (n = 0; m > 0; n++)
    m /= 2;

回答:

for (n = 0; m > 0; m /= 2, n++) ;

Practice14

找出下面程序片段中的错误并修正。

if (n % 2 ==0);
    printf("n is even\n");

回答: 多出了一个分号

if (n % 2 == 0)
    printf("n is even\n");

代码题

code1-max

编写程序,找出用户输入的一串数中的最大数。程序需要提示用户一个一个地输入数。当用户输入0或负数时,程序必须显示出已输入的最大非负数:

Enter a number: 60

Enter a number: 38.3

Enter a number: 4.89

Enter a number: 100.62

Enter a number: 75.2295

Enter a number: 0


The largest number entered was 100.62

代码如下

#include <stdio.h>
#include <float.h>

int main(void) {
    float num, max = FLT_MIN;

    do {
        printf("\nEnter a number: ");
        scanf("%f", &num);

        if (num <= 0)
            break;

        if (num > max) 
            max = num;
    } while (1);

    printf("\nThe largest number entered was %.2f\n", max);

    return 0;
}

编译并运行

Enter a number: 60

Enter a number: 38.3

Enter a number: 4.89

Enter a number: 100.62

Enter a number: 75.2295

Enter a number: 0

The largest number entered was 100.62

code2-GCD

编写程序,要求用户输入两个整数,然后计算并显示这两个整数的最大公约数(GCD):

提示: 求最大公约数的经典算法是Euclid算法,方法如下:分别让变量m和n存储两个数的值。如果n为0,那么停止操作,m中的值是GCD;否则计算m除以n的余数,把n保存到m中,并把余数保存到n中。然后重复上述过程,每次都先判定n是否为0。

Enter two integers: 12 28

Greatest common divisor: 4

代码如下

#include <stdio.h>

int main(void) {
    int m, n, gcd;

    printf("Enter two integers: ");
    scanf("%d%d", &m, &n);

    if (n != 0) {
        while (n) {
            int tmp = m % n;
            m = n;
            n = tmp;
        }
    }
    gcd = m;

    printf("\nGreatest common divisor: %d\n", m);

    return 0;
}

编译并运行

Enter two integers: 12 28             

Greatest common divisor: 4

code3-exp

编写程序,要求用户输入一个分数,然后将其约分为最简分式:

提示:为了把分数约分为最简分式,首先计算分子和分母的最大公约数,然后分子和分母都除以最大公约数。

Enter a fraction: 6/12

In lowest terms: 1/2

代码如下

#include <stdio.h>

int main(void) {
    int m, n, gcd;
    int ms, ns;

    printf("Enter a fraction: ");
    scanf("%d/%d", &m, &n);

    if (n == 0) {
        printf("\nError.\n");
        return 0;
    } else {
        ms = m;
        ns = n;
        while (ns) {
            int tmp = ms % ns;
            ms = ns;
            ns = tmp;
        }

        gcd = ms;
    }

    m /= gcd;
    n /= gcd;

    printf("\nIn lowest terms: %d/%d\n", m, n);

    return 0;
}

编译并运行

Enter a fraction: 6/12

In lowest terms: 1/2

code4-broker2

在5.2节的broker.c程序中添加循环,以便用户可以输入多笔交易并且程序可以计算每次的佣金。程序在输入的交易额为0时终止。

Enter value of trade: 30000

Commission: $166.00

Enter value of trade: 20000


Commission: $144.00

Enter value of trade: 0

代码如下

#include <stdio.h>

int main(void) {
    float commission, value;

    printf("Enter value of trade: ");
    scanf("%f", &value);

    while (value != 0) {
        if (value < 2500.00f)
            commission = 30.00f + 0.017f * value;
        else if (value < 6250.00f)
            commission = 56.00f + .0066f * value;
        else if (value < 20000.00f)
            commission = 76.00f + .0034f * value;
        else if (value < 50000.00f)
            commission = 100.00f + .0022f * value;
        else if (value < 500000.00f)
            commission = 155.00f + 0.0011f * value;
        else   
            commission = 255.00f + .0009f * value;
        
        if (commission < 39.00f)
            commission = 39.00f;
        
        printf("\nCommission: $%.2f\n", commission);

        printf("\nEnter value of trade: ");
        scanf("%f", &value);
    }

    return 0;
}

编译并运行

Enter value of trade: 30000

Commission: $166.00

Enter value of trade: 20000

Commission: $144.00

Enter value of trade: 0

code5-reverse

第4章的编程题1要求编写程序显示出两位数的逆序。设计一个更具一般性的程序,可以处理一位、两位、三位或者更多位的数。提示:使用do循环将输入的数重复除以10,直到值达到0为止。

代码如下

#include <stdio.h>

int main(void) {
    int num;

    printf("Enter a number: ");
    scanf("%d", &num);

    printf("\n");
    while (num) {
        printf("%d", num % 10);
        num /= 10;
    }
    printf("\n");

    return 0;
}

编译并运行

Enter a number: 1234567

7654321

code6-square

编写程序,提示用户输入一个数n,然后显示出1~n的所有偶数平方值。例如,如果用户输入100,那么程序应该显示出下列内容:

4
16
36
64
100

代码如下

#include <stdio.h>

int main(void) {
    int n;
    
    printf("Enter a number: ");
    scanf("%d", &n);

    printf("\n");
    for (int i = 1; i <= n; i++)
        if (i % 2 == 0)
            printf("%d\n", i * i);

    return 0;
}

编译并运行

Enter a number: 10

4
16
36
64
100

优化:

    for (int i = 2; i <= n; i += 2)
        printf("%d\n", i * i);

code7-square4

重新安排程序square3.c,在for循环中对变量i进行初始化、判定以及自增操作。不需要重写程序,特别是不要使用任何乘法。

代码如下

编译并运行

code8-calendar

编写程序显示单月的日历。用户指定这个月的天数和该月起始日是星期几:

此程序不像看上去那么难。最重要的部分是一个使用变量i从1计数到n的for语句(这里n是此月的天数),for语句中需要显示i的每个值。在循环中,用if语句判定i是否是一个星期的最后一天,如果是,就显示一个换行符。

Enter number of days in month: 31

Enter starting day of the week (1=Sun, 7=Sat): 3


       1  2  3  4  5
 6  7  8  9 10 11 12
13 14 15 16 17 18 19
20 21 22 23 24 25 26
27 28 29 30 31

代码如下

#include <stdio.h>

int main(void) {
    int days, start;
    int count = 1;

    printf("Enter number of days in month: ");
    scanf("%d", &days);
    printf("\nEnter starting day of the week (1=Sum, 7=Sat): ");
    scanf("%d", &start);

    for (int i = 0 ; i < start - 1; i++) {
        printf("   ");
    }

    for (int  i = 0; i < (days + (start - 1) + 6) / 7; i++) { // 向上取整算法
        for (int j = 0; j < ((i == 0) ? 7 - (start - 1) : 7); j++) {
            if (count > days)
                break;
            printf("%2d ", count++);
        }
        printf("\n");
    }

    return 0;
}

编译并运行

Enter number of days in month: 31

Enter starting day of the week (1=Sum, 7=Sat): 3
       1  2  3  4  5 
 6  7  8  9 10 11 12 
13 14 15 16 17 18 19 
20 21 22 23 24 25 26 
27 28 29 30 31 

上面的代码复杂且不直观

订正:

#include <stdio.h>

int main(void) {
    int days, start;
    int count = 1;

    printf("Enter number of days in month: ");
    scanf("%d", &days);
    printf("\nEnter starting day of the week (1=Sum, 7=Sat): ");
    scanf("%d", &start);

    for (int i = 1; i < start; i++)
        printf("   ");

    for (int i = 1; i <= days; i++) {
        printf("%d ", i);

        if ((i + start - 1) % 7 == 0)
            printf("\n");
    }
    
    return 0;
}

code9-interest

第2章的编程题8要求编程计算第一、第二、第三个月还贷后剩余的贷款金额。修改该程序,要求用户输入还贷的次数并显示每次还贷后剩余的贷款金额。

代码如下

#include <stdio.h>

int main(void) {
    float amount, rate, payment; 
    int times;

    printf("Enter amount of loan: ");
    scanf("%f", &amount);
    printf("\nEnter interest rate: ");
    scanf("%f", &rate);
    printf("\nEnter monthly payment: ");
    scanf("%f", &payment);
    printf("\nEnter times: ");
    scanf("%d", &times);
    printf("\n\n\n");

    rate = rate / 100 / 12;

    for (int i = 0; i < times; i++) {
        amount = (amount * (1 + rate)) - payment;
        printf("Balance remaining after first payment: $%.2f\n", amount);
    }

    return 0;
}

编译并运行

Enter amount of loan: 30000

Enter interest rate: 5

Enter monthly payment: 300

Enter times: 8



Balance remaining after first payment: $29825.00
Balance remaining after first payment: $29649.28
Balance remaining after first payment: $29472.82
Balance remaining after first payment: $29295.62
Balance remaining after first payment: $29117.69
Balance remaining after first payment: $28939.01
Balance remaining after first payment: $28759.59
Balance remaining after first payment: $28579.43

code10-date

第5章的编程题9要求编写程序判断哪个日期更早。泛化该程序,使用户可以输入任意个日期。用0/0/0指示输入结束,不再输入日期。

Enter a date (mm/dd/yy): 3/6/08


Enter a date (mm/dd/yy): 5/17/07


Enter a date (mm/dd/yy): 6/3/07


Enter a date (mm/dd/yy): 0/0/0


5/17/07 is the earliest date

代码如下

#include <stdio.h>

int main(void) {
    int ed = 0, em = 0, ey = 0;
    int d, m, y;

    printf("Enter a date (mm/dd/yy): ");
    scanf("%d/%d/%d", &m, &d, &y);
    ed = d; em = m; ey = y;

    while (d || m || y) {
        if (y < ey) {
            ed = d; em = m; ey = y;
        } else if (y == ey) {
            if (m < em) {
                ed = d; em = m; ey = y;
            } else if (m == em) {
                if (d < ed) {
                    ed = d; em = m; ey = y;
                }
            }
        }

        printf("Enter a date (mm/dd/yy): ");
        scanf("%d/%d/%d", &m, &d, &y);
    }

    printf("\n%d/%d/%.2d is the earliest date.\n", em, ed, ey);

    return 0;
}

编译并运行

Enter a date (mm/dd/yy): 3/6/08
Enter a date (mm/dd/yy): 5/17/07
Enter a date (mm/dd/yy): 6/3/07
Enter a date (mm/dd/yy): 0/0/0

5/17/07 is the earliest date.

code11-e

数学常量 $e$ 的值可以用一个无穷级数表示:

$$ e = 1 + \frac{1}{1!} + \frac{1}{2!} + \frac{1}{3!} + ... $$

编写程序, 利用下面的公式得到e的近似值

$$ e = 1 + \frac{1}{1!} + \frac{1}{2!} + \frac{1}{3!} + ... + \frac{1}{n!} $$

n为用户输入的整数

代码如下

#include <stdio.h>

int main(void) {
    int n;
    float e = 1;

    printf("Enter n: ");
    scanf("%d", &n);

    for (int i = 1; i <= n; i++) {
        int tmp = 1;

        for (int j = 1; j <= i; j++)
            tmp *= j; // 计算阶乘
        
        e += 1.0f / (float)tmp;
    }

    printf("e = %f\n", e);

    return 0;
}

编译并运行

Enter n: 10
e = 2.718282