宏定义、define、defined、ifdef、ifndef、undef总结

    define、defined、ifdef、ifndef、undef的用法都属于预处理部分,所谓预处理是指在进行第一遍扫描(语法扫描和语法分析)之前所做的工作。当对一个源文件进行编译时,系统将自动引用预处理程序对源程序中的预处理部分做处理,处理完毕自动进入对源程序的编译。C语言提供多种预处理,如宏定义、文件包含、条件编译等。

  

一、 宏定义

 

  1. 带参宏定义

a. 带参宏定义中,宏名和形参表之间不能有空格出现

  如把: #define MAX(x,y)  (x>y)?x:y  写为: #define MAX  (x,y)  (x>y)?x:y将被认为是无参宏定义,宏名MAX代表字符串 (x,y)  (x>y)?x:y

b. 在带参宏定义中,形式参数不分配内存单元,因此不必作类型定义

   如#define SUM(int x, int y)   (x+y) 这样使用是错误的。

c.  在宏定义中的形参是标识符,而宏调用中的实参可以是表达式。

#define SQ(y)   (y)*(y)

int main()

{

  int a = 3, sq; 

  sq = SQ(a+1);

  printf(“sq = %d\n”, sq);

  return 0;

}

首行中宏定义的y是形参,sq = SQ(a+1);语句中宏调用实参为a+1,是一个表达式;宏展开后得到语句:sq = (a+1)*(a+1),这与函数调用不同,函数调用时要把实参表达式的值的求出来再赋予形参:sq = 4*4;

 

d.  在宏定义中,字符串内的形参参通常要用括号括起来,以免出错。如上面的c.中把首行改为: #define SQ(y)   y*y

结果将是:sq = a+1*a+1, 最终输出结果是7,而不是16。

    即使参数两边加括号,还是不够,

如把首行改为:#define SQ(y)   (y)*(y)  

sq = 160/SQ(a+1);

则实际运算为:sq = 160/(a+1)*(a+1) ,因“/”和“*”优先级和结合性相同,所以sq = (160/(a+1))*(a+1) 最终结果是160, 而不是预期的10。

故而宏定义不仅应该在参数两边加括号,也就在整个字符串外加括号:#define  SQ(y)   ((y)*(y)) 

e.  带参的宏和带参函数很相似,但本质上有不同,把同一表达式用函数调用处理和用宏处理,两者结果可能不同。

 

例A:

int SQ(int y)

{

  return ((y)*(y));

}

int main()

{

  int i = 1;

  while(i<=5)

  {

    printf(“%d\n”,  SQ(i++));

  }

    return 0;

}

例B:

#define  SQ(y)   ((y)*(y))

int main()

{

  int i = 1;

  while(i<=5)

  {

    printf(“%d\n”,  SQ(i++));

  }

    return 0;

}

例A中,函数调用是把实参i值传给y后自增1,然后输出函数值,因此要循环5次,输出:1,4,9,16,25;而例B中,宏调用时,仅作代换第一次循环时,表达式中前一个i初值是1,然后自增1变成2,表达式中后面的i初值是2,然后i自增1变成3,因3<5,继续第二次循环:3*4;然后是第三次循环:5*6。最终结果是输出:2,12,30。

 

      2.预处理程序对它不作任何检查,如有错误,只能在编译已被宏展开后的源程序时发现。

 

      3.宏定义不是说明或语句,不必在行末加分号,如加上分号,则连分号也一起置换。

 

      4.宏定义必须写在函数之外,其作用域为宏定义命令起到源程序结束,如要终止作用域,可使用#undef命令,#undef  是删除前面定义的宏名字,它“不定义”宏。如:

# define   PI   3.4

int Func1()

{

   ......

}

#undef  PI 

int Func2()

{

   ......

}

PI只在Func1函数中有效,在Func2函数中无效。

 

      5.宏名若在源程序中用引号括起来,则预处理程序不对其作宏代换。

 

#define OK  100

int main()

{

  printf(“OK”); //此处运行结果是打印OK , OK作为字符串被打印。

  return 0;

}

 

      6.预宏定义

   C规范了5个预宏定义:

__DATE__  进行预处理的日期(“Mmm  dd  yyyy” 形式的字符串文字);

__FILE__   代表当前源代码文件名的字符串文字;

__LINE__   代表当前源代码的等号的整数常量;

__TIME__   源文件编译时间,格式为“hh:mm:ss”  ;

__func__    当前所在函数名;

 

二、使用defined

 

     确定宏M是否定义,可以使用下列两种预处理命令之一:

  #ifdef  M

  #if  defined  M

 

 注意两者都有个define的作用,区别在于使用方式上。前者的通常用法是:

#ifdef  XXX

   ....

#else

   ....

#endif

 

    只能在两者中选择是否有定义。对于后者,常用法是:

 

#if defined   XXX1

   ....

#elif defined XXX2

   ....

#elif defined XXX3

   ....

#endif

 

     可以在多个中选择是否有定义。

这个#if defined  XXX它不管里面的“XXX"的逻辑是“真”还是“假”它只管这个程序的前面的宏定义里面有没有定义“XXX”这个宏,如果定义了x这个宏,那么,编译器会编译中间的…code…否则不直接忽视中间的…code…代码。

 

另外 #if defined(XXX)也可以取反,也就用 #if !defined(XXX)。

 

 

三、#和##

1. “#”预处理操作符,也称字符串化操作符,其作用是把其后的字符串变成用双引号包围的字符串,如:

#define  mkstr(s)  #s

 

int main()

{

    printf(mkstr(Hello Word!)); //预处理程序将此语句变成:printf(“Hello Word!”);

    return 0;

}

 

2.  “##”连接运算符,可以把两个独立的字符串连接成一个字符串,如:

#include SORT(X)   sort_function ##X

int main()

{

  char *array;

  int elements, element_size;

  SORT(3)(array, elements, element_size);

  return 0;

}

此例中,倒数第三行实际上转换为:sort_function3(array, elements, element_size);

从此宏SORT的用法可以看出,如果在运行时才确定要调用哪个函数,则可用“##”运算符动态地构造要调用的函数的名称。

 

四、防止头文件重复编译

 

      #ifndef  GET_INFOR_H

 

      #define  GET_INFOR_H

 

      #include 

 

      ........

      ........

 

      #endif

 

      当第一次包含文件get_infor.h时,GET_INFOR_H还未被定义成预处理变量,故#ifndef检查为真,于是预处理器依次处理#ifndef  GET_INFOR_H后面的语句,则会执行#define  GET_INFOR_H,此时GET_INFOR_H会被定义成预处理变量。故当此文件再次被编译时,#ifndef检查结果变为假,那么此文件将不会再次被编译。

 

      在这整个程序中预处理变量必须是唯一的。虽然其写法可以有很多中,也没有必须的要求,但是考虑到确保预处理变量的唯一性和程序的可读性,通常会基于头文件的名字来定义相应的预处理变量。例如上面的头文件名字为get_infor.h,那么预处理变量则定义为GET_INFOR_H。全部大写也能够较好的确保其唯一性,避免冲突。

 

      另外,头文件即使目前还没有被包含在任何的其他文件中,也应该设置一下头文件保护。这样操作也不会增加程序的复杂度。因此,最好养成随手加头文件保护的好习惯。

 

      巧用#ifndef不仅可以用于头文件保护还可以用来调试程序,或者写程序的时候如果对于某个语句是否执行需要加限制条件,都可以这种方法。因为 #ifdef 和#endif中间的语句是否执行完全取决于你是否加上#define。

你可能感兴趣的:(宏定义、define、defined、ifdef、ifndef、undef总结)