C++中的头文件与编译过程

在这里插入图片描述

下面的内容都是在Linux下进行,C++配合Linux更香哦。在windows中的.cpp在Linux中叫做.cc,下面会混合使用,都是指同个东西。

1 为什么需要头文件

在上一篇文章中我们谈到程序编写的一大原则是“高内聚,低耦合”,这就要求我们尽可能把程序分离成各个比较独立的模块,而一个个模块对应一个个的.cpp文件。既然每个.cpp之间比较独立,那么当A.cpp用到B.cpp的内容时,遇到了两个问题:一个是不知道B.cpp中哪些东西可以拿来用,怎么用?另一个问题是在A.cpp中怎么标明现在用到的这些东西属于B.cpp而不是C.cpp或者D.cpp?

于是头文件.h就诞生了。各个.cpp间比较独立,并不直接沟通,每个.cpp有自己的代言人.h(当然main.cpp没有),借助.h进行沟通。当A.cpp用到B.cpp中的内容时,它不需要去打扰B.cpp,它只要载入B.h就行。在B.h中有B.cpp的内容的梗概,B.h承诺说我的这些梗概都是有对应的实体的,这些实体在某个.cpp文件中,你暂且用着,到时候编译时我再给你这些实体。

2 头文件长什么样

下面编写一个库文件B.cpp与其代言人头文件B.h:

//B.h
#ifndef _B_H_
#define _B_H_

extern int b;
class B {
public:
    void showMe ();
}; 

#endif
//B.cc
#include <iostream>
using namespace std;
#include "B.h"

int b;

void B::showMe() {
    cout << "I am a Object" << endl;
}

//main.cc
#include <iostream>
#include "B.h"
using namespace std;

int b;

int main() {
    B myB;
    myB.showMe();
}

在B.h中给定了一个变量b和一个类B,对于头文件而已,它给出的东西都是虚无,只是一个承诺,对调用者承诺它的这些东西有对应的实体。这个过程叫做声明Delaration,在变量前面需要加一个extern来表示它只是一个承诺。

在B.cc中给出了b变量的实体和类B的函数的具体内容,这个过程叫做定义Definition,它会给出头文件中对应承诺的实体。

我们前面说B.h是B.cc的代言人,实际上果真如此吗?答案是否定的,他们只是不过是萍水相逢罢了。如果我们将B.h改成B1.h,结果照样行得通。就是说我们在.h中给出一些承诺,然后在某个.cc中有对应这些承诺的实体就OK了,.h无所谓这个.cc是谁,只要它有载入自己作为头文件就行了。.h是个大众情人。但是实际在编写库文件中,总是一个.cc对应一个同名的.h,然后里面只放一个类或一些功能可归在一起的函数,这是为了编写着的方便,不是它本来就要这样。

我们可以换个方式,比如把int b;不写在B.cc中,搬到main.cc中去,照样能够正常编译。

3 程序编译过程

.h和.cc间的关系可能是比较错综复杂的,那么实际的编译过程是怎么进行的呢?

实际过程如下:

$ g++ main.cc B.cc
  • 预处理 B.ii main.ii
  • 编译 (编译器) B.s main.s(汇编代码)
  • 汇编 (汇编器) B.o main.o(二进制代码)
  • 链接 (链接器ld) a.out (可执行文件)

a.out是个默认名字,你也可以通过一下命令改成me.out

$ g++ main.cc B.cc -o me.out

在实际编译时,我们只能够看到最后剩下一个a.out文件,中间文件都被删除了,要看到中间文件可以用以下命令。

$ g++ main.cc B.cc --save-temps 

在链接之前的一个原则是以一个.cpp文件为编译单元,.cpp互相独立,看不到彼此。

  • 在预处理过程,操作十分简单,就是将头文件中的东西抄进去.cpp文件,extern 变量抄进去后变成全局变量使用,生成.ii文件。
  • 在编译过程中,会将生成的.ii文件编译成汇编代码.s。
  • 在汇编过程中,会将生成的.s文件汇编成二进制代码.o。
  • 在上编译和汇编过程中,抄进去的头文件内容始终只是一个承诺,没有对应的实体,相当于挖了一个个坑,这些坑由链接器来完成。链接器负责以main.o为中心,将一个个.o文件连在一起,用实体补上坑,最后生成可执行文件.out。

执行一下,

$  ./a.out
I am a Object

4 头文件的格式说明

  • 1)标准头文件结构 Standard Header File Structure
#ifndef _B_H_
#define _B_H_

//Declaration

#endif

在前面的代码中,我们可以看到这样子的结构,它的目的是为了防止重复载入头文件。想象一种情形,我在main.cc中载入了B.h和C.h,在C.h中我也用到了B.h,那么岂不是在main.cc会载入两次B.h,这是就要用标准头文件结构进行规避。每个标准头文件定义了一个宏(Macro),宏的名称实际上可以随便写,不要重复就行。预处理时,若检测到某个头文件中的宏已经存在,说明已经载入过一次了,就不再载入了。

  • 2) < iostream > VS "B.h "

< iostream > :去系统目录下找这个头文件,在linux中这个目录就是/usr/include/,去里面找找,确实有这个文件,在windows下没有系统目录,只能去使用的编译器的制定的目录下寻找。这些一般是标准头文件,不是程序员自己写的。

“B.h” : 去需要预处理的这个.cpp文件的目录下找,即当前目录下找,这些一般是程序员自己写的头文件。

上述的两个用法都还是简单的用法,还可以复杂一点,比如我这个B.h并不是放在当前目录下,而是放在当前目录的文件夹folder里面,那么我可以这么引用:

#include  "./folder/B.h"

你当然也可以把B.h拖进去/usr/include/里面,然后就可以将它荣升为系统目录下的头文件了。

#include <B.h>

既然有iostream,那么有没有iosteam.h,有的!一开始是有iostream.h这东西,后来它升级了,但是呢升级它的人又不想把iosteam.h删掉,就把新的直接命名成iostream。我们知道文件的后缀实质上和它的内容没有关系,只是起一种标示作用,加与不加都没关系。不信的话,你试试把上面的B.h该成B,不影响编译。

5 CMake

接下来再补,写完好累呀,不过终于把头文件这部分搞明白了

附注

参考:gcc/g++命令参数笔记

  • g++常见的命令
g++ -E B.cc		执行到预处理,生成.ii
g++ -S B.cc		执行到编译,生成.s
g++ -c B.cc		执行到汇编,生成.o

记忆:ESc

g++ B.cc -o new.out 		将可执行文件命名为new.out
g++ B.cc -Wall 		 将所有的warning信息都展示出来
g++ B.cc --save-temps 		保存编译过程中临时文件

其次,g++和gcc是不一样的,g++是C++的编译器,能够编译.cc,.cpp和.c,而gcc是C的编译器,只能编译.c,而且本质上它们其实都不是编译器,只是一种驱动器,在编译的各个子过程中调用相应的编译器。

参考:GCC与gcc和g++的区别

  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值