C 语言编程 — 宏定义与预处理器指令


目录
  • 前文列表
  • 预处理器
  • 预处理器指令
  • 预处理器指令示例
  • 预定义宏
  • 预处理器指令运算符
    • 宏延续运算符
    • 字符串常量化运算符
    • 标记(Token)粘贴运算符
    • defined() 运算符
  • 参数化的宏
  • 程序编译流程与 GCC 编译器》
    《C 语言编程 — 基本语法》
    《C 语言编程 — 基本数据类型》
    《C 语言编程 — 变量与常量》
    《C 语言编程 — 运算符》
    《C 语言编程 — 逻辑控制语句》
    《C 语言编程 — 函数》
    《C 语言编程 — 高级数据类型 — 指针》
    《C 语言编程 — 高级数据类型 — 数组》
    《C 语言编程 — 高级数据类型 — 字符串》
    《C 语言编程 — 高级数据类型 — 枚举》
    《C 语言编程 — 高级数据类型 — 结构体与位域》
    《C 语言编程 — 高级数据类型 — 共用体》
    《C 语言编程 — 高级数据类型 — void 类型》
    《C 语言编程 — 数据类型的别名》
    《C 语言编程 — 数据类型转换》

    宏是预处理器指令。它用来将一个标识符(宏名)定义为一个字符串,被定义的字符串称为替换文本。程序在预编译阶段,所有的宏名都会被定义的字符串替换,这便是宏替换。它的功能非常强大,甚至自成一门语言,有兴趣的可以参看宏编程。宏定义通常被用来简化代码的实现,让代码的逻辑更加清晰。

    宏的工作原理是定义一些参数,将这些参数复制到特定的格式(宏定义)中,通过修改宏定义(以 #define 为开头的代码片段)或者参数,宏可以生成我们想要的代码。

    预处理器

    C 预处理器(C Preprocessor)简写为 CPP,又称预编译器,它并不是 C 编译器的组成部分,但是它是编译过程中一个单独的步骤。本质上,C 预处理器不过是一个文本替换工具而已,它们会指示编译器在实际的编译工作之前完成所需的预处理准备。

    预处理器指令

    C 语言中,所有的预处理器指令都是以 # 开头的。它必须是第一个非空字符,通常位于源文件首部。下面列出了所有重要的预处理器指令:

    在这里插入图片描述

    预处理器指令示例

    • 这个指令告诉 CPP 把所有的 MAX_ARRAY_LENGTH 替换为 20。通常用于定义常量。
    #define MAX_ARRAY_LENGTH 20
    
    • 这些指令告诉 CPP 从系统库中获取头文件 stdio.h,并添加文本到当前的源文件中。
    #include 
    
    • 这些指令告诉 CPP 从本地目录中获取头文件 myheader.h,并添加内容到当前的源文件中。
    #include "myheader.h"
    
    • 这个指令告诉 CPP 取消已定义的 FILE_SIZE,并重新定义它为 42。
    #undef  FILE_SIZE
    #define FILE_SIZE 42
    
    • 这个指令告诉 CPP 只有当 MESSAGE 未定义时,才定义 MESSAGE。
    #ifndef MESSAGE
       #define MESSAGE "You wish!"
    #endif
    
    • 这个指令告诉 CPP 如果定义了 DEBUG,则执行处理语句。在编译时,如果向 gcc 编译器传递了 -DDEBUG 开关选型,这个指令就非常有用。它定义了 DEBUG,可以在编译期间随时开启或关闭。
    #ifdef DEBUG
       /* Your debugging statements here */
    #endif
    

    预定义宏

    C 语言中定义了许多宏。在编程中可以直接使用这些预定义的宏,但是不能直接修改它们。

    在这里插入图片描述

    #include 
    
    int main() {
       printf("File :%s\n", __FILE__ );
       printf("Date :%s\n", __DATE__ );
       printf("Time :%s\n", __TIME__ );
       printf("Line :%d\n", __LINE__ );
       printf("ANSI :%d\n", __STDC__ );
       return 0;
    }
    

    运行:

    $ ./main
    File :main.c
    Date :Apr  4 2020
    Time :14:13:10
    Line :7
    ANSI :1
    

    预处理器指令运算符

    C 预处理器提供了下列的运算符来帮助创建宏:

    宏延续运算符

    一个宏通常写在一个单行上。但是如果宏太长,一个单行容纳不下,则使用宏延续运算符 \。例如:

    #define  message_for(a, b) printf(#a " and " #b ": We love you!\n")
    
    // or
    
    #define  message_for(a, b)  \
        printf(#a " and " #b ": We love you!\n")
    

    字符串常量化运算符

    在宏定义中,当需要把一个宏的参数转换为字符串常量时,则使用字符串常量化运算符 #。例如:

    #include 
    
    #define  message_for(a, b) \
        printf(#a " and " #b ": We love you!\n")
    
    int main(void) {
       message_for(Carole, Debra);
       return 0;
    }
    

    运行:

    $ ./main
    Carole and Debra: We love you!
    

    标记(Token)粘贴运算符

    在宏定义中,标记粘贴运算符 ## 会合并两个参数。它允许将两个独立的标记被合并为一个标记。例如:

    #include 
    
    #define tokenpaster(n) printf ("token" #n " = %d", token##n)
    
    int main(void) {
       int token34 = 40;
       
       tokenpaster(34);
       return 0;
    }
    

    运行:

    $ ./main
    token34 = 40
    

    上述示例会从编译器产生下列的实际输出:

    printf ("token34 = %d", token34);
    

    token##n 连接为 token34。在这里,使用了字符串常量化运算符 # 和标记粘贴运算符 ##

    defined() 运算符

    defined() 运算符是用在常量表达式中的,用来判断一个标识符是否已经使用 #define 定义过。如果指定的标识符已定义,则值为真。

    #include 
    
    #if !defined (MESSAGE)
       #define MESSAGE "You wish!"
    #endif
    
    int main(void) {
       printf("Here is the message: %s\n", MESSAGE);  
       return 0;
    }
    

    参数化的宏

    CPP 一个强大的功能是可以使用参数化的宏来模拟函数。

    // 一般函数
    int square(int x) {
       return x * x;
    }
    
    // 使用参数化的宏来模拟函数
    #define square(x) ((x) * (x))
    

    e.g.

    #include 
    
    #define MAX(x,y) ((x) > (y) ? (x) : (y))
    
    int main(void) {
       printf("Max between 20 and 10 is %d\n", MAX(10, 20));  
       return 0;
    }
    

    相关