文章目录
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;
}
运行结果: