C陷阱与缺陷(C Traps and Pitfalls)--学习笔记--第一章:词法陷阱

术语“符号”(token)指的是程序的一个基本组成单元,其作用相当于一个句子中的单词。符号由字符序列组成,单个字符需要结合上下文才知道它的意义。如符号'-'在p->s = “->”中,就有两个意思,第一个是符号->的组成部分,第二个是字符串"->"的组成部分。

编译器中负责将程序分解为一个一个符号的,一般称为“词法分析器”。如下例:if (x > big) big = x;

这个语句的第一个符号是C语言的关键字if,接着下一个符号是左括号,接着是标识符x,接着是>,接着是标识符big,依此下去。在C语言中,符号之间的空白(包括空格符,制表符Tab,换行符)将被忽略,所以上例可将空格符换成制表符而没有影响。如调用函数fun(a);  ,可以写成fun    (a);

   

                            关于  换行符‘\n’和回车符‘\r’  另补如下(转自http://www.cnblogs.com/jacktu/archive/2008/06/12/1218400.html):

                                         顾名思义,换行符就是另起一行,回车符就是回到一行的开头,所以我们平时编写文件的回车符应该确切来说叫做回车换行符  

                                        ' \n' 10 换行(newline)
                                         '\r' 13 回车(return)


                                       也可以表示为'\x0a'和'\x0d'.(16进制)

                                      在windows系统下,回车换行符号是"\r\n".但是在Linux等系统下是没有"\r"符号的。

                                       在解析文本或其他格式的文件内容时,常常要碰到判定回车换行的地方,这个时候就要注意既要判定"\r\n"又要判定"\n"。

                                       写程序时可能得到一行,将其进行trim掉'\r',这样能得到你所需要的string了。

 

1.1 =不同于==

     =   是赋值运算符

     == 是逻辑运算符中的“等于”

     在C语言中,赋值运算符是作为一种操作符对待,所以可以重复进行赋值操作(如a=b=c;从右到左依此进行赋值,值为赋值表达式的值,因此可以将赋值表达式嵌入更大的表达式之中去)。

例1:if (x=y)

              break;    //本意是检查是否相等,实际是赋值,然后检查该值是否为零

例2:while (c='  ' || c ==  '\t' || c== ''\n )     c = getc(f);     

           /*由于 = 的优先级低于 == (优先级链接http://en.cppreference.com/w/cpp/language/operator_precedence) ,实际上是将以下表达式的值赋给了c

                                        '  ' || c ==  '\t' || c== ''\n

因为'  '不等于0 ('  '的ASCII码值为32),那么无论c之前为何值,上述表达式的值都将为1,因此循环将一直进行下去直到整个文件结束。文件结束之后是否还进行下去取决于getc库函数的具体实现,在文件指针到达文件结尾之后是否还允许继续读取字符。如果允许继续,那么循环将一直进行下去,成为一个死循环。

某些C编译器在发现赋值表达式出现在while,if条件中时,会发出警告。如果确实需要赋值并判断新值是否为0,为了避免来自编译器的警告,不应该关闭警告选项,而应改写程序显式进行比较 

如if (x=y)  ---->  if ( (x=y) != 0)

 

反过来,误将 = 写成 == 同样会造成混淆: if ((filedesc == open(argv[i],0)) < 0)   error();

本例中,如果open执行成功,将返回0 或者正数,失败返回 -1 。将 = 写成 == ,==的结果是0或者1,永远不可能小于0,所以error();  将没有机会被调用。

1.2  & 和 | 不同于  && 和  ||

       注意:只有单个字符组成的符号是按位比较。

1.3 词法分析中的“贪心法”/ “大嘴法”

      编译器将程序分解为符号的方法是:从左到右一个字符一个字符的读入,将尽可能多的字符组成一个符号,直到再读入字符不可能组成一个有意义的符号为止。

注意:除了字符串与字符常量,符号的中间不能嵌有空白。例如 == 是一个符号, =  = 是两个符号

a---b  与表达式 a -- - b相同,而与 a  -  --  b不同。如已读入 / ,而之后紧挨着 *,那么无论上下文如何, /* 都被当作多行注释的开始。

例:y = x/*p;  //本意是用x除以指针变量p指向的值,再赋给y。  但实际上/*被当作注释的开始,编译器将剩下的全部当作注释,直到出现 */为止。可改写成  y = x   /  *p;  或者    y = x   /  (*p); 

 

1.4 整型常量

      如果一个整型常量的第一个字符是0,那么该常量将被视为8进制数,因此10与010(8)的含义不同。此外,许多C编译器会把8和9也作为八进制数处理(只要前面有0,也不管八进制数只有0-7),这奇怪的处理方式来自八进制的定义。例如,019将处理成 17,或者 021。我们当然不建议这种用法,ANSI C标准也禁止这种用法。

 

1.5 字符与字符串

      C语言中'  '与 "  "含义迥异, '   '括起的字符实际上代表其  ASCII码值对应的那个整数,因此对于采用ASCII字符集的编译器而言,'a' == 0141 == 97

     " "括起的字符串代表的却是一个指向无名数组起始字符的指针,该数组被"  "之间的字符以及一个额外的ASCII值为0的字符 '\0' (转义符'\'后面接一个八进制数,用于表示ASCII码等于该值的字符)所初始化。

下面的两个语句是等效的:

  1) printf ("He  Wd\n");

   2)char hello[] = {'H','e',' ','W','d','\n','\0'};     printf (hello);

 

然而,某些C编译器对函数参数并不进行类型检查,特别是对printf函数的参数。因此,   如果用
  
         printf(' ');
       来代替正确的

  
         printf(" ");
则会在程序运行的时候产生难以预料的错误,而不会给出编译器诊断信息。

 
    整型数(一般为16位或32为)的存储空间可以容纳多个字符(一般为8位),因此有的C编译器允许在一个字符常量(以及字符串常量)中包括多个字符。也就是说,用'yes'代替"yes"不会被该编译器检测到。后者的含义是依次包括'y'、 'e' 、's'以及空字符'\0'4个连续内存单元的首地址”。前者的含义并没有准确的进行定义,但大多数编译器理解为,一个整数值,由'y'、'e'、's'所代表的整数值按照特定编译器实现中定义的方式组合得到
 
    (注:在Borland C++ v5.5 LCC v3.6中采取的做法是,忽略多余的字符,最后的整数值即第一个字符的整数值;而在Visual C++ 6.0 GCC v2.95中采取的做法是,依次用后一个字符覆盖前一个字符,最后得到的整数值即最后一个字符的整数值。)

 

 

练习:

    建议:将惯用的c == '\t' 写作 '\t' == c。一旦写错成=号,编译器就能检查出来。

1、写一个测试程序,无论对是否允许嵌套注释的编译器都能正确执行,但运行结果不同。
        a = /*/*/0*/**/1;
        允许:/* /* /0 * /* */ 1,则a = 1;
        不允许:/* / */ 0 * /* */ 1,则a = 0×1 = 0。
2、为什么n-->0的含义是n--  >0,而不是n- ->0?

     根据“大嘴法”规则,编译器在读入 >之前,就已经将 -- 作为一个符号了。

3.a+++++b的含义是什么?

   上式唯一有意义的解释是 a ++ + ++ b,可是根据“大嘴法”规则,应为a ++ ++ + b,((a++) ++) + b,但是a++的结果不能作为左值,编译器不会接受a++作为后面的++运算符的操作数。在编程实践中,应避免这样的此法二义性问题。

 

你可能感兴趣的:(C陷阱与缺陷(C Traps and Pitfalls)--学习笔记--第一章:词法陷阱)