关于Integrity of Science的翻译 The C Book - 第一章 C语言概述 (6)
May 26

第1.4节 更多的程序例


乘我们还在不是很严谨的时候,让我们再来看看两个程序例。这次你得自己想想里面的一些代码是做什么的,不过当新的或有趣的东西出现时,我们还是会讲解的。


1.4.1 一个寻找质数的程序

    /*  Dumb program that generates prime numbers. */ 
    #include <stdio.h> 
    #include <stdlib.h> 
    
    main(){ 
        int this_number, divisor, not_prime; 

 

        this_number = 3; 

 

        while(this_number < 10000){ 
            divisor = this_number / 2; 
            not_prime = 0; 
            while(divisor > 1){ 
                if(this_number % divisor == 0){ 
                    not_prime = 1; 
                    divisor = 0; 
                } 
                else 
                    divisor = divisor-1; 
            } 

 

            if(not_prime == 0) 
                printf(”%d is a prime number\n”, this_number); 
            this_number = this_number + 1; 
        } 
        exit(EXIT_SUCCESS); 
    }

 

    Example 1.2

 

这里有什么有趣的东西?也许有几样新东西。这个程序的运行方式是很笨的:为了看一个数是否是质数,它就把这个数用从2到这个数的一半的所有数来除--如果其中有一个能够整除,那么这个数就不是质数。这里有之前没有见过的两个操作符,一个是求余操作符%,另一个是等式操作符,也就是连起来的两个等号==。后者无疑是C语言中产生错误最多的单一因素。

 

像这样测试相等与否的问题在于,如果只放一个等号,它也是合法的语句。连等号==比较两个量是否相等,而这是在如下的程序片段中通常需要用到的:

 

    if (a==b)
    while (c==d)

 

也许有点让人意外的是,赋值操作符=在这些地方也是合法的,不过它的作用就是把右边表达式的值赋给左边的东西。如果你习惯了那些用C语言的赋值操作符做相等比较的编程语言,那么这个问题就更加严重了。这是完全没有办法的事,所以你只能入乡随俗了。(确实,现代的编译器会在它们发现“可疑”的赋值符的时候给出警告,不过如果你是故意这样写的,那也未知非祸。)

 

