4.2 if 语句诞生以前
如果没有 if 语句该如何编写程序呢?我们首先来考察一下这一问题。
为什么会有 if 语句
本章我们使用一种非常原始的程序设计语言——汇编语言。汇编语言中是没有 if 语句的,但是从 C 语言很容易就能编译成汇编语言。接下来,我们用 C 语言先编写带 if 语句的代码,再试着将其编译成汇编语言看一下 1。
1通过编译转换成汇编语言,然后汇编到机器语言,最后链接成一个可执行文件,现在一般把这一整串的动作统称为编译。这里说的编译仅指转换成汇编语言这一个步骤。
C 语言下的源代码如下所示,其含义是如果 X 等于 456 则做相应 处理 2。
2实际实验的代码里,通过使用 asm 在汇编语言里嵌入了注释。这里为了简洁易读做了改写。请参照本书文前的“本书构成”部分获取可执行的源代码。
C语言
int main(){
int x = 123;
/ if语句前 /
if(x == 456){
/ if语句中 /
}
/ if语句后 /
}
编译后输出如下汇编语言代码。
汇编语言
main:
……
movl $123, -8(%rbp) ❶
# if语句前 ¯├-❷
movl -8(%rbp), %eax│
cmpl $456, %eax
jne LBB1_2 ❸
# if语句中 ❹
LBB1_2: ❺
# if语句后
……
我们试着来解读一下:首先,把 -8(%rbp) 理解为原来代码中的 x。❶句把数值 123 代入 x,❷句将 x 的值移存到临时场所后,把它和数值 456 相比较。
接下来的❸句是关键,它表示在前一句的比较中,如果两边不相等则跳转至 LBB1_2 处。换句话说,如果两边相等则不跳转接着执行下面的命令。❹句就是 if 语句中的代码,它只在两边相等时被执行。不相等时程序跳转至 LBB1_2(即❺句处),❹句不被执行 3。
3这几个命令的意思分别是:movel=move long integer,cmpl=compare long integer,jne=jump if not equal。
这种满足条件后跳转的命令很早就有。比如 1949 年发明的 EDSAC 就 有“特定内存值大于零时跳转”和“特定内存值为负时跳转”这两条命令 4。
4准确来讲,不是内存而是累加器(accumulator)。本书把内存一词理解为记忆装置并用在全书中。
为什么会有 if…else 语句
大家在学习 if 语句时,想必同时也把 else 和 else if 语句配套地学习了吧 5。没有 else 和 else if 程序就没法写了吗?非也!本节我们来看一下同样没有 else 和 else if 语句的汇编语言。
5C 语言中,else if 不是独立的语句,而是 else 后紧跟着的 if 语句。这个表达在各种语言中不尽相同,Perl 语言和 Ruby 语言中有 elsif,Sh 语言和 Python 语言中有 elif 这样专门的关键字。
汇编语言中的表达方式
我们先在 C 语言中实现 else 语句,然后把它编译成汇编语言。这是一段处理 x 值为正为负或为零时的代码。
C语言
/ if语句前 /
if(x > 0){
/ 为正时的处理 /
}else if(x < 0){
/ 为负时的处理 /
}else{
/ 为零时的处理 /
}
/ if语句后 /
与前面一样,这段代码编译后结果如下。
汇编语言
_main:
……
# if语句前
movl -8(%rbp), %eax
cmpl $0, %eax ┐
jle LBB1_2 ├-❶
# 为正时的处理 ❷ │
jmp LBB1_5 ❸ ┘
LBB1_2: ❹
movl -8(%rbp), %eax ┐
cmpl $0, %eax │
jge LBB1_4 ├-❺
# 为负时的处理 ❻ │
jmp LBB1_5 ❼ ┘
LBB1_4: ❽
# 为零时的处理 ❾ ┐
LBB1_5: ⓫ ├-❿
# if语句后 ┘
我们按顺序来读一下: ❶ 句指如果 x 小于或等于 0 时跳转至 LBB1_2(❹句),❷句是为正的处理,❸句跳转至 LBB1_5(⓫句)。接着,❺句指 x 大于或等于 0 时跳转至 LBB1_4(❽句),❻句是为负的处理,❼句跳转至 LBB1_5(⓫句)。最后,❿句是为零的处理。6
6jle 是 jump if less or equal 的略称,实现小于等于零时的跳转。jge 与之相反,是 jump if greater or equal 的略称。jmp 是 jump 的略称,表示无条件跳转。
那么,就实际中 x 为正,x 为负,x 为零的情况,我们来追踪下程序是如何执行的。
为正时,❶句的“小于等于零则跳转”不成立,因此不跳转而继续执行❷句中为正时的处理,然后执行❸句中跳转至⓫句,程序结束。
为负时,❶句的“小于等于零则跳转”成立,跳转至❹句,❺句的大于等于零跳转不成立,故不跳转而继续执行❻句中为负时的处理,然后执行❼句中的跳转至⓫句,程序结束。
为零时,❶句的“小于等于零则跳转”成立,跳转至❹句,❺句的“大于等于零跳转”成立,故跳转至❽句,在❾句执行为零时的处理。
由上可见,本来要表达如果等于某值则执行某事的逻辑的,却不得不表达为如果不等于某值则跳转至某处执行某事。如此这般条件颠倒,看起来实在是有些混乱 7。
7也有条件不颠倒的写法,由于篇幅所限,在此不介绍它的代码实现了。
C 语言中的表达方式
这种不使用 else 语句的书写方式在 C 语言中不可能实现吗?不是的。C 语言中,如果使用跳转至指定行的命令 goto 语句的话,这个一样可以实现。实验中的实现代码如下所示。goto END; 语句意指跳转至标示有 END: 的一行。
C 语言
void not_use_if(int x){
if(x <= 0) goto NOT_POSITIVE;
printf("正数\n");
goto END;
NOT_POSITIVE:
if(x >= 0) goto NOT_NEGATIVE;
printf("负数\n");
goto END;
NOT_NEGATIVE:
printf("零\n");
END:
return;
}
使用 if…else 语句的好处
众所周知,C语言程序设计中 else 语句的使用不是必不可少的,替代方案是使用 goto 语句。其功能是跳转至指定的某行,这很好理解。
那么,上面的代码便于理解吗?至少在笔者看来,这段代码烦杂、结构差,如果不瞪大眼睛仔细读很难理解。我们将其和直接使用 if…else 语句的代码比较下,来看看哪种方式更便于理解。
C语言
void use_if(int x){
if(x > 0){
printf("正数\n");
}else if(x < 0){
printf("负数\n");
}else{
printf("零\n");
}
}
导入 if…else 语句的好处正在于此。针对条件为真为假的不同情况分流处理,这样的模式在程序设计中屡见不鲜。为了能简洁地表达并方便轻松地阅读这种逻辑,于是引入了 if…else 语句这种新的规则。
程序设计中 else 语句并不是必需的。但是,笔者还是乐于使用 else 语句,毕竟这能让程序编写更加轻松。另外,笔者也希望别人能使用它,毕竟这也能让理解程序更轻松。
