运行环境: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

注意点

  1. 请勿新建一个项目,然后将代码文件(.cpp .h等)加入项目,折磨且成功率低
  2. 配置前确认项目可以在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++ 技巧,需要用到的时候再针对性学习。