NOIP学习小站
西安交通大学附属中学航天学校

选择结构/分支结构与if语句

现实生活中,我们往往面临选择:今天出门穿什么衣服?中高考结束后填报志愿选择哪所学校?毕业后从事什么样的职业?当然,做出决定之前,我们往往会根据当前的情况来进行分析判断,帮助我们更好地选择。编写程序解决实际问题,往往也要根据不同的情况,完成不同的操作,这就和数学物理学科中的分类讨论一样。

一、选择结构/分支结构

我们首先来看最简单的可以分两种情况来处理的问题:口渴了,我们会喝水;不口渴,不喝水。进一步分析如下:

  • 情况1:口渴,那么喝水;
  • 情况2:不口渴,不喝水。

这是一个典型的分两种对立情况的分类讨论。再进一步分析,按照判断“是否口渴”的结果来描述:

两种情况对应两个条件判断:

  • 如果口渴,那么喝水;
  • 如果不口渴,那么不喝水。

左边的描述还可以精简为:

如果口渴,那么喝水;

否则,不喝水。

上面右侧描述的实质就是判断“口渴”这个条件是否成立,成立的话喝水,不成立的话(也就是“否则”的含义)不喝水。对应算法的描述如下:

伪代码描述如下:

如果 口渴:
     喝水
否则:
     不喝水

流程图描述如右图所示(Y表示YES,条件成立;N表示NO,条件不成立):

再来看一个例子,判断一个整数\(n\)是否为非负数(0或正整数),判断条件可以用\(n \ge 0\)(这个时候是一个量化的条件,而不是像上一个例子是自然语言描述的条件),算法和程序如下:

使用C++语言的if语句来实现:

#include<iostream>
using namespace std;
int main()
{
	int n;
	cin>>n;
	if(n>=0){
		cout<<"YES";
	}else{
		cout<<"NO";
	}
	return 0;
} 

像上面的根据条件判断结果(成立或者不成立)选择执行不同的语句的结构,称为选择结构;从流程图来看,在菱形条件判断处,根据条件成立与否,算法处理流程分成了两条分支,因此也称为分支结构

二、if语句

C++中可以用if语句实现选择结构,if语句的使用方法如下:

if(条件){
    //条件成立时执行的语句(语句组)
}else{
    //条件不成立时执行的语句(语句组)
}

将if读成“如果”,将else读成“否则”,会发现if语句解读下来很容易理解,和自然语言一致。

此外,if语句还可以没有else部分,此情况用于条件成立时要执行一些操作,条件不成立时什么都不干:

没有else部分的if语句格式:

if(条件){
    //条件成立时执行的语句(语句组)
}

没有else部分的if语句,只指定条件成立时要执行的if语句(组),条件不成立时什么都不处理。

需要注意的是,C++书写if语句的时候,if子句部分写在else子句部分前面,但是程序执行的时候if子句和else子句是平级关系,这一点从流程图可以明显看出来。

完整if语句流程图
无else部分的if语句流程图

对于初学者来说,这里有一个容易犯的错误,就是在else后也书写条件,例如下面的代码会出现编译错误:

#include<iostream>
using namespace std;
int main()
{
    int n;
    cin>>n;
    if(n>=0){
        cout<<"YES";
    }else(n<0){ //编译错误,else后不能写条件
        cout<<"NO";
    }
    return 0;
} 

通过前面的分析,我们已经知道else意味着“否则”,也就是if后()括号内条件判断不成立,显然这里是不用再写条件的(画蛇添足!)

使用if语句解决选择结构的问题,需要我们分析问题后,设计算法构思程序的框架,这个时候最初想到的往往是自然语言描述的“如果某某条件成立,就执行XX;否则就执行YY”,下一步编程时有一个难点就是要将自然语言描述的条件转换为量化的条件。接下来为大家介绍描述简单条件的逻辑运算和描述复杂条件的复合逻辑运算。

三、简单条件的描述

最简单的条件就是大小关系比较的判断,例如\(5 > 3\)、\(4 < 5\)、\(a > b\)等,这些称为关系表达式。但是注意C++中的语法与数学书写方法有些是不同的:

逻辑比较运算符
大于>
大于或等于>=
小于<
小于或等于<=
等于==
不等于!=

注意:只有>、<与数学书写一致,>=、<=、==、!=和数学书写都不同。特别是判断相等的==运算符,一定要和赋值运算符=区分开

  1. n=0是赋值语句,将0赋值给变量n;
  2. n==0是关系表达式,判断变量n的值是否等于0。

