《代码之髓》试读:4.2 if 语句诞生以前
如果没有 if 语句该如何编写程序呢?我们首先来考察一下这一问题。
为什么会有 if 语句
本章我们使用一种非常原始的程序设计语言——汇编语言。汇编语言中是没有 if 语句的,但是从 C 语言很容易就能编译成汇编语言。接下来,我们用 C 语言先编写带 if 语句的代码,再试着将其编译成汇编语言看一下 ①。
C 语言下的源代码如下所示,其含义是如果 X 等于 456 则做相应处理 ②。
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 相比较。
------------------------------------
① 通过编译转换成汇编语言,然后汇编到机器语言,最后链接成一个可执行文件,现在一般把这一整串的动作统称为编译。这里说的编译仅指转换成汇编语言这一个步骤。
② 实际实验的代码里,通过使用 _ _asm_ _ 在汇编语言里嵌入了注释。这里为了简洁易读做了改写。请参照本书文前的“本书构成”部分获取可执行的源代码。
------------------------------------
接下来的❸句是关键,它表示在前一句的比较中,如果两边不相等 则跳转至 LBB1_2 处。换句话说,如果两边相等则不跳转接着执行下面的命令。❹句就是 if 语句中的代码,它只在两边相等时被执行。不相等时程序跳转至 LBB1_2(即❺句处),❹句不被执行 ①。
这种满足条件后跳转的命令很早就有。比如 1949 年发明的 EDSAC 就
有“特定内存值大于零时跳转”和“特定内存值为负时跳转”这两条命令 ②。
为什么会有 if...else 语句
大家在学习 if 语句时,想必同时也把 else 和 else if 语句配套地学习 了吧 ③。没有 else 和 else if 程序就没法写了吗?非也!本节我们来看一下 同样没有 else 和 else if 语句的汇编语言。
汇编语言中的表达方式
我们先在 C 语言中实现 else 语句,然后把它编译成汇编语言。这是 一段处理 x 值为正为负或为零时的代码。
C语言
/* if语句前 */
if(x > 0){
/* 为正时的处理 */
}else if(x < 0){
/* 为负时的处理 */
}else{
/* 为零时的处理 */
}
/* if语句后 */
与前面一样,这段代码编译后结果如下。
---------------------------
① 这几个命令的意思分别是:movel=move long integer,cmpl=compare long integer,jne=jump if not equal。
② 准确来讲,不是内存而是累加器(accumulator)。本书把内存一词理解为记忆装置并用在全书中。
③ C 语言中,else if 不是独立的语句,而是 else 后紧跟着的 if 语句。这个表达在各种语言中不尽相同,Perl 语言和 Ruby 语言中有 elsif,Sh 语言和 Python 语言中有elif 这样专门的关键字。
---------------------------
汇编语言
_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(⓫句)。最后,❿句是为零的处理。A
那么,就实际中 x 为正,x 为负,x 为零的情况,我们来追踪下程 序是如何执行的。
为正时,❶句的“小于等于零则跳转”不成立,因此不跳转而继续 执行❷句中为正时的处理,然后执行❸句中跳转至⓫句,程序结束。
为负时,❶句的“小于等于零则跳转”成立,跳转至❹句,❺句的
大于等于零跳转不成立,故不跳转而继续执行❻句中为负时的处理,然 后执行❼句中的跳转至⓫句,程序结束。
为零时,❶句的“小于等于零则跳转”成立,跳转至❹句,❺句的
A jle 是 jump if less or equal 的略称,实现小于等于零时的跳转。Jge 与之相反,是 jump if greater or equal 的略称。jmp 是 jump 的略称,表示无条件跳转。
“大于等于零跳转”成立,故跳转至❽句,在❾句执行为零时的处理。
由上可见,本来要表达如果等于某值则执行某事的逻辑的,却不得
不表达为如果不等于某值则跳转至某处执行某事。如此这般条件颠倒,
看起来实在是有些混乱 ①。
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 语句,毕竟这能让程序编写更加轻松。另外,笔者也希望别人能使用 它,毕竟这也能让理解程序更轻松。
>代码之髓

作者: [日] 西尾泰和
副标题: 编程语言核心概念
原作名: コーディングを支える技術 ~成り立ちから学ぶプログラミング作法
isbn: 7115361533
书名: 代码之髓
页数: 236
出品方: 图灵教育
译者: 曾一鸣
定价: 45.00元
出版社: 人民邮电出版社
出版年: 2014-8
装帧: 平装