这里我们也第一次看到 if 语句。和 while 语句一样,if 语句测试一个表达式是否为真。你也许已经注意到了,它也和 while 语句一样,用来控制 if 语句的表达式是放在括号里的。必须总是如此:所有的流程条件控制语句都必须在其关键字后有一个放在括号里的表达式。关于 if 语句的正式描述是这样的:

    if(表达式

        语句 

 

    if(表达式
        语句 
    else 
        语句

 

这显示了 if 语句有两种形式。当然,它的效果就是,如果表达式的部分为真,那么紧随其后的语句就被执行,则否该语句不执行。如果有一个 else 部分,那么跟着 else 的那个语句仅在表达式为假时被执行。

 

关于 if 语句,有一个很有名的问题。在下面的代码中,语句-2是会被执行还是不会被执行?

    if(1 > 0)

        if(1 < 0) 
            语句-1 
    else 
        语句-2

 

答案是被执行。这里你得忽略缩进(因为它让人误入歧途)。根据上面对于 if 语句的描述,这里的 else 既可以属于第一个 if 也可以属于第二个 if。因此,为了避免歧义,还需要另一条规则。这条规则很简单,就是一个 else 总是从属于在它前面最近的一个没有 else 搭配的 if 。在上例中,如果想让程序按照缩进的格式所隐含的方式运行,我们就必须使用一个复合语句:

 

    if(1 > 0) {
        if(1 < 0) 
            语句-1 
    }
    else 
        语句-2

 

在这里,C语言的处理方式至少和其它大多数编程语言一样的。事实上,很多程序员熟悉存在着这种问题的编程语言,而他们根本从来没有意识到这个问题的存在--他们只是认为这种消除歧义的规则是“显而易见”的。我们希望每个人都会这样认为吧。

 

1.4.2 除法操作符集

除法操作符集包括除法操作符“/”和求余操作符“%”。除法操作符进行的是一般的除法,但当操作数为整数时,结果会被向0舍入取整。比如,5/2的结果是2,5/3得1。求余操作符则可以用来得到整数除法中被舍弃的余数。C语言标准中规定了商和余数的符号如何由除数和被除数决定,具体规定请参见第二章。

 

1.4.3 输入的例子

 

一些程序能够打印出多多少少有意思的列表和表格,而能够进行输入的程序也同样是很有用的。函数库中最简单的一个函数,也就是我们现在要讲到的这个函数,就是getchar函数。它每次从程序的输入中读取一个字符,然后返回一个整数值。返回的值是这个字符的一种编码形式,而这个编码可以用来输出同样的字符。这个编码也可以被用来和某个字符常量或是输入的其它字符相比较。不过,唯一有意义的比较,是看两个字符是否相等。一般来说,比较两个字符哪个大哪个小,是不可移植的。虽然在大多数系统中字符 ‘a’ 是小于字符 ‘b’ 的,但这不能保证在任何情况下都成立。C语言标准唯一的保证就是字符 ‘0′ 到字符 ‘9′ 是连续的。请看下例。

 

    #include <stdio.h> 
    #include <stdlib.h> 
    main(){ 
        int ch; 

 

        ch = getchar(); 
        while(ch != ‘a’){ 
            if(ch != ‘\n’) 
                printf(”ch was %c, value %d\n”, ch, ch); 
            ch = getchar(); 
        } 
        exit(EXIT_SUCCESS); 
    }

 

    Example 1.3

这个例子中有两个要注意的地方。第一点,就是在读入的每一行结束的时候,都会看到一个用 ‘\n’ 表示的字符(字符常量)。这和输出时用printf来产生一个新的行用的是同一个字符。在C语言中,输入输出的模型并不把数据看成一行一行的,而是一个字符一个字符的。如果你想把数据当成是一行一行的,那么你可以用这个 ‘\n’ 字符来标记一行的结尾。当用 %d 来打印的时候,打印的是同一个变量,但显示的将是你的程序用来表示这个字符所用的整数值。

如果你试着运行一下这个程序,你就会发现有些系统并不是一个字符一个字符地把数据送到你的程序,而是会让你一次输入一整行。在这之后,这一整行就成为程序可用的输入数据,每次输入一个字符。初学者常常会被弄糊涂。程序开始运行时,他们敲进去一些数据,但却没有任何输出。这种现象和C语言本身没有关系,而是取决于具体的计算机和操作系统。

 

1.4.4 简单数组

 

C语言中数组的使用,通常对于初学者是个难题。数组的声明,尤其是对于一维数组的声明,其实并不困难。但总是会让人糊涂的,是数组的下标总是从0开始。声明一个包含五个整数的数组,需要用到类似下面的声明语句:

 

    int something[5];

如你所见,C语言中声明数组用的是方括号。数组的下标总是从0向上走,C语言不支持其它的下标范围。在上例中,有效的数组元素就是从 something[0] 到 something[4]。这里需要特别注意的是,something[5] 并是一个有效的数组元素。

下面这个程序从输入中读取一些字符,并按这些字符的数值表达方式排序,最后输出结果。请自己分析这个程序的算法,因为下面我们不会着重讲解它的算法。

    #include <stdio.h> 
    #include <stdlib.h> 
    #define ARSIZE 10 
    main(){ 
        int ch_arr[ARSIZE], count1; 
        int count2, stop, lastchar; 

 

        lastchar = 0; 
        stop = 0; 
        /*
         * 把字符读入数组,直至一行结束或数组已满
         */ 
        while(stop != 1){ 
            ch_arr[lastchar] = getchar(); 
            if(ch_arr[lastchar] == ‘\n’) 
                stop = 1; 
            else 
                lastchar = lastchar + 1; 
            if(lastchar == ARSIZE) 
                stop = 1; 
        } 
        lastchar = lastchar-1; 

 

        /*
         * 这是传统的冒泡排序法 
         */ 
        count1 = 0; 
        while(count1 < lastchar){ 
            count2 = count1 + 1; 
            while(count2 <= lastchar){ 
                if(ch_arr[count1] > ch_arr[count2]){ 
                    /* swap */ 
                    int temp; 
                    temp = ch_arr[count1]; 
                    ch_arr[count1] = ch_arr[count2]; 
                    ch_arr[count2] = temp; 
                } 
                count2 = count2 + 1; 
            } 
            count1 = count1 + 1; 
        } 

 

        count1 = 0; 
        while(count1 <= lastchar){ 
            printf(”%c\n”, ch_arr[count1]); 
            count1 = count1 + 1; 
        } 
        exit(EXIT_SUCCESS); 
    }

 

    Example 1.4

你可能已经注意到了,在程序中我们一直都在使用一个定义的常数 ARSIZE,而不是直接用数组的实际大小。这是因为,如果我们想要改变这个程序可以排序的最大字符个数,我们只需要修改这个常数定义的这一行,然后重新编译。程序中还对数组是否已经放满进行了检查,这看起来不显眼,但对程序的安全至关重要。如果你仔细看看,就会发现,当第 ARSIZE-1 个数组元素被放入的时候,程序就停下来了。这是因为,对于一个有N个元素的数组,我们只能使用从第0个到第N-1个元素(所以总数是N个)。

和其它编程语言不同的是,在C语言中,如果你跑出了数组的界限,你很可能不会得到任何警告。当数组越界的情况发生时,这个程序就会出现所谓的未定义行为,通常在将来的某个时候导致很奇怪的错误。大多数有经验的程序员要么会以严格的测试来保证所使用的算法中数组越界的情况不会发生,要么在存取每一个数组元素之前都做一次明确的测试。数组越界是C语言程序运行时出错的常见原因。切记,切记。

 

小结

 

数组下标总是从0开始,别无选择。

 

一个有 n 个元素的数组,它的元素下标是从 0 到 n-1。第 n 个元素是不存在的。试图存取第 n 个元素是个很大的错误。

 

发表评论

CAPTCHA Image
*