百度360必应搜狗淘宝本站头条
当前位置:网站首页 > 技术分析 > 正文

C语言预处理指令轻松学(5)include指令用法与奇思妙想

liebian365 2024-10-25 15:37 32 浏览 0 评论


对于一个程序员来说,只要写过一个“hello world!”的C程序,就不得不用到#include指令,因为没有#include “stdio.h”,就不可能输出”hello world!”字符串,它除了可以在程序头部包含库函数头文件的用法,其实还有很多强大的用法。今天我们就重新认识一下这个貌不惊人的预处理指令。(如果有经验的程序员,可以直接跳过基础部分,为了照顾不同程度的读者,基础用法也做详细介绍。)

文件搜索机制:双引号模式

include本意就是包含的意思,把另一个文件包含进当前文件中,有两种模式:

  • #include “file1.h”
  • #include <file2.h>

双引号模式分为绝对路径和相对路径两种模式。绝对路径是指从根目录开始按指定路径搜索头文件,“盘符:\”、“盘符:\\”、“盘符:/”就是表示windows下的根目录,形如:

  • #include “盘符:\路径\文件名.扩展名”
  • #include “盘符:\\路径\文件名.扩展名”
  • #include “盘符:/路径/文件名.扩展名”

在linux环境,只能是正斜杠/并且没有盘符的概念,在windows环境里,这三种是等价的用法。

相对路径则是指从源文件所在的当前路径开始搜索头文件,“.”就是当前目录,“..”就是当前目录的父目录,最好只用正斜杠“/”用来连接目录和目录,或目录和文件,虽然也可以用反斜杠,但最好养成windows和linux风格的一致性,因为在实际开发中,几乎都是用相相对路径的方法来搜索头文件,很好用绝对路径的。好处显而易见,就是灵活性强,源文件目录可以随意移动,而不改变头文件的搜索机制。举例如下:

  1. #include “./file.ext”
  2. #include “file.ext”
  3. #include “./sub_path/file.ext”
  4. #include “sub_path/file.ext”
  5. #include “../file.ext”
  6. #include “../brother_path/file.ext”
  7. #include “../../uncle_path/file.ext”

.”表示源文件所在的当前目录,“file.ext”表示文件,两者用正斜杠连接,表示当前目录下的文件,注意,很多教程,甚至资深程序员说“./”是当前目录,是严重错误的!正斜杠只是连接符号。

第一种和第二种是等价的用法,第二种只是第一种的简化写法。第三种和第四种用法是等价的,都表示当前目录的“子目录”下的文件,第四种也省略了当前目录的写法。第五种表示要使用的头文件在父目录下,第六种表示在兄弟目录(父目录的其它子目录),第七种表示文件在“叔叔目录”下。

文件搜索机制:尖括号模式

一般情况下,如果包含的是程序员自定义的头文件,要用到双引号模式的搜索机制,而且最好要用相对目录模式。但是如果是C语言标准的库函数头文件,就用尖括号模式,会更加方便。在尖括号内直接放入要包含的文件名即可。比如:

  • #include <stdio.h>
  • #include <stdlib.h>
  • #include<stdarg.h>

尖括号本身就表示一个特定的目录,所以不用像双引号模式那样显式地指定头文件路径。在windows系统中,安装不同的c语言IDE开发环境,标准库函数的头文件路径都会不同,在同一个开发环境中,不同的库函数所在的目录也可能不一样,当然也可以一样。

比如在visual studio 2022中,以我个人的电脑为例,stdlio.h和stdlib.h都被安装在:

C:\Program Files (x86)\Windows Kits\10\Include\10.0.22621.0\ucrt

而stdarg.h头文件就被安装在:

C:\Program Files\Microsoft Visual Studio\2022\Community\VC\Tools\MSVC\14.37.32822\include

而在我个人电脑安装的CLION2023环境里,stdlio.h和stdlib.h被安装在:

C:\Program Files\JetBrains\CLion 2023.3.2\bin\mingw\x86_64-w64-mingw32\include

而stdarg.h头文件被安装在:

C:\Program Files\JetBrains\CLion 2023.3.2\bin\mingw\lib\gcc\x86_64-w64-mingw32\13.1.0\include

我们不用关心系统提供的标准头文件的具体路径,直接尖括号访问即可。当然,这些头文件的目录可以被修改成新的位置,但是必须要在对应的IDE里设置,而不是简单的移走,我要反复强调的是,千万不要轻易这样干。

头文件查找机制的进阶思考

1)

双引号模式:预处理器会先在指定的路径寻找头文件,如果寻找不到会再到编译器自身的路径中去寻找(存放在IDE或操作系统的环境变量中)。

尖括号:预处理只会到编译器默认的目录中去寻找。

2)

程序员自定义的头文件也可以使用尖括号模式访问,比如#include <myheader.h>。有两种方法,比如可以直接放在编译器的默认目录里,比如include目录,或者其他目录,比如stdio.h所在的目录里都可以。也可以在IDE中设置一个新的路径的环境变量也可以。

类似的上面情况,也可以反过来把标准库函数用双引号模式访问,比如#include “stdio.h”,这两种颠倒或混用的做法,我强烈不建议。

如果你需要把源代码编译成静态链接库lib或者动态链接库dll,提供给其他项目使用,就需要放到标准库的目录里,用尖括号访问是合适的,如果想了解如何用C语言生成静态或动态库,可以看我专栏的相关文章。

否则,尖括号和双引号可以让代码阅读者,能够一眼分辨出哪些是自定义头文件,哪些是标准头文件,混用的情况下,会让维护者迷惑,不是一种良好的编程实践。

包含防护机制

头文件的包含防护机制,非常重要,我在介绍预处理指令#pragma once、条件编译指令的用法时,都提到过。

include指令就是将头文件的全部内容原样复制到当前文件里,如果一个头文件被间接或直接的多次被“包含”到当前源文件,编译的时候必然报错。比如:

//code.c
#include “myheader.h”
#include “other.h”
//other.h
#include “nyheader.h”

这是最简单的例子,在other.h里间接的重复包含了myheader.h文件的内容。为了避免一个头文件被多次重复包含进同一个源文件的问题,要么使用在头文件的顶部插入一条#pragma once指令,表示这个头文件在同一个源文件里只能包含一次,关于#pragma指令的俄详细用法,可以参见《段誉和语言》的相关文章,要么使用条件编译指令,条件编译指令的用法就成为头文件包含防护机制,使用起来也很简单,就是在头文件的顶部插入如下一段代码:

#ifndef _MY_HEADER_H
#define _MY_HEADER_H
......
#endif/*my_header.h*/

_MY_HEADER_H要替换为头文件的文件名的大写字母,当这个头文件第一次被包含时,标识符 _MY_HEADER_H是没有被定义的,所以#ifndef _MY_HEADER_H语句为真,里面内容会被包含进去,省略号指代的是头文件内容。当这个头文件第二次或多次被包含时,因为#ifndef _MY_HEADER_H已经被定义了,所以#define _MY_HEADER_H为假,就不被执行,就不会被重复包含。关于条件编译指令的详细用法,可以看《段誉和语言》的相关文章。

include的本质:到底什么是“文件包含”

include本意就是包含的意思,把另一个文件包含进当前文件中,实际上只要是文件内容是文本格式,都可以被包含进来,比如.c、.h、.txt都是文本格式,就可以被包含进来。打个比方,让下面这段代码:

#include <stdio.h>
int main(){
int x,y;
x=3;
y=2;
printf(“%d\n”,x+y);
return0;
}

我们完全可以把其中部分代码放进另一个文件,比如file.txt,然后包含进来:

#include <stdio.h>
intmain(){
int x,y;
#include “file.txt”
return 0;
}
//file.txt
x = 3;
y = 2;
printf(“%d\n”,x+y);

现实中,没人会这样写,这个例子就是为了演示include的本质局势简单的包含功能。而且照样可以编译通过。但是,我们一般约定俗成的吧源代码保存为后缀为c的文件,函数的声明保存后缀为h的文件。

巧用这个特性,我们可以有很多使用有趣的用法,举两个例子分享一下。

include化简数组

比如我们定义了一个数组,元素非常多,放在源程序里影响看代码,可以放在另外的文本文件里保存,既让代码清爽,又易于修改和维护。比如:

//src.c
char *namelst[] = {
#include “names.txt”
};
//names.txt
“Tom”,
“Jack”,
......
“vicky”

通过#include “names.txt”,会让src.c文件清爽易读,而又不用去关心数组元素的具体内容。这里有个细节要说到,那就是:

#include指令必须要独占一行

用#include包含文件时,一定要单独占一行,也就是#include语句当前行的前后必须都是空白。因为预处理器会将被包含的文件的全部内容从当前行的下一行开始插入,前后不为空白,就会出错。

之前我在另一篇讲#include用法时,提到将大量数组元素包进文本文件,有个粉丝测试发现int a[]={#include “items.txt”};报错,编译不通过,就是这个原因。

include调试程序

有时候我们需要对几个代码块进行调试,一般都是通过条件编译指令来控制哪一个代码块生效,但是如果用include就会非常方便和省事,比如:

