C 语言中以 # 开头的就是预处理指令,例如 #include

预处理指令的用途

所有的预处理指令都会在 GCC 编译过程的预处理步骤解析执行,替换为对应的内容。在下一步编译过程中,看不到任何预处理信息,只需要对独立的程序文件进行操作即可。

预处理指令的类型

  • 包含类 #include 头文件

    通过 #include 引入的头文件,会在 GCC 编译过程中的预处理步骤进行展开,替换为完整的头文件内容。
  • 宏定义类 #define 宏名 宏体

    定义的宏在预处理时不进行语法检查,原样替换。等到编译的时候再进行语法检查。宏体如果有多个组成部分时,通常要加括号,防止发生优先级的问题。

    • 宏变量:支持表达式。例如 #define MY_VAR 3+4,使用的时候如果是这样 int i = MY_VAR * 5,会在预处理时替换为 int i = 3+4 * 5,最终得到的 i 是23。而 #define MY_VAR (3+4)则无次问题
    • 宏函数:可以将定义的宏当做函数处理。例如 #define MY_FUN(x) (3+x),使用 int i = MY_FUN(4)*5 得到的 i 是 35。
  • 条件编译 #ifdef #ifndef #else #endif
  • 预定义宏:全部以两个下划线开始和结束,方便调试
    • FILE:文件名
    • FUNCTION:函数名
    • LINE:行号,数值
    • DATE:编译的日期
    • TIME:编译的时刻

例如,对于这个 C 文件:

#include <stdio.h>

int main()
{
printf("file is:%s, function is:%s, line is:%d, %s, %s", __FILE__, __FUNCTION__, __LINE__, __DATE__, __TIME__);
}

执行结果如下:

file is:define.c, function is:main, line is:5, Jan 19 2019, 15:10:57

宏定义

预处理时,会把所有出现宏名的地方自动替换为对应的宏值。

#define 宏名 宏值

宏定义的规则:

  • 宏值中可以有其他宏的名字,预处理时也会被替换,例如:
#define PI 3.14
#define PI2 PI * 2
  • 宏值超过一行时,可以用 \ 换行,例如:
#define PRT printf("%f ", PI); \
printf("%f", PI2)
  • 如果不需要宏值,可以定义无值的宏:
#define DEBUG
  • 预定义宏可以在任何地方直接使用。
  • 宏定义结尾不能加分号 ;,否则会被替换导致异常。

综合示例:

#include <stdio.h>

#define PI 3.14
#define PI2 PI*2
#define PRT printf("%f ", PI); \
printf("%f\n", PI2)
#define MIN(a, b) ((a) < (b) ? (a) : (b)) int main()
{
PRT;
printf("%d", MIN(2+3*5, 666));
}

有参数的宏

可以带一个或多个参数。有参数的宏的原则是:

一切地方都有加括号,放在因为运算的优先级导致异常

  • 整个宏值要加括号
  • 每个参数都有加括号
#include <stdio.h>

#define MIN(a, b) ((a) < (b) ? (a) : (b))
#define FUN1(a) (a * 5)
#define FUN2(a) (a) * 5 int main()
{
printf("%d\n", MIN(2+3*5, 666));
printf("%d\n", FUN1(5 + 6)); //5 + 6 * 5 = 35
printf("%d\n", 100 / FUN2(5)); //100 / 5 * 5 = 100
}

宏定义的优缺点

  • 优点:有参数的宏定义比函数速度快,无参数的宏定义可以在C语言的各个版本中使用。
  • 缺点:无法进行参数类型的检查,可能编译通过但运行时报错。

可以使用 inline 内联函数替代有参数的宏定义,速度快的同时还可以检查参数类型。

对于 C99 之后的版本,可以使用常量定义来代替无参数的宏定义:

const double PI 3.14;

条件预处理

通过条件预处理指令,可以在预处理阶段控制要编译的代码,从而实现条件预编译。对编译器来说这个步骤是透明的,它只需要编译所有它看见的代码即可。例如:

#include <stdio.h>
#define DEBUG int main()
{
#ifdef DEBUG
printf("debug info");// 只有定义了 DEBUG,才会执行这段代码
#endif
printf("hello world\n");
return 0;
}

当然,在文件中直接定义宏的话,每次都有修改源文件,不灵活。GCC 提供了一个指定宏定义的选项 gcc -D,例如 gcc -DDEBUG -o build 1.c 会自动在代码中添加一段定义 DEBUG 的指令 #define DEBUG,这样就可以在上面的源代码中删除 #define DEBUG 这一行了。

预处理中 # 和 ## 的区别

在宏体中(宏名中不可以使用)可以使用一个或两个 # 作为前缀

  • #:字符串化,如果需要向宏方法中传字符串参数时可以使用
  • ##:连接符号,用于连接字符串和变量

示例:

#include <stdio.h>
#define MY1(x) #x //传入的参数转为字符串
#define MY2(x) day##x //传入的参数自动拼接到 day 后面,例如出入1时为 day1 int main()
{
int day1 = 666; printf(MY1(abc\n)); // 打印 abc 字符串后换行
printf("%d", MY2(1));// 打印 666,即变量 day1
}

