预处理详解

简介: 预处理详解

一、预处理符号

C语言设置了一些预定义符号,可以直接使用,预定义符号也是在预处理期间处理的。

  __FILE__ //进⾏编译的源文件
  __LINE__ //文件当前的行号
  __DATE__ //文件被编译的日期
  __TIME__ //文件被编译的时间
  __STDC__ //如果编译器遵循ANSI C,其值为1,否则未定义

写代码输出来看一下:

#include <stdio.h>
int main()
{
  printf("%s\n", __FILE__);
  printf("%d\n", __LINE__);
  printf("%s\n", __DATE__);
  printf("%s\n", __TIME__);
  printf("%s\n", __STDC__);
  return 0;
}

这里如果使用VS编译器会报错

这样就说明VS编译器不遵循 ANSI  C

注释掉这行代码再来看一下:

可以看到输出了文件的路径名称,行号,编译日期和编译时间。

二、 #define 定义常量

基本语法(使用方法)

#define name stuff

看几个例子:

1、

#define MAX 100
 
int main()
{
  printf("%d", MAX);
  return 0;
}

这里会输出100,在预处理的过程中,会将代码中的MAX 替换成 100

       这里如果是这样

#define N 2+3
int main()
{
  printf("%d", 2*N);
  return 0;
}

这里输出结果是7,而不是10

这是因为预处理将N替换成了2+3,而不是替换成5;

当然#define 还可以定义关键字或者符号等

2、

#define CASE break;case

我们知道switch 语句中,每一个case分支都需要在末尾加上break,才能真正实现分支,而我们有时候就很容易以往break,这时我们可以这样来写:

#define CASE break;case
 
int main()
{
  int n = 0;
  scanf("%d", &n);
  switch(n)
  {
    case 0:
      ;
    CASE 1:
      ;
    CASE 2:
      ;
    default:
      break;
  }
  return 0;
}

这里需要注意:第一个分支应该写case,而不是CASE。

3、

看以下代码

#define DEBUG_PRINT printf("file:%s\tline:%d\t \
              date:%s\ttime:%s\n" ,\
              __FILE__,__LINE__ , \
              __DATE__,__TIME__ )

       如果定义的 stuff过长,可以分成几行写,除了最后一行外,每行的后面都加一个反斜杠(也叫续行符)。

在define定义标识符时,要不要加上 ;  ?

#define MAX 1000;
#define MAX 1000

建议不要加上 ;  容易导致问题,比如:

在我们写代码的时候,我们会在语句最后写分号,这时如果定义标识符时,结尾加了;(分号)就会导致问题

if(condition)
    max = MAX;
else
    max = 0;

       这里如果加了分号,替换以后,if与else之间就是两条语句,而不加大括号时,if之后只能有一条语句,就会出现语法错误

三、 #define 定义宏

       宏

#define 机制包括了一个规定,允许把参数文本替换到文本中,这种实现通常称为宏或者宏定义

声明方式

#define name( parament-list ) stuff

注意:

       参数列表的左括号必须与name紧邻,如果两者之间存在任何空白,参数列表就会被解释为stuff的一部分。

举例:

使用宏,求n * n

#define SQUARE(n) n*n
int main()
{
  int n;
  scanf("%d", &n);
  printf("%d\n", SQUARE(n));
  return 0;
}

这里确实实现了宏定义求n的平方,但是还存在问题,

#define SQUARE(n) n*n
int main()
{
  int n;
  scanf("%d", &n);
  printf("%d\n", SQUARE(n+1));
  return 0;
}

当我们把n+1传给define定义的宏时,所求的5+1的平方结果是11,而不是36?

       这是因为,宏参数是直接替换的,就是我们将n+1传宏,这是计算的就是 n+1*n+1 这样的结果自然就是11。

那我们该怎么解决这个问题呢?

       其实很简单,就是加括号,(在宏定义时不要吝啬括号)

#define SQUARE(n) ((n)*(n))
int main()
{
  int n;
  scanf("%d", &n);
  //printf("%d\n", SQUARE(n));
  printf("%d\n", SQUARE(n + 1));
  return 0;
}

这样写,SQUARE求n+1时就替换成 ((n+1)*(n+1))这样计算结果就不会出错了

注意:

       所有用于对数值表达式进行求值的宏定义都应该用这种方式加上括号,避免在使用宏时由于参数中的操作符和邻近操作符不可预料的相互作用

       带有副作用的宏

当我们写宏时,宏参数在宏的定义中出现超过一次时,如果参数带有副作用,那我们在使用这个宏时就可能出现危险,导致不可预测的后果。

       这里副作用就是指:表达式求值的时候出现永久性效果(就比如,自加,自减)

这里写一个比较大小的宏来看一个这种错误:

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

这里x++ ,y++ 预处理完结果

z = ((x++)>(y++)?(x++):(y++));

这样输出的结果:

       宏替换的规则

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

  1. 在调用宏时,首先对参数进行检查,看看是否包含任何由#define定义的符号。如果是,它们首先被替换。
  2. 替换文本随后被插入程序中原来文本的位置。对于宏,参数名被它们的值所替换。
  3. 最后,再次对结果文件进行扫描,看看它是否包含任何由#define定义的符号。如果是,就重复上述过程

注意

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

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

       宏函数的对比

我们可以发现宏和函数很相似,但是宏又不同于函数

四、 # 和 ##

       #运算符将宏的一个参数转换为字符串字面量。它仅允许出现在带参数的宏的替换列表中。

#运算符所执行的操作可以理解为“字符化”

