IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> C++知识库 -> c:预处理指令(#include、#define、#if等) -> 正文阅读

[C++知识库]c:预处理指令(#include、#define、#if等)

环境:

  • window11
  • x86_64-8.1.0-release-win32-seh-rt_v6-rev0.7z (gcc8.1.0)

环境准备参考:《c/c++: window下安装mingw-w64》

一、从一个test.c文件的编译说起

首先,我们要知道一个 test.c 文件是如何一步步编译成 test.exe的。
总体来说:

  • 第一步:预处理器先处理c文件中的预处理指令,如: #include#if...等,得到一个没有预处理指令的代码文件(txt格式) test.i
  • 第二步:编译器进行词法/语法分析、优化、编译上一步生成的test.i,得到汇编文件(txt格式)test.s
  • 第三步:编译器将上一步得到的test.s翻译成二进制格式文件test.o
  • 第四步:链接器将上一步得到的test.o和引用的资源做链接生成test.exe

如下面的示例:

file:test.c

#include <stdio.h>

int main()
{	
	printf("ok");
	return 0;
}

编译如下:
在这里插入图片描述
我们注意到:预处理器是在编译器开始之前工作的,预处理器的工作内容包含:

摘自:《C程序设计(第四版)学习辅导》
在预处理阶段,预处理器把程序中的注释全部删除;
对预处理指令进行处理,如:把#include指令指定的头文件(如:stdio.h)的内容复制到#include指令处;
#define指令,进行指定的字符替换(如:将程序中的符号常量用指定的字符串代替),同时删除预处理指令。

当预处理器处理完后,生成的test.i将不再包含预处理指令了。

二、预处理指令有哪些

在c语言中主要有以下三种预处理指令:

  • 文件包含:#include
  • 宏定义:#define
  • 条件编译:#if...

下面,我们一一讲解:

注意:在后面的test.i文件中我们可能看到#pragma,但它不是预处理指令,#pragma是用来指导编译器行为的。。

三、预处理之文件包含

#include:简单来讲,它就是将指定的文件拷贝到这个指令的地方,并删除这个#include指令。

打开,上面我们生成的test.i文件:
在这里插入图片描述
这里,因为牵扯到系统库,有很多级联的东西,我们可以改下test.c的代码:
在这里插入图片描述
然后进行编译:
在这里插入图片描述
然后,我们再来观察生成的 test.i
在这里插入图片描述

这下我们能一目了然了吧:

  • #include 做文件内容的拷贝和替换,同时删除所有注释。
  • 头文件可以嵌套引用,替换时做遍历,直到把所有引用的都找到;

另外,C语言不允许头文件之间循环引用,如果你在c.h中再引用a.h,那么 gcc test.c -E -o test.i将会死循环:
在这里插入图片描述

另外,我们注意到:test.c中使用了printf()函数,但是我们并没有#include <stdio.h> 也没有报错,这就证明 预处理器只是做了源代码文件的整理和替换,并没有涉及到编译。

还有几项问题:

  • <stdio.h>"stdio.h" 有设么区别?标准库的路径在哪里,我们能不能指定头文件的寻找目录?

    前者表示直接从标准库里找这个头文件,后者表示先在c文件同目录下寻找,找不到再去系统目录下寻找。
    我们从上面第一次编译输出的test.i里能清晰的看到gcc寻找标准库的目录。
    一般我们约定,使用C语言的标准库就是<>这种形式,而其他第三方库或自己写的都用""这种形式。
    .
    如果,我们的头文件和C文件不在一块,可以添加gcc参数,如下:
    在这里插入图片描述
    如果我们用的是vs,可以在工程中配置:
    在这里插入图片描述

四、预处理之宏定义

4.1 简单宏定义

直接看示例:
在这里插入图片描述
我们可以看到,预处理器处理完后,代码中不再有 PI这个字符串,它已经被替换成 3.1415926了,就连 #define PI 3.1415926这行也没有了。
这就是最简单的宏定义了,它的本质和#include一样,还是字符串替换。

不过,预处理器也并不是无脑的替换,比如,当PI出现在字符串位置时就不会被替换:
在这里插入图片描述
另外,宏定义是可以嵌套使用的,如下:
在这里插入图片描述
注意:虽然可以嵌套使用,但我们不要死循环了(就像 #include 一样)!!!

4.2 带参数的宏定义

宏定义也可以带参数的,就像定义函数一样。。。直接看示例吧:
在这里插入图片描述
不过,有几点我们需要注意下:

  • 带参数的宏定义虽然可以实现函数的功能,但它本质还是字符串替换,使用时尤其小心;
  • 带参数的宏定义要求宏名和(之间不能有空格,否则,,,看示例:

    在这里插入图片描述

  • 带参数的宏最好将参数用括号包裹起来,否则,,,看示例:
    在这里插入图片描述

4.3 取消宏定义

我们可以使用#undef 取消宏定义,看如下示例:
在这里插入图片描述

五、预处理之条件编译

一般情况下,C文件中的所有行都会参与编译,但有时希望程序中的一部分只在满足一定条件时才能参与编译。

对应的语法为:

#ifdef 标识符
	程序段1
#elif 标识符
	程序段2
#else
	程序段3
#endif

还有

#if 标识符
	程序段1
#elif 标识符
	程序段2
#else
	程序段3
#endif

直接看示例:
在这里插入图片描述
上面是我模拟不同平台的代码,实际上,有个经典的例子:

在window和linux下调用sleep函数让程序睡眠是不一样的,如果我们代码想同时兼容linux和window的话,我们可以像下面这样写:

在这里插入图片描述
我们注意到,#if#ifdef有点像啊,但它们是不同的,#if 后面可以跟常量表达式,而 #ifdef后面只能跟宏名:
在这里插入图片描述
在这里插入图片描述
另外,补充下,还有 #ifndef ,这个表示某个宏不被定义时,如stdio.h中的使用:
在这里插入图片描述

六、关于预处理的一些思考

预处理的存在让我们有机会在编译之前去修整我们的代码(本质是字符串替换),但它实际上也算是"篡改"我们的源代码了。

正因为如此,我们可以在上面test.i中看到会有专门标识各个原文件行号的地方,并且预处理指令删除的地方仍然保留空行,这就是为了在实际编译报错的时候能准确定位到test.c中的位置,而非是test.i中的。

在这里插入图片描述

  C++知识库 最新文章
【C++】友元、嵌套类、异常、RTTI、类型转换
通讯录的思路与实现(C语言)
C++PrimerPlus 第七章 函数-C++的编程模块(
Problem C: 算法9-9~9-12:平衡二叉树的基本
MSVC C++ UTF-8编程
C++进阶 多态原理
简单string类c++实现
我的年度总结
【C语言】以深厚地基筑伟岸高楼-基础篇(六
c语言常见错误合集
上一篇文章      下一篇文章      查看所有文章
加:2022-09-15 01:47:57  更:2022-09-15 01:49:08 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年5日历 -2024/5/19 16:19:41-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码