关系表达式的结果是bool值,条件成立则结果为true,条件不成立则是false

bool类型(1Byte)是最简单的数据类型,bool类型的值只有true(相当于1)和false(相当于0)。

例如语句bool b = false;将bool类型变量b赋值为false;又例如语句bool b = 5>3;因为5>3成立,结果是true,赋值后bool类型变量b的值为true。

其实if语句的条件可以是一个数字或者是变量(一般是整数类型),此时只要作为条件的数字或者变量是非0值,那么条件就成立,例如:

if(1){    //条件成立
}
if(-1){   //条件成立
}
if(0){    //条件不成立
}
if(n){    //整型变量n的值不为0则条件成立
}

四、经典例题

1.求最值

求两个整数的最大值(根据情况分别输出):

#include<iostream>
using namespace std;
int main()
{
	int a,b;
	cin>>a>>b;
	if(a>b){
		cout<<a<<endl;
	}else{
		cout<<b<<endl;
	}
	return 0;
} 

求两个整数的最大值(根据情况计算结果,最后输出:输入数据→计算结果→输出结果):

#include<iostream>
using namespace std;
int main()
{
	int a,b,max;
	cin>>a>>b;
	//if语句{}中如果只有一条语句或者是一个整体
	//可以不写{},甚至写到一行中
	if(a>b) max = a;
	else max = b;	
	cout<<max<<endl;
	return 0;
} 

2.求绝对值

if语句的if分支部分和else分支部分,如果只有一条语句(或者是一个完整的整体),那么{}可以省略不写,甚至可以将语句与if(或者else)放在同一行中。

#include<iostream>
using namespace std;
int main()
{
	int n;
	cin>>n;
	if(n>=0) cout<<n<<endl;
	else cout<<-n<<endl;
	return 0;
} 
#include<iostream>
using namespace std;
int main()
{
	int n;
	cin>>n;
	if(n<0) n = -n;
	cout<<n<<endl;
	return 0;
} 

3.判断奇偶

#include<iostream>
using namespace std;
int main()
{
	int n;
	cin>>n;
	if(n%2==0) cout<<"even";
	else cout<<"odd";
	return 0;
} 
#include<iostream>
using namespace std;
int main()
{
	int n;
	cin>>n;
	//下面的语句中的条件n%2=0会出现编译错误
	if(n%2=0) cout<<"even";
	else cout<<"odd";
	return 0;
} 

上面右侧的程序会出现编译错误!就是将判断相等的==写成了=(其实是赋值语句),而赋值语句的左边必须是变量,这里是\(n\%2\),不符合语法规则!

再来看下面的一个求解方法:

#include<iostream>
using namespace std;
int main()
{
	int n,mod;
	cin>>n;
	mod = n%2;    //计算余数
	//下面语句中的条件mod=0会出现逻辑错误
	if(mod=0) cout<<"even";
	else cout<<"odd";
	return 0;
}

同上面右侧程序一样,也将==写成了=。此时不会出现编译错误(思考原因),但是测试时会发现,不管输入奇数还是偶数,程序的输出都是odd

什么原因呢?mod=0是赋值语句,赋值语句的结果是mod的值0,这里相当于是用整数0作为if语句的条件。根据上面的知识点可知,此时条件不成立会执行else部分输出odd

上面给大家演示了一个典型的因为书写问题导致的逻辑错误,大家编程时要注意细节,这样的逻辑错误对于初学者来说还不太容易发现!

4.正整数除法向上取整

输入两个正整数a、b,计算输出 a÷b 向上取整的结果。

因为a、b都是正整数,那么 a/b的结果是商。如果a%b==0(也就是a能被b整除),a÷b就是整数,那么最终结果就是a/b;如果a%b!=0(也就是a不能被b整除),a÷b就是浮点数,那么最终结果就是a/b+1。

更简单地,结果就是a/b+(a%b!=0)a%b!=0 (判断a不能被b整除)是关系表达式,计算结果是bool,条件成立结果是true(相等于1),条件不成立结果是false(相当于0)。如果a不能被b整除,a%b!=0成立,那么a/b+(a%b!=0)相当于a/b+1;如果a能被b整除,a%b!=0不成立,那么a/b+(a%b!=0)相当于a/b+0

