
1.预定义符号C语言设置了一些预定义符号可以直接使用预定义符号也是在处理期间处理的--FILE-- //进行编译的源文件--LINE--//文件当前的行号--DATE--//文件被编译的日期--TIME--//文件被编译的时间--STDC--//如果编译器遵循ANSI C其值为1否则未定义2.#define定义成常量基本语法在define定义标识符的时候不要在最后加上3.#define定义宏#define机制包括了一个规定允许把参数替换到文本中这种实现通常称为宏macro或定义宏define macro下面是宏的申明方式其中的parament_list是一个由逗号隔开的符号表他们可能出现在stuff中注意参数列表的左括号必须与name紧邻如果两者之间有任何空白存在参数列表就会被解释为stuff这个宏接收一个参数x,如果在上述声明之后你把SQUARE5置于程序中预处理器就会用下面这个表达式替换上面的表达式5*5警告这个宏存在一个问题乍一看你可能觉得折断的代码将打印36事实上它将打印11实际替换文本时参数x被替换成a1所以这条语句实际就变成了printf%d\n,a1*a1;在宏定义上加上两个括号这个问题便轻松解决了#define SQUARExx*x这样预处理之后就产生了预期的效果这里还有一个宏定义#define DOUBLExxx定义中我们使用了括号想避免之前的问题但是这个宏可能会出现新的错误替换之后printf(%d\n,10*(5)(5));这个问题的解决办法是在宏定义表达式两边加上一对括号就可以了#define DOUBLE(x) ((x)(x))提示所以对于数值表达式进行求值的宏定义都应该用这种方式加上括号避免在使用宏时由于参数中的操作符和临近操作符之间不可预料的相互作用4.带有副作用的宏参数当宏参数在宏的定义中出现超过一次的时候如果参数带有副作用那么你在使用这个宏的时候就可能出现危险副作用就是表达式求值的时候出现的永久效果x1;//不带副作用x;//带有副作用MAX宏可以证明具有副作用的参数所引起的问题#define MAX(a,b) ((a)(b)?(a):(b))....x5;y8;zMAX(x,y);printf(x%d y%d z%d\n,xx,y,z);所以输出结果是x6 y10 z95.宏替换规则在程序中扩展#define定义符号和宏时需要涉及几个步骤1.在调用宏时首先对参数进行检查看看是否包含任何由#define定义的符号如果是他们首先被替换2.替换文本随后被插入到程序中原来文本的位置对于宏参数名被他们的值所替换3.最后再次对结果文件进行扫描看看他是否包含任何由#define定义的符号如果是就重复上述处理过程注意1.宏参数和#define定义中可以出现其他#define定义的符号但是对于宏不能出现递归2.当预处理器搜索#define定义符号的时候字符串常量的内容并不被搜索6.宏函数的对比宏通常被应用于执行简单的运算比如在两个数字中找出交大的一个时写成下面的宏更有优势一些#define MAX(a,b) ((a)(b)?(a):(b))为什莫不用函数来完成这个任务原因一1.用于调用函数和从函数返回的代码可能比实际执行这个小型计算机工作所需的时间更多所以宏比函数在程序的规模和速度方面更胜一筹2.更为重要的是函数的参数必须声明为特定的类型所以函数只能在类型合适的表达式上使用反之这个宏怎可以适用于整型长整型浮点型等可以用来比较的类型宏的参数是类型无关的和函数相比宏的劣势1.每次使用宏的时候一份宏定义的代码将插入到程序中除非宏比较短否则可能大幅度增加程序的长度2.宏是没法调试的3.宏由于类型无关也就不够严谨4.宏可能会带来运算优先级的问题导致程序容易出现错误宏有时候可以做到函数做不到的事情比如宏的参数可以出现类型但是函数做不到7.#和##7.1#运算符#y运算符将宏的一个参数转换为字符串字面量它仅允许出现在带参数的替换列表中#运算符所执行的操作可以理解为“字符串化”当我们有一个变量int a10;的时候我们想打印出the value of a is 10就可以写成:#define PRINT(n) printf(the value of#nis %d,n);当我们按照下面的方式调用的时候PRINTa;//当我们把a 替换到宏的体内时就出现了#a,而a就是转换为“a”时一个字符串代码就会被预处理为:printf(the value ofa is %d,a);运行代码就能在屏幕上打印the value of a is 107.2##运算符##可以把位于它两边的符号合成一个符号它允许宏定义从分离的文本片段创建标识符##被称为记号粘合这样的连接必须产生一个合法的标识符否则其结果是未定义的这里我们想想写一个函数求2个数的较大值时不同的数据类型就得写不同的函数比如8.命名约定一般来讲函数和宏的使用语法很相似。所以语言本身没法帮我们区分二者那我们平时的一个习惯是把宏名全部大写函数名不要全部大写9.#undef这条指令用于移除一个宏定义#undef NAME//如果现存的一个名字需要被重新定义那么他的旧名字首先要被移除10.命令行定义许多C的编译器提供了一种能力允许在命令行中定义符号用于启动编译过程例如当我们根据同一个源文件要编译出一个程序的不同版本的时候这个特性有点用处假定某个程序声明了一个某个长度的数组如果机器内存有限我们需要一个很小的数组但是另外一个机器内存大些我们需要一个数组能够大些//linux环境演示gcc-D ARRAY_SIZE10 programe.c11.条件编译在编译一个程序的时候我们如果将一条语句一组语句百衲衣或者放弃是很方便的因为我们有条件编译指令比如说调试性代码删除可惜保留又碍事所以我们可以选择性的编译①#if 常量表达式表达式为真这部分代码参与编译#endif❗#if后面必须是常量由预处理器在编译算出结果不能放变量②#define __DEBUG__1#if__DEBUG__printf(调试信息\n#endif当__DEBUG__等于1printf代码保留等于0直接删掉这段代码程序里不存在③多分支#if..#elif...#else..#endif类似C的if else if else预处理分支#if A10//代码A#elif A5//代码B#else#endif只会保留满足条件那一段代码进入编译其余全部删掉④判读宏是否定义#ifdef symbol 如果symbol这个宏已经被#define定义代码生效#ifndef symbol 如果symbol没有被定义代码生效defined(宏名是预处理器内置运算符专门检测宏有没有定义返回1/0#ifdef 只是#if defined()的简写#ifndef是#ifdefined()简写⑤嵌套条件编译可以多层嵌套使用#if definedOS_UNIX)#ifdef OPTION1//同时满足OS_UNIX宏存在OPTION1代码才保存#endif#endif❗if(普通C语句运行时判读所有代码都会编译进程序#if\#ifdef(预处理条件编译编译前筛选不满足分支直接删掉代码不占用程序空间最经典用途头文件保护#ifndef)#ifndef __HEAD__H#define __HEAD__H//头文件内容#endif//防止头文件被重复包含12头文件的包含12.1头文件被包含的方式12.11本地头文件的包含#include“filename查找策略先在源文件所在目录下查找如果该头文件未找到编译器就像查找库函数头文件一样在标准位置查找头文件如果找不到就提示错误Linux环境的标准头文件的路径/usr/includeVS环境的标准头文件的路径C:\Program Files(x86)\Microsoft Visual Studio 12.0\VC\include//这是VS2013的默认路径注意按照自己的安装路径去找12.12库文件的包含#includefilename.h查找头文件直接去标准路径下去查找如果找不到就提示错误这样是不是可以说对库文件也可以使用的形式包含可以但是这样做查找的效率就低些也不容易区分是库文件还是本地文件12.2嵌套文件包含我们已经知道#include指令可以是另外一个文件被编译就像它实际出现于#include指令的地方一样这种替换很简单预处理器先删除这条指令并用包含文件的内容替换一个头文件被包含10次那就实际被编译10次如果重复包含对编译的压力就比较大如果直接这样写test.c文件中将test.h包含5次那么tets.h文件的内容将会被拷贝5份在test.c中如果test.h文件比较大这样预处理后代码量会剧增解决办法条件编译每个头文件的开头写或者已完结