C 语言中的预处理的更多相关文章

  1. C语言中的预处理命令

    预处理功能是C语言的重要功能. 问:为什么要预处理,什么是预处理? 答:我们知道高级语言的运行过程是通过编译程序(编译器)把源代码翻译成机器语言,实现运行的.编译程序的工作包含:语法分析.词法分析.代 ...

  2. C语言中 *.c和*.h文件的区别!

    C语言中 *.c和*.h文件的区别!  http://blog.163.com/jiaoruijun07@126/blog/static/68943278201042064246409/        ...

  3. C语言中的命名空间

    C语言中的命名空间 命名空间是为了解决 "在相同作用域内如何区分 相同的标识符". 说明: ①只有在相同作用域的情况下才能使用到命名空间去区分标识符,在嵌套的作用域.不同的作用域区 ...

  4. C语言中的宏定义

    目录(?)[-] 简单宏定义 带参数的宏 运算符 运算符 宏的通用属性 宏定义中圆括号 创建较长的宏 较长的宏中的逗号运算符 宏定义中的do-while循环do 空操作的定义 预定义宏 C语言中常用的 ...

  5. C C语言中关键词,以及知识点复习

    C语言学习 C语言练习知识点 auto        局部变量(自动储存) break       无条件退出程序最内层循环 case        switch语句中选择项 char         ...

  6. c语言中条件编译相关的预编译指令

    一. 内容概述 本文主要介绍c语言中条件编译相关的预编译指令,包括#define.#undef.#ifdef.#ifndef.#if.#elif.#else.#endif.defined. 二.条件编 ...

  7. C语言中变量名及函数名的命名规则与驼峰命名法

    一.C语言变量名的命名规则:(可以字母,数字,下划线混合使用) 1. 只能以字母或下划线开始:2. 不能以数字开始:3. 一般小写:4. 关键字不允许用(eg:int float=2//error  ...

  8. c语言中会遇到的面试题

    预处理器(Preprocessor) 1 . 用预处理指令#define 声明一个常数,用以表明1年中有多少秒(忽略闰年问题)         #define SECONDS_PER_YEAR (60 ...

  9. C语言中一些术语的梳理_持续更新

    关键字.标识符.宏.预定义.预处理.编译.替换.预处理指令. ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 标识符: ...

随机推荐

  1. 009-通过jmx监控tomcat

    前言想理解怎么监控tomcat,必需识下图(图片源出网络) zabbix-Web前端界面,它通过数据库里数据展示.和其它组件不直接关联zabbix-server运行在10051端口,Zabbix-se ...

  2. zencart前台小语种后台英文 导入批量表 前后台不显示产品的问题

    admin\includes\init_includes\init_languages.php 前台小语种后台英文导致批量表导入后,前后台不显示产品的问题将红色部分修改成前台语言对应的值,前台语言对应 ...

  3. SpringMVC的数据转换&&数据格式化&&数据校验

    1 SpringMVC的数据绑定流程 SpringMVC将ServletRequest对象及目标方法的入参实例传递给WebDataBinderFactory实例,以创建DataBinder实例对象. ...

  4. ZROI 19.08.03 分治与离线

    经典问题,给一张图,支持加边/删边/询问两点连通性. 离线统计边权(删除时间),lct维护最大生成树即可. 也可以按时间分治,维护一个可回退并查集即可. 主定理 很好用,但是记不住. 有一种简明的替代 ...

  5. IP地址(参考百度百科)

    题目1:用子网掩码划分网络时,如果划分8个子网,则需要4位.因为24-2>=8.(全0代表网络自身,全1代表广播地址,所以减2) 以C类地址为例,(1)8个子网,则4位子网,主机号为后4位,主机 ...

  6. 4.瀑布流js

    <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8&quo ...

  7. sql server 表2字段更新到表1,mysql

    UPDATE kingdee_pro_stock set kingdee_pro_stock.org_name=ERP_BASIC_BILLNO_PREFIX.org_name,kingdee_pro ...

  8. c# B/S下 如何优化文件上传速度和实现断点续传问题

    IE的自带下载功能中没有断点续传功能,要实现断点续传功能,需要用到HTTP协议中鲜为人知的几个响应头和请求头. 一. 两个必要响应头Accept-Ranges.ETag 客户端每次提交下载请求时,服务 ...

  9. [LibreOJ 3120]【CTS2019】珍珠 【生成函数】【计数】

    Description Solution 有一个直观的思路是考虑每种颜色个数的奇偶性,奇数个数的颜色不能超过\(n-2m\) 因此若\(n-2m\geq D\)则答案一定是\(D^n\) 否则由于每种 ...

  10. python3:csv的读写

    前言快要毕业那会儿,在下编写了一个招聘网站招聘岗位的爬虫提供给前女神参考,最开始我是存到mysql中,然后在到处一份csv文件给前女神.到了参加工作后,由于经常使用excel绘制图表(谁叫公司做报表全 ...