术语“符号”(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++作为后面的++运算符的操作数。在编程实践中,应避免这样的此法二义性问题。