C词法分析中的“贪心”法[转]

本章探讨的是符号和组成符号的字符之间的关系,以及有关符号含义的一些常见的误解。

   当我们阅读一个句子时,我们并不去考虑组成这个句子的单词中单个字母的含义,而是把单词作为一个整体来理解。确实,字母本身并没有什么意义,我们总是将字母组成单词,然后给单词赋予一定的意义。对于C语言或其他语言编写的程序,道理也是一样的。

   编译器中负责将程序分解为一个一个符号的那部分,一般称为”词法分析器”,在C语言中符号之间的空白将被忽略,包括空格符,制表符和换行符。


=和==


   一般而言,更为常用的操作用更为简短的表达,以节省大家的时间。故符号=作为赋值,符号==作为比较运算,因为赋值比使用的更为频繁。

   =的左边的操作数为左值(lvalue),右边的为右值(rvalue),关于lvalue和rvalue,详情请参阅《The C programming Language》(K&R/TCPL)的附录A.5和A.7。

   潜在的问题代码:
   if (x = y)
           foo();

   程序员的本意是判断(x == y)还是直接的赋值(x = y)呢?现在的gcc编译器在发现形如e1 = e2的表达式出现在条件判断的部分时,会给出相关的警告信息,如果编译的时候把警告打开的话。

   比如下面这段代码:

   #include <stdio.h>

   #include <stdlib.h>

  

   int main(int argc, char **argv)

   {

           int x = 1;

           int y = 1;

           if ( x = y)

               printf(“x = y is truen”);

           exit(0);

   }

   不打开警告的编译方法和打开警告的编译方法分别为:

   gcc -o prog prog.c

   gcc -Wall -o prog prog.c

   则可以看到如果打开警告,编译器会有提示”warning: suggest parentheses around assignment used as truth value”,如果不打开警告的话,就没有任何警告提示了。

   作为一种解决方法和一种良好的编程习惯,作者建议写作显式比较的形式:

   if ((x = y) != 0)

           foo();

   既能去除警告,也使得代码的意图一目了然,只是多了几个字符,何乐而不为!

C语言的某些符号,例如/、* 和=,只有一个字符长,称为单字符符号。而C语言中的其他符号,例如/*和==,以及标识符,包括了多个字符,称为多字符符号,当C编译器读入一个字符‘/’后又跟了一个字符’*’,那么编译器就必须做出判断:是将其作为两个分别的符号对待,还是合起来作为一个符号对待。C语言对问题的解决方案可以归纳为一个很简单的规则:每一个符号应该包含尽可能多的字符。也就是说,编译器将程序分解成符号的方法是,从左到右一个字符一个字符地读入,如果该字符可能组成一个符号,那么再读入下一个字符,判断已经读入的两个字符组成的字符串是否可能是一个符号的组成部分;如果可能,继续读入下一下字符,重复上述判断,直到读入的字符组成的字符串已不再可能组成一个有意义的符号。这个处理策略有时候被称为“贪心法”,或者,更口语化一点,称为“大嘴法”。Kernighan与Ritchie对这个方法的表述如下:“如果(编译器的)输入流截止到某个字符之前都已经被分解为一个个符号,那么下一个符号将包括从该 字符之后可能组成一个符号的最长字符串”。


     根据上述说法,我们可以将下述表达式正确解析:

    ab <====> a b

    而下述表达式写得时候也应该注意:

     y =x/*p (错误写法)

     y =x/ *p (正解写法)

     y =x/(*p)(正解写法)

   那么表达式 “a+++++b” 该如何解析呢?

   根据贪心法,我觉得应该这样解析“(a++)++ + b ”,在GCC 4.1里面测试了下,发现该表达式根本就是错误的,下面是我的测试程序的截图:

字符与字符串
    这个相对容易理解多了,单引号和双引号嘛。下面说说两者的实质的区别:
    单引号:单引号字符本质上是代表一个整数,其值对应于该字符在编译器采用的字符集中的序列值。对于普通的采用ASCII字符集的编译器,如gcc,’a’就是97或0141(八进制)。
    双引号:双引号的字符串代表的是一个指向无名数组起始字符的指针,该数组被双引号之间的字符以及一个额外的二进制值为零的字符’’初始化。
   
   下面的语句:
    printf(“Hello worldn”);
   
    char hello[] = {‘H’, ‘e’, ‘l’, ‘l’, ‘o’, ‘ ‘, ‘w’, ‘o’, ‘r’, ‘l’, ‘d’, ‘n’, ‘’}
    printf(hello);
    两者是等价的。

    这个很好理解,用一种形而上学的理解记忆,一个串相当N多字符的组合,要放在一起使用,当然只能用数组的形式了,也就是说串就是字符的无名数组形式了,既然无名,就只能使用指针来引用了,串就是这个指针。而这个数组最后的’’代表串的结束(总得有一个东西来代表结束吧)。
    关于这个结束字符串的东西,在《Expert C Programming: Deep C Secrets》(《C 专家编程》)中的第二章的2.1小节的小启发中说是NUL,而NULL代表空指针,表示什么也不指向,而NULLL则说明需要检查一下是不是拼写错误了。这里的NUL其实ASCII码表0值的char字面意思,NUL并没有在标准C中定义,当然如果自己想用的话,#define一下也不错。
    一句话总结就是,单引号的字符代表一个整数,双引号的则代表一个指针,混用的话编译器的类型检查功能会检测到这样的错误的,gcc就可以。