C语言入门(四)——分支语句

简介: C语言入门(四)——分支语句

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

左结合的,请读者想一下这个表达式应如何求值。

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既大于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 。

image.png

image.png

 除了第1行之外,这些公式都是每两行一组的,每组的两个公式就像对联一样:把其中一个公式中 的*换成++换成*0换成11换成0,就变成了与它对称的另一个公式。这些定理都可以通过真 值表证明,更多细节可参考有关数字逻辑的教材,例如[数字逻辑基础]

目前为止介绍的这些运算符的优先级顺序是: ! 高于 * / % ,高于 + - ,高于 > < >= <= ,高于 == != ,

高于 && ,高于 || ,高于 = 。写一个控制表达式很可能同时用到这些运算符中的多个,如果记不清楚

运算符的优先级一定要多套括号。我们将在 运算符总结 总结 C 语言所有运算符的优先级和结合性。

switch语句


switch语句可以产生具有多个分支的控制流程。它的格式是:

switch (控制表达式) {

case 常量表达式: 语句列表

case 常量表达式: 语句列表

...

default: 语句列表

}

例如以下程序根据传入的参数1~7分别打印Monday~Sunday

image.png

如果传入的参数是 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 来利用这个特性,例如:

image.png

switch 语句不是必不可缺的,显然可以用一组 if ... else if ... else if ... else ... 代替,

但是一方面用 switch 语句会使代码更清晰,另一方面,有时候编译器会对 switch 语句进行整体优

化,使它比等价的 if/else 语句所生成的指令效率更高。




相关文章
|
2月前
|
C语言
初识C语言2——分支语句和循环语句
初识C语言2——分支语句和循环语句
77 5
|
2月前
|
安全 编译器 C语言
C++入门1——从C语言到C++的过渡
C++入门1——从C语言到C++的过渡
66 2
|
14天前
|
存储 NoSQL 编译器
【C语言】指针的神秘探险:从入门到精通的奇幻之旅 !
指针是一个变量,它存储另一个变量的内存地址。换句话说,指针“指向”存储在内存中的某个数据。
66 3
【C语言】指针的神秘探险:从入门到精通的奇幻之旅 !
|
2月前
|
C语言
【c语言】分支语句
C语言通过三种基本结构——顺序、选择和循环,构建复杂的程序逻辑。本文主要介绍了C语言的选择结构,即if-else语句及其变体,包括简单的if语句、if-else组合、else if多分支判断、嵌套if以及解决悬空else问题的方法。此外,还详细讲解了逻辑运算符和关系运算符的使用,以及如何利用条件操作符简化逻辑判断。最后,文章对比了if-else与switch语句在实现多分支逻辑时的应用,并解释了switch语句中的break和default关键字的作用。
33 8
|
2月前
|
存储 Java 编译器
初识C语言1——C语言入门介绍
初识C语言1——C语言入门介绍
33 1
|
2月前
|
Serverless C语言
C语言控制语句:分支、循环和转向
C语言控制语句:分支、循环和转向
|
2月前
|
算法 编译器 C语言
【C语言】实现猜数字游戏(分支语句与循环语句的运用)
【C语言】实现猜数字游戏(分支语句与循环语句的运用)
|
2月前
|
C语言
教你快速理解学习C语言的循环与分支
教你快速理解学习C语言的循环与分支
17 0
|
2月前
|
C语言
回溯入门题,数据所有排列方式(c语言)
回溯入门题,数据所有排列方式(c语言)
|
2月前
|
程序员 C语言
【C语言】分支语句(逻辑运算符与关系运算符)
【C语言】分支语句(逻辑运算符与关系运算符)