C语言-第5章-流程控制之分支(选择)结构

本章介绍了分支语句,包括最常用的if语句和switch语句,分支语句之间还可以嵌套,分支语句中的条件可以组成复杂的逻辑关系式子,这为解决较为复杂的问题提供了帮助。也是学习的重点和难点。使用分支语句是要避免一些常见的错误,这对后续的循环语句也是有效的。

5.0 一个导入的例子

当输入的成绩不及格的时候可以给出警示信息,这个可以用if语句来实现

if (grade < 60){
  printf("小朋友,这次考试不及格,打起精神哦\n");
}

if语句作为一个整体,当if后的小括号里面的条件式子结果为真(条件成立)就执行其后大括号里面的语句。否则,跳过大括号里面的语句。不论条件是否成立,执行完if后继续执行if后面的语句。

大括号不是必须的,但是如果没有大括号,那么只有紧跟在if (条件式子)后面的一条语句是属于if语句的,即当条件成立会执行的语句/条件不成立会跳过的语句。而用大括号的话里面包含的所有语句都是属于if语句的。

不论不及格还是及格都给出文字提示的话可以像下面这样来实现

if (grade < 60){
  printf("小朋友,这次考试不及格,打起精神哦\n");
}
if (grade >= 60){
  printf("恭喜,及格通过\n");
}

注意到上述两条if语句是没有关系的,只是先后执行的独立的语句,所以条件式子要计算两次,但是显然,它们的条件式子具有相反的逻辑值,这种情况下, 可以直接使用if-else语句

if (grade < 60){
  // if分支里面的语句,条件成立时执行
  printf("小朋友,这次考试不及格,打起精神哦\n");
}else{
  // else分支里面的语句,条件不成立时执行  
  printf("恭喜,及格通过\n");
}

if-else的含义是当条件式子成立就执行if分支里面的语句,否则,执行else分支里面的语句。

我们知道成绩除了不及格、及格,还有良好、优秀等评价,假设在60到80之间为及格,在80到90之间为良好,90到100之间为优秀。怎样利用if-else语句根据分数来输出相应的评价呢?可以在else里面嵌套使用if-else的方式来完成

if (grade < 60){
  printf("不及格\n");
}else{
  if (grade < 80){
    printf("及格\n");
  }else{
    if (grade < 90){
      printf("良好\n");
    }else{
      printf("优秀\n");
    }
  }
}

上述代码看起来很复杂,实际上就是在else分支里面继续使用if-else语句。这种用法有一个专门的名词,叫级联式的if-else语句。级联式的if-else语句推荐的写法如下

if (grade < 60){
  printf("不及格\n");
}else if (grade < 80){
  printf("及格\n");
}else if (grade < 90){
  printf("良好\n");
}else{
  printf("优秀\n");
}

上述写法和有缩进的写法是等价的,只是去掉了一些可以去掉的大括号,并将if写到了与else一行。这种写法是适合级联式if-else语句的特殊写法。

以上程序完整的代码如下

#include <stdio.h>

int main(){

  int grade;
  printf("请输入考试成绩:");
  scanf("%d", &grade);
  
  if (grade < 60){
    printf("不及格\n");
  }else if (grade < 80){
    printf("及格\n");
  }else if (grade < 90){
    printf("良好\n");
  }else{
    printf("优秀\n");
  }
  
  return 0;
}

由于成绩是输入到程序中的,用户还可以输入负数或者超过100分的成绩,显然它们是不合理的,在这种情况下程序可以给出文字提示,实现方案如下,

方案1.继续使用级联if-else方式

  if (grade < 0){
    printf("输入错误\n");
  }else if (grade < 60){
    printf("不及格\n");
  }else if (grade < 80){
    printf("及格\n");
  }else if (grade < 90){
    printf("良好\n");
  }else if (grade <= 100){
    printf("优秀\n");
  }else{
    printf("输入错误\n");
  }

方案2. 使用逻辑运算符且&&

if (0 <= grade && grade <= 100){
  if (grade < 60){
    printf("不及格\n");
  }else if (grade < 80){
    printf("及格\n");
  }else if (grade < 90){
    printf("良好\n");
  }else{
    printf("优秀\n");
  }
}else{
  printf("输入错误\n");
}

逻辑运算符且&&的含义是当其两边的条件式子都为真,它的结果才为真,否则只要有某一个为假,结果就是假。

方案3. 使用逻辑运算符或||

if (grade < 0 || grade > 100){
  printf("输入错误\n");
}else{
  if (grade < 60){
    printf("不及格\n");
  }else if (grade < 80){
    printf("及格\n");
  }else if (grade < 90){
    printf("良好\n");
  }else{
    printf("优秀\n");
  }
}

逻辑运算符或||的含义是当其两边的条件式子只要有一个为真,它的结果就是为真,否则只有当两边都为假,结果才是假。

5.1 if语句

5.1.1 基本的if语句

下面实现程序:输入球的半径值,计算球的体积并输出。先考虑整个程序的框架

#include <stdio.h>
int main()
{
  // 定义变量半径、体积,输入值到变量中 
  // 根据半径计算体积
  // 输出体积
}

然后给出代码

#include <stdio.h>
#define PI 3.14
int main()
{
  // 定义变量半径、体积,输入值到变量中 
  float radius, volumn;
  printf("请输入球的半径:");
  scanf("%f", &radius);
   
  // 根据半径计算体积
  volumn = 4.0f / 3.0f * PI * radius * radius * radius;
  
  // 输出体积
  printf("半径为%.2f的球的体积是%.2f\n", radius, volumn);
  
  return 0;
}

这里,为了引出分支语句,我们故意假设用户在输入半径的时候可能输入负数,显然,如果程序不做特殊处理,负数的半径值会算出负的体积值。换句话说,只有在输入半径值大于等于0的时候才应该执行计算和输出体积的操作。这要求程序的伪代码是:

#include <stdio.h>
int main()
{
  // 定义变量半径、体积,输入值到变量中 
  // 如果半径值大于等于0,则根据半径值计算体积,然后输出体积
}

转换成代码就是

#include <stdio.h>
#define PI 3.14
int main()
{
  // 定义变量半径、体积,输入值到变量中 
  float radius, volumn;
  printf("请输入球的半径:");
  scanf("%f", &radius);
   
  if (radius >= 0 ){
    // 根据半径计算体积
    volumn = 4.0f / 3.0f * PI * radius * radius * radius;
  
    // 输出体积
    printf("半径为%.2f的球的体积是%.2f\n", radius, volumn);
  }
  
  return 0;
}

这里,我们使用了if语句来选择性的执行计算球的体积和输出体积的语句。if语句的基本结构如下:

...//if语句前的语句
if (条件式子){
   //条件式子为真时执行的语句
   语句
   ...
}
...//if语句后的语句

注意,条件式子两边的圆括号是必需的,它们是if语句的一部分,而不是条件式子的内容。
当程序执行到上述if语句,逻辑上看if语句作为一个整体来执行,而它的执行又分为几小步:首先计算圆括号中的条件式子的值,得到逻辑值真或者假,然后再根据逻辑值的真或者假,选择性的执行大括号里面的语句。如果为真则执行大括号里面的所有语句,如果条件式子的值为假,则不执行大括号里面的语句。这样,整个if语句就算执行完毕,再执行if语句后面的语句。

在这里插入图片描述

if语句中大括号{}里面的代码的缩进不是必须的,就像main函数大括号里面的代码相对main有缩进一样,这个缩进是书写代码的推荐写法,体现了程序的包含关系。

这里要声明一点,if语句中的大括号{}不是必需的,如果if语句中没有大括号,那么只有后续的一条语句是属于if语句的,即是当条件式子为真时会执行的语句,当条件式子为假时不执行这条语句。故如果条件为真时需要执行的是一条语句,包含该条语句的大括号可以省略,

if (i > 0){
  printf("i是正数\n");
}

可以省略大括号,写为

if (i > 0)
  printf("i是正数\n");

反之,当条件成立要执行的语句是多条语句,大括号是不能省略的,否则,程序的逻辑就错误了,比如,下面的代码忘记使用大括号将计算体积和输出体积的代码括起来了,

#include <stdio.h>
#define PI 3.14
int main()
{
  // 定义变量半径、体积,输入值到变量中 
  float radius, volumn;
  printf("请输入球的半径:");
  scanf("%f", &radius);
   
  if (radius >= 0 )
    // 根据半径计算体积
    volumn = 4.0f / 3.0f * PI * radius * radius * radius;
  
    // 输出体积,现在它是if语句之后的语句,不论radius>=0是否成立,都会执行
    printf("半径为%.2f的球的体积是%.2f\n", radius, volumn);
  
  
  return 0;
}

运行程序,输入半径值-1,程序没有输出正确的结果,而是出现运行时错误

在这里插入图片描述

这是由于,if语句只包含一条语句,就是计算体积的语句,而输出体积的语句与if语句无关,是if语句后面不论radius>=0的条件是否成立,一定会执行的语句。当输入了负数的半径值,if条件不成立,故没有执行体积计算的语句,体积变量是未初始化的。而printf语句中对它的访问就导致VS2010出现了运行时错误。

上述代码反映程序结构的正确缩进应该是

#include <stdio.h>
#define PI 3.14
int main()
{
  // 定义变量半径、体积,输入值到变量中 
  float radius, volumn;
  printf("请输入球的半径:");
  scanf("%f", &radius);
   
  if (radius >= 0 )
    // 根据半径计算体积
    volumn = 4.0f / 3.0f * PI * radius * radius * radius;
  
  // 输出体积,现在它是if语句之后的语句,不论radius>=0是否成立,都会执行
  printf("半径为%.2f的球的体积是%.2f\n", radius, volumn);
  
  
  return 0;
}

因此,如果if语句中没有大括号,那么只有后续的一条语句是属于if语句的。而且可以看到,输出体积的printf语句的缩进与它是否属于if语句没有任何关系,即代码的缩进对程序的结构没有影响,代码的缩进只是辅助人们辨认程序结构的编码书写规范。

建议不论选择语句要执行一条还是多条语句,都用大括号括起来,避免必须使用大括号时遗漏大括号。

