
6.6 嵌套的if()...else...语句
有时我们可以在if()...else...结构的其中一个分支或两个分支里面撰写嵌套的if()...else...结构,以便更加清晰地表达想要实现的逻辑。
对isLeapYear()这个例子来说,如果有人不太清楚格里高利历(Gregorian calendar,也叫格里历,就是现行公历)的发展过程,或者不太了解如何判断整百的年份是否为闰年,那么看到这种由多个if()...else...所构成的fall-through逻辑时可能会有点困惑。实际上这个例子还算是比较简单的,有些例子比这要复杂得多。为了把判断逻辑表达得更清楚一些,我们可以在大的if()...else...结构中嵌入小的if()...else...结构。
请复制leapYear2.c,命名为leapYear3.c,我们要在这个副本上修改。修改后的isLeapYear()函数是这样的:

这次我们也是把判断结果保存在名为isLeap的局部变量里面。如果程序执行的是if结构的最后那个else分支,那么意味着受测年份肯定能够被4整除。于是,我们在该分支里面嵌入一个小的if()...else...结构,以便正确处理整百的年份。有人觉得像这样采用嵌套式的if结构来判断闰年是比较清晰的,另一些人则不这么认为。
还要注意的是,我们把这个小的if()...else...结构用一对花括号括了起来,让它形成一个语句块。这样做不仅让代码看起来更加清楚,而且能够避免dangling else问题。
dangling else问题
有时你只想给if()...结构的true分支书写一条语句,但是编译器却按照C语言的规则把你想用来表示false分支的那个else语句与刚才那条语句优先组合成了一个if()...else...结构。这样程序的执行效果可能就跟你想要的不同了。涉及这种else语句的问题叫作dangling else问题。我们用下面这段代码举例:

这段代码中的else...到底属于哪一个if()...?是第一个(外面那个)还是第二个(里面那个)?为了消除歧义,我们总是应该把if()...else...结构的相应分支用花括号括起来,也就是用一条复合语句来实现这个分支,这样就不会引发歧义了。刚才那种写法会让许多编译器发出警告:warning: add explicit braces to avoid dangling else [-Wdangling-else]。
但也有一些编译器不会发出这种警告。总之,要想消除误解,最好的办法就是用花括号把外面那个if结构的true分支所包含的语句给括起来。比方说,如果我们这样修改,那么既能够消除误解,又能够让编译器不再发出警告:

这种写法会把else...明确地跟外面那个if()...关联起来,以表示那个if结构的false分支。于是,在x不等于0的情况下,我们会看到程序输出x does not equal 0。
还有一种改法是把else...分支跟第二个if()...括在一起,以便明确地同第二个if()...关联起来。这会让程序在x为0且y不为0的情况下输出y does not equal 0。依照这种写法,第一个(外面那个)if()...结构没有false分支:

注意,这种写法能够明确表示出外面那个if(x==0)结构里嵌套了一个小的if(y==0) else...结构。
根据笔者的编程经验,很少会遇到if()...else...结构里面的两个分支都是简单语句的情况。就算一开始是这样,以后也经常需要向里面继续添加逻辑代码,因此,到时还是得把那些代码括在一对花括号里,让它们合起来构成一条复合语句。既然这样,那不如一开始就拿花括号把分支里面的语句括起来,这样以后就可以向其中直接添加其他语句,而不用担心自己会忘记写花括号了。总之,笔者的办法是,在还没有开始写条件表达式与那两个分支里面的语句之前,先把if结构的轮廓搭建好,让两个分支都以复合语句的形式出现:

有些人可能觉得,else分支应该另起一行,而不要紧跟着if分支的右花括号,但另一些人还是觉得刚才那种写法比较好。下面这段代码演示了else分支另起一行的写法:

第一种写法是把else分支的起始花括号(左花括号)跟if分支的收尾花括号(右花括号)写在同一行。第二种写法是把else分支与它的起始花括号写在另外一行。第一种写法篇幅较短,第二种写法比它多一行。这两种写法都没有问题,具体应该采用哪种需要考虑花括号括起来的这段代码是不是比较长、比较复杂。
有人喜欢让某个分支的起始花括号独占一行,例如把if分支的起始花括号单独写在一行里面:

有人不把if分支的起始花括号单独写在一行里面,但却把else分支的起始花括号单独写在一行里面:

有人觉得起始花括号总是应该另起一行,有人则认为这种花括号应该跟上一个分支的收尾花括号写在同一行里。这两种写法都对。你可以根据个人喜好选用其中的某种写法,然而你还得考虑到你所在的代码库有没有针对花括号的风格制定规范,如果制定了,那就应该遵从那种规范。总之,一旦选定了其中某种写法,就必须坚持下去。