就比如,我们有一个变量 int a = 10 ;我想要输出 the value of a is 10

就这样来写:

 

#define PRINT(n) printf("the value of "#n " is %d", n);

在经过预处理之后代码就被预处理为

printf("the value of" "a" "is %d" , a);

就可以输出我们想要的结果

#define PRINT(n) printf("the value of "#n " is %d", n);
int main()
{
  int a = 10;
  PRINT(a);
  return 0;
}

       ##运算符

## 运算符可以将位于它两边的符号合成一个字符,它允许宏定义从分离的文本片段创建标识符。

## 被称为记号粘合

这里我们写一个求两个数的较大值的代码

int int_max(int x, int y)
{
     return x>y?x:y;
}
float float_max(float x, float y)
{
     return x>yx:y;
}

用函数写,我们对于不同的数据类型就要写不同的函数,太麻烦了,我们现在写宏

#define GENERIC_MAX(type) \
type type##_max(type x, type y)\
{ \
 return (x>y?x:y); \
}
 
GENERIC_MAX(int) //替换到宏体内后int##_max ⽣成了新的符号 int_max做函数名
GENERIC_MAX(float) //替换到宏体内后float##_max ⽣成了新的符号 float_max做函数名
int main()
{
  int m = int_max(2, 3);
  printf("%d\n", m);
  float fm = float_max(3.5f, 4.5f);
  printf("%f\n", fm);
  return 0;
}

这样我们就可以写一个宏来满足任何数据的求较大值

五、 #undef

这条指令用于一处一个宏定义

六、 命名约定

这个主要来区分宏和函数名

       一般来说,

       把宏名全部大写

       函数名不要全部大写

七、 命令行定义

许多C语言编译器中都提供了一种能力,允许在命令行中定义符号。用于启动编译过程。

       当我根据同一个源文件要编译出不同的版本时,这个就很有用。(假设一个程序中声明了一个某个长度的数组,如果机器内存有限,我们需要一个很小的数组,但是另一个机器内存大,我们需要一个大的数组)

#include <stdio.h>
int main()
{
  int array[ARRAY_SIZE];
  int i = 0;
  for (i = 0; i < ARRAY_SIZE; i++)
  {
    array[i] = i;
  }
  for (i = 0; i < ARRAY_SIZE; i++)
  {
    printf("%d ", array[i]);
  }
  printf("\n");
  return 0;
}

编译指令如下:

//Linux 系统下
gcc -D ARRAY_SIZE=10 programe.c

八、条件编译

在编译程序的过程中,我们如果想要将一条语句放弃是非常容易的。我们就要使用条件编译

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

常见的条件编译指令有很多

//1.
#if //常量表达式
//...
#endif
//常量表达式由预处理器求值。
如:
#define __DEBUG__ 1
#if __DEBUG__
//..
#endif
//2.多个分⽀的条件编译
#if 常量表达式
//...
#elif 常量表达式
//...
#else
//...
#endif
//3.判断是否被定义
#if defined(symbol)
#ifdef symbol
#if !defined(symbol)
#ifndef symbol
//4.嵌套指令
#if defined(OS_UNIX)
  #ifdef OPTION1
    
  #endif
#ifdef OPTION2
  
#endif
#elif defined(OS_MSDOS)
  #ifdef OPTION2
 
  #endif
#endif

九、头文件包含

头文件的包含方式有两种:

       本地文件包含

查找策略:现在源文件所在目录下查找,如果该头文件未找到,编译器就像查找库函数头文件那样在标准位置查找头文件

       Linux环境下标准头文件的路径

/user/include

       Vs环境的便准头文件路径

C:\program Files (x86) \ Microsrft Visual Stdio 12.0 \ VC \ include

       库文件包含

直接在标准路径下去查找,如果找不到就提示编译错误

这样,我们是不是可以对于库函数也能用 “”来包含,这样当然是可以的

但是这样查找的效率就低一些,当然这样也不容易区分是库文件还是本地文件了

学到这里,我们知道#include指令预处理器会删除这条指令,并用包含文件替换

       但是,如果一个文件被包含很多次,那实际就编译多次,如果重复包含,这对编译压力就比较大

对于这个问题,我们可以在文件的开头这样写

#ifndef __TEST_H__
#define __TEST_H__
//文件内容
#endif

这中相当于把文件的内容直接写在了条件编译中,我们也可以直接写

#pragma

这样可以避免头文件重复引入

十、其他预处理指令

还有很多预处理指令

       比如

这里就不做过多的介绍了

#error
#pragma
#line

感谢观看,希望一下内容对你有所帮助,如果内容对你有作用,可以一键三连加关注,作者也正在学习中,有错误的地方还请指出,感谢!!!

相关文章
|
6月前
|
编译器 C++
C 预处理器
C 预处理器。
84 10
|
5月前
|
安全 编译器 C语言
预处理详解
预处理详解
41 0
|
6月前
|
编译器 C语言
预处理深入
预处理深入
36 0
预处理深入
|
6月前
|
编译器 C语言
c预处理器
c预处理器
39 0
|
6月前
|
Linux C语言 Windows
C预处理分析
C预处理分析
40 2
|
6月前
|
安全 C语言
程序预处理:全解版-1
程序预处理:全解版
38 0
|
6月前
|
编译器 C语言
程序预处理:全解版-2
程序预处理:全解版
37 0
|
6月前
|
编译器 C++
c++预处理器
c++预处理器
38 0
|
12月前
|
安全 编译器 C语言
详解预处理(1)
详解预处理(1)
78 1