if语句圆括号中条件式子的结果是逻辑值真或者假,条件式子往往是关系运算符或者逻辑运算符算出的结果。接下来介绍关系运算符。

5.1.2 关系运算符

关系运算符用来比较两个值之间的大小、以及相等关系。它们是

运算符数学关系含义例子(x值为1)逻辑值实际值
<<小于x < 00
<=小于或等于x <= 00
>>大于x > 01
>=大于或等于x >= 01
===等于x == 00
!=不等于x != 01

在C语言中,并没有逻辑类型以及逻辑值真和逻辑值假。关系运算符得到的结果是整数值1或者0。在逻辑成立的时候得到的值是1,在逻辑不成立的时候得到的值是0。比如4 > 0的值为1,4 <= 0的值为0。

printf("%d\n", 4 > 0);  // 1
printf("%d\n", 4 <= 0); // 0

在C语言中,非零值会被视为真,零会被视为假。故在if语句判断圆括号中的表达式为真假的时候,4>0就被视为真,4<=0就被视为假。

不同数值类型也可以比较,1 < 2.5的值为1。

关系运算符的优先级低于算术运算符。例如,表达式i + j < k - 1意思是(i + j) < (k - 1)。而关系运算符中,==以及!=运算符的优先级低于<等大小关系运算符。例如,表达式i < j == j < k等价于表达式(i < j) == (j < k)。关系运算符都是左结合的,即相同优先级的条件下表达式是从左向右计算的。

5.1.3 带else子句的if语句

有时,在if表达式为假的情况下可能需要执行一些语句,比如在上述计算球体积的程序中,如果输入半径为负值,程序并没有任何输出,这样用户体验是不好的,当输入为负值时可以输出文字提示用户输入的值没有意义,这样用户就可以明确到底发生了什么。程序可以这样修改,

#include <stdio.h>
#define PI 3.14
int main()
{
  // 定义变量半径、体积,输入值到变量中 
  float radius, volumn;
  printf("请输入球的半径:");
  scanf("%f", &radius);
   
  if (radius >= 0 ){
    // 根据半径计算体积
    volumn = 4.0f / 3.0f * PI * radius * radius * radius;
  
    // 输出体积
    printf("半径为%.2f的球的体积是%.2f\n", radius, volumn);
  }
  
  if (radius < 0){
    printf("错误!输入的半径为负值!\n");
  } 
  
  return 0;
}

上述写法中的两条if语句并没有特殊的关系,只是两条先后执行的语句。可以想到,两条if语句的条件式子具有相反的真假关系。也可以用带else的if语句来实现程序,

#include <stdio.h>
#define PI 3.14
int main()
{
  // 定义变量半径、体积,输入值到变量中 
  float radius, volumn;
  printf("请输入球的半径:");
  scanf("%f", &radius);
   
  if (radius >= 0 ){
    // 根据半径计算体积
    volumn = 4.0f / 3.0f * PI * radius * radius * radius;
  
    // 输出体积
    printf("半径为%.2f的球的体积是%.2f\n", radius, volumn);
  }else{
    printf("错误!输入的半径为负值!\n");
  } 
  
  return 0;
}

这样写比使用两条if语句的好处是条件式子只算了一次,而且代码的逻辑更加清晰。带有else的if语句的基本结构如下

...//if语句前一条的语句

if (条件式子){
  // 条件式子为真执行的语句(if子句)
  语句
  ...
}else{
  // 条件式子为假执行的语句(else子句)
  语句
  ...
}

...//if语句后一条语句

当程序顺序执行到上述if-else语句时,类似于不带else的if语句,该if-else语句作为一个整体的语句来执行,但是内部也会按照一定的顺序执行。先计算条件式子得到真假值。如果式子为真,执行if子句中大括号里面的语句。如果为假,则会执行else子句大括号里面的所有语句。不论执行哪个,任何一个执行完成也就意味着整个if-else语句执行完成。继续执行if-else语句后续的语句。

在这里插入图片描述

与if语句一样,如果else子句中只包含一条语句,可以省略掉else子句中的大括号。仍然建议无论是一条语句还是多条,始终用大括号括起来。

以下是一些if-else语句的例子:

  1. 判断输入整数的奇偶

    #include <stdio.h>
    
    int main()
    {
     int x, r;
    
     printf("请输入一个整数:");
     scanf("%d", &x);
    
     r = x % 2;
    
     if (r == 0){
       printf("%d是偶数\n", x);
     }else{
       printf("%d是奇数\n", x);
     }
    
     return 0;
    }
    
  2. 找出输入的两个整数的大的那个

     #include <stdio.h>
    
    int main()
    {
      int x, y, max;
    	
      printf("请输入两个数(用空格或者换行隔开):");
      scanf("%d%d", &x, &y);
    
      if( x > y){
        max = x;
      }else{
    	max = y;
      }
    	
      printf("%d和%d中间大的是%d\n", x, y, max);
    	
      return 0;
    }
    

