7.3 蛮力法实现重复执行代码

要想用蛮力法反复执行一系列语句,我们需要将这套语句手工复制许多遍,直至达到要求的次数为止。这是最僵化的一种重复执行方式,因为重复次数是写死的,无法在程序运行的过程中修改。

用这种办法做重复执行有许多缺点。首先,如果你想修改这一系列语句之中的某一条或某几条,那该怎么办?这时你要做的工作必定是十分枯燥的,因为你只有两个办法,一种是逐个修改(这当然很容易出错),另一种是把它们全删掉,然后重新写出一个修改之后的版本,再把这个版本手工复制许多遍(这当然也很容易出错)。另外,这样做还会让源文件毫无必要地膨胀。如果你复制的这套语句只有10行,那或许不会有太大问题,但如果有成百上千行,那就会大幅增加文件的尺寸。

然而,有些情况下,我们确实需要把同一条语句复制许多遍,这主要发生在需要执行循环展开(loop unrolling或loop unwinding)时,这是个高深的话题,不在本书的讨论范围之内。如果你学完本章之后有兴趣研究这个话题,可以自己寻找更多的资料。这里只提醒一句,就是它跟某些对性能要求很高的特殊需求有关。你在开始学习这项技术之前,还有许多基础知识要学。

下面这个sum100viaBruteForce()函数用蛮力法来解决早前提到的连加问题,这个函数的代码是这样的:

请大家注意,我们这里并没有把函数中的一百多行代码全写出来,那样相当乏味。这个函数确实可以正确计算出1~100的整数和,但它只对这一个例子有效。如果我们要计算的是1~10或1~50的整数和,那么这个用蛮力法实现出的函数就不再适用了。这个缺点让该函数显得更加无聊。

还有一种蛮力解法是通过C语言的自增运算符来实现。这个版本叫作sum100viaBrute-Force2(),它的代码是这样写的:

请注意,这次我们也没有把函数中的一百多行代码全写出来。这个函数虽然不要求把1~100的每个数字都写一遍,但它跟早前那个函数一样烦琐,而且笔者觉得,这个函数实际上比刚才那个(也就是sum100viaBruteForce())更难写,因为这次我们很难看出当前已经写了多少个sum+=++num;语句。为此,笔者每隔几行代码就要加一条注释,以便记录当前的重复次数,大家打开gauss_bruteforce.c文件就可以看到这些注释。

这两个函数的代码都超过了一百行,这种感觉就好比你在大热天开车穿过一百英里的荒野,而且中途不能休息、不能喝冷饮一样。编译器当然不会觉得这有什么麻烦,但阅读和修改代码的人确实相当难受。

你在录入这种代码的时候当然可以用复制与粘贴功能来简化输入。

下面是gauss_bruteforce.c程序的main()函数:

请创建名为gauss_bruteforce.c的文件,然后录入main()函数以及三个sum函数的代码。接下来,用cc gauss_bruteforce.c -o gauss_bruteforce命令编译这个程序。其中的-o选项用来指定编译之后所产生的可执行文件(以前我们并没有指定这个选项,因此编译之后的可执行文件会默认叫作a.out)。运行gauss_bruteforce程序,你应该会看到类似下面这样的输出信息。

从这张截图中大家看到,这三个函数都能计算出1~100的整数和,而且计算结果都是正确的。

所幸我们有更好的办法实现连加,这些办法虽然不如直接套用高斯的求和公式,但毕竟要比蛮力法强,而且它们可以用来演示本章要讲的话题,也就是循环。

我们接下来讲解这些循环方式的时候,使用的例子都是这个高斯求和问题。这样做有两个好处。第一,由于这个问题我们已经用公式法与蛮力法解决过了,因此,在利用循环法来求解的过程中,如果控制迭代次数的计数器有错,那我们很容易就能通过求和结果意识到这个错误:只要结果跟以前不同,就说明循环写得不对。第二,由于我们已经通过两种方式解决了该问题,因此这已经不是一个新问题了,大家对这个问题应该已经比较熟悉了,于是,我们在改用各种形式的循环来解决这个问题时会比较容易。