1、静态库的创建
-
解说
静态库类似于将一个项目的源代码(实现部分)进行打包,封装在一个.lib二进制文件中,在需要使用这个库的项目中进行配置,指定.lib文件所在的路径。
-
空项目 --> 静态库项目
配置过程:项目 --> 属性 --> 配置属性 --> 常规 --> 配置类型,选择静态库即可。
-
配置完成之后即可生成。
-
根据上面的配置可以对中间目录和输出目录进行修改,这里修改到staticlibary目录中。该目录在解决方案文件夹内部一级。更改后的静态库生成结果目录图:
- 生成之后的静态库核心在于.lib文件和静态库相关的头文件,现在在staticlibary中创建一个include目录用于存放静态库的相关头文件。
- 现在创建一个可执行程序项目使用静态库。
项目属性 --> C/C++ --> 附加包含目录。此时可以引入静态库中的相关头文件。
项目属性 --> 链接器 --> 输入 --> 附加依赖项。将.lib文件名称添加进去。告诉项目在链接阶段需要的依赖项,默认会去项目目录查找,也就是测试项目test_lib目录中查找。可以将上面生成的.lib文件放在这个目录下,但是不推荐。此时就可以生成项目,然后执行。
通常,.lib文件会放在专用的目录下(lib目录),然后在链接器 --> 常规的附加库目录中添加.lib文件所在的目录即可。此时可以正常执行。
2、动态库
-
解说:动态库是在应用程序执行时寻找其具体实现,相比于静态库,多了一个.dll文件,该文件是在应用程序执行时会去寻找的一个文件,默认情况下,会在项目目录中查找,还有就是在可执行程序.exe所在的目录中查找。
动态库也有.lib文件,负责在编译链接时告诉项目使用的动态库的信息。可以说,从开发层面上,动态库只是比静态库多了一个.dll文件,且这个文件不在项目属性中配置它的路径,而是程序运行时自动在默认的目录中查找的。
-
相比于静态库项目配置,动态库没有很大不同,对于中间目录和输出目录的位置修改也是一样的。只是动态库中的代码会有所不同,如下:
-
动态库的使用
首先准备好头文件(同静态库放到dynamiclibary目录中),.lib 和 .dll库文件。三者都位于dynamiclibary中。此时创建test_dll项目,使用动态库。
此时,进行test_dll项目的属性配置,操作步骤和细节都和静态库一样,都是为了项目能够编译链接通过。
最后,如果项目要能够执行,必须将.dll放置到项目目录中,或者是项目的输出目录中。负责会报错,找不到对应的动态库。最好是放置到项目的输出目录中,如果放在项目目录中就必须是通过vs启动程序才可以,如果直接点击输出目录中的可执行程序还是会报错找不到动态库。直接放在输出目录中,通过vs启动程序也能找到,更加通用。
-
说明
在一个解决方案中,可能有多个项目,有的是动态库,有的是静态库以及可执行的项目,此时可以把可执行项目和动态库的输出目录设置成一样的,这样就保证了.exe和.dll文件在同一个目录中。.lib文件是在项目编译链接时就要确定的,不参与考虑。
3、vs项目结构
3.1 结构解说
vs的项目由解决方案管理,内部可以有多个项目,多种项目。
3.2 关于筛选器和文件夹管理项目内部的头文件和cpp文件
正常情况下,创建一个项目都会有头文件、源文件和资源文件三类筛选器,其实筛选器只是在vs的ui界面中的逻辑文件夹,在磁盘上,这三类的所有文件都在项目目录的同一级中。因此在引入头文件时都通常使用 "xxx.h"来引用。
其实也可以使用目录来管理项目中的各个文件,使用目录时,在引入头文件是就需要看头文件相对于当前的目录是怎么样的,如下伪代码所示。但是筛选器和目录不会同时使用。
- project # 项目文件夹
- Module1 # 项目模块1
- Headers
module1.h
- Source
module1.cpp // 引入module1.h #include "../Headers/module1.h"
- Module2 # 项目模块2
- Headers
module2.h
- Source
module2.cpp
Application.cpp
3.3 VS的生成操作和清理操作
生成操作会在输出目录和中间目录中输出,清理操作会把输出目录中的文件全部清理,中间目录中生成记录。
3.4 VS的预编译头
使用预编译头技术可以大大提高编译速度。但是如果使用预编译头技术就必须在所有的cpp文件中包含预编译头。在预编译头中包含的头文件最好是那种不会经常变动的。
使用预编译头:
第一步:在项目中创建stdafx.h和stdafx.cpp文件,将常用的且不常发生改变的头文件放入到stdafx.h中。
第二步:右键项目属性 --> c/c++ --> 预编译头,选择使用预编译头,预编译头文件填写stdafx.h,默认也是这个,还有预编译结果的输出目录,也可以改变。
第三步:点击stdafx.cpp源文件的属性 --> c/c++ --> 预编译头,选择创建预编译头,预编译头文件填写stdafx.h。
最后,在项目中的所有.cpp文件都需要包含预编译头文件stdafx.h。只要stdafx.h和stadafx.cpp文件不变化,然后包含的头文件不变化,预编译头文件就不会发生变化,不用重新生成。
3.5 pdb调式文件
该文件是为调试而起作用的,通常会默认创建这个文件,无论是在debug和release中都会生成,如果在debug设置不创建这个文件,那么在打断点调试的时候将会是无效的。而在release的情况下,可以不生成这个pdb文件,设置如下图:
3.6 预定义处理器
- 预处理器选项可以用来设置预处理器定义,这些定义会在编译时用于条件编译。
这里使用c语言中的scanf来说明,因为scanf在vs中会提示为不安全,并且会报错。如下图:
- 点开项目属性进行预定义处理的配置:
- 关于上面这个问题也可以使用宏定义处理,如下图:
- 深入了解预处理器:预处理本身就是在编译时期起作用的,能起到条件编译的作用,如下图,在项目属性中的预处理器添加了自定义的一个_MYBUG宏,然后可以在代码中实现条件编译(如果需要的话)。
- 既然能够在cpp文件中定义宏,还要有项目属性的预处理器配置,是因为作用域的问题,在cpp中的定义其实是为当前文件服务的,如果项目中的文件一多,很多文件都出现了这个问题,那么就会需要每个文件添加,很影响效率。而预处理器可以起到项目作用域的作用,大大提高了开发效率。如下图展示未进行全局设置的情况:
- 在一个大型项目中,可能需要对各个源文件都进行一些预定义处理的设置。这些设置包含编译选项,宏定义,库文件路径等等。手动在每个源文件中添加这些设置既繁琐又容易出错。
- 使用之前的预编译头技术也是可以实现项目级别的宏定义,因为每个cpp都必须包含预编译头文件,间接的加载到了项目级别,只是个人的一种想法。如下图:
3.7 #pragma once指令
- 这个指令是一个预处理指令,用于确保头文件只被编译一次。在C++中,经常会将类和函数定义放在头文件中,并在其他文件中包含这个头文件。如果不使用 "#pragma once"或者其他方式,可能会出现多次定义同样的内容的问题,导致编译错误。
- 这个指令在很多语言中都可以使用,被广泛采纳。此外,在C++中也可以使用宏定义实现。
//头文件student.h
#ifndef STUDENT_H //如果没有定义STUDENT_H这个宏
#define STUDENT_H //那么就定义
xxxx代码内容(类定义函数定义等等)
#endif //结束ifndef
- 如果项目只是在一个特定的编译器和平台上运行,则可以优先考虑使用#pragma once指令。如果项目需要跨平台移植,则可以采用头文件保护宏定义。
3.8 警告问题
在生成代码时,vs总是会提示一些信息,最常见的就是size_t 和 int 的转换问题,在循环遍历容器时经常遇到。以下提供了两种方式进行处理:
- 1、警告等级设置
-
2、禁用特定警告
如果直接关闭或者降低警告等级可能会出现一些可以处理的警告,但是没有处理,可能会导致程序运行错误的问题,所以在不更改上面默认警告等级的情况下,选择性的禁用特定的警告。
3.9 自定义生成工具
- 在某些情况下,C++的编译器无法编译特定的项目文件。比如,在项目中使用了Qt框架,不可避免地就需要使用Qt的信号槽事件等特有的特性,这个时候就必须在类声明时添加Q_OBJECT宏,然而添加了这个宏的类是无法通过C++编译编译通过的,必须要使用Qt库中的moc.exe编译工具进行处理(这个命令在qt库的bin目录中)。当然只是使用qt的控件的API,而不需要使用Q_OBJECT宏时是不需要自定生成工具的。
- 首先创建一个空项目,然后编写相关代码。这里编写了一个登录界面的窗体类,一个和Qt无关的空类,以及一个主程序。
- 鼠标右键LoginWidget.h头文件,然后点击属性。
- 自定义生成工具配置
- 对于自定义生成工具的头文件在编译之后就会输出moc_xxx.cpp文件,在头文件的对应的cpp文件中需要包含这个新生成的moc_xxx.cpp文件。