码字不易,对你有帮助 点赞/转发/关注 支持一下作者
微信搜公众号:不会编程的程序圆
看更多干货,获取第一时间更新
代码,练习上传至:
https://github.com/hairrrrr/C-CrashCourse
看更多干货
https://mp.weixin.qq.com/s/QkO9wYp8M3PqlXwqaMhFBw
一 预处理器
在严格意义上的编译过程开始之前,C 语言预处理器首先对程序代码作了必要的转换处理。因此,我们运行的程序实际上并不是我们所写的程序。预处理器使得编程者可以简化某些工作,它的重要性可以由两个主要的原因说明(当然还有一些次要原因,此处就不赘述了)。
第一个原因是,我们也许会遇到这样的情况,需要将某个特定数量(例如,某个数据表的大小)在程序中出现的所有实例统统加以修改。我们希望能够通过在程序中,只改动一处数值,然后重新编译就可以实现。预处理器要做到这一点可以说是轻而易举,即使这个数值在程序中的很多地方出现。我们只需要将这个数值定义为一个显式常量(manifest constant), 然后在程序中需要的地方使用这个常量即可。而且,预处理器还能够很容易地把所有常量定义都集中在一起, 这样要找到这些常量也非常容易。
第二个原因是,大多数 C 语言实现在函数调用时都会带来重大的系统开销。因此,我们也许希望有这样一种程序块, 它看上去像一个函数, 但却没有函数调用的开销。举例来说,getchar 和 putchar 经常被实现为宏,以避免在每次执行输入或者输出一个字符这样简单的操作时,都要调用相应的函数而造成系统效率的下降。
虽然宏非常有用,但如果程序员没有认识到宏只是对程序的文本起作用,那么他们很容易对宏的作用感到迷惑。也就是说,宏提供了一种对组成 C 程序的字符进行变换的方式,而并不作用于程序中的对象。因而,宏既可以使一段看上去完全不合语法的代码成为一个有效的 C 程序,也能使一段看 上去无害的代码成为一个可怕的怪物。
1. 不能忽视宏定义中的空格
一个函数如果不带参数,在调用时只需在函数名后加上–对括号即可加以调用了。而一个宏如果不带参数,则只需要使用宏名即可,括号无关紧要。只要宏已经定义过了,就不会带来什么问题:预处理器从宏定义中就可以知道宏调用时是否需要参数。与宏调用相比,宏定义显得有些“暗藏机关”。例如,下面的宏定义中f是否带了一个参数呢?
#define f (x) ((x)-1)
答案只可能有两种:
或者 f(x)
代表 ((x)-1)
或者 f 代表 (x) ((x)-1)
在上述宏定义中,第二个答案是正确的,因为在 f 和后面的 (x) 之间多了一个空格!所以,如果希望定义 f(x) 为 ((x)-1),必须像下面这样写:
#define f(x) ((x)-1)
这一规则不适用于宏调用,而只对宏定义适用。因此,在上面完成宏定义后,f(3) 与 f (3) 求值后 都等于 2。
2. 宏并不是函数
因为宏从表面上看其行为与函数非常相似,程序员有时会禁不住把两者视为完全等同。因此,我们常常可以看到类似下面的写法:
#define abs(x) (((x)>=0)?(x) :-(x))
或者:
#define max(a,b) ((a)>(b)?(a):(b))
请注意宏定义中出现的所有这些括号,它们的作用是预防引起与优先级有关的问题。例如,假设宏 abs 被定义成了这个样子:
#define abs(x) x>0?x:-x
让我们来看 abs(a-b) 求值后会得到怎样的结果。表达式
abs(a-b)
会被展开为
a-b>0?a-b:-a-b
这里的子表达式 -a-b 相当于 (-a)-b,而不是我们期望的 -(a-b),因此上式无疑会得到一个错误的结果。因此,我们最好在宏定义中把每个参数都用括号括起来。同样,整个结果表达式也应该用括号括起来,以防止当宏用于一个更大一些的表
达式中可能出现的问题。如果不这样,
abs(a)+1
展开后的结果为:
a>0?a:-a+1
这个表达式很显然是错误的,我们期望得到的是 -a,而不是 -a+1! abs 的正确定义应该是这样的:
#define abs(x) (((x)>=0)?(x):-(x))
这时,abs (a-b) 才会被正确地展开为: ((a-b)>0? (a-b):-(a-b))
,
而 abs (a)+1 也会被正确地展开为: ((a)>0?(a):-(a))+1
即使宏定义中的各个参数与整个结果表达式都被括号括起来,也仍然还可能有其他问题存在,比如说,一个操作数如果在两处被用到,就会被求值两次。例如,在表达式 max(a,b )中,如果 a 大于 b,那么 a 将被求值两次:第一次是在 a 与 b 比较期间,第二次是在计算 max 应该得到的结果值时。
这种做法不但效率低下,而且可能是错误的:.
biggest = x[0];
i = 1;
while (i < n)
biggest = max(biggest, x[i++]);
如果max是一个真正的函数,上面的代码可以正常工作;而如果max是一个宏,那么就不能正常工作。要看清楚这一点,我们首先初始化数组x中的一些元素:
x[0] = 2;
x[1] = 3;
x[2] = 1;
然后考察在循环的第一次迭代时会发生什么。上面代码中的赋值语句将被扩展为:
biggest =