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 语句,毕竟这能让程序编写更加轻松。另外,笔者也希望别人能使用它,毕竟这也能让理解程序更轻松。