参考-
https://segmentfault.com/a/1190000008634703?utm_source=sf-similar-article
1为什么用库
- 在项目中,可以用文件来管理类与函数的声明,但也有局限性。为了更有效的在之后的项目中使用头文件,那么自然会针对每一种类或函数去书写一份头文件,这就会导致头文件的数量变得很多;而且头文件是不参与编译的,为也能让项目编译通过,还要再把对应的实现源文件也引入到工程中。
- 最重要的一点是保密性。头文件和源文件如果要给别人使用,那么别人都是可以直接修改源码
- 用库的最大优势在于,库是编译过的,库只参与生成应用程序的链接过程——这也就是“链接库”这个名字的来历。
2库的优点
- 使用库,程序开发者可以不用去操心这个库是怎么实现的
- 既然我们会想到把很有技术含量而且还又是很多人都会用到的功能写成库以便再次使用,那么有商业头脑的人自然也能想得到。库是编译器编译过后的产物,对于C++来说,编译是不可逆的——就算是有逆向工程,那么不可能百分百还原,尚且逆向还都是靠有经验的程序员去猜编译前可能会是什么样子的。使用库我们就可以很好的隐藏自己的实现细节,而只把预留的接口给用户
- 因为库的编译过的,所以使用库而不是直接引入源代码在编译大型项目时会大大提高编译效率
3windows编写静态库
3.1基本概念
静态库是编译产生的二进制文件,在使用静态库的程序的最终编译过程中,链接器从库中复制这些函数与数据并把它们和应用程序的其他模块组合起来,生成可执行文件。库是不可以被执行的——或者说,库没有执行这个概念。
因为静态库是编译过后的产物,所以用户是没法知道库中的实现信息的,但这也导致了一个问题:库中的函数和类用户也是不知道如何调用。为了绕开这个问题,我们可以利用C++中把声明与实现分离的技巧,给库配备一个面向用户的用于声明接口的头文件。下面我们先创建一个静态库:
这里选用vs 2017
静态库属性
3.2 将函数包装成静态库
先在项目中新建一个头文件:InitializeArray.h以及对应的源文件:InitializeArray.cpp,然后在头文件中加入我们要实现的函数的声明:
由于是vs2017有预编译头的问题
这里
1.选择【项目】-【属性】-【C/C++】-【预编译头】-【创建/使用预编译头】
关闭预编译头后,可以删除这些文件,然后在源文件和头文件中创建文件
InitializeArray.h
#pragma once
bool InitializeArray(int *&pArray, const unsigned int size, const int minimum = 0, const int maximum = 100);
InitializeArray.cpp
#include "InitializeArray.h"
#include"stdafx.h"
#include <stdlib.h>
#include <time.h>
using namespace std;
bool InitializeArray(int *&pArray, const unsigned int size, const int minimum, const int maximum)
{
srand(time(nullptr));
if (size == 0 && minimum > maximum)
{
pArray = nullptr;
return false;
}
pArray = new int[size];
for (unsigned int i = 0; i < size; i++)
{
int temp = rand() % maximum + 1;
pArray[i] = (temp > minimum) ? temp : temp + minimum;
}
return true;
}
这样就得到我们的静态库了
接下来我们测试一下编译生成的静态库,顺便看一下如何在程序中使用。在另一个项目中,我们需要将静态库文件引入到工程内,我这里将库拷贝到项目目录中的lib文件夹内:
有两种方法可以通知链接器在链接过程中将我们的静态库链接进来,下面分别给出:
方法一:在项目属性中设置链接器
方法2
这里也要去掉编译头如上写下代码
// ConsoleApplication1.cpp : 此文件包含 "main" 函数。程序执行将在此处开始并结束。
//
#include <iostream>
#include".\include\InitializeArray.h"
//#pragma comment(lib,".\lib\StaticLib2.lib")
using namespace std;
int main()
{
cout << "请输入元素个数";
int size;
cin >> size;
int *arr;
InitializeArray(arr, size);
for (int i = 0; i < size; i++)
{
cout << arr[i] << " ";
}
cout << endl;
return 0;
}
运行
成功调用
3.3将类打包成静态库
这里增加一个类的头文件和源文件
这里还用到了重载int符号
#pragma once
#include <vector>
#define VISIBLE_BEGIN
#define VISIBLE_END
class Integer
{
VISIBLE_BEGIN
public:
Integer();
Integer(int integer);
Integer(const Integer &);
~Integer();
//判断整数是否为奇数
bool IsOdd();
//判断整数是否为偶数
bool IsEven();
//将整数分解质因数
void Decomposition(std::vector<int> *pVec);
//将整数逆序重组
int Reverse();
//取绝对值
int Abs();
//取两数较大者
static int GetMaximum(int numberOne, int numberTwo);
//取两数较小者
static int GetMinimum(int numberOne, int numberTwo);
//获取不大于整数的所有质数的集合
static void GetPrimeArray(unsigned int integer, std::vector<int> *pResult);
//取两数最大公因数
static int GCD(unsigned int numberOne, unsigned int numberTwo);
//取两数最小公倍数
static int LCM( int numberOne, int numberTwo);
//将整数隐示转换为int
operator int();
private:
bool m_bNegative;
long m_lInteger;
VISIBLE_END
};
Integer.cpp这里只用了部分我需要的,没有用全部的函数
Integer::Integer(int integer)
{
m_lInteger = integer;
}
Integer ::~Integer()
{
cout << "say goodbye" << endl;
}
//int()重载不能带参数和指定返回类型
Integer::operator int() {
cout << "(int)m_lInteger called!" <<endl;
return static_cast<int>(m_lInteger);
}
int Integer::LCM(int numberOne, int numberTwo)
{
if (numberTwo == 0)
return numberOne;
return Integer::LCM(numberTwo, numberOne%numberTwo);
}
同样操作加入我们的执行文件中
这里尝试了在代码中添加并成功
#include <iostream>
#include".\include\InitializeArray.h"
#include".\include\Integer.h"
//#pragma comment(lib,".\lib\StaticLib2.lib")
#pragma comment(lib, "./lib/StaticLib3.lib")
using namespace std;
int main()
{
cout << "请输入元素个数 ";
int size;
cin >> size;
int *arr;
InitializeArray(arr, size);
for (int i = 0; i < size; i++)
{
cout << arr[i] << " ";
}
cout << endl;
cout << "输入一个整数:";
cin >> size;
Integer integer(size);
cout << "输入另一个整数:";
cin >> size;
cout << (int)integer << " 和 " << size << "的最小公倍数为 " << Integer::LCM((int)integer, size)
<< endl;
return 0;
}
成功
4动态链接库
什么是动态链接库?答案很简单:库。动态库与静态库一样,都是库,也就是都是实现代码重用的一种手段,都是函数与数据的集合。从名字上能看出,两者的区别在于静态库是要在编译时进行链接——此时链接器已经知道要去链接哪一个静态库,而动态库则是在程序中任何时间都可以由程序编写者手工控制进行动态地链接。
4.1与静态库的对比
1更加灵活
上面的解释可能会显得动态库有些多余,因为静态库明明可以完成工作,为什么还要再引入动态库这个概念?所以下面重点介绍下静态库的局限性与不得不使用动态库的理由。
静态库因为是在编译之前就要引入到工程中的,所以必须一次指定要在程序用到的所有静态库并告知链接器。而动态库则不然,调用动态库的工作可以在程序真正需要使用动态库中的某些功能时才进行链接,并且随时可以卸载所调用的动态库。
2资源利用更合理
静态库中包括的源代码将会在链接时全部塞进编译后的可执行文件中,所以编译出的程序都比较大——参考静态编译。而动态库则不然,动态库文件虽然在磁盘中,但是只有程序运行才会把需要用到的部分加载到内存中供程序调用
3支持更复杂的引入与调用
静态库是不支持在一个库中再引入另一个库的,而动态库则没有这个限制,这在完成一些复杂调用的时候还是很有帮助的
4支持多个模块程序同时调用
动态链接库最功能强大的特性就是,一个动态库可以同时被若干程序调用,而内存中只需要加载一份代码。这引申含义就是说, 一个程序的动态链接库可以被其他程序调用。再说得直白一些,任何一个动态链接库,理论上我们写的程序都可以调用其提供的函数和数据。但注意,是 理论上, 具体原因下面会讲解。
5更多的高级特性
动态链接库因为其灵活的特性,实际使用的时候往往可以做出很多更高级的特技,比如在Windows下很实用的DLL注入技术。这些高级特技在熟悉了动态链接库之后,自然都会在程序中慢慢发掘、应用。
4.2创建动态库
创建动态链接库和静态库的步骤差不多,只不过要选中相应的选项。
打开Visual Studio选择创建新项选择动态链接库(DLL),点击下一步
分别创建用于声明和定义函数的头文件.h和源文件.cpp
MSVC编译器提供了一系列C/C++的扩展来指定符号的导入导出,即__declspec属性关键字。
__declspec(dllexport) 表示该符号是从本DLL导出的符号。
__declspec(dllimport) 表示该符号是从别的DLL中导入的。
因此我们可以用一个一个宏来定义我们的导出导入关键字,如果定义了宏就设置了__declspec(dllexport)修饰符,若未定义则设置__declspec(dllimport)。
现在我们头文件中一个用于加法和减法的函数并加上宏定义的修饰符,以及声明一个需要导出的类的函数。
在我们创建的.cpp文件中包含我们的.h头文件并且实现我们的函数
calc.h
#ifndef CALC_H__2832ab37_92f0_4fa4_ad9c_7c5570c90c7f
#define CALC_H__2832ab37_92f0_4fa4_ad9c_7c5570c90c7f
//define CALC_API macro to export or import
#ifdef DYNAMICLIBRARY_EXPORTS
#define CALC_API __declspec(dllexport)
#else
#define CALC_API __declspec(dllimport)
#endif
CALC_API int Add(int x, int y);
class CALC_API Rectangle
{
public:
Rectangle(unsigned int length = 0, unsigned int width = 0);
unsigned int Area() const;
private:
unsigned int m_uiLength;
unsigned int m_uiWidth;
};
#endif
test.cpp
#define CALC_API __declspec(dllexport)
#include "calc.h"
#include <iostream>
using namespace std;
int Add(int x, int y)
{
return x + y;
}
Rectangle::Rectangle(unsigned int length /* = 0 */, unsigned int width /* = 0 */)
{
this->m_uiLength = length;
this->m_uiWidth = width;
}
unsigned int Rectangle::Area() const
{
return this->m_uiLength * this->m_uiWidth;
会生成lib dll文件这里复制这两个和.h文件到新文件夹
这里dll文件在和主文件放一起,lib文件放自己创建的lib文件夹里
#include"./include/calc.h"
#include <iostream>
using namespace std;
#pragma comment(lib, "./lib/Dll2.lib")
int main()
{
cout << "1 + 3 = " << Add(1, 3) << endl;
return EXIT_SUCCESS;
这里再用一下类
#include"./include/calc.h"
#include <iostream>
using namespace std;
#pragma comment(lib, "./lib/Dll2.lib")
int main()
{
cout << "1 + 3 = " << Add(1, 3) << endl;
int input1, input2;
cout << "input number 1" << endl;
cin >> input1;
cout << "input number 1" << endl;
cin >> input2;
cout << "the area of the square" << endl;
Rectangle rect(input1, input2);
cout << rect.Area() << endl;
return EXIT_SUCCESS;
}
效果