【C语言】一篇带你玩转 预处理指令(上)

简介: 【C语言】一篇带你玩转 预处理指令(上)

很多人好奇预处理是什么,C程序中插入传给编译程序的各种指令(宏),这些指令被称为预处理器指令,它们扩充了程序设计的环境,也称预处理符号。

这一节我们就讲解预处理,


预定义符号



  • FILE 进行编译的源文件
  • LINE 文件当前的行号
  • DATE 文件被编译的日期
  • TIME 文件被编译的时间
  • STDC 如果编译器遵循ANSI C,其值为1,否则未定义
  • FUNCTION 获取函数名


这些预定义符号都是语言内置的。

那该如何使用呢?

  • 代码实例


#include<stdio.h>
#include<stdlib.h>
int main()
{
  int a[] = { 1,2,3,4,5, };
  int sz = sizeof(a) / sizeof(a[0]);
  int i = 0;
  for (i = 0; i < sz; i++)
  {
   printf("%d , %s , %s , %s , lene:%d\n", a[i], __FILE__, __TIME__, __DATE__, __LINE__);
  }
  system("pause");
  return 0;
}


最终于输出结果:

1 , D:\C.Code\2023test.c\test_4_2\test_4_2\test.c , 00:26:17 , Apr 4 2023 , lene:90

2 , D:\C.Code\2023test.c\test_4_2\test_4_2\test.c , 00:26:17 , Apr 4 2023 , lene:90

3 , D:\C.Code\2023test.c\test_4_2\test_4_2\test.c , 00:26:17 , Apr 4 2023 , lene:90

4 , D:\C.Code\2023test.c\test_4_2\test_4_2\test.c , 00:26:17 , Apr 4 2023 , lene:90

5 , D:\C.Code\2023test.c\test_4_2\test_4_2\test.c , 00:26:17 , Apr 4 2023 , lene:90


知识点扩展:

补充: 其实编译器在代码编译的时候,会对函数和变量名重命名的//在C语言中重命名的规则基本就是: 加_


#define



#define 定义标识符


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


我们用VS code gcc编译打开预处理看看

输入gcc test.c -E -o test.i 打开预处理指令

#define MAX 定义的标识符都给替换成100了


55fbeff5d69944d1bf5552ca06634eb7.png


当然用#define重命名也是可以的

  • 比如


#define reg register          //为 register这个关键字,创建一个简短的名字


此时打开预处理指令显示


5be8d75ab95847f38421715b6c1faf37.png


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

  • 比如


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


如果一直按回车跳到下一行,语法会出现问题,所以要使用 \ 反斜杠(续行符)

注意:续行符后面不能添加其他东西


注意事项:

在define定义标识符的时候,不要加上 ; 分号


  • 那为什么呢?我们用gcc编译器打开预处理看看


535465bee1634f449a2fe753a2adb5ad.png


如果#define加了 ; 在其他语句也会默认替换成; 所以一定要牢记 #define不要加分号


#define 定义宏


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

宏(define macro)。


下面是宏的申明方式:


#define ADD(x) x + x


注意:

参数列表的左括号必须与ADD紧邻。

如果两者之间有任何空白存在,参数列表就会被解释为x+x的一部分。


在置于程序中,预处理器会将传进来的参数原封不动的替换宏定义的表达式、

-比如

f2073634371a41c1afbaabc95d667697.png


警告:

宏存在一定的问题,我们要尽量避开:

观察下面的代码段:


#define SQUABE(x) x * x
int main()
{
  printf("%d", SUL(1 + 7));
  return 0;
}


下面输出结果是15,为什么呢?就是因为我们刚刚所说的,传进来的参数原封不动的替换宏定义的表达式.替换文本时,参数x被替换成1 + 7,所以这条语句实际上变成了:

printf (“%d\n”,1+7 * 1+7 );

这样就比较清晰了,由替换产生的表达式并没有按照预想的次序进行求值。


44443cbf4ff847aca5197a1719b4fa7a.png


那该怎么处理?

在宏定义上加上两个括号,这个问题便轻松的解决了:>


#define SQUABE(x) (x) * (x)


这样预处理之后就产生了预期的效果:


printf ("%d\n",(1+7) * (1+7) );


warning:

这里还有一个宏定义:

比如

#define DOUBLE(x) (x) + (x)
int main()
{
  printf("%d", SUL(1 + 7));
  return 0;
}


  • 定义中我们使用了括号,想避免之前的问题,但是这个宏可能会出现新的错误。

看上去,好像打印100,但事实上打印的是55.

我们发现打开预处理替换之后:


printf ("%d\n",10 * (5) + (5));


乘法运算先于宏定义的加法,所以出现了

55 .


这个问题的解决办法是在宏定义表达式两边加上一对括号就可以了。


#define DOUBLE( x)   ( ( x ) + ( x ) )


提醒:

所以用于对数值表达式进行求值的宏定义都应该用这种方式加上括号,避免在使用宏时由于参数中

的操作符或邻近操作符之间不可预料的相互作用。


#define 替换规则


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


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

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

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


注意:

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

归。

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


#和##


#.

我们直接看代码:

#include<stdio.h>
 int main()
 {
     int a = 10;
     printf("the value of a is %d\n", a);
     int b = 20;
     printf("the value of b is %d\n", b);
     float f = 3.14f;
     printf("the value of f is %f\n", f);
     return 0;
 }


这里写是不是太麻烦了,每次都要打印。那分装一个函数能不能解决,答案是不行的,因为不能每次都不能把the value of a,b,f这些字符串跟着改变,那该怎么办?


可以用宏来实现!

当然我们这里先要做下知识铺垫


int main()
 {
     printf("hello world\n");
     printf("hello " "world\n");
     return 0;
 }


最终输出结果:

hello world

hello world

这样写的格式也会自然合成一个字符串


  • 宏的方式


 #define print_format(num, format) \
             printf("the value of num is "format, num)
 int main()
 {
     int a = 10;
     print_format(a, "%d\n");
     //printf("the value of a is %d\n", a);
     int b = 20;
     print_format(b, "%d\n");
     //printf("the value of b is %d\n", b);
     float f = 3.14f;
     print_format(f, "%f\n");
     //printf("the value of f is %f\n", f);
     system("pause");
     return 0;
 }


最终输出结果:

the value of num is 10

the value of num is 20

the value of num is 3.140000


我们发现这样写还是不能实现该功能,num那里应该是a,b,f才对阿,那该怎么办?

这样我们就要用到我们的 #和C语言自带的天然字符串格式,我们只需要把 the value of和is变成字符串就可以了,任何num用一个#来修饰。


  • 下面来看代码吧

#num 会让这个参数不是替换进来,而是对应他的名字所对应的字符串。


 #define print_format(num, format) \
             printf("the value of "#num" is "format, num)
 int main()
 {
     int a = 10;
     print_format(a, "%d\n");
     //printf("the value of a is %d\n", a);
     int b = 20;
     print_format(b, "%d\n");
     //printf("the value of b is %d\n", b);
     float f = 3.14f;
     print_format(f, "%f\n");
     //printf("the value of f is %f\n", f);
     system("pause");
     return 0;
 }


最终输出结果:

the value of a is 10

the value of b is 20

the value of f is 3.140000


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


##.

##的作用

##可以把位于它两边的符号合成一个符号。

它允许宏定义从分离的文本片段创建标识符。


  • 代码实例

##把Class和110合并成Class110


 int Class110 = 2023;
 #define CAT(x,y) x##y
 //Class110
 int main()
 {
     printf("%d\n", CAT(Class, 110));
     system("pause");
     return 0;
 }


输出结果:

2023


注:

这样的连接必须产生一个合法的标识符。否则其结果就是未定义的。比如上面没有int Class110 = 2023; 产生的结果是未定义的


##的使用场景很少


目录
相关文章
|
2月前
|
编译器 C语言
C语言--预处理详解(1)
【10月更文挑战第3天】
|
2月前
|
编译器 Linux C语言
C语言--预处理详解(3)
【10月更文挑战第3天】
|
2月前
|
自然语言处理 编译器 Linux
【C语言篇】编译和链接以及预处理介绍(上篇)1
【C语言篇】编译和链接以及预处理介绍(上篇)
43 1
|
16天前
|
IDE 编译器 开发工具
【C语言】全面系统讲解 `#pragma` 指令:从基本用法到高级应用
在本文中,我们系统地讲解了常见的 `#pragma` 指令,包括其基本用法、编译器支持情况、示例代码以及与传统方法的对比。`#pragma` 指令是一个强大的工具,可以帮助开发者精细控制编译器的行为,优化代码性能,避免错误,并确保跨平台兼容性。然而,使用这些指令时需要特别注意编译器的支持情况,因为并非所有的 `#pragma` 指令都能在所有编译器中得到支持。
93 41
【C语言】全面系统讲解 `#pragma` 指令:从基本用法到高级应用
|
22天前
|
程序员 编译器 C语言
C语言中的预处理器指令,涵盖其基本概念、常见指令(如`#define`、`#include`、条件编译指令等)、使用技巧及注意事项
本文深入解析C语言中的预处理器指令,涵盖其基本概念、常见指令(如`#define`、`#include`、条件编译指令等)、使用技巧及注意事项,并通过实际案例分析,展示预处理器指令在代码编写与处理中的重要性和灵活性。
56 2
|
1月前
|
C语言
【c语言】你绝对没见过的预处理技巧
本文介绍了C语言中预处理(预编译)的相关知识和指令,包括预定义符号、`#define`定义常量和宏、宏与函数的对比、`#`和`##`操作符、`#undef`撤销宏定义、条件编译以及头文件的包含方式。通过具体示例详细解释了各指令的使用方法和注意事项,帮助读者更好地理解和应用预处理技术。
27 2
|
2月前
|
编译器 Linux C语言
【C语言篇】编译和链接以及预处理介绍(下篇)
【C语言篇】编译和链接以及预处理介绍(下篇)
37 1
【C语言篇】编译和链接以及预处理介绍(下篇)
|
2月前
|
C语言
C语言--预处理详解(2)
【10月更文挑战第3天】
|
2月前
|
编译器 C语言
C语言预处理详解
C语言预处理详解
|
2月前
|
存储 C语言
【C语言篇】编译和链接以及预处理介绍(上篇)2
【C语言篇】编译和链接以及预处理介绍(上篇)
41 0