这些都是c/c++编译运行时期所产生的文件,作为一个没用过这门语言的人很容易搞混。
简单分类
简单分三类,也就是三个过程需要的文件
.h
/.cpp
/.hpp
/.c
:代码源文件
.o
/.obj
/.lib
/.a
:编译过程需要的文件
.dll
/.so
/.exe
:运行过程需要的文件
源代码
cpp 与 c
.cpp
和.c
分别为c++和c的源代码文件。早期都只用.c
文件,后来为了便于区分就为c++产生了.cpp
文件。但这也只是约定俗成的,还有可能将c++源文件命名为其他后缀,例如.cxx
等
h(头文件)
假如A.cpp
需要引入B.cpp
中的方法,则需要在A.cpp
的头部引入#include "B.cpp"
。其实这样就可以了。但更为常见的做法是将B.cpp
中的所有方法声明都提取出来单独写到一个B.h
文件中,需要用B.cpp
中的方法时,只需要引入B.h
即可。这很类似java中的接口模式,但不管是实践还是思想都是完全不同的
为什么要这样做?
- 有的时候依赖包的源码并不是公开的,依赖提供者只会提供已经编译好的
.lib
文件与其对应的.h
文件,此时若需要调用lib中的方法就需要先引入该头文件。 - 事实上
include
关键字仅仅作用与编译期,即将include的文件原封不动地复制到本文件。所以假如被引用的头文件中声明了一些常量,你又在本地声明了一样的常量,这个问题就能在编译器就被发现。
hpp
个人理解和.cpp
文件功能上一样,都是讲方法声明和实现写在一个文件中,但又在性能上有优化。
注:hpp=header plus plus,意思是将实现写进头文件中,概念上还是属于头文件,但看起来像cpp文件。
每个.cpp
文件都会被单独编译成一个.o
文件,然后再通过链接步骤将它们链接在一起。但hpp文件不会单独编译
上面说 include 其实就是将代码原封不动复制过来,include的hpp文件会和源文件一起只生成一个.o
文件,减少了编译次数
编译
.o 与 .obj
它们都是源文件编译后的产物,类似于java文件编译后产生的class文件
不同点在于 .obj
是在windows下的编译产物,.o
是linux下的。
.lib 与 .a
每个cpp文件都会被单独编译成.o
文件。假如它们之间有依赖关系(例如A.cpp引用了B.cpp中的方法),那么它们怎么找到相应的方法实现呢(因为此时A.cpp只引用了B.h,该文件中只有方法声明,没有实现)?
所以在编译结束后还需要一个链接操作,即将调用方法的符号引用替换成真实的方法地址。然后将它们一起打包成一个.lib
(windows下).a
(linux下)文件,最终生成一个.exe
文件。
注:此时又有静态链接和动态链接的概念,见下文中“运行”部分
运行
静态链接
将整个项目所有的源码及依赖全都打包在一起,此时整个项目没有任何外部依赖,直接打开就能运行,这种打包方式称为静态链接
例如上述“编译”过程最后,将所有的文件都打包进一个.lib
文件中,运行过程中若需要找方法体,直接在该lib文件中就能找到。
静态链接的好处是兼容性强,不受外部环境的干扰。因为所有的依赖它都已经自带了,可以自力更生了。
缺点是包比较大,同样是因为它携带的东西太对了。
动态链接
有的依赖确实不需要随身携带,例如cout
这种方法的依赖,一般系统下都有其实现并且不会轻易去更改。此时就可以使用动态链接。动态链接不打包方法体,只打包方法声明和调用地址。
例如上述“编译”过程的最后,打包成的.lib
文件中仅仅包含被调用方法的声明,此时如果使用这些方法,程序就会在运行过程中动态地去系统中寻找依赖库文件,即.dll
(windows下)或.so
(linux下)文件。
这也是为什么一些软件运行过程中会突然报却是某种dll的错误
动态链接的优点是打包的体积小,但缺点也很明显,对环境依赖高。一旦环境中没有所依赖的动态库软件就无法运行。
.dll 与 .exe
dll只是一个依赖包,就好比是一个工具箱。
exe就是在上述dll中加入了一个main方法,使得其可以直接执行。
总结
lib和dll总结
如果是静态链接,lib类似于自家的厨房,自己想吃什么就做什么,所有的材料也都是充沛的。此时lib就是所有方法的声明加实现。
如果是动态链接,lib就类似于餐厅的公共菜单,想吃什么直接点就行,不用去管怎么做。但不同的餐厅可能做法不同。且可能你点的菜某家餐厅没有,那就会报材料缺失的错误。此时lib相当于只是方法的一个索引了。