#include<iostream>
#include<cmath>
using namespace std;
int main()
{
    int a,b,ans;
    cin>>a>>b;
    ans = ceil(1.0*a/b);
    cout<<ans<<endl;
    return 0;
} 
#include<iostream>
using namespace std;
int main()
{
	int a,b;
	cin>>a>>b;
	if(a%b==0)
		cout<<a/b;
	else
		cout<<a/b+1;
	return 0;
} 
#include<iostream>
using namespace std;
int main()
{
	int a,b,ans;
	cin>>a>>b;
	ans = a/b+(a%b!=0);
	cout<<ans<<endl;
	return 0;
} 

这里给大家介绍一个条件运算符:关系表达式?表达式1:表达式2。作用是判断关系表达式是否成立,成立的话执行表达式1,否则执行表达式2。

前面几个例子的代码可以使用条件运算符来简化:

max = a>b?a:b;          //求a、b最大值

n = n<0?-n:n;           //求n的绝对值

cout<<(n%2==0?"even":"odd")<<endl;     //判断奇偶输出结果

五、复合逻辑运算

先来看一个问题,判断输入的整数n的绝对值是否小于5。下面给出两种解法:

#include<iostream>
using namespace std;
int main()
{
	int n;
	cin>>n;
	if(n<0) n = -n;//求绝对值 
	if(n<5) cout<<"YES";
	else cout<<"NO";
	return 0;
} 
#include<iostream>
using namespace std;
int main()
{
	int n;
	cin>>n;
 	//注意下面条件的写法
	if(n>-5 && n<5) cout<<"YES";
	else cout<<"NO";
	return 0;
} 

右边代码中的条件是:n>-5 && n<5&&是复合逻辑运算符,表示并且。此外还有表示或者的||运算符,表示对一个条件取反(否定)的运算符!

复合逻辑运算符格式用途举例
&&条件1 && 条件2表示两个条件的并且
只有两个条件同时成立才成立
n>-5 && n<5
a>b && a>c
||条件1 || 条件2表示两个条件的或者
两个条件至少有一个成立就成立
n<-5 || n>5
n<0 || n>0
!!(条件)表示对一个条件的否定
原条件成立,否定后不成立;反之亦反
!(n==0)
!(n>0)

下面的程序会出现逻辑错误:

#include<iostream>
using namespace std;
int main()
{
	int n;
	cin>>n; 
	//注意:下面的条件描述有逻辑错误
	if(-5<n<5) cout<<"YES"<<endl;
	else cout<<"NO"<<endl;
	return 0;
}

左边的程序条件的描述有误,将n>-5 && n<5错误写成数学中的写法-5<n<5。此时编译不会出错,但是测试运行时会发现不管输入什么整数,都会输出YES(也就是作为条件的-5<n<5恒成立)。

-5<n<5这样的表达式会从左到右依次计算,首先计算-5<n,然后将结果与整数5比较。根据前面内容可知-5<n的结果是bool值,成立时结果为true相当于1,不成立时结果为false相当于0。那么-5<n<5就相当于1<5或者0<5了,当然结果恒成立。

再来看一个例子,判断输入的正整数n代表的年份是否为闰年。闰年的情况有两种:

  1. n能被4整除,但不能被100整除;
  2. n能被400整除。

上面两种情况都是闰年,也就是两种情况的或者。对于情况1,条件可以描述为 n%4==0 && n%100!=0。对于情况2,条件可以描述为n%400==0

#include<iostream>
using namespace std;
int main()
{
	int n;
	cin>>n; 
	//下面条件中的()可省略,直接用 n%4==0 && n%100!=0 || n%400==0
	//还可以用 n%400==0 || n%4==0 && n%100!=0
	if((n%4==0 && n%100!=0) || n%400==0) 
		cout<<"YES"<<endl;
	else
		cout<<"NO"<<endl;
	return 0;
} 

注意,这里的条件可以直接用n%400==0 || n%4==0 && n%100!=0,并不需要使用n%400==0 || (n%4==0 && n%100!=0)。这里就像5+4*2一样,&&运算优先级高于||(就像*运算优先级高于+一样),所以n%400==0 || n%4==0 && n%100!=0会优先计算n%4==0 && n%100!=0。不过为了增强程序的可读性,可以加上小括号n%400==0 || (n%4==0 && n%100!=0),这样阅读者不清楚运算优先级(或者忘记了谁优先计算而产生疑惑)的情况下也能很好理解语句作用。

注意,!运算其实并不常用,例如!(n==0)可以用相同功能的n!=0替代。!的运算优先级很高,所以n==0这个条件的否定要写作!(n==0),如果写成!n==0,那么会优先计算!n