int main(){
....
//功能1:
//#include “func1.txt”
//功能2:
//#include “func2.txt”
....

当我们需要调试代码块1的时候,只要去掉#include “func1.txt”前面的注释就可以了,不挑食的就注释掉,调试的就去掉注释,非常的方便和间接,否则就必须要提哦啊见编译指令,或者用注释成块掉。我这个例子非常简单,反应不出来include的作用,在这种简单场景下,注释更方便,在复杂的情况下,就会显出include的省事。

还有更重要的一个用法,有时候我们会需要再一个项目里包含多个源文件,每个源文件都有一个main函数,因为main函数都对应某个功能的程序,编译肯定不给通过。

我们常规且简单的做法,就是一个main函数的源文件都单独为一个项目,要么通过cmake来控制在同一时间只能有一个main函数来启用。这个用法在我的另一篇文章里也有详细的介绍过。

今天我介绍另一个更加巧妙的办法,因为cmake是微软visual studio IDE的弱项,功能支持比较差,用起来还不如CLION里用的方便,但是有时候又需要使用VS,因为编译器调试能力非常优秀,所以我经常这样干:建立一个源文件:

#include “main01.h”
#include “main02.h”
#include “main03.h”
......

每个main文件里都有一个main函数,需要调试的去掉注释,不调试的就注释掉。非常简单好用,我放一个我的截图如下:

这个截图是我的项目文件夹的组织结构,一个源.c的文件作为开关控制要编译的头文件,每个.h文件都对应一个要调试的小功能。下面的图2就是源文件的内容:

下面的图就是其中一个要调试的功能,这里举的是B.h 这个文件,其实后缀改为.c也没问题,主要是为了不和源文件混淆。B.h的内容如下,注意做上角的红箭头,指明了是B.H文件:

本篇include介绍就写到这里,其他有趣的玩法,请继续关注我的技术专栏:段誉和语言。

段誉,写于合肥。

相关推荐

4万多吨豪华游轮遇险 竟是因为这个原因……

(观察者网讯)4.7万吨豪华游轮搁浅,竟是因为油量太低?据观察者网此前报道,挪威游轮“维京天空”号上周六(23日)在挪威近海发生引擎故障搁浅。船上载有1300多人,其中28人受伤住院。经过数天的调...

“菜鸟黑客”必用兵器之“渗透测试篇二”

"菜鸟黑客"必用兵器之"渗透测试篇二"上篇文章主要针对伙伴们对"渗透测试"应该如何学习?"渗透测试"的基本流程?本篇文章继续上次的分享,接着介绍一下黑客们常用的渗透测试工具有哪些?以及用实验环境让大家...

科幻春晚丨《震动羽翼说“Hello”》两万年星间飞行,探测器对地球的最终告白

作者|藤井太洋译者|祝力新【编者按】2021年科幻春晚的最后一篇小说,来自大家喜爱的日本科幻作家藤井太洋。小说将视角放在一颗太空探测器上,延续了他一贯的浪漫风格。...

麦子陪你做作业(二):KEGG通路数据库的正确打开姿势

作者:麦子KEGG是通路数据库中最庞大的,涵盖基因组网络信息,主要注释基因的功能和调控关系。当我们选到了合适的候选分子,单变量研究也已做完,接着研究机制的时便可使用到它。你需要了解你的分子目前已有哪些...

知存科技王绍迪:突破存储墙瓶颈,详解存算一体架构优势

智东西(公众号:zhidxcom)编辑|韦世玮智东西6月5日消息,近日,在落幕不久的GTIC2021嵌入式AI创新峰会上,知存科技CEO王绍迪博士以《存算一体AI芯片:AIoT设备的算力新选择》...

每日新闻播报(September 14)_每日新闻播报英文

AnOscarstatuestandscoveredwithplasticduringpreparationsleadinguptothe87thAcademyAward...

香港新巴城巴开放实时到站数据 供科技界研发使用

中新网3月22日电据香港《明报》报道,香港特区政府致力推动智慧城市,鼓励公私营机构开放数据,以便科技界研发使用。香港运输署21日与新巴及城巴(两巴)公司签署谅解备忘录,两巴将于2019年第3季度,开...

5款不容错过的APP: Red Bull Alert,Flipagram,WifiMapper

本周有不少非常出色的app推出,鸵鸟电台做了一个小合集。亮相本周榜单的有WifiMapper's安卓版的app,其中包含了RedBull的一款新型闹钟,还有一款可爱的怪物主题益智游戏。一起来看看我...

Qt动画效果展示_qt显示图片

今天在这篇博文中,主要实践Qt动画,做一个实例来讲解Qt动画使用,其界面如下图所示(由于没有录制为gif动画图片,所以请各位下载查看效果):该程序使用应用程序单窗口,主窗口继承于QMainWindow...

如何从0到1设计实现一门自己的脚本语言

作者:dong...

三年级语文上册 仿写句子 需要的直接下载打印吧

描写秋天的好句好段1.秋天来了,山野变成了美丽的图画。苹果露出红红的脸庞,梨树挂起金黄的灯笼,高粱举起了燃烧的火把。大雁在天空一会儿写“人”字,一会儿写“一”字。2.花园里,菊花争奇斗艳,红的似火,粉...

C++|那些一看就很简洁、优雅、经典的小代码段

目录0等概率随机洗牌:1大小写转换2字符串复制...

二年级上册语文必考句子仿写,家长打印,孩子照着练

二年级上册语文必考句子仿写,家长打印,孩子照着练。具体如下:...

一年级语文上 句子专项练习(可打印)

...

亲自上阵!C++ 大佬深度“剧透”:C++26 将如何在代码生成上对抗 Rust?

...

取消回复欢迎 发表评论: