• 预处理详解


    1.预定义符号

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

    如:

    1. __FILE__ //进行编译的源文件
    2. __LINE__ //文件当前的行号
    3. __DATE__ //文件被编译的日期
    4. __TIME__ //文件被编译的时间
    5. __STDC__ //如果编译器遵循ANSIC,其值为1,否则为定义

     

    运行结果:

    当在vs中,运行__STDC__时,则出现下面的情况:

    2.#define定义常量

    基本语法:

    #define name stuff

     例子:

    1. #define MAX 1000
    2. #define reg register //将关键字简写
    3. #define DEBUG_PRINT printf("file:%s\tline:%d\t \
    4. date:%s\ttimt:%s\n", \
    5. __FILE__,__LINE__,__DATE__, \
    6. __TIME__)
    7. //当我们发现需要定义的stuff过长时,可以通过在每一行后面加反斜杠(\),将其分成几行来进行书写。

    注:在define定义标识符时,在后面最好不要加;

    3.#define定义宏

    #define机制允许把参数替换到文本中,这种实现通常称为宏

    下面是宏的申明方式:

    #define name(parament—list) stuff

    其中的parament—list是一个由逗号隔开的符号表,它们可能出现在stuff中

    注:参数列表的左括号必须和name紧邻,中间不能有空白,如两者中间有空白,参数列表就会被解释成stuff的一部分。宏的参数不会运算,直接替换到宏的体内。

    宏的参数中如有操作符,那宏的内容中的操作符会由于存在优先级的问题,可能会导致运算的顺序与你所期望的运算顺序不一致,会导致运算结果不达预期。因此为了解决这一问题,我们在书写宏时,会在宏定义表达式两边加上一对括号。

    如下:

    1. #define DOUBLE(n) (n)+(n)
    2. int main()
    3. {
    4. printf("%d\n", 10 * DOUBLE(5));
    5. return 0;
    6. }

    上面的代码打印的结果是100吗?

    运行结果:

    我们发现上述代码的运行结果是55,不是100,这是为什么呢?

    我们在上面的学习中,知道了#define宏的参数不会参与运算,直接代入代码中,会得到下面的代码:

    1. #define DOUBLE(n) (n)+(n)
    2. int main()
    3. {
    4. printf("%d\n", 10 * (5) + (5));
    5. return 0;
    6. }

    上面的运算会优先算乘法,因此结果是55。我们为了避免这种情况的发生,会在定义宏时,会在表达式两边加上括号,如下:

    1. #define DOUBLE(n) ((n)+(n))
    2. int main()
    3. {
    4. printf("%d\n", 10 * DOUBLE(5));
    5. return 0;
    6. }

    这样运行的结果就是100了,如图:

    4.带有副作用的宏参数

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

    例题:

    1. #define MAX(a,b) ((a) > (b) ? (a) : (b))
    2. int main()
    3. {
    4. int x = 5, y = 8;
    5. int m = MAX(x + 1, y + 1);
    6. printf("x=%d y=%d m=%d\n", x, y, m);
    7. int n = MAX(x++, y++);
    8. printf("x=%d y=%d n=%d\n", x, y, n);
    9. return 0;
    10. }

     通过观察上面的运行结果,我们可以发现在运行MAX(x++,y++)时,x和y的值发生了永久性的改变,这就是带有副作用的宏参数。因此在使用宏时,我们应尽量避免带有副作用的宏参数。

    5.宏的替换规则

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

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

    注:

    1. 宏参数和#define定义中可以出现其他#define定义的符号。但对于宏,不能出现递归
    2. 当预处理器搜索#define定义的符号的时候,字符串常量的内容并不会被搜索

    6.宏和函数的对比

    宏通产被应用于执行简单的运算

    宏和函数的对比:

    7.#和##

    7.1#运算符

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

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

    如下:

    1. #define PRINT(n) printf("the value of " #n " is %d\n", n)
    2. int main()
    3. {
    4. int a = 10;
    5. PRINT(a);
    6. return 0;
    7. }

    7.2##运算符

    ##可以把位于它两边的符号合成一个符号,它允许宏定义从分离的文本片段创建表示符。##被称为记号粘合。

    这样的连接必须产生一个合法的标识符。否则其结果就是为定义的。

    如下:

    1. #define TYPE(type) \
    2. type type##_max(type x,type y) \
    3. { \
    4. return (x>y?x:y); \
    5. }
    6. TYPE(int);
    7. TYPE(float);
    8. int main()
    9. {
    10. int a = int_max(3, 5);
    11. printf("%d\n", a);
    12. float f = float_max(3.5f, 4.5f);
    13. printf("%f\n", f);
    14. return 0;
    15. }

    8.函数和宏的命名约定

    宏名全部大写

    函数名不要全部大写

    9.#undef

     这条指令用于移除一个宏定义。

    1. #undef NAME
    2. //若现存的一个名字需要被重新定义,那么他的旧名字要先被移除

    10.条件编译

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

    比如:

    调试性的代码,删除可惜,但保留又碍事,因此我们可以选择性的编译。

    1. #define __DEBUG__
    2. int main()
    3. {
    4. int i = 0;
    5. int arr[10] = { 0 };
    6. for (i = 0; i < 10; i++)
    7. {
    8. arr[i] = i + 1;
    9. #ifdef __DEBUG__
    10. printf("%d ", arr[i]); //观察是否赋值成功
    11. #endif
    12. }
    13. return 0;
    14. }

    如果要观察就可以定义__DEBUG__,不过不想观察了,就可以把 __DEBUG__删掉。

    常见的条件编译指令:

    1. 1.
    2. #if 常量表达式
    3. // ...
    4. #endif
    5. //常量表达式由预处理器求值
    6. 2.多个分支的条件编译
    7. #if 常量表达式
    8. // ...
    9. #elif 常量表达式
    10. // ...
    11. #endif
    12. 3.判断是否被定义
    13. #if defined(symbol)
    14. #ifdef symbol
    15. //若被定义
    16. #if !define(symbol)
    17. #ifndef symbol
    18. //若没有被定义
    19. 4.嵌套指令
    20. #if defined(OS_UNIX)
    21. #ifdef OPYION1
    22. unix_version_option1();
    23. #endif
    24. #ifdef OPTION2
    25. unix_version_option2();
    26. #endif
    27. #elif defined(OS_MSDOS)
    28. #ifdef OPTION2
    29. msdos_version_option2();
    30. #endif
    31. #endif

     11.头文件的包含

    11.1头文件被包含的方式

    11.1.1本地文件包含

    #include "filename.h"

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

    如果都找不到就会提示编译错误。

    11.1.2库文件包含

    #include 

    查找头文件直接去标准路径下去查找,如果找不到就会提示编译错误。

    11.2嵌套文件包含

    为了避免同一个头文件被多次重复引入的问题,可以使用条件编译解决这一问题。

    每个头文件的开头写:

    1. #ifdef __TEST_H__
    2. #define __TEST_H__
    3. //头文件的内容
    4. #endif //__TEST_H__

    #pragma once

    以上两种方式就可以避免头文件的重复引入

  • 相关阅读:
    机器学习中的偏差漂移:挑战与缓解
    Linux-进程、任务和作业管理
    学透这份 300 页的 2022 最新 java 面试题及答案,让你成功定位阿里 P8
    基于ProXmoX VE的虚拟化家庭服务器(篇一)—ProXmoX VE 安装及基础配置
    查询企业联系方式的途径有哪些?
    C++:new 和 delete
    《golang设计模式》第二部分·结构型模式-05-门面模式Facade)
    【数据结构】二叉树的前序遍历(七)
    Centos 7分区失败,进入 dracut 页面,恢复操作
    传统纸业如何实现数字化,S2B2C系统网站赋能渠道提升供应链管理效率
  • 原文地址:https://blog.csdn.net/N_N12/article/details/136306269