5.1.4 if语句常见问题

  1. 忘记大括号

    如果if子句或者else子句包含多条语句,那么这些语句要用大括号括起来。否则,或者编译报错,或者程序运行时逻辑可能出错。
    比如,下面的程序用来判断输入的整数是否为负数以及给出它的绝对值:

    #include<stdio.h>
    
    int main()
    { 
    	int x, y;
    
    	printf("请输入一个整数:");
    	scanf("%d", &x);
    
        // 不论if分支还是else分支,都包含了多条语句,大括号不能省略
    	if (x >= 0){
    		printf("%d是非负数\n", x);
    		y = x;
    	}else{
    		printf("%d是负数\n", x);
    		y = -x;
    	}
    
    	printf("%d的绝对值是%d\n", x, y);
    
    	getchar();
    	getchar();
    	return 0;
    }
    

    上述程序中,如果删除if分支的一对大括号,则编译报错;如果删除else分支的一对大括号,则程序运行结果有错(可以思考下在输入什么样的整数时运行有错?)。

  2. 小括号后面打分号

    if (x < 0);
        printf("%d是负数", x);
    

    上面的程序原意是在x为负数的时候输出x是负数的文字。但是由于在if后面的小括号后加了一个多余的分号;,程序逻辑结构变成了

     if (x < 0)
         ;
     printf("%d是负数", x);
    

    也就是说,单独的一个;构成了空语句,由于它放在if的小括号后面,所以成了当条件成立执行的一条语句。(尽管是条空语句,没有任何作用)于是,printf语句变成了与if无关的语句了,不论if条件是否成立,肯定会执行。

  3. 判断相等用了=

    在选择语句的条件式子里面将判断两个数相等应该使用关系运算符==写为一个等号=。一个等号表示赋值运算符,同时赋值表达式也是有值的,就是赋值后左边变量的值。故一旦将关系运算符==写为一个等号=,那么不但会修改了赋值号左边变量的值(如果左边是变量的话),而且式子是否成与两边的值是否相等无关了。比如,在判断整数是奇数还是偶数的程序中,误写为

    if (r = 0){  // 这里==误写为=
       printf("%d是偶数\n", x);
     }else{
       printf("%d是奇数\n", x);
     }
    
  4. 比较两个浮点数是否相等用==

    由于浮点数在计算机中存储有误差,故不能用==判断两个浮点数是否相等,比如

    double x = 1.0 - 0.1 - 0.1 - 0.1 - 0.1 - 0.1;
    if (x == 0.5){
      printf("x is 0.5\n");
    }else{
      printf("x is not 0.5\n");
    }
    

    输出的结果是x is not 0.5。因此,不能用==判断两个浮点数是否相等,可以比较两个浮点数是否足够相近的方式来近似判断它们是否相等:|x < y| < Ε 。如果比较的是两个双精度浮点型double,Ε取10-14,如果是两个单精度浮点型float,Ε取10-7

    #include <math.h>
    
    #define EPSILON 1E-14
    
    int main()
    {
        ...
        double x;
        
        // 对x赋值
        ...
        
        if (fabs(x - 0.5) < EPSILON){
          printf("x大约为0.5");
        }else{
          printf("x不等于0.5");
        }
        ...
    }
    

    其中,fabs是标准库函数,返回浮点数的绝对值,它的使用声明包含在头文件math.h中。

  5. if-else语句写成两条单独的if语句

    比如判断输入的整数是偶数还是奇数

    #include <stdio.h>
    
    int main()
    {
      int x, r;
    
      printf("请输入一个整数:");
      scanf("%d", &x);
    
      r = x % 2;
    
      if (r == 0){
        printf("%d是偶数\n", x);
      }
      
      if (r != 0){
        printf("%d是奇数\n", x);
      }
    
      return 0;
    }
    

    这个并不算错误,但是相比将它们写成if-else语句,判断次数变多了,而且程序的逻辑清晰性也没有用if-else语句好。

5.2 if语句的拓展

5.2.1 if中嵌套if语句

C语言对可以出现在if语句内部的语句的类型没有限制。事实上,在if语句内部嵌套if语句是非常普遍的。考虑下面的if语句,其功能是找出i、j、k三个数中的最大值,并将值保存到max中。这个程序可以这样实现

#include <stdio.h>

int main()
{
	int a, b, c, max;

	printf("请输入空格隔开的三个数:");
    scanf("%d%d%d", &a, &b, &c);

    if (a > b){
      if (a > c){
        max = a;
	  }else{
	  	max = c;
	  }
	}else{
	  if (b > c){
	  	max = b;
	  }else{
	  	max = c;
	  }
	}

    printf("%d,%d,%d中间最大的数是%d\n", a, b, c, max);
    
    return 0;
  }

由于内层的if-else语句里面不论if还是else分支只包含一条语句,而内层的if-else语句整体可以看成一条语句,所以上述代码中的大括号都可以去掉,

#include <stdio.h>

int main()
{
	int a, b, c, max;

	printf("请输入空格隔开的三个数:");
    scanf("%d%d%d", &a, &b, &c);

    if (a > b)
      if (a > c)
        max = a;
	  else
	  	max = c;
	else
	  if (b > c)
	  	max = b;
	  else
	  	max = c;

    printf("%d,%d,%d中间最大的数是%d\n", a, b, c, max);
    
    return 0;
  }

程序的流程图如下

在这里插入图片描述

当然,我们也可以用下面的方式找到max值

