# 基础知识预备
### 1、C/C++程序编译的四个过程 (以g++编译器为例)
- 预处理:宏的替换,还有注释的消除,还有找到相关的库文件,将#include文件的全部内容插入。若用<>括起文件则在系统的INCLUDE目录中寻找文件,若用""括起文件则在当前目录中寻找文件。
- 编译:生成汇编文件,用编辑器打开就都是汇编指令。
- 汇编:汇编变为目标代码(机器代码)生成.o的文件,.o是gcc生成的目标文件,用编辑器打开就都是二进制机器码。
- 链接:链接生成可执行文件。这个过程可以去别的文件中寻找.cpp文件中声明的函数,如果找到就可以调用了。(个人理解是这样,如有不准确的地方,欢迎指正)
### 2、静态库与动态库的理解:
- 库的概念:库是写好的现有的,成熟的,可以复用的代码。现实中每个程序都要依赖很多基础的底层库。
- 静态库:静态库在链接阶段,会将汇编生成的目标文件与引用到的库一起链接打包到可执行文件中,对应的链接方式称为静态链接。试想一下,静态库与汇编生成的目标文件一起链接为可执行文件,那么静态库必定跟目标文件文件格式相似。其实一个静态库可以简单看成是一组目标文件的归档集合,即很多目标文件经过压缩打包后形成的一个文件。静态库特点总结:
- 静态库对函数库的链接是放在编译时期完成的。
- 程序在运行时与函数库再无瓜葛,移植方便。
- 浪费空间和资源,因为所有相关的目标文件与牵涉到的函数库被链接合成一个可执行文件。
- 除了上面提到的浪费空间和资源的弊端,使用静态库还有另一个问题:对程序的更新、部署和发布页会带来麻烦。如果静态库liba.lib更新了,所以使用它的应用程序都需要重新编译、发布给用户(对于玩家来说,可能是一个很小的改动,却导致整个程序重新下载,全量更新)。
- 动态库:动态库在程序编译时并不会被连接到目标代码中,而是在程序运行是才被载入。不同的应用程序如果调用相同的库,那么在内存里只需要有一份该共享库的实例,规避了空间浪费问题。动态库在程序运行是才被载入,也解决了静态库对程序的更新、部署和发布页会带来麻烦。用户只需要更新动态库即可,增量更新。
# 序言
### 1. IDE工具
- VSCode
- 语法检测:头文件(include头文件) + 编译内置的语法
- 添加头文件检测:ctrl + shift + p->C++配置编辑,添加头文件所在目录;
### 2. 控制终端
- 命令
- 环境变量
- PATH: echo %PATH%
- set指令显示所有环境变量
- PATH:window执行程序的搜索的路径;每个路径使用`;`分号分隔
- 有可视化的设置的
### 3. VisualStudio 开发环境的设置
- 使用脚本的文件来设置,脚本是vcvars64.bat
- 设置vcvars64.bat的目录到PATH环境变量;
- `PATH=C:\Program Files (x86)\Microsoft Visual Studio\2019\Enterprise\VC\Auxiliary\Build`
- 使用可视化设置环境变量,需要重启终端;
### 4. 开发工具
- cl 编译器
- link 连接器 (PE格式)
- lib 库归档工具(静态库:目标文件的归档)
- dumpbin PE与目标文件格式分析工具
# 工程组织与编译器
### 1. 编译器
- `cl 源代码吗` 编译/链接为执行文件
- `/EHsc`
- `/MD`
- `/utf-8`
- `/source-charset:utf-8`
- `/execution-charset:utf-8`
- 头文件目录
### 2. 说明:
- 第一次你输出,汉字可能是乱码
1. 使用chcp命令改变终端的代码页。utf-8的code page= 65001
- `chcp 65001`
2. 在区域/语言中直接设置编码(整个系统设置为utf-8)
- opencv不识别中文目录;(系统设置编码)
### 3. 链接器
- link
- cl负责编译 : 检测语法,生成目标文件
- link负责链接:负责生成PE格式文件,需要信息动态库信息
- link 选项 目标文件s
- `/out`
- `/MACHINE:X64`
- 第三方的库的库目录
- 第三方的库名
# 静态库
- 前提:lib/dll这两个文件解释清楚;
- lib:静态库
- dll:动态库
## 实现一个库函数
- `gk_math.h`
```C++
#ifndef GK_MATH_H
#define GK_MATH_H
extern int gk_add(int, int);
#endif
```
- `gk_maath.cpp`
```C++
#include "gk_math.h"
int gk_add(int p1, int p2){
return p1 + p2;
}
```
## 编译成静态库
```bash
@rem 静态库的编译
@rem 编译
@cl /c /EHsc /MD /nologo /source-charset:utf-8 /execution-charset:utf-8 /Fo:gkmath.obj gk_math.cpp
@rem 静态库链接
lib /MACHINE:X64 /nologo /OUT:gkmath.lib gkmath.obj
```
## 代码的组织
- 使用shell脚本或者bat处理脚本,比较麻烦的是多个操作需要写成多个bat文件;实际引入一个专门的工程组织脚本Makefile;
- 工程组织的方式:
- 通用
- Makefile
- CMake
- QMake
- 个性化:
- Visual Studio
- Qt Creator
- QMake
- Eclipse C++
- C++ Builder
- Makefile脚本的语法:Makefile
1. 定义变量
2. 任务(Task)
- 依赖(任务依赖另外一任务)
3. 指令
- Makfile例子
```bash
CL_FLAGS = /c \
/EHsc \
/MD \
/nologo \
/source-charset:utf-8 \
/execution-charset:utf-8
LINK_FLAGS = /MACHINE:X64 \
/nologo
OBJS = gkmath.obj
SOURCES = gk_math.cpp
TARGETS = gkmath.lib
main:$(TARGETS) main.cpp
@ cl /nologo /MD /Fe:main.exe main.cpp $(TARGETS)
$(TARGETS):$(OBJS)
@lib $(LINK_FLAGS) /OUT:$(TARGETS) $(OBJS)
$(OBJS): gk_math.h gk_math.cpp
@cl $(CL_FLAGS) /Fo:$(OBJS) $(SOURCES)
clean:
@del *.exe *.obj *.lib 2>/Nul
```
## 使用静态库
### 1. 在链接的时候使用静态库
```C++
#include <stdio.h>
#include "gk_math.h"
int main(int argc, char **argv, char **arge){
printf("C++程序编程!静态库调用结果:%d\n", gk_add(45, 55));
return 0;
}
```
- 编译脚本
-----
```bash
CL_FLAGS = /c \
/EHsc \
/MD \
/nologo \
/source-charset:utf-8 \
/execution-charset:utf-8
LINK_FLAGS = /MACHINE:X64 \
/nologo
OBJS = gkmath.obj
SOURCES = gk_math.cpp
TARGETS = gkmath.lib
main:$(TARGETS) main.cpp
@ cl /nologo /MD /Fe:main.exe main.cpp $(TARGETS)
$(TARGETS):$(OBJS)
@lib $(LINK_FLAGS) /OUT:$(TARGETS) $(OBJS)
$(OBJS): gk_math.h gk_math.cpp
@cl $(CL_FLAGS) /Fo:$(OBJS) $(SOURCES)
clean:
@del *.exe *.obj *.lib 2>/Nul
```
### 2. 在代码中使用静态库
```C++
#include <stdio.h>
#include "gk_math.h"
#pragma comment(lib, "gkmath.lib")
int main(int argc, char **argv, char **arge){
printf("C++程序编程!静态库调用结果:%d\n", gk_add(45, 55));
return 0;
}
// cl /nologo /MD /Fe:main.exe main_lib.cpp
```
- 编译命令:
- `cl /nologo /MD /Fe:main.exe main_lib.cpp`
---------------
- 回顾
1. 开发工具
1. cl编译器 (mac:clang/g++,posix:gnu g++,hp:acc: intel:cc, sun:cc)
- 默认是调用link默认链接
- /link 后面直接包含link选项
2. link连接器(posix:ld)
- link步骤很多编译器中默认自动调用
3. lib(ar)
- 静态库
4. dumpbin(nm)
- 分析目标文件与PE执行文件
5. nmake(make)
- nmake task
- nmake task -f makefile文件
2. vcvars64.bat / vcvars32.bat (mac/linux不需要单独的设置,默认在系统设置)
- 空格转义: "C:\Program Files (x86)\Microsoft Visual Studio"
3. makefile的语法
- 任务目标:依赖
- 指令(使用tab开始)
- 伪任务目标:
- 文件不存在
---------------
# 动态库
## 实现代码
- gk_math.h文件
```C++
#ifndef GK_MATH_H
#define GK_MATH_H
extern int gk_add(int, int);
#endif
```
- gk_math.cpp文件
```C++
#include "gk_math.h"
int gk_add(int p1, int p2){
return p1 + p2;
}
```
## 编译动态库
- 准备:link选项
- /DLL:不需要main入口
- /IMPLIB : 指定链接的时候产生导入的符号,使用lib静态库的方式存放;
- /EXPORT : 指定哪些函数可以被别人调用 `= /DEF:DEF导出函数的描述文件`
- /MACHINE:指定CPU结构X64/X86/ARM/ARM64/EBC
- /OUT:指定输出文件名,dll输出名字
- 编译脚本
```bash
# 编译选项设置一个变量
CL_ARGS=/EHsc /MD /source-charset:utf-8 /execution-charset:utf-8 /nologo
# 链接选项设置一个变量
LINK_ARGS=/MACHINE:X64 /NOLOGO /DLL
# 文件设置成变量
SOURCES = gk_math.cpp
OBJS = gk_math.obj
OUTLIBS = gk_math.lib
OUTDLLS = gk_math.dll
# 目标指令实现
$(OUTDLLS):$(SOURCES)
@cl /c $(CL_ARGS) /Fo:$(OBJS) gk_math.cpp
@link /MACHINE:X64 /NOLOGO /DLL /OUT:$(OUTDLLS) /IMPLIB:$(OUTLIBS) /EXPORT:gk_add $(OBJS)
clean:
@del *.obj *.lib *.dll *.ilk *.exe *.exp 2>/Nul
```
## 动态库的调用方式1 (显式调用)
- 直接使用dll调用函数(lib根本不需要,只需要dll) 【过程繁琐,不推荐,开发时不常用】
- 准备技术:
1. HMODULE = LoadLibraryA(LPCSTR dllfilename): 加载动态库到内存
2. FARPROC = GetProcAddress(HMODULE hModule, LPCSTR functioname) 找到函数
3. 函数类型转换
4. 调用
5. 释放dll空间:BOOL FreeLibraray(HMODULE)
- 代码实现
- call_matual_dll.cpp文件
```C++
#include <stdio.h>
#include <windows.h>
// typedef int(*type_f)(int,int);
int main(int argc, const char**argv){
// 加载dll模块
HMODULE h = LoadLibraryA("gk_math.dll");
if (h == NULL){
printf("加载失败!\n");
return -1;
}
printf("加载成功!");
// 查找函数
FARPROC f = GetProcAddress(h, "gk_add"); // ?gk_add@@YAHHH@Z
printf("%p\n", f);
// 类型转换
// type_f myfunc = (type_f)f;
int (*myfunc)(int, int) = (int(*)(int, int))f;
// 调用
printf("结果:%d\n", myfunc(45,55));
// 释放模块
FreeLibrary(h);
}
```
- build.bat文件
```bash
@cl /c /utf-8 /nologo /MD /Fo:call_manual_dll.obj call_manual_dll.cpp
@link /NOLOGO /OUT:main.exe /DYNAMICBASE Kernel32.lib call_manual_dll.obj
```
- /I : 指定头文件的路径
- 结果展示
# 动态库的调用方式2
- 在编译的时候调用函数(根本不需要dll,只需要lib,但是运行的时候需要dll,不需要lib)
- 代码实现
-call_auto_dll.cpp文件
```c++
#include <stdio.h>
#include "gk_math.h"
#pragma comment(lib,"gk_math.lib")
int main(int argc, const char *argv[]){
printf("调用结果:%d",gk_add(45,55));
return 0;
}
```
- 编译 Makefile文件
```bash
# 编译选项设置一个变量
CL_ARGS=/EHsc /MD /source-charset:utf-8 /execution-charset:utf-8 /nologo
# 链接选项设置一个变量
LINK_ARGS=/MACHINE:X64 /NOLOGO /DLL
# 文件设置成变量
SOURCES = gk_math.cpp
OBJS = gk_math.obj
OUTLIBS = gk_math.lib
OUTDLLS = gk_math.dll
# 目标指令实现
$(OUTDLLS):$(SOURCES)
@cl /c $(CL_ARGS) /Fo:$(OBJS) gk_math.cpp
@link /MACHINE:X64 /NOLOGO /DLL /OUT:$(OUTDLLS) /IMPLIB:$(OUTLIBS) /EXPORT:gk_add $(OBJS)
clean:
@del *.obj *.lib *.dll *.ilk *.exe *.exp 2>/Nul
main1:call_auto_dll.cpp
@cl /c $(CL_ARGS) /Fo:main1.obj call_auto_dll.cpp
@link /OUT:main1.exe /DYNAMICBASE gk_math.lib main1.obj
main2:call_auto_dll.cpp
@cl /c $(CL_ARGS) /Fo:main2.obj call_auto_dll.cpp
@link /OUT:main2.exe main2.obj
```
- 结果展示
# Qt编译环境设置
### 1、掌握的重点:
1. GUI (Qt应用 + QtWidgets(QDialog))
### 2、Qt程序
```C++
#include <iostream>
//Qt GUI模块:QtWidgets
//Qt 底层模块:QtCore
//Qt 图形的绘制模块QtGui
#include <QtWidgets/QApplication>
#include <QtWidgets/QDialog>
int main(int argc, char **argv){
//1.构建Qt应用:QApplicationn
QApplication app(argc, argv);
//2.窗体创建
QDialog dlg;
//窗体的属性(函数对getter/setter)
dlg.setWindowTitle("解征的第一个Qt开发");
dlg.resize(640,480);
dlg.move(200,200);
dlg.show();
//3.消息循环处理
int status = app.exec(); //block函数(消息循环)
//4.退出程序,返回状态码给系统0~255(-1=255)
return status;
}
```
### 3、Qt编译
```C++
INCLUDES = /I "E:\qt\Qt-5.14.0\include"
LIBS = /LIBPATH:"E:\qt\Qt-5.14.0\lib" \
/DYNAMICBASE \
"Qt5Widgets.lib" \
"Qt5Gui.lib" \
"Qt5Core.lib"
CL_ARGS = /EHsc \
/MD \
/source-charset:utf-8 \
/execution-charset:utf-8 \
/nologo
LINK_ARGS = /MACHINE:X64 /NOLOGO
main:qmain.cpp
@cl /c $(CL_ARGS) /Fo:qmain.obj $(INCLUDES) qmain.cpp
@link $(LINK_ARGS) $(LIBS) /OUT:main.exe qmain.obj
clean:
@del *.exe *.obj *.exp 2>/Nul
```
### 4、运行结果
# qt 报错的解决方案
1.缺少依赖的运行库
- 如果出现 qt.qpa.plugin: Could not find the Qt platform plugin "windows" in ""
-解决办法:windeployqt main.exe
2.输入法问题
- 切换至英文输入法
# OpenCV的环境
1. cmake configure
2. generate
3. VS2019 打开:BUILD_ALL / INSTAL
# 作业
1. 写Qt程序,并编译链接成执行文件,且能执行成功;
- 动态的使用
- 编译器/连接器
-
# 补充:疑难解决:c++中头文件与源文件的作用
自己的c++编程还属于菜鸟级别的,因此上了一天的课,头脑中有一个大大的疑问:我们为什么总是要单独编辑头文件,很麻烦,这有什么用呢?通过查阅资料,有如下收获:
通常,在一个 C++ 程序中,只包含两类文件―― .cpp 文件和 .h 文件。其中,.cpp 文件被称作 C++ 源文件,里面放的都是 C++ 的源代码;而 .h 文件则被称作 C++ 头文件,里面放的也是 C++ 的源代码。
C++ 语言支持"分别编译"(separatecompilation)。也就是说,一个程序所有的内容,可以分成不同的部分分别放在不同的 .cpp 文件里。.cpp 文件里的东西都是相对独立的,在编译(compile)时不需要与其他文件互通,只需要在编译成目标文件后再与其他的目标文件做一次链接(link)就行了。比如,在文件 a.cpp 中定义了一个全局函数 "void a(){}",而在文件 b.cpp 中需要调用这个函数。即使这样,文件 a.cpp 和文件 b.cpp 并不需要相互知道对方的存在,而是可以分别地对它们进行编译,编译成目标文件之后再链接,整个程序就可以运行了。
这是怎么实现的呢?从写程序的角度来讲,很简单。在文件 b.cpp 中,在调用 "void a()" 函数之前,先声明一下这个函数 "voida();",就可以了。这是因为编译器在编译 b.cpp 的时候会生成一个符号表(symbol table),像 "void a()" 这样的看不到定义的符号,就会被存放在这个表中。再进行链接的时候,编译器就会在别的目标文件中去寻找这个符号的定义。一旦找到了,程序也就可以顺利地生成了。
注意这里提到了两个概念,一个是"定义",一个是"声明"。简单地说,"定义"就是把一个符号完完整整地描述出来:它是变量还是函数,返回什么类型,需要什么参数等等。而"声明"则只是声明这个符号的存在,即告诉编译器,这个符号是在其他文件中定义的,我这里先用着,你链接的时候再到别的地方去找找看它到底是什么吧。定义的时候要按 C++ 语法完整地定义一个符号(变量或者函数),而声明的时候就只需要写出这个符号的原型了。需要注意的是,一个符号,在整个程序中可以被声明多次,但却要且仅要被定义一次。试想,如果一个符号出现了两种不同的定义,编译器该听谁的?
这种机制给 C++ 程序员们带来了很多好处,同时也引出了一种编写程序的方法。考虑一下,如果有一个很常用的函数 "void f() {}",在整个程序中的许多 .cpp 文件中都会被调用,那么,我们就只需要在一个文件中定义这个函数,而在其他的文件中声明这个函数就可以了。一个函数还好对付,声明起来也就一句话。但是,如果函数多了,比如是一大堆的数学函数,有好几百个,那怎么办?能保证每个程序员都可以完完全全地把所有函数的形式都准确地记下来并写出来吗?很显然,答案是不可能。但是有一个很简单地办法,可以帮助程序员们省去记住那么多函数原型的麻烦:我们可以把那几百个函数的声明语句全都先写好,放在一个文件里,等到程序员需要它们的时候,就把这些东西全部 copy 进他的源代码中。
这个方法固然可行,但还是太麻烦,而且还显得很笨拙。于是,头文件便可以发挥它的作用了。所谓的头文件,其实它的内容跟 .cpp 文件中的内容是一样的,都是 C++ 的源代码。但头文件不用被编译。我们把所有的函数声明全部放进一个头文件中,当某一个 .cpp 源文件需要它们时,它们就可以通过一个宏命令 "#include" 包含进这个 .cpp 文件中,从而把它们的内容合并到 .cpp 文件中去。当 .cpp 文件被编译时,这些被包含进去的 .h 文件的作用便发挥了。