if语句
if/else语句
布尔代数
switch语句
if语句
目前我们写的简单函数中可以有多条语句,但这些语句总是从前到后顺序执行的。除了顺序执行之
外,有时候我们需要检查一个条件,然后根据检查的结果执行不同的后续代码,在
C
语言中可以用
分支语句(
Selection Statement
)实现,比如:
if (x != 0) {
printf("x is nonzero.\n");
}
其中
x != 0
表示
“x
不等于
0”
的条件,这个表达式称为控制表达式(
Controlling Expression
)如果条
件成立,则
{}
中的语句被执行,否则
{}
中的语句不执行,直接跳到
}
后面。
if
和控制表达式改变了程
序的控制流程(
Control Flow
),不再是从前到后顺序执行,而是根据不同的条件执行不同的语
句,这种控制流程称为分支(
Branch
)。上例中的
!=
号表示
“
不等于
”
,像这样的运算符有:
关系运算符和相等性运算符
运算符 | 含义 |
== | 等于 |
!= | 不等于 |
> | 大于 |
< | 小于 |
>= | 大于或等于 |
<= | 小于或等于 |
注意以下几点:
1.这里的
==
表示数学中的相等关系,相当于数学中的
=
号,初学者常犯的错误是在控制表达式中把==
写成
=
,在
C
语言中
=
号是赋值运算符,两者的含义完全不同。
2.如果表达式所表示的比较关系成立则值为真(
True
),否则为假(
False
),在
C
语言中分别
用
int
型的
1
和
0
表示。如果变量
x
的值是
-1
,那么
x>0
这个表达式的值为
0
,
x>-2
这个表达式的
值为
1。
3.在数学中a<b<c表示b既大于a又小于c,但作为C语言表达式却不是这样。以上几种运算符都是
左结合的,请读者想一下这个表达式应如何求值。
4.这些运算符的两个操作数应该是相同类型的,两边都是整型或者都是浮点型可以做比较,但
两个字符串不能做比较,在后面我们会介绍比较字符串的方法。
5.==和!=称为相等性运算符(Equality Operator),其余四个称为关系运算符(Relational
Operator
),相等性运算符的优先级低于关系运算符。
总结一下,
if (x != 0) { ... }
这个语句的计算顺序是:首先求
x != 0
这个表达式的值,如果值为0
,就跳过
{}
中的语句直接执行后面的语句,如果值为
1
,就先执行
{}
中的语句,然后再执行后面的语句。事实上控制表达式取任何非0
值都表示真值,例如
if (x) { ... }
和
if (x != 0) { ...}是等价的,如果
x
的值是
2
,则
x != 0
的值是
1
,但对于
if
来说不管是
2
还是
1
都表示真值。
和
if
语句相关的语法规则如下:
语句
→ if (
控制表达式
)
语句
语句
→ {
语句列表}
语句
→ ;
在
C
语言中,任何允许出现语句的地方既可以是由
;
号结尾的一条语句,也可以是由
{}
括起来的若干
条语句或声明组成的语句块(
Statement Block
),语句块和上一章介绍的函数体的语法相同。注意语句块的}
后面不需要加
;
号。如果
}
后面加了
;
号,则这个
;
号本身又是一条新的语句了,在
C
语言中一个单独的;
号表示一条空语句(
Null Statement
)。上例的语句块中只有一条语句,其实没必要写成语句块,可以简单地写成:
if (x != 0)
printf("x is nonzero.\n");
语句块中也可以定义局部变量,例如:
void foo(void)
{
int i = 0;
{
int i = 1;
int j = 2;
printf("i=%d, j=%d\n", i, j);
}
printf("i=%d\n", i); /* cannot access j here */
}
和函数的局部变量同样道理,每次进入语句块时为变量
j
分配存储空间,每次退出语句块时释放变
量
j的存储空间。语句块也构成一个作用域,和
作用域的分析类似,如果整个源文件是一张
大纸,
foo
函数是盖在上面的一张小纸,则函数中的语句块是盖在小纸上面的一张更小的纸。语句
块中的变量
i
和函数的局部变量
i
是两个不同的变量,因此两次打印的
i
值是不同的;语句块中的变
量
j
在退出语句块之后就没有了,因此最后一行的
printf
不能打印变量
j
,否则编译器会报错。语句
块可以用在任何允许出现语句的地方,不一定非得用在
if
语句中,单独使用语句块通常是为了定义
一些比函数的局部变量更
“
局部
”
的变量。
if/else语句
if
语句还可以带一个
else
子句(
Clause
),例如:
if (x % 2 == 0)
printf("x is even.\n");
else
printf("x is odd.\n");
这里的
%
是取模(
Modulo
)运算符,
x%2
表示
x
除以
2
所得的余数(
Remainder
),
C
语言规定
%
运
算符的两个操作数必须是整型的。两个正数相除取余数很好理解,如果操作数中有负数,结果应该
是正是负呢?
C99
规定,如果
a
和
b
是整型,
b
不等于
0
,则表达式
(a/b)*b+a%b
的值总是等于
a,再结表达式讲过的整数除法运算要Truncate Toward Zero,可以得到一个结论:
%运算符的结果总是与被除数同号(想一想为什么)。其它编程语言对取模运算的规定各不相同,也有规定结果和除数同号的,也有不做明确规定的。
取模运算在程序中是非常有用的,例如上面的例子判断
x
的奇偶性(
Parity
),看
x
除以
2
的余数是不
是
0
,如果是
0
则打印
x is even.
,如果不是
0
则打印
x is odd.
,读者应该能看出
else
在这里的作用
了,如果在上面的例子中去掉
else
,则不管
x
是奇是偶,
printf("x is odd.\n");
总是执行。为了
让这条语句更有用,可以把它封装(
Encapsulate
)成一个函数:
void print_parity(int x)
{
if (x % 2 == 0)
printf("x is even.\n");
else
printf("x is odd.\n");
}
把语句封装成函数的基本步骤是:把语句放到函数体中,把变量改成函数的参数。这样,以后要检
查一个数的奇偶性只需调用这个函数而不必重复写这条语句了,例如:
print_parity(17);
print_parity(18);
if/else
语句的语法规则如下:
语句
→ if (
控制表达式
)
语句
else
语句
右边的
“
语句
”
既可以是一条语句,也可以是由
{}
括起来的语句块。一条
if
语句中包含一条子语句,
一条
if/else
语句中包含两条子语句,子语句可以是任何语句或语句块,当然也可以是另外一
条
if
或
if/else
语句。根据组合规则,
if
或
if/else
可以嵌套使用。例如可以这样:
if (x > 0)
printf("x is positive.\n");
else if (x < 0)
printf("x is negative.\n");
else
printf("x is zero.\n");
也可以这样:
if (x > 0) {
printf("x is positive.\n");
} else {
if (x < 0)
printf("x is negative.\n");
else
printf("x is zero.\n");
}
现在有一个问题,类似
if (A) if (B) C; else D;
形式的语句怎么理解呢?可以理解成
if (A)
if (B)
C;
else
D;
也可以理解成
if (A)
if (B)
C;
else
D;
在
第
1
节
“
继续
Hello World”
讲过,
C
代码的缩进只是为了程序员看起来方便,实际上对编译器不起
任何作用,你的代码不管写成上面哪一种缩进格式,在编译器看起来都是一样的。那么编译器到底
按哪种方式理解呢?也就是说,
else
到底是和
if (A)
配对还是和
if (B)
配对?很多编程语言的语法
都有这个问题,称为
Dangling-else
问题。
C
语言规定,
else
总是和它上面最近的一个
if
配对,因此
应该理解成
else
和
if (B)
配对,也就是按第二种方式理解。如果你写成上面第一种缩进的格式就很
危险了:你看到的是这样,而编译器理解的却是那样。如果你希望编译器按第一种方式理解,应该
明确加上
{}
:
if (A) {
if (B)
C;
} else
D;
顺便提一下,浮点型的精度有限,不适合用
==
运算符做精确比较。以下代码可以说明问题:
double i = 20.0;
double j = i / 7.0;
if (j * 7.0 == i)
printf("Equal.\n");
else
printf("Unequal.\n");
不同平台的浮点数实现有很多不同之处,在我的平台上运行这段程序结果为
Unequal
,即使在你的
平台上运行结果为
Equal
,你再把
i
改成其它值试试,总有些值会使得结果为
Unequal
。等学习了
第
4
节
“
浮点数
”
你就知道为什么浮点型不能做精确比较了。
布尔代数
在
第
1
节
“if
语句
”
讲过,
a<b<c
不表示
b
既大于
a
又小于
c
,那么如果想表示这个含义该怎么写呢?可
以这样:
if (a < b) {
if (b < c) {
printf("b is between a and c.\n");
}
}
我们也可以用逻辑与(
Logical AND
)运算符表示这两个条件同时成立。逻辑与运算符在
C
语言中写成两个&
号(
Ampersand
),上面的语句可以改写为:
if (a < b && b < c) {
printf("b is between a and c.\n");
}
对于
a < b && b < c
这个控制表达式,要求
“
a < b
的值非
0”
和
“
b < c
的值非
0”
这两个条件同时成立
整个表达式的值才为
1
,否则整个表达式的值为
0
。也就是只有两个条件都为真,它们做逻辑与运算
的结果才为真,有一个条件为假,则逻辑与运算的结果为假,如下表所示:
A | B | A AND B |
0 | 0 | 0 |
0 | 1 | 0 |
1 | 0 | 0 |
1 | 1 | 1 |
这种表称为真值表(
Truth Table
)。注意逻辑与运算的操作数以非
0
表示真以
0
表示假,而运算结果
以
1
表示真以
0
表示假(类型是
int
),我们忽略这些细微的差别,在表中全部以
1
表示真以
0
表示 假。C
语言还提供了逻辑或(
Logical OR
)运算符,写成两个
|
线(
Pipe Sign
),逻辑非(
Logical
NOT
)运算符,写成一个
!
号(
Exclamation Mark
),它们的真值表如下:
A | B | A OR B |
0 | 0 | 0 |
0 | 1 | 1 |
1 | 0 | 1 |
1 | 1 | 1 |
逻辑或表示两个条件只要有一个为真,它们做逻辑或运算的结果就为真,只有两个条件都为假,逻
辑或运算的结果才为假。逻辑非的作用是对原来的逻辑值取反,原来是真的就是假,原来是假的就
是真。逻辑非运算符只有一个操作数,称为单目运算符(
Unary Operator
),以前讲过的加减乘 除、赋值、相等性、关系、逻辑与、逻辑或运算符都有两个操作数,称为双目运算符(Binary
Operator
)。
关于逻辑运算的数学体系称为布尔代数(
Boolean Algebra),以它的创始人布尔命名。在编程语
言中表示真和假的数据类型叫做布尔类型,在
C
语言中通常用
int
型来表示,非
0
表示真,
0
表示假[
6
]
。布尔逻辑是写程序的基本功之一,程序中的很多错误都可以归因于逻辑错误。以下是一些布尔代数的基本定理,为了简洁易读,真和假用1
和
0
表示,
AND
用
*
号表示,
OR
用
+
号表示(从真值表可以看出AND
和
OR
运算确实有点像乘法和加法运算),
NOT
用
¬
表示,变量
x
、
y
、
z
的值可能是0
也可能是
1
。
除了第1行之外,这些公式都是每两行一组的,每组的两个公式就像对联一样:把其中一个公式中 的*换成+、+换成*、0换成1、1换成0,就变成了与它对称的另一个公式。这些定理都可以通过真 值表证明,更多细节可参考有关数字逻辑的教材,例如[数字逻辑基础]。
目前为止介绍的这些运算符的优先级顺序是:
!
高于
* / %
,高于
+ -
,高于
> < >= <=
,高于
== !=
,
高于
&&
,高于
||
,高于
=
。写一个控制表达式很可能同时用到这些运算符中的多个,如果记不清楚
运算符的优先级一定要多套括号。我们将在
运算符总结
总结
C
语言所有运算符的优先级和结合性。
switch语句
switch
语句可以产生具有多个分支的控制流程。它的格式是:
switch (
控制表达式
) {
case
常量表达式: 语句列表
case
常量表达式: 语句列表
...
default
: 语句列表
}
例如以下程序根据传入的参数
1~7
分别打印
Monday~Sunday
:
如果传入的参数是
2
,则从
case 2
分支开始执行,先是打印相应的信息,然后遇到
break
语句,它的
作用是跳出整个
switch
语句块。
C
语言规定各
case
分支的常量表达式必须互不相同,如果控制表达
式不等于任何一个常量表达式,则从
default
分支开始执行,通常把
default
分支写在最后,但不是
必须的。使用
switch
语句要注意几点:
1.case后面跟表达式的必须是常量表达式,这个值和全局变量的初始值一样必须在编译时计算
出来。
2.
第
2
节
“if/else
语句
”讲过浮点型不适合做精确比较,所以C语言规定case后面跟的必须是
整型
常量表达式。
3.进入case后如果没有遇到break语句就会一直往下执行,后面其它case或default分支的语句
也会被执行到,直到遇到
break
,或者执行到整个
switch
语句块的末尾。通常每个
case
后面都
要加上
break
语句,但有时会故意不加
break
来利用这个特性,例如:
switch
语句不是必不可缺的,显然可以用一组
if ... else if ... else if ... else ...
代替,
但是一方面用
switch
语句会使代码更清晰,另一方面,有时候编译器会对
switch
语句进行整体优
化,使它比等价的
if/else
语句所生成的指令效率更高。