// 找到i、j中的最大值放到max中
if (i > j){
  max = i;
}else{
  max = j;
}
// 找到k和max中的最大值,放到max中
if (k > max){
  max = k;
}

上面的程序中if-else语句和if语句没有嵌套关系,而是两条先后执行的独立的语句。可以看到为了完成一个功能,可以采用多种流程。

5.2.2 级联式if-else语句

可以把普通的if-else语句的看成用来完成“”双项选择“”功能,比如,判断一个数是小于0,还是大于等于0

if (x < 0){
  printf("小于0\n");
}else{
  printf("大于等于0\n");
}

如果程序要完成“多项选择”呢,比如,判断一个数是小于0,等于0,还是大于0。在这个程序中,我们要判断一个数的三种可能性。可以先用一个if语句判断数是否小于0,如果不小于,那还可能等于0和大于0,继续用一个if来判断这两种可能性。

在这里插入图片描述

这个过程有点类似切蛋糕,整个蛋糕中切下来一块,然后根据需要,继续在剩下的蛋糕里一块一块切。

if (x < 0){
  printf("小于0\n");
}else{
  // x必然等于或者大于0
  if (x == 0){
    printf("等于0\n");
  }else{
    // x必然大于0
    printf("大于0\n");
  }
}

在这里插入图片描述

后面的例子可以看到还可能有更多的“选项”,为了处理这些“选项”,可以在最后的else继续嵌套if-else,依次类推。这种在else子句中正好嵌套一条if-else语句,并可能重复这样做的语句称为级联式if-else语句。

级联式if-else语句的条件判断数值如果有次序关系,一般以从小到大或者从大到小的次序对这些数值依次判断,这样可以避免遗漏需要的判断,程序的逻辑也更加清晰,代码更方便维护和扩展。

如果像上面的代码那样严格遵循编码书写规范(else里面的语句相对else语句有缩进),那么最终整个级联式if-else语句会占过宽的文字宽度,反而不利于代码阅读。因此鼓励缩进的代码规范对级联式if-else语句开了一个特例,级联式的if-else语句的推荐写法是:

if (x < 0){
  printf("小于0\n");
}else if (x == 0){
  printf("等于0\n");
}else{
  printf("大于0\n");
}

级联式if-else语句的一般的格式为(如果语句为单条语句,可以省略包围它的大括号)

if (条件式子){
  语句
}else if (条件式子){
  语句
...
}else if (条件式子){
  语句
}else{
  语句
}

比如,如果需要继续判断当大于0时是否小于10或者大于等于10,那么可以在最后的else继续嵌套if-else。

if (x < 0){
  printf("小于0\n");
}else if (x == 0){
  printf("等于0\n");
}else if(x < 10){
  printf("0和10之间\n");
}else{
  printf("大于等于10\n");
}

注意,级联式if语句不是新的语句类型,它仅仅是普通的if语句,只是碰巧有另外一条if语句作为else子句(而且这条if语句又有另外一条if语句作为它自己的else子句,以此类推)。

级联式if-else语句案例:

按照最新计算个人所得税的国家规定,个人所得税根据全年应纳税所得额分部分乘以各部分不同税率累加得到。注意,下表的“全年应纳税所得额”去掉了免税所得额6万元。
计算税费
累进税率计算算法图示如下:
个税计算说明

比如,一个人年应收入10万元(扣除五险一金后的收入),扣除免税6万元,还有4万元应纳税收入。4万元分为3.6+0.4万元,3.6万元部分,税率为3%,0.4万元部分,税率为10%。所以最终应纳税3.6 * 0.03 + 0.4 * 0.1 = 0.148万元。

假设应纳税收入在30万以内(年收入在36万元以内),编写程序,输入年收入,输出应纳税额度。

#include <stdio.h>
int main()
{
  
  double income, incomeBaseTax, tax = 0.0;
  
  printf("请输入年收入(万元):");
  scanf("%lf", &income);
  
  incomeBaseTax = income - 6;
  
  if (incomeBaseTax < 0){
  	tax = 0.0;
  }else if (incomeBaseTax < 3.6){
  	tax = incomeBaseTax * 0.03;
  }else if (incomeBaseTax < 14.4){
  	tax = 3.6 * 0.03 + (incomeBaseTax - 3.6) * 0.1;
  }else{
  	tax = 3.6 * 0.03 + (14.4 - 3.6) * 0.1 + (incomeBaseTax - 14.4) * 0.2;
  }
  
  printf("年收入%.4lf万元, 应纳税%.4lf万元\n", income, tax);
  
  return 0;
}

5.2.3 “悬空else”问题

下面的代码有两个if子句,一个else子句,else子句和哪个if匹配呢?

int i = 1, j = 2, k= 3;
if (i > j)
  if (i > k)
    printf("A\n");
else  // 错误的缩进
  printf("B\n");    

我们可能会说,按照代码缩进来看,else子句应该和第一个if匹配,但是实际上else子句是和第二个if匹配的。这种情况称为“悬空else”问题。C语句中else子句的匹配规则是else子句总是和离它最近的尚未匹配的if匹配。因此,反映上述代码的真实语句关系的缩进应该是

int i = 1, j = 2, k= 3;
if (i > j)
  if (i > k)
    printf("A\n");
  else  // 正确的缩进
    printf("B\n");    

运行这段代码,并没有输出,这进一步验证了else是和离它最近的if匹配的,否则,程序应该输出B和换行。

如果希望else和第一个if匹配,可以用大括号改变匹配关系

int i = 1, j = 2, k= 3;
if (i > j){
  if (i > k)
    printf("A\n");
}else
  printf("B\n");    

此时,运行代码将输出B和换行。

5.2.4 逻辑运算符

以上学习的if语句表达了当某个条件成立就做某个事情的逻辑,但是有时我们需要处理当两个/多个条件都成立(为真)才做某个事情,或者当两个/多个条件只要有一个成立(为真)才做某个事情。这两种场景其实可以用一般的if语句来完成。假设条件是A、B。要表达当A和B都成立才执行语句C的逻辑,可以这样实现

if (A){
  if (B){
    C;
  }
}

要表达当A或B其中任何一个成立就执行语句C的逻辑,可以这样实现

if (A){
  C;
}
if (B){
  C;
}

在C语言中针对以上问题有更加合适的做法,即使用逻辑运算符且&&和逻辑运算符或||。逻辑且&&的含义是当A和B都为真,整个表达式才为真。逻辑或||的含义是当A或者B其中任意一个为真,整个表达式就为真。我们可以用它们来完成上述逻辑:

// A和B都成立执行C
if (A && B){
  C;
}
// A或B只要有一个成立就执行C
if (A || B){
  C;
}

比如,一位同学的数学和计算机成绩出来了,孩子的中国妈妈对孩子比较严格,认为只有这两门成绩都不低于90分孩子才是优秀的孩子,而孩子的美国爸爸比较宽松,认为只要有任何一门成绩不低于90分孩子就是优秀的孩子。如果需要将他们的判断逻辑实现出来,就可以分别用逻辑且&&和逻辑或||

// 中国妈妈的评价
if (math >= 90 && computer >= 90){
  printf("优秀的孩子");
}else{
  printf("not优秀的孩子");
}
// 美国爸爸的评价
if (math >= 90 || computer >= 90){
  printf("优秀的孩子");
}else{
  printf("not优秀的孩子");
}

逻辑运算符&&(且、与)、||(或)、!(非),可以以逻辑值作为操作数,构成逻辑表达式。

逻辑值!逻辑值例子(x是1)
!(x > 0)是假
!(x == 0)
逻辑值1逻辑值2逻辑值1 && 逻辑值2例子(x是1,y是2)
x > 0 && y < 0
x < 0 && y > 0
x < 0 && y < 0
x > 0 && y > 0
逻辑值1逻辑值2逻辑值1 ¦¦ 逻辑值2例子(x是1,y是2)
x > 0 ¦¦ y < 0
x < 0 ¦¦ y > 0
x < 0 ¦¦ y < 0
x > 0 ¦¦ y > 0
  • 如果表达式的值为假(0),那么!表达式的结果为真(1)
  • 如果表达式1和表达式2都是真(非零),那么表达式1 && 表达式2的结果为真(1)
  • 如果表达式1或者表达式2任意一个为真(非零值),那么表达式1 || 表达式2的结果为真(1)。
  • 在其它情况下,这些运算符产生的结果都为假(0)。

关系运算符比较数值之间的大小、相等关系得到逻辑值,而逻辑运算符作用在逻辑值上,按照它们的“逻辑”关系得到新的逻辑值。如果对逻辑运算符还感到疑惑,可以思考这个关系:表达式x >= 10 相当于 x > 10 || x == 10

运算符!的优先级较高,和一元正负号的优先级相同,运算符&&和||的优先级低于关系运算符。例如,表达式i < j && k == m等价于表达式(i < j) && (k == m)。运算符!是右结合的,而运算符&&和||是左结合的。

与关系运算符一样,逻辑运算符的结果如果为真,用1作为逻辑运算的结果,如果为假,用0作为逻辑运算的结果。比如

int x = 10, y = 20, z;
z = x > y || x > 5; // 逻辑表达式为真,故表达式的值为1,z被赋值为1
z = !(x != y && y < 30); // 逻辑表达式为假,故表达式的值为0,z被赋值为0

运算符&&和||都有“短路”特性。运算符&&和||都是先算左表达式后算右表达式。但是在整个表达式的值可由左表达式推导出来的情况下就不算右表达式了。具体来说,表达式1 && 表达式2在表达式1为真的情况下才计算表达式2的值,否则就不计算表达式2的值,因为此时整个逻辑表达式一定为假。表达式1 || 表达式2在表达式1为假的情况下才计算表达式2的值,否则就不计算表达式2的值,因为此时整个表达式一定为真。
借助于短路特性,下式在i为0的情况下,j / 0的运算就不会发生了。
(i != 0) && (j / i > 0)

&&短路特性说明程序1.

#include<stdio.h>

int main()
{ 
	int x = 0, y = 0;

	if (x && (y = 5)){  // x值为0,表示假,故y=5被跳过了
		printf("true\n");
	}else{
		printf("false\n");
	}

	printf("y = %d\n", y);

	getchar();
	return 0;
}

