• 作者:老汪软件技巧
  • 发表时间:2023-12-28 22:00
  • 浏览量:

文章目录

C现代方法笔记(&6) 第5章 选择语句 5.1 逻辑表达式

包括if语句在内的某些C语句必须测试表达式的值是“真”还是“假”。例如,if语句可能需要检测表达式i < j,若取得真值则说明i小于j。在许多编程语言中,类似i < j这样的表达式都具有特殊的“布尔”类型或“逻辑”类型。这样的类型只有两个值,即假和真。而在C语言中,i < j这样的比较运算会产生整数:0(假)或1(真)。先记住这一点,下面来看看用于构建逻辑表达式的运算符。

5.1.1 关系运算符

C语言的关系运算符( )跟数学上的、≤和≥运算符相对应,只不过用在C语言的表达式中时产生的结果是0(假)或1(真)。例如,表达式10 < 11的值为1,而表达式11 < 10的值为0。

关系运算符可以用于比较整数和浮点数,也允许比较混合类型的操作数。

从优先级上来看,关系运算符的优先级低于算术运算符。

请注意,表达式i < j < k在C语言中是合法的,但是由于 0 ? i: f)是哪一种类型的值?

答:如问题所述,当int型和float型的值混合在一个条件表达式中时,表达式的类型为float型。如果i > 0为真,那么变量i转换为float型后的值就是表达式的值。

问3:为什么_Bool这个名字就不会影响已有的程序呢?

答:C89标准指出,以下划线开头,后跟一个大写字母的名字是保留字,程序员不应该使用。

第6章 循环

循环(loop)是重复执行其他语句(循环体)的一种语句。在C 语言中,每个循环都有一个控制表达式( )。每次执行循环体(循环重复一次)时都要对控制表达式求值。如果表达式为真(即值不为零),那么继续执行循环。

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

本章最后两节致力于讨论与循环相关的C语言特性。break语句用来跳出循环并把程序控制传递到循环后的下一条语句,语句用来跳过本次循环的剩余部分,而goto语句则可以跳到函数内的任何语句上。

6.1 while语句

在C语言所有设置循环的方法中,while语句是最简单也是最基本的。while语句的格式如下所示:

while (表达式) 语句

圆括号内的表达式是控制表达式,圆括号后边的语句是循环体。

执行while语句时,首先计算控制表达式的值。如果值不为零(即真值),那么执行循环体,接着再次判定表达式。这个过程(先判定控制表达式,再执行循环体)持续进行,直到控制表达式的值变为零才停止。

请注意,虽然循环体必须是单独的一条语句,但这只是个技术问题;如果需要多条语句,那么只要用一对花括号构造成一条复合语句就可以了,比如:

while (i > 0) {
  printf("flsdjfklsdjlkfjklsdj");
  i--;
}

6.1.1 无限循环

如果控制表达式的值始终非零(为真),while语句将无法终止。事实上,C程序员有时故意用非零常量作为控制表达式来构造无限循环:

[惯用法] while (1) ...

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

小知识:如何把输出整齐地排成两列?窍门是使用类似d这样的转换说明代替%d,并利用了函数在指定宽度内输出右对齐的特性。

下面是一个利用循环进行数列求和的例子:

/* Sums a series of numbers */ 
#include  
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("The 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 
*/

注意,条件n != 0在数被读入后立即进行判断,这样可以尽快终止循环。此外,程序中用到了两个完全一样的scanf函数调用,在使用while循环时往往很难避免这种现象。

6.2 do语句

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

do 语句 while (表达式)

和处理while语句一样,do语句的循环体也必须是一条语句(当然可以用复合语句),并且控制表达式的外面也必须有圆括号。

执行do语句时,先执行循环体,再计算控制表达式的值。如果表达式的值是非零的,那么再次执行循环体,然后再次计算表达式的值。在循环体执行后,若控制表达式的值变为0,则终止do语句的执行。

do语句和while语句往往难以区别。两种语句的区别是,do语句的循环体至少要执行一次,而while语句在控制表达式初始值为0时会完全跳过循环体。

下面是一个利用do循环计算整数的位数的例子:

/* Calculates the number of digits in an integer */ 
#include  
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: 60 
The number has 2 digit(s). 
*/

6.3 for语句

for语句是C语言循环中最后一种循环,也是功能最强大的一种循环。不要因为for语句表面上的复杂性而灰心;实际上,它是编写许多循环的最佳方法。for语句非常适合应用在使用“计数”变量的循环中,当然它也可以灵活地用于许多其他类型的循环中。格式如下:

for (声明或者表达式1;表达式2;表达式3) 语句

其中表达式1、表达式2 和表达式3全都是表达式。下面是一个例子:

for (i = 10; i > 0; i--) 
  printf("T minus %d and counting\n",i); 
/*
在执行for语句时,变量i先初始化为10,接着判定i是否大于0。因为判定的结果为真,所以打印信息T minus 10 and counting,然后变量i进行自减操作。随后再次对条件i > 0进行判定。循环体总共执行10次,在这一过程中变量i从10变化到1。
*/

for语句和while语句关系紧密。事实上,除了一些极少数的情况以外,for循环总可以用等价的while循环替换:

表达式1;
while (表达式2) {
  语句
  表达式3;
}

6.3.1 for语句的惯用法

对于“向上加”(变量自增)或“向下减”(变量自减)的循环来说,for语句通常是最好的选择。对于向上加或向下减共n次的情况,for语句经常会采用下列形式中的一种。

模仿上面的书写格式有助于避免C语言初学者常犯的下列错误:

6.3.2 在for语句中省略表达式

通常for语句用三个表达式控制循环,但是有一些for循环可能不需要这么多,因此C语言允许省略任意或全部的表达式。

注意,保留第一个表达式和第二个表达式之间的分号。即使省略掉某些表达式,控制表达式也必须始终有两个分号。

某些程序员用下列for语句建立无限循环:for (;;) ...

6.3.3 C99中的for语句

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

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

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

for语句声明的变量不可以在循环外访问(在循环外不可见)。顺便提一下,for语句可以声明多个变量,只要它们类型相同。

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

6.3.4 逗号运算符

有些时候,我们可能喜欢编写有两个(或更多个)初始表达式的for语句,或者希望在每次循环时一次对几个变量进行自增操作。使用逗号表达式(comma )作为for语句中第一个或第三个表达式可以实现这些想法。

逗号表达式的格式如下所示:

表达式1, 表达式2

这里的表达式1和表达式2是两个任意的表达式。逗号表达式的计算要通过两步来实现:

i = 1, j = 2, k = i + j
/*
因为逗号表达式的左操作数在右操作数之前求值,所以赋值运算i = 1、j = 2 和k = i + j是从左向右进行的。
*/

提供逗号运算符是为了在C语言要求只能有一个表达式的情况下,可以使用两个或多个表达式。换句话说,逗号运算符允许将两个表达式“粘贴”在一起构成一个表达式。(注意它与复合语句的相似之处,后者允许我们把一组语句当作一条语句来使用。)

需要把多个表达式粘在一起的情况不是很多。正如后面的某一章将介绍的那样,某些宏定义( 14.3 节)可以从逗号运算符中受益。除此之外,for语句是唯一可以发现逗号运算符的地方。

6.4 退出循环

然而,有些时候也需要在循环中间设置退出点,甚至可能需要对循环设置多个退出点。break语句可以用于有上述这些需求的循环中。

语句会跳过某次迭代的部分内容,但是不会跳出整个循环。goto语句允许程序从一条语句跳转到另一条语句。因为已经有了break和这样有效的语句,所以很少使用goto语句。

6.4.1 break语句

break语句把程序控制从语句中转移出来,还可以用于跳出while、do或for循环。

值得注意的是,break语句把程序控制从包含该语句的最内层while、do、for或语句中转移出来。因此,当这些语句出现嵌套时,break语句只能跳出一层嵌套。

while (...) { 
  switch (...)  { 
    ... 
    break; 
    ... 
  } 
}
/*
break语句可以把程序控制从switch语句中转移出来,
但是不能跳出while循环。
*/

6.4.2 语句

break语句刚好把程序控制转移到循环体末尾之后,而语句刚好把程序控制转移到循环体末尾之前。break语句会使程序控制跳出循环,而语句会把程序控制留在循环内。break语句和语句的另外一个区别是,break语句可以用于语句和循环(while、do和for),而语句只能用于循环。

6.4.3 goto语句

goto语句在早期编程语言中很常见,但在日常C语言编程中已经很少用到它了。

break、、语句(本质上都是受限制的goto语句)和exit函数( 9.5 节)足以应付在其他编程语言中需要goto语句的大多数情况。

goto语句则可以跳转到函数中任何有标号的语句处。[C99增加了一条限制:goto语句不可以用于绕过变长数组( 8.3 节)的声明。]

执行语句goto L;,控制会转移到标号L后面的语句上,而且该语句必须和goto语句在同一个函数中。

while (...) { 
  switch (...) { 
    ... 
    goto loop_done;     /* break won’t work here */ 
    ... 
  } 
} 
loop_done: ... 
//goto语句对于嵌套循环的退出也是很有用的。

6.5 空语句

语句可以为空,也就是除了末尾处的分号以外什么符号也没有。下面是一个示例:

i = 0; ; j = 1;

这行含有三条语句:一条语句是给i赋值,一条是空语句,还有一条是给j赋值。

空语句主要有一个好处:编写空循环体的循环。(注意空语句单独放置在一行。)

for (d = 2; d < n && n % d != 0; d++) 
  /* empty loop body */; 

C程序员习惯性地把空语句单独放置在一行。否则,有些人阅读程序时可能会搞不清for语句后边的语句是否是其循环体。

请注意!如果不小心在if、while或for语句的圆括号后放置分号,则会创建空语句,从而造成if、while或for语句提前结束。

问与答

问1:

while (i > 0)  
 printf("T minus %d and counting\n", i--);  
为什么不删除“> 0”判定来进一步缩短循环呢?  
while (i)  
 printf("T minus %d and counting\n", i--);  
这种写法的循环会在i 达到0 值时停止,所以它应该和原始版本一样好。

答:新写法确实更加简洁,许多C程序员也这样写循环。但是,它也有缺点。

首先,新循环不像原始版本那样容易阅读。新循环可以清楚地显示出在i达到0值时循环终止,但是不能清楚地表示是向上计数还是向下计数。而在原始的循环中,根据控制表达式i > 0可以推断出这一信息。

其次,如果循环开始执行时i碰巧为负值,那么新循环的行为会不同于原始版本。原始循环会立刻终止,而新循环则不会。

问2:6.3节提到,大多数for循环可以利用标准模式转换成while循环。能给出一个反例吗?

答:当for循环体中含有语句时,6.3节给出的while模式将不再有效。

问3:哪个无限循环格式更可取,while(1)还是for(;;)?

答:C程序员传统上喜欢for(;;)的高效性。这是因为早期的编译器经常强制程序在每次执行while循环体时测试条件1。但是,对于现代编译器来说,在性能上两种无限循环应该没有差别。

问4:听说程序员应该永不使用语句。这种说法对吗?

答:语句的确很少使用。尽管如此,语句有时还是非常方便的。假设我们编写的循环要读入一些输入数据并测试其有效性,如果有效则以某种方法进行处理。如果有许多有效性测试,或者如果它们都很复杂,那么语句就非常有用了。循环将类似于下面这样:

 for(; ;) {  
  读入数据; 
  if(数据的第一条测试失败) 
    continue; 
  if(数据的第二条测试失败) 
    continue;   .   .   . 
  if(数据的最后一条测试失败) 
    continue; 
  处理数据; 
}

问5:goto语句有什么不好?

答:goto语句不是天生的魔鬼,只是通常它有更好的替代方式。使用过多goto语句的程序会迅速退化成“垃圾代码”,因为控制可以随意地跳来跳去。垃圾代码是非常难于理解和修改的。

问6:除了说明循环体为空外,空语句还有其他用途吗?

答:非常少。空语句可以放在任何允许放语句的地方,所以有许多潜在的用途。但在实际中,空语句只有一种别的用途,而且极少使用。

问7:除了把空语句单独放置在一行以外,是否还有其他方法可以凸显出空循环体?

答:一些程序员使用虚设的语句:

for (d = 2; d < n && n % d != 0; d++) 
  continue;

还有一些人使用空的复合语句:

for (d = 2; d < n && n % d != 0; d++) 
  {}

写在最后

本文是作者阅读《C语言程序设计:现代方法(第2版·修订版)》时所做笔记,日后会持续更新后续章节笔记。欢迎各位大佬批评指正,希望对诸位有所帮助,Thank you very much!