文章目录

C语言中的语句1. 选择语句1.1 if语句复合语句

1.2 else语句1.3 级联式if语句1.4 悬空else问题1.5 条件表达式1.6 布尔值1.7 switch语句case穿透练习

2. 循环语句2.1 while语句无限循环

2.2 do...while语句练习

2.3 for语句省略for语句中的表达式C99中的for语句逗号表达式

3. 跳转语句3.1 break3.2 continue3.3 goto练习

C语言中的语句

到目前为止,我们只见过两种语句: return 语句和表达式语句。

根据语句对执行顺序的影响,C 语言其余语句大多属于以下 3 大类:

选择语句:if语句和switch语句循环语句:while语句,do...while语句和for语句跳转语句:break语句,continue语句和goto语句(return语句也属于此类)

C 语言还有其他两类语句,一类是复合语句 (把几条语句组合成一条语句),一类是空语句 (不执行任何操作)。

1. 选择语句

1.1 if语句

if语句最简单的格式为:

if (expr) statement

比如下面这个示例:

if (line_num == MAXLINES)

line_num = 0;

注意: 不要混淆 == 和 =。

语句 if (i == 0) … 测试 i 是否等于 0;

而语句 if (i = 0) … 则是先把 0 赋值给 i,然后测试赋值表达式的值是否非零,在这种情况下,测试总是失败的。

复合语句

如果我们想用 if 语句控制两条或者更多条语句,该怎么办呢?这时,就需要引入复合语句了。复合语句格式如下:

{ statements }

通过将多条语句用花括号括起来,可以强制编译器将其作为一条语句来处理。如:

if (line_num == MAX_LINES){

line_num = 0;

page_num++;

}

1.2 else语句

if 语句还可以有 else 子句,其格式为:

if (expr)

statement

else

statement

如果 expr 的值为 0,那么就执行 else 子句。如:

if (i > j)

max = i;

else

max = j;

if语句的嵌套:

if (i > j) {

if (i > k)

max = i;

else

max = k;

} else {

if (j > k)

max = j;

else

max = k;

}

1.3 级联式if语句

级联式if语句的格式如下:

if (expr)

statement

else if (expr)

statement

...

else if (expr)

statement

else

statement

请记住:级联式 if 语句不是新的语句类型。它仅仅是普通的 if 语句,只是碰巧它的 else 子句又是一条新的 if 语句,以此类推…

if (n < 0)

printf("n is less than 0\n");

else if (n == 0)

printf("n is equal to 0\n");

else

printf("n is greater than 0\n");

1.4 悬空else问题

当 if 语句嵌套时,我们需要当心臭名昭著的"悬空 else"问题。思考下面这个例子:

if (y != 0)

if (x != 0)

result = x / y;

else

printf("Error: y is equal to 0\n");

上面 else 子句究竟属于哪一个 if 语句呢?缩进暗示它属于最外层的 if 语句。然而 C 语言遵循的规则是 else 子句应该属于离它最近的,且还没有和其他 else 匹配的 if 语句。因此,在这个例子中, else 子句属于内层的 if 语句。

为了使 else 子句属于外层的 if 语句,我们可以用花括号将内层的 if 语句括起 来:

if (y != 0) {

if (x != 0)

result = x / y;

} else

printf("Error: y is equal to 0\n");

1.5 条件表达式

C 语言提供了一种特殊的运算符——条件运算符,这种运算符可以根据条件产生两个值中的一个。条件运算符由 ? 和 : 组成,其格式如下:

expr1 ? expr2 : expr3

条件运算符是 C 语言中唯一一个有 3 个操作数的运算符,因此我们又把它称为三目运算符。

条件表达式的求值步骤是:首先计算 expr1 的值,如果该值不为 0,则计算 expr2 的值,并且把 expr2 的值当作整个表达式的值;如果 expr1 的值为 0,那么计算 expr3 的值,并把 expr3 的值当作整个表达式的值。

#include

int main(void) {

int i, j, k;

i = 1;

j = 2;

k = i > j ? i : j; // k = 2

k = i > j ? i++ : j++; // k = 2, j = 3, i = 1

k = (i >= 0 ? i : 0) + j; // k = 4

return 0;

}

1.6 布尔值

在最初的 C 语言中,我们是用非零值表示 true,用零值表示 false。

缺少布尔类型多多少少会带来一些麻烦。因此在 C99 中,定义了 _Bool 类型, _Bool 类型的变量只能赋值为 0 或者 1。一般来说,往 _Bool 类型中存储非零值,会导致该变量赋值为 1。

_Bool flag = 5; /* flag is assigned 1 */

C99 中还提供了一个新的头文件 stdbool.h,该头文件定义了bool 宏,用它来表示_Bool ;而且该头文件还定义了 true 和 false 两个宏,它们分别代表 1 和 0。因 此,以后我们可以这样写程序:

#include

...

bool flag = true;

1.7 switch语句

C 语言提供了 switch 语句作为级联式 if 语句的替换。如下面这个例子:

switch (grade) {

case 4:

printf("Excellent");

break;

case 3:

printf("Good");

break;

case 2:

printf("Average");

break;

case 1:

printf("Poor");

break;

case 0:

printf("Failing");

break;

default:

printf("Illegal grade");

break;

}

执行这条语句时,变量 grade 的值与 4, 3, 2, 1 和 0 进行比较。如果值和 4 匹配,则打印 Excellent,然后 break 语句会把控制传递给switch后面的语句。如果grade的值和列出的任何选择都不匹配,那么执行 default 分支,显示 Illegal grade。

switch 语句往往比级联式 if 语句更容易阅读。此外,switch 语句的执行速度也会比 if 语句快一些。

switch 语句相对来说比较复杂,下面我们来看以下它的组成成分:

控制表达式。 switch 后边表达式的值必须是整数类型。C 语言把字符类型也当作整数来处理,因此 switch 语句也可以对字符类型进行判定。但是,不能判定浮点数和字符串 (why?)。

分支标号。 case 后边必须跟常量表达式(能够在编译期间求值的表达式),并且常量表达式的值必须是整数(字符类型也可以)。

语句。每个case 后面可以跟任意数量的语句 (不需要用花括号括起来)。每组语句的最后通常是一条break 语句。

C 语言不允许有重复的分支标号,但对分支的顺序没有要求,特别是 default 分支不一定要放到最后。而且 switch 语句不要求一定要有 default 分支。如果 default 不存在,而且控制表达式的值和任何一个分支标号都不匹配,控制会直接传递给 switch 后面的语句。

多个分支标号。可以共用一组语句。如:

switch (grade) {

case 4: case 3: case 2: case 1:

printf("Passing");

break;

case 0:

printf("Failing");

break;

default:

printf("Illegal grade");

break;

}

case穿透

如果一个分支的最后没有break 语句,那么控制会从一个分支继续到下一个分支,这种现象我们称之为case 穿透。思考下面的语句:

#include

int main() {

int grade = 3;

switch (grade)

{

case 4:

printf("Excellent\n");

case 3:

printf("Good\n");

case 2:

printf("Average\n");

case 1:

printf("Poor\n");

case 0:

printf("Failing\n");

default:

printf("Illegal grade\n");

}

return 0;

}

// 如果 grade 的值为 3,那么会显示什么呢?

// 结果:显示

// Good Average Poor Failing Illegal grade

练习

利用 switch 语句编写一个程序,把用数字表示的成绩转化为字母表示的等级。

评定规则为: A为 90~100,B为 80~89,C为 70~79,D 为 60~69,F 为 0~59。如果成绩高于100或者低于0,则显示出错消息。

2. 循环语句

在 C 语言中,每个循环语句都有一个控制表达式。每次执行循环体时,都要对控制表达式求值。如果表达式为真,那么继续执行循环语句;否则执行循环语句的下一条语句。

C 语言提供了 3 种循环语句,即 while 语句, do...while 语句和 for 语句。 while 语句在循环体执行之前测试控制表达式, do...while 循环在循环体执行之后测试控制表达式, for 语句则非常适合那些递增或递减计数变量的循环。

2.1 while语句

在所有循环语句中, while 语句是最简单也是最基本的。 while 语句的格式如下:

while (expr) statement

其中圆括号内的表达式称为控制表达式,圆括号后面的语句是循环体。下面是一个示例:

i = 1;

while (i < n)

i = i * 2;

// 复合语句

i = 10;

while (i > 0) {

printf("Counting down: %d\n", i);

i--;

}

无限循环

如果控制表达式的值始终非零,那么 while 语句将永远执行下去。事实上,有时候我们会故意用非零的常量表达式作为控制表达式,以此来构造无限循环。

/* idiom */

while (1) ...

除非循环体内含有跳出循环的控制语句 (break, goto, return) 或者调用了导致程序终止的函数,否则上述形式的 while 语句将永远执行下去。

2.2 do…while语句

do...while 语句和 while 语句关系紧密。事实上, do...while 语句本质上就是 while 语句,只不过其控制表达式是在每次执行完循环体之后进行判定的。 do...while 语句的格式如下:

do statement while (expr) ;

执行 do...while 语句时,先执行循环体,再计算控制表达式的值。如果表达式的值非零,那么继续执行循环体,然后再计算表达式的值;如果表达式的值为零,则终止do...while 语句的执行。如:

i = 10;

do {

printf("Counting down: %d\n"; i);

i--;

} while (i > 0);

do...while 语句和 while 语句的唯一区别是:do...while 语句的循环体至少会执行一次,而 while 语句在控制表达式的值初始为 0 时,一次都不会执行。

练习

编写一个程序计算一个整数的位数。

#include

int main() {

int num = 0;

int digitNum = 0;

printf("Enter an integer: ");

scanf("%d", &num);

do {

num = num / 10;

digitNum++;

} while (num);

printf("The number has %d digit(s)", digitNum);

return 0;

}

2.3 for语句

现在介绍 C 语言中最后一种循环,也是功能最强大的一种循环: for 语句。 for 语句非常适合那些递增或递减计数变量的循环,当然它也可以灵活地应用在许多其他类型的循环中。 for 语句的格式如下:

for (expr1; expr2; expr3) statement

下面是一个例子:

for(i = 10; i > 0; i--)

printf("Counting down: %d\n"; i);

执行 for 语句时,变量 i 先初始化为 10,接着判定 i 是否大于 0。因为 10 > 0,因此打印 Counting down: 10,然后变量 i 自减。随后再次对条件 i > 0 进行判定…

从上面的例子,我们可以看到 for 语句和 while 语句关系非常紧密。

事实上,除了一些极少数的情况以外(你能举出例子吗?), for 循环总可以用等价的 while 循环替换:

expr1;

while (expr2) {

statement

expr3;

}

从等价的 while 循环可以看出,expr1 是循环开始执行前的初始化步骤,只执行一次;expr2 是控制循环终止的;expr3 是每次循环中最后被执行的一个操作。

省略for语句中的表达式

for 语句远比现在看到的更加灵活,C 语言允许 for 语句省略一些或者是全部的表达式。

例如,某些程序员利用下列的 for 语句建立无限循环:

/* idiom */

for ( ; ; ) ...

C99中的for语句

在 C99 中, for 语句的第一个表达式可以替换为一个声明,这一特性使得程序员可以声明一个用于循环的变量:

for (int i = 0; i < n; i++)

...

变量 i 不需要在该语句前进行声明。如果变量 i 在之前已经进行了声明,这个语句将创建一个新的 i,且该变量只能在循环内使用。

for语句声明的变量在循环体外是不可见的:

for (int i = 0; i < n; i++) {

...

printf("%d", i); /* legal */

...

}

printf("%d", i); /* Wrong */

让 for 语句自己声明循环控制变量通常是一个好办法:这样很方便,而且程序的可读性更强。

顺便提一下,for 语句可以声明多个变量,只要它们的类型相同:

for (int i = 0, j = 0; i < n; i++)

...

逗号表达式

有时候,我们可能需要编写有两个 (或更多个) 初始表达式的 for 语句,或者希望在每次循环时对几个变量进行自增操作。这时我们就需要使用逗号表达式了。

逗号表达式的格式如下:

expr1, expr2

**逗号表达式的求值分为两步:第一步,计算 expr1 并且扔掉计算出的值;第二步,计算 expr2,把这个值作为整个表达式的值。**对 expr1 的计算应该产生一些副作用,否则 expr1 就没有存在的必要了。举个例子:

i = 1;

j = 5;

k = (++i, i + j); // k = 7

C 语言之所以提供逗号运算符,是为了在只能有一个表达式的地方可以使用两个甚至是多个表达式。换句话说,逗号运算符允许将两个表达式"粘"在一起,构成一个表达式 (和复合语句类似,复合语句可以把一组语句当成一条语句来使用)。

例如,我们可以把原来的程序

sum = 0;

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

sum += i;

改写成这样:

for (sum = 0, i = 1; i <= N; i++)

sum += i;

3. 跳转语句

目前我们已经知道如何在循环体之前 ( while 语句和 for 语句) 和之后 ( do...while 语句) 设置退出点。然而,有些时候我们也需要在循环的中间设置退出点,甚至可能需要对循环设置多个退出点。break 语句可以满足上述需求。

在学习完break 语句后,我们将看到两个相关的语句: continue 语句和 goto 语句。由于已经有了break 和continue 这样好用的语句,所以现在很少使用 goto 语句了。

3.1 break

前面已经讨论过,break 可以跳出 switch 语句。break 还可以用于跳出 while , do...while 或者 for 循环。比如,我们可以通过下面的代码测试 n 是否为素数:

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);

break 可以跳出 switch , while , do...while 和 for 语句。但是当这些语句嵌套时,break 只能跳出包含break 语句的最内层嵌套。如:

while (...) {

switch (...) {

...

break;

...

}

}

上述 break 语句只能将控制从 switch 语句中转移出来,但是不能跳出 while 循环。(思考:如何跳出外层的 while 语句? ([goto语句](#3.3 goto “3.3节”)))

3.2 continue

break 语句会把控制转移到整个循环的后面,而 continue 会将控制转移到循环体的末尾。

break 语句会跳出循环,而 continue 语句仍然留在循环体内。

break 语句和 continue 语句还有另外一个区别:break 语句可以用于 switch 语句和循环,而 continue 只能用于循环。

下面我们通过一个例子来说明这一点:对 10 个非零的整数进行求和。

count = 0;

sum = 0;

while (count < 10) {

scanf("%d", &i);

if (i == 0)

continue;

sum += i;

count++;

/* continue jumps to here */

}

3.3 goto

break 和 continue 语句都是受限制的,break 只能跳出到 switch 语句或者循环语句后面的那一点,而 continue 语句只能跳转到循环体的末尾。

goto 语句就没有这些限制,它可以跳转到函数中任何有标号的语句处。它的唯一限制是只能在函数内进行跳转。

标号是放置在语句开始处的标识符:

identifier: statement

一条语句可以有多个标号。

goto语句的格式如下:

goto identifier;

很显然,我们可以用 goto 语句来实现 break 或者是 continue 的效果 (但是不推荐这么做)。

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);

在早期的编程语言中,goto 语句还是很常见的,但是现在基本上已经很少使用了 (why?)。break, continue, return 语句以及 exit 函数足以应付 goto 语句的大多数使用场景。

为何避免使用goto语句?

编程中避免使用GOTO语句的原因主要有5个:代码可读性降低、程序结构变得混乱、维护难度提升、代码调试困难、以及引发错误的概率增加。特别地,代码可读性问题是最直接和显著的。当程序员在代码中过多使用GOTO语句,会使得代码的执行流程难以追踪。阅读者可能必须在多个不同的位置来回跳转,才能理解程序的工作流程。这种非顺序控制流的使用,使得理解代码的逻辑变得异常困难,尤其是对于复杂的程序。

虽然如此,但是 goto 语句偶尔还是很有用的。考虑前面提到过的 switch 语句嵌套在 while 语句的场景,break 语句是不可以跳出 while 循环的,但是 goto 语句可以解决这个问题:

while (...) {

switch (...) {

...

goto loop_done;

...

}

}

loop_done:

...

练习

现在请开发一个记账程序。程序将为用户提供选择菜单:清空账户余额,往账户上存钱,从账户上取钱,显示当前余额,退出程序。选择项分别用整数 0、1、2、3 和 4 表示。

#include

int main() {

int num = 0;

float money = 0.0f;

float creditMoney = 0.0f;

float debitMoney = 0.0f;

printf("*** checkbook-balancing program ***\n");

printf("Commands: 0=clear, 1=credit, 2=debit, 3=balance, 4=exit\n\n");

while (1)

{

printf("Enter command: ");

scanf("%d", &num);

switch (num)

{

case 0:

money = 0.0f;

printf("your money has been cleared\n");

break;

case 1:

printf("Enter amount of credit: ");

scanf("%f", &creditMoney);

money += creditMoney;

break;

case 2:

do

{

printf("Enter amount of debit: ");

scanf("%f", &debitMoney);

if (debitMoney > money)

printf("have not enough money\n");

} while (debitMoney > money);

money -= debitMoney;

break;

case 3:

printf("Current balance: $%.2f\n", money);

break;

case 4:

printf("see you!\n");

return 0;

default:

printf("Invalid commond\n");

printf("Commands: 0=clear, 1=credit, 2=debit, 3=balance, 4=exit\n");

break;

}

}

return 0;

}

运行结果: