1.前言
前面我们一直提到静态库与动态库,但都只是大致说了一下,并没有过于深入的讲解。
但随着后面的C/C++学习中,你将会遇到大量的关于动态库与静态库的使用,因为各种第三方库基本都是会提供静态库或动态库的。
所以本章将详尽介绍一下在Windows
平台动态库与静态库的制作流程。
请注意,不同平台的静态库与动态库并不相同,需要各自单独制作。
2.静态库
前面已经说过了静态库的作用,其主要目的是为了便于使用以及不泄露源码,很大程度上都是等价的替换了所有的源文件(.cpp
),所以使用的时候我们仍然需要头文件(.h
)。
还是老规矩,先建立项目,不过这次不同,得建立三个!分别为静态库项目、动态库项目以及可执行程序项目。
先建立个控制台的可执行项目,这就不多说了,名称就叫 day10-exe
了。
这里主要讲解一下建立静态库与动态库项目的过程:
名称就分别叫day10-static
与day10-dynamic
即可。
动态库的项目就先不管了,后面再说,我们先来看静态库,其默认生成了下面这些文件:
先来说说这些文件的作用:
pch
: 预编译文件
所谓预编译,其意思就是把不会更改的源代码先行编译了,此后就可以直接使用,比如C++的iostream
,windows的windows.h
等等,我们不可能去改它们的源码,那就可以把它们包含进pch.h
中,以后如果我们想使用这些标准库文件,就只需要包含pch.h
即可。
不过要注意,这些官方的库一般都是已经被编译过了的,所以这样做并没有太大的用处,非要说的话,可以让我们只包含一个pch.h
就能间接包含pch.h
中的所有头文件,可以方便我们使用。
其最大的用处其实是对我们自己写的项目代码,有些代码文件功能是一次写好很久都不会动的,那就可以将其包含进来。
所起的作用是这些源文件将只在第一次运行时被编译,只要你后面不更改这些源文件,那就不会再编译,这可以大大减少我们编译程序的时间。
framework
:一个框架文件,一般主要用于包含一些宏之内的东西。
可以看到pch
文件的官方说明:
其中就包含了framework.h
文件。
这里还有一个宏的特殊用法:
#ifndef PCH_H
#define PCH_H
#endif //PCH_H
ifndef
:if not define
,即如果没有定义后面这个宏。define
:那我就定义这个宏endif
:结束标志
ifndef
是一种宏用法,除了它,还有ifdef
等,看名字也知道它是什么意思了,可以用来判断紧跟其后的这个宏被定义了没。
这个宏的名字是可以随便写的,但为了防止与其它文件定义的宏重名,所以一般就会采用本文件名大写、后面跟着后缀_H
来作为宏的名字,字母H代表这是头文件(Header)。
它就和if
判断语句一样,只有满足了条件才会执行里面的内容。
比如这里,只有以前没有定义过PCH_H
宏,其里面包含的头文件才会被包含进来,并同时将这个宏定义了。
这是为了避免重复包含,比如这里包含了两次
#include<pch.h>
#include<pch.h>
但展开后其实是:
#ifndef PCH_H
#define PCH_H
//头文件
#endif //PCH_H
#ifndef PCH_H
#define PCH_H
//头文件
#endif //PCH_H
这样就只包含了上面一段,下面这段就被编译器给忽略了,因为上面那段已经定义了宏。
如果项目里面使用了预编译文件,比如这里的pch
,那么你写的所有其它源文件(.cpp
)都必须添加pch.h
头文件,否则会编译失败!
当然,这些文件并不是必要的,如果你不关心编译时间,完全可以删除:
这里我将自动生成的其它三个文件都删除了,并添加了一个day10-static.h
的头文件,用做函数声明。
然后我们就可以向平常一样,写函数即可,比如写一个加法函数:
设置静态库项目为启动项,编译试一试:
正如前面所说,如果使用了预编译,那么就必须要在源文件中包含它,虽然我们已经删除了该文件,但设置里面还没关闭使用源文件。
来到该项目的属性界面,设置一下即可:
注意项目属性设置页面中,上面的配置要和你项目配置相同,即都是Debug
、x64
模式,现在返回编译就可以了。
不过由于静态库以及后面要介绍的动态库本身都是无法运行的,所以你点击上方的“本地WIndows调试器”、或者“运行”,都会报错,因为它本来就无法被运行。
不过也无所谓,因为运行的第一步就是编译,所以是不用管这个报错信息的。
编译后,找到对应的头文件与lib
文件:
将上面两者复制到我们前面创建的exe
项目中:
然后在exe
项目中,添加main.cpp
文件,设置为启动项目,写上下面的代码:
#include<iostream>
using namespace std;
#include"day10-static.h"
#pragma comment(lib,"day10-static.lib")
int main() {
cout << add(1, 3) << endl;
}
然后运行一下试一试:
静态库的使用是不是很简单?
当然,一开始使用,可能会遇到各种各样的问题,这时候可以去浏览器搜索解决,或者询问deepseek之类的大模型。
3.动态库
动态库就和静态库有点不一样了。
通过前面的实验可以发现,静态库的作用其实就是替换了一下源文件(.cpp
)而已,生成的程序与我们正常用.cpp
文件编译的程序没有任何区别。
而动态库可完全不是这样,它主要生成两个文件,一个.lib
文件和一个.dll
文件。
这里的.lib
文件与上面我们使用静态库生成的lib
文件是一样的,只是这个里面并没有包含源代码,而只是包含了一些关于代码的基本信息。
比如函数在dll
中的地址等,所以体积会比静态库项目生成的lib
文件要小很多,因为其实际的代码编译到了相应的.dll
文件中。
各个文件之间的关系就是,我们通过头文件知道这个动态库里面有些什么东西,编译器通过lib
文件知道各个函数或其它信息在dll
文件中的哪个地方,然后进行调用。
注意动态库生成的.dll
文件,必须要跟着.exe
文件走,否则.exe
文件无法启动!
这就是动态库的缺点与优点,麻烦,但生成的.exe
文件小,所以我们常常看到一些软件安装目录里面有一大堆dll
文件,就是为了减少生成exe
文件的大小,同时方便升级,比如偶尔只需要升级某个dll
即可。
不好理解还是直接来实战吧,现在将动态库项目设置为启动项: