逻辑运算符
备选拼写:iso646.h头文件
C是在美国用标准美式键盘开发的语言,但是在世界各地,并非所有的键盘都有和美式键盘一样的符号,因此C99标准新增了***可代替逻辑运算符***的拼写,他们被定义在iso646.h头文件中,如果在程序中包含该头文件,便可用and代替&&、or代替||、not代替!上述程序就可相应代替更改
为什么C不直接用and or not
因为C一直坚持尽量保持较少的关键字
优先级:!>算术运算符>关系运算符>逻辑运算符>赋值运算符>逗号运算符
与自增运算符优先级相同,只比圆括号的优先级低
求值顺序
除了两个运算符共享一个运算对象的情况外(a++只能对a进行相应的运算),C通常不保证先对复杂表达式中哪部分求值,例如:apples=(5+3)(9+6)可能先对5+3求值,也可能先对9+6求值;
*C把先计算哪部分的决定权留给编译器的设计者,以便针对特定系统优化设计。*****
但是对于逻辑运算符是个例外,C保证逻辑表达式的求值顺序是从左往右,**&&和||都是序列点***,所以程序在从一个运算对象执行到下一个运算对象之前,所有的副作用都会生效(副作用是指什么:)而且,C保证一旦发现某个元素让整个表达式无效,便立即停止求值,正是由于这些规定,才能写出这样结构的代码:
while ((c=getchar())!=’ ’ && c != ’ \n ') 副作用:如果没有求值顺序的保证,编译器可能在给C赋值之前先对后面的表达式求值
重要例子:
while ( x++ < 10 && x+y < 20)
实际上,&&是一个序列点,这表示在对&&右侧的表达式求值之前,已经递增了x.
小结:逻辑运算符和表达式
逻辑运算符:
逻辑运算符的运算对象通常是关系表达式。!运算符通常只需要一个运算对象,其他两个逻辑运算符都需要两个运算对象,左侧一个,右侧一个。
求值顺序:逻辑表达式的求值顺序是从左往右,一旦发现有使整个表达式为假的因素,立即停止求值。
范围:
&& 运算符可用于测试范围
需要注意不要使用数学上的写法:if(range >= 90 && range <= 100 )
不能写成 if(90<= range <= 100)
问题是代码有语义错误而不是语法错误,所以编译器不会捕获这样的问题(虽然可能会给出警告)
许多代码都用范围测试来确定一个字符是否是小写字母。
例如:假设ch 是 char 类型的变量:
if ( ch >= ’ a ’ && ch <= ’ z ')
printf ( "That is a lowercase character . \n ");
注意:该方法仅对于像ASCII这样的字符编码有效,这些编码中相邻字母与相邻数字一一对应
但是对于像EBCDIC这样的代码就没用了。相应的可移植方法是,用 ctype.h 系列中的 islower() 函数
if( islower(ch))
printf ( " That ’ s a a lowercase character . \n ");
无论使用哪种特定的字符编码 , islower( ) 函数都能正常运行(不过,一些早期的编译器没有 ctype.h系列)
一个统计单词的程序
编写一个统计单词数量的程序(即该程序读取并报告单词的数量)。该程序还可以计算字符数和行数。
思路:1.该程序要逐个字符读取输入,知道何时停止读取。
2.该程序能识别并计算这些内容:字符、行数和单词。据此,我们编写的伪代码如下:
读取一个字符
当有更多输入时
递增字计数
如果读完一行,递增行数计数
如果读完一个单词,递增单词计数
读取下一个字符
前面有一个输入循环的模型:
while ( ( ch = getchar ( ) ) != STOP )
{
…
}
这里,STOP表示能标识输入末尾的某个值。以前我们用过换行符和句点标记输入的末尾,但是对于一个通用的统计单词程序,他们都不合适。我们暂时选用一个文本中都不常用的字符(如,|)作为输入的末尾标记。第八章中会介绍更好的方法,以便程序既能处理文本文件,又能处理键盘输入。
现在,考虑循环体,因为该程序使用getchar ()进行输入,所以每次迭代都要通过递增计数器来计数,为了统计行数,程序要能检查换行字符,如果输入的字符是一个换行符,该程序应该递增行数计数器。这里要注意STOP字符位于一行的中间的情况,是否递增行数计数,即设有换行符的一行字符,可以通过记录之前读取的字符识别这种情况,即如果读取时发现STOP字符的上一个字符不是换行符,那么这行就是特殊行。
最棘手的部分是识别单词,首先必须定义什么是程序识别的单词。我们用一个相对简单的方法把一单词定义为一个不含空白(即,没有空格、制表符、或换行符)的字符序列。即程序读取的第一个非空白字符就是一个单词的开始,当读到空白字符时结束,判断非空白字符最直接的测试表达式是
c != ' ' && c != ' \n ' && c != ' \t ' /*如果C不是空白字符,该表达式为真*/
c == ' ' || c == ' \n ' || c == ' \t ' /* 如果C是空白字符,该表达式为真*/
然而,使用 ctype.h 头文件中的函数isspace( ) 更简单,如果该函数的参数是空白字符,则返回真,所以,如果c是空白字符,isspace©为真;如果c不是空白字符,!isspace( c ) 为真。
要查找一个单词里是否有某个字符,可以在程序读入单词的首字符时把一个标记(名为inword)设置为1.也可以在此时递增单词计数。然后,只要inword为1(或true),后续的非空白字符都不计为单词的开始。下一个空白字符,必须重置标记为0(或fause),然后程序就准备好读取下一个单词。我们把以上分析写成伪代码:
如果C不是空白字符,且inword为假
设置inword 为真,并给单词计数
如果C是空白字符,且 inword 为真
设置inword为假
这种方法在读到每个单词的开头时把inword 设置为1(真),在读到每个单词的末尾时把inword设置为0(假),只有在标记从零设置为1时,递增单词计数。如果能使用_BOOL类型,可以在程序中包含stdbool.h头文件,把inword类型设置为bool,其值用true或false来表示,如果编译器不支持这种用法,就把inword的类型设置为int,其值用1和0表示,如果使用Bool类型的变量,通常习惯把变量自身作为测试条件。如下所示:
用if ( inword ) 代替 if( inword == true )
用if ( !inword) 代替if ( inword == false)
可以这样做的原因是,如果 inword 为 true ,则表达式 inword == true ;如果 inword 为false,则表达式 inword == true为false.所以还不如直接用inword作为测试调件。类似的,!inword的值与表达式inword == false的值相同(非真即false,非假即true)
与之相应的程序,要注意的地方以及思路都要记住
!inword 与 inword == false等价。上面的整个测试条件比单独判断每个空白字符的可读性高:
if ( c != ’ ’ && c != ’ \n ’ && c != ’ \t ’ && !inword)
上面的两种形式都表示“如果C不是空白字符,且如果C不在单词里”。如果两个条件都满足
,则一定是一个新单词的开头,所以要递增 n_words.如果位于单词中,满足第一个条件,但是inword为true,就不递增n_word,当读到下一个空白字符时,inword 被再次设置为false.检查代码,查看一下,如果单词之间有多个空格时,程序是否能正常运行。后续会讲解如何修正这个程序,让该程序能统计文件中的单词量。
7.5条件运算符 ?:
C提供条件表达式作为表达if else 语句的一种便捷方式,该表达式使用 ?: 条件运算符。该运算符分为两部分,需要三个运算对象,带一个运算对象的运算符 称为一元运算符,带两个运算对象的运算符称为二元运算符。以此类推,带三个运算对象的运算符称为三元运算符。条件运算符是C语言中唯一的三元运算符。下面的代码得到一个数的绝对值:
x = ( y < 0 ) ? -y : y ;
在 = 和 ;之间的内容就是条件表达式 ,该语句的意思是“ 如果 y 小于 0 , 那么 x = -y , 否则 x= y”
用if else 语句可以这样替换:
if (y<0)
x=-y;
else x=y;
条件表达式的通用形式如下:
语句1?语句2:语句3
如果1为真,那么整个表达式的值与2相同,如果1为假,那么整个表达式的值与3相同
需要把两个值中的一个赋给变量时,就可以用条件表达式。典型的例子是,把两个值中的最大值赋给变量:
max = ( a > b ) ? a : b ;
使用条件运算符优点:代码更简洁,而且编译器可以生成更紧凑的程序代码
注意:上面输出的地方也说明了运算符的第二个和第三个运算对象可以是字符串
7.6:循环辅助:CONTINUE 和 BREAK
一般而言,程序进入循环后,在下一次循环测试之前会执行完循环体中的所有语句。continue 和 break 语句可以根据循环体中的测试结果来忽略一部分内容,甚至结束循环。
***continue语句***三种循环都可以使用,执行到该语句时,会跳过本次迭代的剩余部分,并开始下一轮迭代,如果continue 语句在嵌套循环内,则只会影响包含该语句的内层循环
注意,有两种方法可以避免使用continue
1.省略continue ,把剩余部分放在一个 else 块中
if ( score < 0 || score > 100 )
{
printf("");
}
else
{
语句…
}
另外一种方法是,用以下格式代替:
if ( score > 0 && score < 100)
{
语句…
}
这种情况下,使用 continue 好处是减少主语句组中的一级缩进
当语句很长或嵌套较多时,紧凑简洁的格式提高了代码的可读性。
continue 还可用作占位符
例如:下面的循环读取并丢弃输入的数据,直至读到行末尾:
while ( getchar() ! = ’ \n ')
;
当程序已经读取一行中的某些内容时,要跳至下一行开始处时,这种用法很方便。问题是,一般很难注意到一个单独的分号。如果使用continue 可读性会更高:
while ( getchar() ! = ’ \n ')
continue;
如果用了 continue 没有简化代码反而让代码更加复杂,就不要使用coontinue .例如 ,考虑下面的程序段:
while ( getchar() ! = ’ \n ')
{
if ( ch == ‘\t’ )
continue;
putchar(ch);
}
该循环跳过制表符,并在读到换行符时退出循环。以上代码这样表示更简洁:
while ( getchar() ! = ’ \n ')
{
if ( ch != ‘\t’ )
putchar(ch);
}
通常,在这种情况下,把 if 的测试条件的关系反过来便可避免使用contnue .
while 和 for 循环在遇到 continue 时的区别:
count = 0 ;
while ( count < 10)
{
ch = getchar ( ) ;
if ( ch == ’ \n ')
continue ;
putchar( ch );
count ++ ;
}
该循环读取10个字符(除换行符外,因为当 ch 是换行符,程序会跳过 count++ ;语句)并重新显示他们,其中不包括换行符。执行continue 后,下一个被求值的表达式是循环测试条件
对于for循环,执行continue 后,首先递增,然后是对循环测试表达式求值。例如:考虑下面的循环:
for(count = 0 ;count < 10;count ++ )
{
ch = getchar ( ) ;
if ( ch == ' \n ')
continue ;
putchar( ch );
}
该例中,执行完continue后,首先递增count,然后将递增后的值和10做比较,因此,该循环与上面while循环的例子稍有不同。while循环的例子中,除了换行符,其余字符都显示;而本例中,换行符也计算在内,所以读取的四个字符中包括换行符。
Break语句
continue是省略循环接下来的过程,进入下一轮循环,Break是跳出当前循环,执行循环外的部分
详细描述:程序执行到循环中的Break语句时,会终止包含他的循环,并继续执行下一阶段,如果BREAK语句位于嵌套循环内,则只会影响包含他的当前循环;
Break还可用于因其他原因退出循环的情况。
书上还写到可以这样控制循环:
while(scanf("%f %f",&Length,&Width) == 2)(但没有成功,了解有这种用法即可)
但是,用break 可以方便显示用户输入的值。
和continue一样,如果用了break代码反而更复杂,就不要用break,例如:考虑下面的循环:
while( (ch = getchar () )!= ‘\n’)
{
if ( ch == ‘\t’ )
break;
putchar(ch );
},果把两个测试条件放在一起,逻辑就更清晰了:
while( ( ch = getchar ())!= ‘\n’ && ch != ‘\t’ )
putchar(ch);
break语句对于稍后讨论的switch 语句而言至关重要。
在for 循环中break 和 continue 的情况不同执行完break 语句后,会直接执行循环后面的第一条语句,连更新部分也跳过
嵌套循环内层的break只会让程序跳出包含它的当前循环,要跳出外层循环,还需要一个Break;
int p,q ;
scanf ( " %d ",&p);
while ( p > 0 )
{
printf ( " %d \n ", p);
scanf ( "%d \n", &q);
while ( q > 0 )
{
printf ( " %d \n ", p*q);
if ( q > 100 )
break; //跳出内层循环
scanf ( " %d ", &q );
}
if ( q > 100)
break; //跳出外层循环
scanf ( " %d ",&p );
}
7.7 多重选择 : switch 和 break
使用条件运算符和 if else 语句很容易编写 二选一的程序。
然而 有时程序需要在多个选项中进行选择,使用switch 更方便;
## switch 语句
要对紧跟在关键字switch后圆括号中的表达式求值。输入值,直到发现与之匹配的值为止,如果没有匹配的标签:如果有default : 标签行,就跳转至该行 否则,程序继续执行在switch后面的语句。
break 语句在其中起什么作用:它让程序离开switch语句,跳至switch语句后面的下一条语句,如果没有break 语句,就会从匹配标签开始执行到Switch末尾
break 用于循环和switch 语句中,但是continue只能用于循环中。尽管如此,如果switch 语句在一个循环中,continue便可作为 switch的一部分,这种情况下,就像在其他循环中一样,continue让程序跳出循环的剩余部分,包括switch语句的其他部分
另外,C语言的case 一般都指定一个值,不能使用一个范围。
switch 在圆括号中的测试表达式的值应该是一个整数值(包括char 类型)。case 标签必须是整数类型(包括char类型)的常量或整型常量表达式(即,表达式中只包含整型常量表达式)。不能用变量作为case 标签。switch 的构造如下:
switch ( 整型常量表达式 )
{
case 常量1:
语句 <–可选
default:
语句 <–可选
}
#include "stdio.h"
#include "ctype.h"
int main(void)
{
char ch;
printf(" letter \n");
printf("# end \n");
while((ch = getchar()) != '#')
{
if( ch == '\n' ) //处理首字符就是换行符的特殊情况
continue;
ch = tolower(ch); ///将大写字符转换成小写字符
switch(ch)
{
case 'a':
printf("ant.\n");
break;
case 'b':
printf("bee\n");
break;
case 'c':
printf("cee\n");
break;
case 'd':
printf("dee\n");
break;
default:
printf("I love all animals\n");
}
while(getchar() != '\n')
continue; ///continue 只用于循环中,输入all l不等于回车则跳过switch循环,进入下一次循环
}
return 0;
}
///注意:有无BREAK的程序 运行的区别
只读每行的首字符
上述程序的另一个独特之处是它读取输入的方式。运行程序时读者可能注意到了,当输入dab时,只处理了第一个字符。这种丢弃一行中其他字符的行为,经常出现在响应单字符的交互程序中:可以用下面的代码实现这样的行为:
while(getchar() != ‘\n’)
continue; ///continue 只用于循环中,输入all l不等于回车则跳过switch循环,进入下一次循环
***循环从输入中读取字符,包括按下Enter键产生的换行符***注意函数的返回值并没有赋给ch,以上代码所做的只是读取并丢弃字符。由于最后丢弃的字符是换行符,所以下一个被读取的字符是下一行的首字母。在外层的while循环中,getchar()读取首字母并赋给ch.
假设用户一开始就按下Enter键,那么程序读到的首个字母就是换行符。下面的代码处理这种情况:
if ( ch == ‘\n’)
continue;
多重标签
可以在switch 中采用多重标签
上述animals.c的程序中,如果输入了大写的字母,会转换成小写的字母
如果不用可以转换的函数,,那么就要使用多重标签
多重标签解释:
B 当输入大写字母B时,会跳转到B的语句,但B的语句是空语句,没有BREAK ,则会跳转到a的语句中,并执行a的break;
switch 和 if else
如果是根据浮点类型的变量或表达式来选择,就无法使用 switch .如果根据变量在某范围内决定程序流的去向,使用switch 就很麻烦,这种情况用if 就很方便;
if ( integer < 1000 && integer > 2 )
使用switch 要涵盖以上范围,需要为每个整数(3–999)设置 case 标签。但是,如果使用switch,程序通常运行快一些,生成的代码少一些;
goto 语句
建议不要使用
goto 语句有两部分:goto 和标签名。标签的命名遵循变量命名的规则,如下所示:
goto part2;
要让这条语句正常工作,函数还必须包含另一条标为part2的语句,该语句以标签名后紧跟一个冒号开始,
part2: printf(" GOOD MORNING \n ");
避免使用 goto
这部分还没有看,
如果以后学习 BASIC 和 FORTRAN ,再了解即可
对应课本178页
本次学习的是第七章内容