在这里插入图片描述
&&短路特性说明程序2.

#include<stdio.h>

int main()
{ 
	int x = 10, y = 0;

	if (x && (y = 5)){  // x值为10,表示真,故y=5被运行,赋值后y为5,表示真
		printf("true\n");
	}else{
		printf("false\n");
	}

	printf("y = %d\n", y);

	getchar();
	return 0;
}

在这里插入图片描述
注意,上面的两个例子用到了两个知识点:

  • 在C语言中,非零表示真,零表示假
  • 赋值语句本身也是表达式,其值是赋值后左边变量的值.

使用逻辑运算符的典型例子包括判断某一年是否是闰年。判断任意年份是否为闰年,需要满足以下条件中的任意一个:
① 该年份能被 4 整除同时不能被 100 整除;
② 该年份能被400整除。

if ((year % 4 == 0 && year % 100 != 0) 
                  || (year % 400 == 0)) 

利用逻辑运算符,找出三个数最大值也可以像如下实现

if (a >= b && a >= c){
  max = a;
}else if (b >= a && b >= c){
  max = b;
}else{
  max = c;
} 

5.2.5 常见问题

  1. 判断x是否在(a, b)范围内,用了表达式a < x < b

    if (a < x < b)中表达式a < x < b先计算a < x得到真或者假,但是在C语言中没有真假值,用1表示了真,0表示了假,故a < x < b变成了0 < b或者1 < b中的某一个表达式,其最终结果与正确写法if ( a < x && x < b)是不等价的。

  2. 应该用if-else或者级联式if-else,但是只用了单独的if语句

    比如,评价成绩的程序,写为了

    if (grade < 60){
     printf("不及格\n");if (grade < 80){
      printf("及格\n");
    }
    if (grade < 90){
      printf("良好\n");
    }
    if (grade >= 90){
      printf("优秀\n");
    }
    

    这样写是错误的,因为假如grade是50,程序会输出多个评价

    不及格
    及格
    良好

    可能还有这样的写法

    if (grade < 60){
    printf("不及格\n");if (60 <= grade && grade < 80){
     printf("及格\n");
    }
    if (80 <= grade && grade < 90){
     printf("良好\n");
    }
    if (grade >= 90){
     printf("优秀\n");
    }
    

    这样程序是正确的,但是相比级联式if-else,比较次数变多了,因为每个if表达式都会计算并判断是否为真。

  3. 级联式if-else的if增加了多余的条件或者else后面多加了if
    比如,将一个整数的奇偶判断写成了如下形式

    #include <stdio.h>
    
    int main()
    {
     int x, r;
    
     printf("请输入一个整数:");
     scanf("%d", &x);
    
     r = x % 2;
    
     if (r == 0){
       printf("%d是偶数\n", x);
     }else if (r != 0){  // 多了不必要的判断
       printf("%d是奇数\n", x);
     }
    
     return 0;
    }
    

    显然,else里面是没有必要再用if判断余数是否不等于0,因为外层的if已经判断了余数是否等于0,如果程序运行到了else这里,就已经说明余数一定是不等于0的,这也是if-else的基本含义。相比上一条问题的可以用if-else但是没有用,这里的问题是过度用了级联式if-else语句。

    再比如,

     #include <stdio.h>
    
    int main(){
      int grade;
      printf("请输入考试成绩:");
      scanf("%d", &grade);
      
      if (grade < 60){
        printf("不及格\n");
      }else if (60 <= grade && grade < 80){
        printf("及格\n");
      }else if (80 <= grade && grade < 90){
        printf("良好\n");
      }else if (grade >= 90){
        printf("优秀\n");
      }
      return 0;
    }
    

    上面的程序是正确的,但是相比于一般级联式if-else的代码,写了多余的判断,像60 <= grade && grade < 80中的60 <= grade。这是因为,前面的if条件grade < 60不成立,那么60 <= grade是一定成立的,故没有必要写出来了。80 <= grade && grade < 90中的判断80 <= grade也是类似的。以及最后的if(grade >= 90)也是没有必要的。

5.4 其它分支语句

5.4.1 switch语句

下面的程序根据成绩(0到4分,整数分)输出评价

if (grade == 4){
  printf("Excellent");
}else if(grade == 3){
  printf("Good");
}else if(grade == 2){
  printf("Average");
}else if(grade == 1){
  printf("Poor");
}else if(grade == 0){
  printf("Failing");
}else{
  printf("Illegal grade");
}

显然,它用了级联式if语句。在C语言中,像这样if语句,如果判断依据是某个整数类型的变量值是否是某些值之一的话,可以用swich语句来实现。下面的代码等价于前面的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;                                                            
}

执行switch语句时,grade值和4、3、2、1、0相比较,和其中任何一个值相等的话,会执行对应于那个值的冒号后面的代码,而break语句的作用是结束switch语句的执行,把控制传递给switch后面的语句。比如,grade是2的话会输出Average,然后break语句结束了整个switch语句。如果没有任何case后的值和grade的值相匹配,那么执行default分支的语句,显示Illegal grade文字,并结束整个switch语句。

switch语句的一般格式为:

switch (表达式) {
  case 常量表达式: 语句...
  ...
  case 常量表达式: 语句...
  default: 语句
}

switch语句比if语句要复杂,具体来看:

  • 控制表达式。switch后面必须跟着由圆括号括起来的表达式,而且表达式的值必须是整数类型的,比如整型变量或者整型变量参与的算术表达式,算术表达式的值需要为整数。由于C语言把字符当做整数处理,因此这里也可以是字符类型的值。但是,表达式的值不能是浮点数和字符串。
  • 分支标号。每个分支的开头都有一个标号,格式如下:
    case 常量表达式:
    常量表达式含义是值为常量的表达式,比如5,10,5 + 10,‘a’,但是像n + 10就不是常量表达式(假如n是赋值了的变量)。常量需要是整数或者字符类型。
  • 语句。分支标号后面可以跟任意数量的语句。不需要用花括号将这些语句括起来。每组语句的最后通常是break语句。
  • break。break是可选的。或者出现在某些个分支标号后面跟着的语句的最后,或者不出现。如果出现,那么执行到它则整个switch语句结束。否则,继续执行后续各个分支标号后面跟着的语句(包括default分支的),直到所有语句都执行完成,整个switch语句结束。或者在执行过程中遇到某个break语句,整个switch语句结束。
  • default语句。default语句是可选的。一般放在switch语句的最后。如果表达式的值与全部常量表达式的值都不相等,那么会执行default分支后面的语句。

C语言不允许有重复的分支标号,即多个case后面的常量表达式的值相同。但是对分支的顺序没有要求,特别是default分支不一定要放置在最后(一般都是放在所有分支的最后)。

尽管switch语句比较复杂,但是在最理想、最简单的switch语句的格式下,

switch (表达式) {
  case 常量表达式1: 语句...;break;
  ...
  case 常量表达式k: 语句...;break;
  default: 语句...;break;
}

其含义还是比较清晰的。就是根据表达式的值为哪个分支下的常量表达式,就执行哪个分支下的语句,执行完成switch语句结束。如果表达式的值不和任何常量表达式的值相同,那么如果有default分支则执行该分支中的语句,执行完毕switch语句结束,如果没有default分支,switch语句直接结束。所以,这种switch语句和用级联式if-else语句写出来的代码是等价的。

if (表达式 == 常量表达式1){
  语句
  ...
}
  ...
}else if (表达式 == 常量表达式k){
  语句
  ...
}else{
   语句
   ...
}

但是break关键字使switch语句与级联式if语句又有不同。当switch语句根据表达式的值进入某个分支中执行里面的语句时,如果遇到break语句,整个switch语句结束。否则,执行完那个分支里面的语句后,会继续执行下面一个分支里面的代码。同理,只要没有遇到break语句,接下来所有后续分支里面的代码都会被执行到。例如,下面的代码

switch (grade) {
  case 4: printf("Excellent");             
  case 3: printf("Good");
  case 2: printf("Average");
  case 1: printf("Poor");
  case 0: printf("Failing");
  default: printf("Illegal grade");                                              
}

如果grade的值为2,那么输出
AveragePoorFailingIllegal grade

显然,上面的例子是忘记使用break语句了,但是有时候我们会利用这个特性,故意在某些分支中不使用break语句。比如,如果希望程序根据grade的值输出那么*,grade值越高,输出的*越多,那么下面的代码可以实现

switch (grade) {
  case 4: printf("*");             
  case 3: printf("*");
  case 2: printf("*");
  case 1: printf("*");
  case 0: break;
  default: printf("Illegal grade");break;                                              
}

5.4.2 条件表达式

C语言的if语句允许程序根据表达式的值来执行两个操作中的一个。C语言还提供了一种特殊的运算符,可以根据表达式的值来产生两个值中的一个。
条件运算符由符号?和符号:组成,由它组成的条件表达式格式如下
表达式1 ? 表达式2 : 表达式3
条件运算符是C运算符中唯一一个要求3个操作数的运算符。因此,它属于三元运算符。
其含义是首先计算表达式1的值,如果它为真(不为零),则计算表达式2的值作为整个条件表达式的值,否则(表达式1的值为零),计算表达式3的值作为整个条件表达式的值。

int i=1, j=2, k;
k = i > j ? i : j;  // k是2
k = (i >= 0 ? i : 0) + j; // k是3

在第二条语句中,i > j不成立,故以j的值作为整个条件表达式的值。在第三条语句中,i >= 0成立,故以i的值作为整个条件表达式的值。第三条语句中条件表达式括在圆括号中是有必要的,因为除了赋值类运算符,条件运算符的优先级低于先前介绍过的所有运算符。

条件运算符是右结合的,比如

int i = -2;
int n = i == -2 ? 99 : i == -1 ? 11 : 22;

等价于

int i = -2;
int n = i == -2 ? 99 : (i == -1 ? 11 : 22);

而不是

int i = -2;
int n = (i == -2 ? 99 : i == -1) ? 11 : 22;

找两个数最大值的代码,可以用条件运算符写成:

max = x >= y ? x : y;

5.4.3 常见问题

  1. switch忘记用break
    这是很常见的switch语句的错误,应该是break终止switch语句接下来分支的执行但是却忘记了。
  • 2
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值