首页 > 代码库 > 第一章:引言

第一章:引言

1.1文学程序

文学程序(literate program):接口及其实现的代码与对其进行解释的正文交织在一起。文学程序由英文正文和带标签的程序代码块组成。例如,

1     〈compute x * y〉≡  2         sum = 0;  3         for (i = 0; i < n; i++)  4                 sum += x[i]*y[i]; 

定义了名为〈compute x * y〉的代码块,其代码计算了数组x和y的点积。在另一个代码块中使用该代码块时,直接引用即可:

1     〈function dotproduct〉≡  2         int dotProduct(int x[], int y[], int n) {  3             int i, sum;  4      5         〈compute x o y〉  6             return sum;  7     } 

 文学程序可以按各个小片段的形式给出,并附以完备的文档。英文正文包含了传统的程序注释,这些并不受程序设计语言的注释规范的限制。

下面是文学编程系统的另一个特性,她有助于逐点对程序进行描述;下面以一个检测输入中相邻的相同单词的double程序,作为文学程序的例子:

 先从定义根代码块来实现double,该代码块将使用对应于程序各个组件的其他代码块:

1double.c 3〉≡  2  〈includes 43  〈data 44  〈prototypes 45  〈functions 3

 main函数处理double的参数。它会打开各个文件,并调用doubleword扫描文件:

 1     〈functions 3〉≡   2       int main(int argc, char *argv[]) {   3           int i;   4       5           for (i = 1; i < argc; i++) {   6               FILE *fp = fopen(argv[i], "r");   7               if (fp == NULL) {   8                   fprintf(stderr, "%s: can‘t open ‘%s‘ (%s)\n",   9                       argv[0], argv[i], strerror(errno));  10                   return EXIT_FAILURE;  11               } else {  12                       doubleword(argv[i], fp);  13                       fclose(fp);  14                   }  15               }  16               if (argc == 1) doubleword(NULL, stdin);  17               return EXIT_SUCCESS;  18           }  19      20        〈includes 4〉≡  21         #include <stdio.h> 22         #include <stdlib.h> 23         #include <errno.h> 

 getword从打开的文件读取下一个单词,复制到buf [0..size  1]中,并返回1;在到达文件末尾时该函数返回0。

 1 functions 3〉+ 2   int getword(FILE *fp, char *buf, int size) {   3       int c;   4   5       c = getc(fp);   6      〈scan forward to a nonspace character or EOF 5 7      〈copy the word into buf[0..size-1] 5 8       if (c != EOF)   9           ungetc(c, fp);  10       return〈found a word? 5〉;  11   }  12  13 〈prototypes 4〉≡  14   int getword(FILE *, char *, int);

 

 getword除了从输入获取下一个单词之外,每当遇到一个换行字符时都对linenum加1。doubleword输出时将使用linenum。

 1     〈data 4〉≡   2       int linenum;   3       4     〈scan forward to a nonspace character or EOF 5〉≡   5       for ( ; c != EOF && isspace(c); c = getc(fp))   6           if (c == \n)   7               linenum++;   8       9     〈includes 4〉+10       #include <ctype.h> 

 size的值限制了getword所能存储的单词的长度,getword函数会丢弃过多的字符并将大写字母转换为小写:

1     〈copy the word into buf[0..size-1] 5〉≡  2       {  3           int i = 0;  4           for ( ; c != EOF && !isspace(c); c = getc(fp))  5               if (i < size - 1)  6                   buf[i++] = tolower(c);  7           if (i < size)  8               buf[i] = \0;  9       } 

 剩下的代码逻辑是,如果buf中保存了一个单词则返回1,否则返回0:

1     〈found a word? 5〉≡  2       buf[0] != \0 

 doubleword读取各个单词,并将其与前一个单词比较,发现重复时输出。它只查看以字母开头的单词:

 1     〈functions 3〉+ 2       void doubleword(char *name, FILE *fp) {   3           char prev[128], word[128];   4       5           linenum = 1;   6           prev[0] = \0;   7           while (getword(fp, word, sizeof(word)) {   8               if (isalpha(word[0]) && strcmp(prev, word)==0)   9                  〈word is a duplicate 610               strcpy(prev, word);  11           }  12       }  13     〈prototypes 4〉+14      15       void doubleword(char *, FILE *);  16      17     〈includes 4〉+18       #include <string.h> 

 输出是很容易的,但仅当name不为NULL时才输出文件名及后接的冒号:

1     〈word is a duplicate 6〉≡  2       {  3            if (name)  4                printf("%s:", name);  5            printf("%d: %s\n", linenum, word);  6        } 

 总结:整个程序的设计符合学习C语言经常提的:自顶向下,逐步细化,模块化!