C语言预编译详解( 宏的定义与使用、条件编译)

目录

1. 预编译 (预处理)主要操作 test.c——>test.i

2. 利用 #define 定义宏

3、 #define 替换规则

4、#和##

5、带副作用的宏参数

6、宏和函数的对比

7、命名约定

8、#undef

9、条件编译


1. 预编译 (预处理)主要操作 test.c——>test.i

① 包含头文件

② 删除注释(使用空格替代)

③ 替换#define 中的内容

注意: #define 的使用 一定是 替换!

2. 利用 #define 定义宏

eg:

     #define name(参数) 内容

     #define mult(n) n*n

注意:

①定义宏时 名字部分和左括号一定要紧邻,中间不能有空格,否则会认为名字后面的内 容是 替换的内容,出现错误。

②注意一定是 替换!,与相邻的 式子进行运算时,一定要注意运算符的优先级,否则可能不能得到预期的结果。 如:

#include <stdio.h>
#define MAX(n) n+n
int main()
{
   int a=5;
   printf("%d",10*MAX(a));
    return 0;
}

得到的结果是 55,而不是 100. 因为 替换完之后 得到 10*5+5 =55.

可通过加()调整运算优先级,解决该问题。

3、 #define 替换规则

在程序中扩展#define定义符号和宏时,需要涉及几个步骤。

1. 在调用宏时,首先对参数进行检查,看看是否包含任何由#define定义的符号。如果是,它们首先被替换。

2. 替换文本随后被插入到程序中原来文本的位置。对于宏,参数名被他们的值所替换。

3. 最后,再次对结果文件进行扫描,看看它是否包含任何由#define定义的符号。如果是,就重复上 述处理过程。

注意:

1. 宏参数和#define 定义中可以出现其他#define定义的符号。但是对于宏,不能出现递归。

2. 当预处理器搜索#define定义的符号的时候,字符串常量的内容并不被搜索。

4、#和##

①# 的作用 是把一个宏参数变成对应的字符串

代码举例:

我们通常利用printf函数打印无法实现对字符串中的字符替换为变量相应的字符。

#include <stdio.h>
#define MAX(n) n+n
int main()
{
   int a=5;
   printf("%d",10*MAX(a));
    return 0;
}

该代码得到的结果:

如果想实现a随着变量名的变化而变化,可使用#。

代码举例:

#define PRINT(x) printf(#x" is %d number\n",x);
 int main()
 {
     int a=5;
     int b=7;
     PRINT(a);
     PRINT(b);
 }

该代码得到的结果:

实现了预期效果。那么是如何实现的呢? 其实是在预编译时,先将x替换成了a或b,在结合前面的#号,相当于变成 “a”或者“b”。所以

# 的作用 是把一个宏参数变成对应的字符串。

②##

## 的作用:##可以把位于它两边的符号合成一个符号。它允许宏定义从分离的文本片段创建标识符。

#define PRINT(x) English##sroce 
int main()
 { 
    int Englishsroce =85;
    int ret = PRINT(English sroce);
    printf("%d ",ret); 
}

该代码得到的结果:

5、带副作用的宏参数

当宏参数在宏的定义中出现超过一次的时候,如果参数带有副作用,那么你在使用这个宏的时候就可能出现危险,导致不可预测的后果。副作用就是表达式求值的时候出现的永久性效果。

x+1;//不带副作用

x++;//带有副作用

define MAX(a, b) ( (a) > (b) ? (a) : (b) )
...
x = 5;
y = 8;
z = MAX(x++, y++);
printf("x=%d y=%d z=%d\n", x, y, z);//输出的结果是什么?

这里我们得知道预处理器处理之后的结果是什么:

所以输出的结果是:

所以我们在使用宏时尽量不使用带副作用的参数,不然容易出现意料之外的结果。

6、宏和函数的对比

我们发现宏和函数的作用很相似,那我们在平常使用中究竟是使用函数较好,还是使用宏较好呢?下面我们从几个方面分别看看 宏和函数的优劣势对比。

宏通常被应用于执行简单的运算。 比如在两个数中找出较大的一个。 那为什么不用函数来完成这个任务?

原因有二:

1. 用于调用函数和从函数返回的代码可能比实际执行这个小型计算工作所需要的时间更多。所以宏比函数在程序的规模和速度方面更胜一筹。

2. 更为重要的是函数的参数必须声明为特定的类型。所以函数只能在类型合适的表达式上使用。反之这个宏怎可以适用于整形、长整型、浮点型等可以用于>来比较的类型。宏是类型无关的。

当然和函数相比宏也有劣势的地方:

1. 每次使用宏的时候,一份宏定义的代码将插入到程序中。除非宏比较短,否则可能大幅度增加程序的长度。

2. 宏是没法调试的。

3. 宏由于类型无关,也就不够严谨。

4. 宏可能会带来运算符优先级的问题,导致程容易出现错。宏有时候可以做函数做不到的事情。比如:宏的参数可以出现类型,但是函数做不到。

代码举例: 可用于较方便使用动态 内存分配函数。

#define MALLOC(num, type) (type *)malloc(num * sizeof(type))
 int main() 
{ 
    int*p; 
    p= MALLOC(10, int);//类型作为参数 
}

宏和函数的一个对比:

属性

#define定义宏

函数

代码长度

每次使用宏代码都会被插入到程序中,除了使用较小的宏之外,程序的长度会大幅度增长

函数代码只出现于一个地方;每 次使用这个函数时,都调用那个 地方的同一份代码

执行速度

更快

存在函数的调用和返回的额外开 销,所以相对慢一些

操作符优先级

宏参数的求值是在所有周围表达式的上下文环境里, 除非加上括号,否则邻近操作符的优先级可能会产生 不可预料的后果,所以建议宏在书写的时候多些括 号。

函数参数只在函数调用的时候求 值一次,它的结果值传递给函 数。表达式的求值结果更容易预 测。

带有副作用的参数

宏使用带副作用的参数容易导致得不到预期的计算结果

函数参数只在传参的时候求值一 次,结果更容易控制。

参数类型

宏可以传递参数类型,如用于较方便的使用动态内存分配函数

函数无法传递类型

调试

宏无法调试

函数可以逐语句调试

递归

宏无法进行递归

函数可以递归

7、命名约定

命名约定 一般来讲函数的宏的使用语法很相似。所以语言本身没法帮我们区分二者。那我们平时的一个习惯是: 把宏名全部大写 函数名不要全部大写。

8、#undef

我们可以用#define 定义一个宏,要删除一个宏时可以用 #under。

9、条件编译

在编译一个程序的时候我们如果要将一条语句(一组语句)编译或者放弃是很方便的。因为我们有条件编译指令。

比如说: 调试性的代码,删除可惜,保留又碍事,所以我们可以选择性的编译。

代码举例:

#include <stdio.h>
#include <stdlib.h>
#define __DEBUG__
int main()
{
    int i,arr[10]={0};
    for(i=0;i<10;i++)
    {
        arr[i]=i;
    }
    #ifdef __DEBUG__
     for(i=0;i<10;i++)
        printf("%d ",arr[i]); //观察数组是否被赋值成功
    #endif // __DEBUG__
    return 0;
}

常见的条件编译指令:

 

 ①  #if 常量表达式
      #endif
   ②  多个分支条件语句
      #if 常量表达式
      #elif 常量表达式
      #else
      #endif
   ③  判断是否被定义
      #if defined(symbol)
      #if def(symbol)
      
      #if !defined(symbol)
      #ifndef symbol
    ④  嵌套指令
    #if defined(OS_UNIX)
      #ifdef OPTION1
        unix_version_option1();
      #endif
      #ifdef OPTION2
         unix_version_option2();
      #endif
    #elif defined(OS_MSDOS)
         #ifdef OPTION2
            msdos_version_option2();
          #endif
     #endif


版权声明:本文为qq_62144826原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明。