运行环境:Windows 10 22H2 x64 + Visual Studio 2022
vcpkg
vcpkg 是 Microsoft 的跨平台开源软件包管理器,极大地简化了 Windows、Linux 和 macOS 上第三方库的配置与安装。官网
配置教程
Step 1: 从 GitHub 克隆 vcpkg
git clone https://github.com/Microsoft/vcpkg.git
cd vcpkg
注意vcpkg目录的位置,确保文件路径长度合适以及可以存储较大数据。
Step 2: 运行 bootstrap
./bootstrap-vcpkg.bat
Step 3: 将包自动集成到 vs 的工程中
./vcpkg integrate install
之后缺哪个运行库只需在该文件夹打开 powershell 运行 vcpkg 安装指令即可。
常用命令
搜索包
./vcpkg search [packages]
安装包(输入上面搜出的包名)
./vcpkg install <pkg>
查看已安装包列表
./vcpkg list
删除包
./vcpkg remove <pkg>
帮助
./vcpkg help
卸载 vcpkg(并在之后删除此文件夹)
./vcpkg integrate remove
举例
.\vcpkg install eigen3:x64-windows
.\vcpkg install opencv4[contrib]:x64-windows
.\vcpkg install qt5-base:x64-windows
清理空间
在文档docs/about/faq.md
中提到:
## How can I remove temporary files?
You can save some disk space by completely removing the `packages\`, `buildtrees\`, and `downloads\` folders.
参考 @zhc 笔记,删除buildtrees\
可能导致静态库无法调试。
网络问题
使用代理服务器进行网络加速时,PowerShell 终端默认不走代理端口。为此可以在 PowerShell 中运行:
$env:HTTP_PROXY="localhost:7890"
$env:HTTPS_PROXY="localhost:7890"
上述代理服务器端口因设备而异,仅改变本次端口设置。
如果需要设置全局代理,可在环境变量中设置
http_proxy http://127.0.0.1:7890/
https_proxy http://127.0.0.1:7890/
CMake 时遇到网络问题:在 CMakeLists.txt 的前面添加如下代码
set(ENV{http_proxy} "http://127.0.0.1:7890")
set(ENV{https_proxy} "http://127.0.0.1:7890")
项目配置
CMake
方法一:使用 CMake-GUI
选择源代码位置、build 目录,只需 Configure、Generate、Open Project 三步。
若出错则需要对应报错信息找错。大部分错误是找不到依赖库。
方法二:使用 VS 内置
新建一个空的 VS 项目,文件-打开-CMake,选中 CMakeLists.txt
打开CMake设置-高级-CMake生成器-改为VS2022 Win64
注意点
- 请勿新建一个项目,然后将代码文件(.cpp .h等)加入项目,折磨且成功率低
- 配置前确认项目可以在win环境生成,如某些依赖库只支持 Linux 系统,需要使用 WSL 生成
字体
YaHei Consolas Hybrid 1.12
Fira Code
命令行参数
项目-属性-配置属性-调试-命令行参数
包含目录出错
无法打开源文件:fstream
包含目录只要有一个错的,后面的包含目录都会出错。
删去不正确的包含目录即可。
Eigen 库使用
svd 分解
Eigen::JacobiSVD<Eigen::Matrix3d> svd(J, Eigen::ComputeFullU| Eigen::ComputeFullV); Eigen::Matrix3d U = svd.matrixU();
Eigen::Matrix3d S = svd.singularValues();
Eigen::Matrix3d V = svd.matrixV(); //J = U * S * V.T
Learn C++
学习链接:The Cherno的视频系列
C++编程基础
打开debug模式的代码优化:解决方案属性 - C/C++ - 优化 - 最大优化 、 代码生成 - 基本运行时检查 - 默认
输出文件 - 汇编程序输出 - 仅有程序集的列表 (/FA):可在 *.asm 文件查看输出的汇编程序。用这种方法可以查看常量是怎样被优化的。(比如:return 5 * 2
被优化成 mov eax, 10
)
多行同时输入:alt+鼠标选中位置 或者 alt+shift+方向键
选中当前单词:鼠标双击 或者 ctrl+W
调试 - 窗口 - 内存:查看当前内存的内容。把想查看的变量拖到地址区域。(string 能看到一些有趣的内容)
VS 的输出目录:$(SolutionDir)bin\$(Platform)\$(Configuration)\
VS 多项目 library 使用:解决方案-添加-新建项目(编写lib) 添加附加包含目录 当前项目-添加-引用-选中 library 项目
高级调试:右键添加条件或操作,并在输出中查看调试信息。
预编译头:.cpp 属性 - C/C++ - 预编译头 - 创建; 项目属性 - 预编译头 - 使用; 预编译头文件 - .h
VS 编译时间查看:选项 - 项目和解决方案 - 生成计时
C++进阶内容
类中定义的静态变量:就算创建了多个类元素也共享一个存储空间。
C++ 防止创建类的实例:将构造函数写为 private。如 class Log { private:Log(); };
或将构造函数写为 Log() = delete;
C++ 初始化列表:采用如下写法 Player(const std::string& name) : m_Name(name){}
。这种方法可以避免重复初始化,以及const常量只能这样初始化。
虚函数写法:需要继承的类函数添加 virtual 前缀。纯虚函数:参考视频28
protected : 当前类和派生类可访问到的变量
new 关键词分配的内存:生存周期直到它被 delete 或者程序终止。
static constexpr int size = 5;
int example[size]; // 如果 size 是 const int 会报错
std::string 文档:链接
一些特殊的字符串定义方式:
const wchar_t* chs = L"dhui";
const char16_t* chs2 = u"dhui";
const char32_t* chs3 = U"dhui";
using namespace std::string_literals; //C++14
std::string name0 = "Cherno"s + " hello"; //这种写法允许字符串拼接
const char* name2 = R"(dhui
dhui2)"; //这种写法允许回车
const int 不可以改变指针指向的内存数据。int const 不可以改变指针让它指向其他东西。
mutable 变量:在 const 函数中也可以被更改
explicit 关键字:写在类的构造函数前,不允许隐式类型转换
std::cout << 运算符重载:
std::ostream& operator<<(std::ostream& stream, const Entity& other) {
stream << other.GetName() << ", " << other.GetAge();
}
智能指针(unique pointer) 与 共享指针(shared pointer) 与 弱指针(weak pointer)
std::unique_ptr<Entity> entity(new Entity());
std::shared_ptr<Entity> shardEntity = std::make_shared<Entity>();
std::weak_ptr<Entity> e0 = shardEntity;
获取类中的变量的offset:(int)&((Vector3*)0)->x;
Templete 写法:template<typename T>
C++ 宏:可让代码在不同配置下执行不同的函数,如只让 Debug 模式输出调试信息。 反斜杠 \
用于换行。
为类型定义新的名称:
using Devicemap = [type];
typedef [type] Devicemap
auto 关键字:传引用 auto&
函数指针:一种 confusing 的用法
typedef void(*HelloworldFunction)(int);
HelloworldFunction function = Helloworld;
STL模板库
迭代 vector 元素的方法:for (Vertex& v : vertices);
最好传引用
删除一个 vector 元素:vertices.erase(vertices.begin() + 1);
为 vector 预分配空间 vertices.reserve(3);
(有别于resize,之后可以push_back)
使用 vertices.emplace_back(1, 2, 3);
避免类重新被 copy constructor 构建
array: std::array<int,5> data;
可以调用 data.size()
函数。在栈上开辟内存,而 vector 在堆上。
Lambda 函数:参考。传入函数可能需要 std::function 。部分代码如下
#include <functional>
void print(int value, const std::function<void(int)>& func) { func(value); }
int a = 5;
auto lambda = [&](int value) { std::cout << "Value:" << value << "\t a=" << a << std::endl; };
print(9, lambda);
std::find_if 的一个应用示例:
std::vector<int> values = { 1, 5, 4, 2, 3 };
auto it = std::find_if(values.begin(), values.end(), [](int value) {return value > 3; });
std::map 与 std::unordered_map:建立两者的关系,类似数组。第一个必须是 hashable 的,如指针,否则要写个 hash 函数。
遍历 std::map :若要有序输出,对于第一个变量要有 operator< 函数重载。视频演示
for (auto& [name, city] : cityMap){} // C++17
for (auto& kv : cityMap){ kv.first; kv.second; }
std::thread :分出一个线程干别的事情,并继续后面的代码。
std::thread worker(DoWork);
// Do something
worker.join();
thread_local 关键字:让每个线程中创建一个实例,互不干扰。
计时函数 Chrono
auto start = std::chrono::high_resolution_clock::now();
// Do something
auto end = std::chrono::high_resolution_clock::now();
std::chrono::duration<float> duration = end - start;
std::cout << "Time = " << duration.count() << std::endl;
或者直接构建结构体,用于计时。在需计时的函数中创建变量 Timer timer;
注意 timer 的生存周期。
struct Timer {
std::chrono::time_point<std::chrono::steady_clock> start, end;
std::chrono::duration<float> duration;
Timer() {
start = std::chrono::high_resolution_clock::now();
}
~Timer() {
end = std::chrono::high_resolution_clock::now();
duration = end - start;
std::cout << "Timer took = " << duration.count() * 1000.0f << "ms" << std::endl;
}
};
std::sort 时间复杂度:$O(NlogN)$
static_cast / dynamic_cast :后者会更加安全,涉及类的继承关系时可使用后者。
多返回值: std::tuple / std::pair (后者仅允许双返回值)
std::tuple<std::string, int> person = { "Cherno", 24 }; //初始化
std::string& name = std::get<0>(person); //取元素
std::tie(name, std::ignore) = person; //解包
std::optional (C++17)
std::optional<int> count = {};
if (count.has_value()) {}; // 判断 count 有没有值
int c = count.value_or(100);
std::variant (C++17)
std::variant<std::string, int> data;
data = "Cherno";
data.index(); // 判断data的类型
std::any (C++17)
std::async 写法较复杂,但可以调试查看每个线程正在执行的任务(调试->窗口->并行堆栈) 视频参考
std::string_view 用于读取string数据,而不用初始化string
benchmarking: 生成 json 文件导入到 chrome://tracing
中, 代码
完结于 2024.6.5,后续教程 (SINGLETON开始) 基本是 C++20 或是更加进阶的 C++ 技巧,需要用到的时候再针对性学习。