• 把《C语言接口与实现》读薄之第一章:引言


    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)
    ",  
     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 == '
    ')  
     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] = '';  
    9       } 

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

    1     〈found a word? 5〉≡  
    2       buf[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] = '';  
     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
    ", linenum, word);  
    6        } 

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

    1.2程序设计风格

    上例double说明了本书中程序所使用的风格惯例。一般来说,较长且富于语义的名称用于全局变量和例程,而数学符号般的短名称则用于局部变量。

    代码中几乎没有注释,因为围绕对应代码块的正文代替了注释。本书将效法C程序设计方面的典范,最低限度地使用注释。

    库函数strcpy将一个字符串复制到另一个字符串中并返回目标字符串,对该函数的不同实现就说明了"地道的C语言"和新手C程序员编写的代码之间的差别,后一种代码通常使用数组:

    1     char *strcpy(char dst[], const char src[]) {  
    2         int i;  
    3      
    4         for (i = 0; src[i] != ''; i++)  
    5             dst[i] = src[i];  
    6         dst[i] = '';  
    7         return dst;  
    8     } 

     "地道"的版本则使用指针:

    1     char *strcpy(char *dst, const char *src) {  
    2         char *s = dst;  
    3      
    4         while (*dst++ = *src++)  
    5             ;  
    6         return s;  
    7     } 

     这两个版本都是strcpy的合理实现。指针版本使用通常的惯用法将赋值、指针递增和测试赋值操作的结果合并为单一的赋值表达式。它还修改了其参数dst和src,

    这在C语言中是可接受的,因为所有参数都是传值的,实际上参数只不过是已初始化的局部变量。

    1.3 效率

    程序员似乎被效率问题困扰着。他们可能花费数小时来微调代码,使之运行得更快。遗憾的是,大部分这种工作都是无用功。当猜测程序的运行时间花费在何处时,程序员的直觉非常糟糕。

    微调程序是为了使之更快,但通常总是会使之更大、更难理解、更可能包含错误。除非对执行时间的测量表明程序太慢,否则这样的微调没有意义。程序只需要足够快即可,不一定要尽可能快。

    如果I/O占了程序运行时间的60%,在搜索例程中节省1%是无意义的。

    微调:

    1. 微调通常会引入错误。最快崩溃的程序绝非胜者。可靠性比效率更重要;与交付足够快的可靠软件相比,交付快速但会崩溃的软件,从长远看来代价更高。
    2. 微调经常在错误的层次上进行。快速算法的直接简明的实现,比慢速算法的手工微调实现要好得多。例如,减少线性查找的内层循环的指令数,注定不如直接使用二分查找。
    3. 微调无法修复低劣的设计。如果程序到处都慢,这种低效很可能是设计导致的。当基于编写得很糟糕或不精确的问题说明给出设计时,或者根本就没有总体设计时,就会发生这种令人遗憾的情况。

    一些C程序员在寻求提高效率的途径时,大量使用宏和条件编译。只要有可能,本书将避免使用这两种方法。使用宏来避免函数调用基本上是不必要的。仅当客观的 测量结果表明有问题的调用的开销大大超出其余代码的运行时间时,使用宏才有意义。操作I/O是较适宜采用宏的少数情况之一例如,标准的I/O函数 getc、putc、getchar和putchar通常实现为宏。

    条件编译通常用于配置特定平台或环境的代码,或者用于代码调试的启用/禁用。这些问题是实际存在的,但条件编译通常只是解决问题的较为容易的方法,而且总会使代码更难于阅读。如果应用程序必须在编译时配置,与C语言的条件编译工具相比,版本控制工具更擅长完成该工作。

  • 相关阅读:
    ThinkPhp6.x+AntDesign+Vue前后端分离快速开发平台
    Laravel8.x+AntDesign+Vue前后端分离快速开发平台
    JavaWeb v1.0.0后台开发框架,专业版发布——细节完善,体验优化
    高并发情况下的DB重复插入解决方案随笔
    jQuery.qrcode.js客户端生成二维码,支持中文并且可以生成LOGO
    新能源车牌的问题
    OCR识别移动端的实现与应用
    在拥有vin码识别的时代,您还在傻乎乎手工录入吗?
    国内的车牌识别算法怎样选择
    一码在手运车无忧
  • 原文地址:https://www.cnblogs.com/thinker-lj/p/3872137.html
Copyright © 2020-2023  润新知