C笔记系列六:编译预处理

C中的预处理均以#开头,末尾不加;。它们可出现在程序中的任何位置,其作用域是自出现点到所有源程序的末尾。
编译预处理是在编译前对源程序进行的一些加工,由预处理程序来处理

宏定义

不带参数

定义:#define 宏名 字符串
如:#define PI 3.141592653589793
编译预处理程序遇到宏时,就用其代表的字符串(无双引号)替换,即所谓的宏替换
注意:

  • 宏定义是用宏名代替字符串,宏扩展时仅作简单替换,不做语法检查。语法检查在编译时进行
  • 宏定义不是c语句,后面不能有;,否则,会连同;一起替换。如:#define PI 3.1416;,则return (PI * r * r);会被替换成return (3.1419; * r * r);
  • 宏定义可以引用已定义好的宏名,如:
    c
    #define PI 3.14
    #define R 2.0
    #define AREA PI * R * R

带参数

定义:#define 宏名(参数列表) 字符串
如:#define max (x,y) ((x) > (y) ? (x) : (y))
注意:

  • 宏名与(参数列表)之间不能有空格,若有,则会将(参数列表)视为字符串的一部分被替换,即效果等价于一个不带参数的宏替换。如:
#define max (x,y) ((x) > (y) ? (x) : (y))
printf(max(2, 3));
=> printf((x,y) ((x) > (y) ? (x) : (y))(2, 3));
  • 整个宏扩展及各参数均要用()括起来。否则如下:
#define square(x) x*x
a = square(n+1);
=> a = n+1*n+1;
  • 宏扩展最外面的括号也必需,否则:
#define square(x) (x)*(x)
a = 27 / square(3);
=> a = 27 / (3)*(3);
  • 宏调用时是以实参替代形参,而非值传递

宏调用与一般函数调用的区别:

  • 时空效率不同:宏替换时要用宏体去替换宏名,这往往会使程序体积膨胀,加大系统存储开销。但它不像函数调用要进行参数传递、保存现场、返回等操作,所有时间效率比函数高。故,通常对简洁的表达式,以及在调用频率高、要求快速响应的场合,才用宏调用比采用函数调用合适
  • 宏虽可带有参数,但是宏替换只是进行字符串替换,不作任何其它操作,如参数值的计算、传递等

解除宏定义

定义:#undef 宏定义名
如:

int main() {
#define PI 3.14
    a = PI * 2 * 2;  // 正确
#undef PI
    b = PI * 2 * 2;  // 错误,PI宏定义已被解除,不存在
    return 0;
}

文件包含

文件包含,即一个源文件可以将另一个源文件的全部内容包含到自己的文件中。
主要有两种包含方式

  1. #include "文件标识":文件标识中包含文件路径。按照这种格式定义时,预处理程序首先在原来的源文件目录中检索该指定的文件;若未找到,则按照系统指定的标准方式检索其它文件目录,直至找到为止。
  2. #include <文件标识>:按此方式指定文件时,预处理程序仅会按照系统指定的标准方式检索文件目录。

故,引用的是系统提供的头文件时,(1)和(2)均可,但推荐(2),因为可减少检索时间;但引用的是自定义文件时,则只能使用(1)

条件定义与条件编译

条件编译使得同一源程序在不同的编译条件下得到不同的目标代码。常是为了增强程序的可移植性或者便于程序的调试

#ifdef 宏名
程序段1       // 若宏名已被 #define 定义,即已经存在,则编译程序会选择编译程序段1,否则编译程序段2 
#else
程序段2
#endif

#ifndef 宏名
程序段1       // 若宏名未被 #define 定义,即不存在,则编译程序会选择编译程序段1,否则编译程序段2 
#else
程序段2
#endif

#if 表达式1
程序段1
#elif 表达式2 
程序段2
#else
程序段3
#endif

其中第三类条件编译的例子如下:

#define A 1
#define B 2
#define C A

#if A == B
程序段1
#elif A == C
程序段2
#else
程序段3
#endif

需注意,由于条件编译是发生在程序执行之前,故表达式中引用的任何变量均无效,只能引用宏定义的符号常量

猜你喜欢

转载自blog.csdn.net/deepinc/article/details/79673965
今日推荐