【C语言】一文带你读懂C语言预处理器、宏、预处理指令

编程涉及两个概念:指令、程序

指令Instruction:指示的命令

程序Program: 识别指令并计算

预处理器

预处理器从属性来说并不是编译器的组成部分,它可以单独出现(参考书籍《C语言从入门到精通》许东平)。

它对源代码进行预处理,生成经过处理的代码供编译阶段使用。

预处理器的定义(直观版本):"#"开头的

预处理器的主要功能包括:

  1. 宏定义和宏替换:预处理器通过宏定义的方式,将一段代码片段用一个标识符代替。在程序中使用这个标识符时,预处理器会自动将其替换为相应的代码片段。这样可以提高代码的复用性和可维护性。 例如:#define MAX_VALUE 100
  2. 条件编译:预处理器通过条件编译指令(如 #ifdef、#ifndef、#if、#else 等)可以根据条件的真假来选择性地编译代码。这种机制可以根据不同的编译环境或预定义的宏来切换代码执行路径。 例如:#ifdef DEBUG
  3. 包含文件:预处理器可以使用 #include 指令将其他头文件的内容插入到当前文件中,从而实现代码模块的复用和组织。这样可以将程序分割成多个独立的文件,提高代码的结构性和可读性。 例如:#include "stdio.h"
  4. 其他预处理指令:预处理器还提供了其他一些指令,如 #pragma、#error、#line 等,用于控制编译器的行为、生成错误信息、修改行号等。

预定义宏

c 复制代码
#include<time.h>
#include<stdio.h>
int main(){
    printf("File:%s\n",__FILE__);
    printf("Data:%s\n",__DATE__);
    printf("Time:%s\n",__TIME__);
}

#运算符

预处理器创建宏的一种方式

  1. 宏延续运算符"\"
  2. 字符串常量运算符"#"
c 复制代码
#include<stdio.h>
#define message_for(x,y) \
printf(#x " and " #y":thank you");

int main(){
    message_for(lily,lucy);
    return 0;
}

##运算符(标记粘贴)

宏的编译需要合并两个参数

c 复制代码
#include "stdio.h"
#define tokenpaster(n) printf("token" #n "=%d",token##n)
int main()
{
    int tokenA='B';
    tokenpaster(A);
    return 0;
}

带参数的宏

预处理器可以用带参数的宏来模拟函数(必须用指令#define 定义

c 复制代码
int square(int a){
    return a*a;
}

//等价于

#define square(a) ((a)*(a))
c 复制代码
#include<stdio.h>
#define square(a) ((a)*(a))

int main(){
    printf("%d",square(3));
    return 0;
}

宏的空参数

宏的空参数=无参数的宏定义

格式:

c 复制代码
#define 标识符 替换列表

//eg:
#define PI 3.14
#defeine N =1//预处理器用=代替N
#define N 5;//定义时一般不报错,但运行该宏时会

条件编译

目的:防止同文件的多重包含

#if、#endif、#elif、#else

【Instructions】

  • #if指令:如果表达式的值为真,则编译后面的代码直到出现 #else、#elif 或 #endif 为止,否则不编译。
  • #endif指令:该指令用于终止 #if 指令。
  • #else指令:用于 #if 指令之后,当前面的 #if 指令的条件不为真时,就编译 #else 后面的代码。
  • #elif指令:如果前面的#if 给定条件不为真,当前条件为真,则编译下面代码。

【usage】

  1. #if与#endif相匹配
  2. 在#if与#endif之前,#elif的指令数目不限,但只能有一个#else
  3. #else必须是#endif之前的最后一个指令
  4. #if、#endif、#elif、#else可以嵌套在其他#if指令的
  5. 每个嵌套的#endif、#elif、#else应属于前面最近的一个#if指令
c 复制代码
#if e==1
printf("1");
#elif e==2
printf("2");
#else
printf("unknown");
#endif

#defined

目的:确定是否已经被#define定义过

c 复制代码
#include<stdio.h>
#if !defined (message)
#define message "hello,my reader"
#endif 

int main(){
    printf("%s",message);
    return 0;
}

#ifdef、#ifndef

目的:与#defined目的相同

【Instructions】

  • #ifdef 指令:如果宏已经被定义,那么它的检测结果为真,否则返回假。【相当于#if defined】
  • #ifndef 指令:与 #ifdef 指令相反,它表示如果宏未被定义,那么它的检测结果为真,否则为假。【相当于#if !defined】

【usage】

  1. 凡是#if指令可以出现的地方都可以用#ifdef和#ifndef指令
  2. 当标识符被定义时,#ifdef 等同于 #if 1;当标识符未被定义时,#ifdef 等同于 #if 0
  3. 由第二条推出,当标识符被定义时,条件为假;当标识符未被定义时,条件为真

其他指令

#error指令

这个指令通常被用来在编译过程中生成一个错误信息。当编译器遇到#error指令时,会停止编译并显示指定的错误消息。

目的:通常用于条件编译中,可以帮助开发者快速发现代码中的问题。

用法示例:

c 复制代码
#ifdef DEBUG
#error "Debug模式下禁止使用该功能"
#endif

#line指令

#line指令的作用是改变当前的行号和文件名,通常用于生成器或宏中,可以为调试提供帮助,也常用于错误信息的标记(使用#line可以准确知道错误行号的位置)。

目的:强调编译器需要按照设定的行号去对源代码进行重新编译

用法示例:

c 复制代码
#line 42 "custom_file.c"
//4将当前行号设置为 42,将当前文件名设置为 "custom_file.c"

#pragma指令

功能很强大且复杂的指令!

#pragma指令提供了一种方法,使程序员可以控制编译器的行为。它通常用来设定编译器的一些特定属性或者调用特定的函数。

作用:设定编译器的编译状态or指示编译器进行特定操作

用法示例:

c 复制代码
#pragma pack(1) // 设定结构体的对齐方式为1字节
#pragma message //打印出检测的结果
#pragma once //如果出现在开头,可确保头文件只被编译一次
#pragma hdrstop //编译的头文件从这里断开,后续无需编译

这些指令在特定的情况下可以帮助开发者更好地控制代码的编译行为,提高代码的可靠性和稳定性。

总结

预处理器有以下常用指令,以下是每个指令的功能和用途:

  1. #define:用于定义常量、宏、函数宏等,将一段代码片段用一个标识符代替。
  2. #include:用于包含头文件,将其他文件的内容插入到当前文件中,实现代码的模块化和复用。
  3. #ifdef、#ifndef、#if、#else、#elif、#endif:条件编译指令,根据条件的真假选择性地编译代码,用于根据不同的编译环境或预定义宏来切换代码执行路径。
  4. #undef:用于取消宏定义,可以取消之前使用 #define 定义的宏。
  5. #pragma:用于设置编译器的一些特定属性,或者调用特定的功能函数。不同编译器的 #pragma 指令功能可能不同。
  6. #error:生成一个编译错误,停止编译过程,并显示指定的错误消息。
  7. #line:用于改变当前的行号和文件名,通常用于生成器、宏或调试信息的标记。
相关推荐
向阳121810 分钟前
什么是 Go 语言?
开发语言·后端·golang
好奇的菜鸟16 分钟前
Go语言的零值可用性:优势与限制
开发语言·后端·golang
糖拌西红柿多放醋16 分钟前
SpringBoot整合Mybatis-Plus实践汇总
java·spring boot·后端·mybatis
jjjxxxhhh12324 分钟前
c++设计模式之策略模式
c++·设计模式·策略模式
天涯学馆38 分钟前
从零到英雄:Solidity 智能合约开发全攻略
后端·智能合约·solidity
埋头编程~44 分钟前
【C++】踏上C++的学习之旅(六):深入“类和对象“世界,掌握编程的黄金法则(一)
开发语言·c++·学习
ChinaRainbowSea2 小时前
4. Spring Cloud Ribbon 实现“负载均衡”的详细配置说明
java·spring boot·后端·spring·spring cloud·ribbon·负载均衡
如意.7592 小时前
【C++】—— map 与 set 深入浅出:设计原理与应用对比
开发语言·c++
起名字真南2 小时前
【C++】深入理解自定义 list 容器中的 list_iterator:迭代器实现详解
数据结构·c++·windows·list
蹊黎2 小时前
C++模版初阶
开发语言·c++