1.词法分析中的“贪心法”
C语言的某些符号,例如/、*和=,只有一个字符长,称为单字符符号。而C语言中的其他符号,例如/*和==,以及标识符等都包含了多个字符,称为多字符符号。当C编译器读入一个字符‘/‘后又跟了一个字符‘*‘,那么编译器就必须做出判断:是将其作为两个分别的符号对待,还是合起来作为一个符号来对待。C语言对这个问题的解决方案可以归纳为一个很简单的规则:每一个符号应该包含尽可能多的字符。也就是说,编译器将程序分解成符号的方法是,从左到右一个字符一个字符地读入,如果该字符可能组成一个符号,那么再读入下一个字符,判断已经读入的两个字符组成的字符串是否可能是一个符号的组成部分;如果可能,继续读入下一个字符,重复上述判断,直到读入的字符组成的字符串已不再可能组成一个有意义的符号。
但是需要注意的是,除了字符串与字符常量,符号的中间不能嵌有空白(空格符、制表符和换行符)。注意下面两个例子:
a---b等价于a -- - b
a---b不等价于a - -- b
y = x/*p 不能实现预期的目的,可以修改为y = x/(*p)或y = x/ *p
2.用双引号引起的字符串,代表的是一个指向无名数组起始字符的指针,该数组被双引号之间的字符以及一个额外的二进制值为0的字符‘\0‘初始化。
3.‘yes‘这种写法的字符,根据编译器的不同会得到不同的处理:大多数编译器会理解为一个整数值,由‘y‘、‘e‘、‘s‘所代表的整数值按照特定编译器实现中定义的方式组合得到;Borland C++v5.5和LCC v3.6采取的做法是忽略多余的字符,最后的整数值即第一个字符的整数值;在Visual C++6.0和GCC v2.95中采取的做法是,依次用后一个字符覆盖前一个字符,最后得到的整数值即最后一个字符的整数值。
4.任何C变量的声明都由两部分组成:类型以及一组类似表达式的声明符。声明符从表面上看与表达式有些类似,对它求值应该返回一个声明中给定的类型的结果。
float ff();
这个声明的含义:表达式ff()求值的结果是一个浮点数,也就是说,ff是一个返回值为浮点类型的函数。
float *pf;
这个声明的含义:*pf是一个浮点数,也就是说pf是指向一个浮点数的指针。
float *g();
这个声明的含义:*g()是一个浮点数,g()等价于上面的pf一样理解,g是一个函数,函数的值就是函数的返回值,所以g()返回的值应该是上例中的pf一致的类型,也就是一个浮点型的指针。
float (*h)();
这个声明的含义:(*h)()是一个浮点数,然而(*h)()是一个函数,所以h就是一个函数指针,h指向的这个函数返回一个浮点型的数。
5.强制类型转换符的构建
从声明变量出发,去掉声明中的变量名和声明末尾的分号,然后将剩余的部分用一个括号括起来即可。
float (*h)();-->(float (*)())
后面的强制类型转换符的含义:返回值为浮点类型的函数的指针。
现在来分析(*(void(*)())0)()的含义
很明显在常数0之前是一个强制类型转换符:(void(*)()),这个强制类型转换符的含义是:返回值类型为void类型的函数指针。所以这整个表达式的含义就是,将0转换为一个返回值类型为void类型的函数指针,然后通过*运算符取得这个0所指向的函数,然后调用它。使用typedef来实现这个功能更为清晰:
typedef void (*funcptr)();
(*(funcptr)0)();
复杂一点的一个例子:
void (*signal(int, void(*)(int)))(int);
含义:首先把signal(int, void(*)(int))看做一个整体,这样的话这个整体应该代表的是一个函数指针,外层通过*运算符调用这个指针指向的函数。然而signal(int, void(*)(int))本身就比较好理解了,signal是一个函数,接收的参数第一个为int类型的值,另一个参数为一个函数指针。然而既然这个整体表示一个函数指针,前面也提到过函数本身代表一个值的话其实也就是说该函数的返回值是一个什么样的值,所以signal函数返回一个函数指针,该指针类型为void (*)(int)。那么void
(*signal(int, void(*)(int)))(int)声明的signal如何调用呢?其实用法很简单:
signal(int m, void(*ptr)(int);就可以实现调用,上面这个声明方式的用处在于使得这个函数的返回值变得更加有意义,调用者可以定义一个void (*)(int)类型的指针来接收signal函数的返回值。
这个功能同样可以由typedef来实现,同样会变得更为清晰:
typedef void (*HANDLER)(int);
HANDLER signal(int, HANDLER);
6.当需要为一个变量添加符号的时候可以采用下面的这种写法:
int a = 3;
a = -a;
7.C语言的初始化例表中允许多书写一个逗号,这有什么作用?
分析:这是为了词法分析器的方便,这样书写之后,每一个用来初始化的变量都是以逗号结束,编译器处理起来更为方便。
8.定义一个数组a之后,a除了作为运算符sizeof的参数之外,其他的所有情形中a都代表的是a中下标为0的元素的指针,这个在多维数组的情况下仍然适用,因为在C语言中多维数组是通过一维数组模拟出来,只不过一维数组的每个元素都可以是其他的各种类型的变量,当然也可以是另一个数组。所以如下的例子:
int a[3];
*a = 2;
*(a+1) = 3;
分别操作的是a数组中的第0个元素和第1个元素,但是这样操作起来往往比较麻烦,所以现在常用的写法a[0]、a[1]、a[2]是上面的写法的简写形式。*(a+1)等价于*(1+a),而*(a+1)的简写形式为a[1],而*(1+a)的简写形式为1[a],所以a[1]等价于1[a]写法,很多编译器都不会报错,但是不推荐第二种写法!!
9.下面的例子输出会是多少?
int a[2][3];
printf("%d, %d", sizeof(a+1), sizeof(a[1]));
分析:先不说这个输出结果为多少,首先来一步步分析需要输出的内容,sizeof(a+1)编译器在碰到这句话的时候,会把a当做一个指针来处理,因为数组+1没有意义,只有数组的指针+1才有意义,所以这里测试出来的仍然只是一个指针的大小,那么如果是32位的PC的话输出就会是4。再来分析sizeof(a[1]),这个测的是一维数组a的第2个单元的大小,然而第二个单元里面存放的是什么呢?第二个单元里面存放的是一个3个单元的数组,所以这个表达式测试出来的值为12。
答案:4, 12
10.malloc()函数分配内存失败的时候会返回一个空指针,在使用malloc的时候一定要判断返回的值是否为空指针,否则会产生意想不到的后果。