目录
[1. 箭头运算符](#1. 箭头运算符)
[1.1 基本用法](#1.1 基本用法)
[1.2 箭头运算符的重载](#1.2 箭头运算符的重载)
[1.3 箭头运算符与偏移量](#1.3 箭头运算符与偏移量)
[2. std::vector 动态数组](#2. std::vector 动态数组)
[2.1 std::vector 的基本工作原理](#2.1 std::vector 的基本工作原理)
[2.2 优化std::vector在C++中的使用](#2.2 优化std::vector在C++中的使用)
[2.3 总结](#2.3 总结)
[3. 静态链接与动态链接](#3. 静态链接与动态链接)
[3.1 为什么 C++ 的库管理更棘手?](#3.1 为什么 C++ 的库管理更棘手?)
[3.2 什么是静态链接?](#3.2 什么是静态链接?)
[3.3 什么是动态链接?](#3.3 什么是动态链接?)
[3.4 静态链接 vs 动态链接](#3.4 静态链接 vs 动态链接)
[3.5 如何配置静态链接(以 GLFW 库为例)](#3.5 如何配置静态链接(以 GLFW 库为例))
[3.6 如何配置静态链接(以 GLFW 库为例)](#3.6 如何配置静态链接(以 GLFW 库为例))
1. 箭头运算符
1.1 基本用法
-
对于指针类型 ,
->运算符用于访问指针所指向对象的成员(变量或函数)。 -
它等价于先解引用再访问:
ptr->member等同于(*ptr).member。 -
主要作用:简化语法,避免显式写
*和括号。
1.2 箭头运算符的重载
为什么需要重载?
-
自定义类(如智能指针、迭代器)希望表现得像指针一样,让用户通过
->访问其管理的对象。 -
重载
->必须返回一个指针 或另一个重载了->的类对象(编译器会递归调用,直到得到原始指针)。
cpp
class SmartPointer {
private:
Entity* m_Ptr;
public:
SmartPointer(Entity* ptr) : m_Ptr(ptr) {}
Entity* operator->() const { // 返回原始指针
return m_Ptr;
}
};
SmartPointer sp(new Entity{5});
sp->Print(); // 调用 operator-> 返回 Entity*,然后调用Entity的成员方法Print()
支持链式重载:
cpp
class Proxy {
Entity* m_Ptr;
public:
Proxy(Entity* ptr) : m_Ptr(ptr) {}
Entity* operator->() { return m_Ptr; }
};
class DoubleProxy {
Proxy m_Proxy;
public:
DoubleProxy(Entity* ptr) : m_Proxy(ptr) {}
Proxy operator->() { return m_Proxy; }
};
DoubleProxy dp(new Entity{42});
dp->Print(); // 先调用 DoubleProxy::operator-> 返回 Proxy,
// 再调用 Proxy::operator-> 返回 Entity*,最后调用 Print()
1.3 箭头运算符与偏移量
利用空指针计算成员偏移量:
cpp
struct Vector2 {
float x, y;
};
int offset = (int)&(((Vector2*)nullptr)->y);
-
(Vector2*)nullptr创建一个指向地址0的Vector2*指针(空指针)。 -
->y试图访问该指针的y成员。虽然空指针解引用在运行时是未定义行为,但在编译时 ,编译器只需要计算出y相对于结构体起始地址的偏移量(offset),而不会实际访问内存。 -
&(((Vector2*)nullptr)->y)取y的地址。由于基地址是0,该地址的数值就等于y在结构体中的偏移字节数。 -
最后强制转换为
int得到偏移量。
2. std::vector 动态数组
2.1 std::vector 的基本工作原理
-
std::vector是一个动态数组,在堆上连续存储元素。 -
它维护三个指针(或大小/容量):
-
大小(size):当前实际元素个数。
-
容量(capacity):已分配内存能容纳的元素个数。
-
数据指针(data):指向堆内存的起始位置。
-
-
当
size == capacity时,再次添加元素会触发重新分配:-
分配一块新的更大的内存。
-
将旧元素拷贝(或移动)到新内存。
-
销毁旧内存上的元素并释放原内存。
-
2.2 优化std::vector在C++中的使用
拷贝开销来源
1.push_back 中的临时对象拷贝
cpp
vertices.push_back({1,2,3}); // {1,2,3} 是临时 Vertex,先构造,再拷贝到 vector 内部
-
步骤:构造临时
Vertex→ 拷贝构造到 vector 内部 → 销毁临时对象。 -
进行一次拷贝和一次销毁。
2.容量不足导致的重新分配与拷贝
cpp
vertices.push_back({2,3,4}); // 容量不足,触发重新分配
- 旧元素被逐一拷贝到新内存,然后逐一析构。当元素是复杂对象时,开销巨大。
优化策略
1.使用 reserve() 预分配容量
cpp
vertices.reserve(3); // 一次性分配至少能容纳 3 个元素的内存
-
避免多次重新分配和拷贝。
-
应在填充元素前调用,且只需分配一次足够大的容量。
2. 使用 emplace_back() 代替 push_back()
cpp
vertices.emplace_back(1,2,3); // 直接在 vector 内存中构造 Vertex,无需临时对象
-
emplace_back接受构造参数,在 vector 的尾部内存中直接构造对象,零拷贝。 -
相比
push_back({1,2,3}),省去了一次临时对象拷贝和销毁。
3. 避免不必要的传值拷贝(函数参数)
cpp
void Clear1(std::vector<Vertex> vertices) { vertices.clear(); } // 拷贝整个 vector
void Clear2(std::vector<Vertex>& vertices) { vertices.clear(); } // 引用,无拷贝
-
示例所示,
Clear1不会影响原始 vector,因为传参时发生了深拷贝。 -
只读操作传
const std::vector<T>&;修改操作传std::vector<T>&。
2.3 总结
| 场景 | 推荐做法 |
|---|---|
| 已知元素数量 | 使用 reserve() 预分配容量 |
| 插入元素 | 优先使用 emplace_back() 而非 push_back() |
| 函数传参 | 需要修改传引用,只读传 const& |
| 避免不必要的拷贝 | 遍历用 const auto&,避免按值传递容器 |
3. 静态链接与动态链接
3.1 为什么 C++ 的库管理更棘手?
C++ 没有像 Python、C#、Java 那样的包管理器,无法简单地通过一条命令(如 import)来引入库。不同平台、不同配置(Debug/Release)、不同架构(32位/64位)都需要匹配对应的库文件。
3.2 什么是静态链接?
静态链接 是在编译、链接阶段 完成所有符号解析和地址重定位的过程。链接器会将程序所依赖的静态库 (.lib 或 .a 文件)中的目标代码直接拷贝并合并 到最终的可执行文件(.exe 或 .out)中。
3.3 什么是动态链接?
动态链接 将链接过程推迟到程序运行时 。可执行文件中只记录所需动态库 (.dll / .so / .dylib)的名称及需要的符号信息,而不包含库的机器码。在程序加载或执行过程中,由动态链接器(dynamic linker / loader)负责找到、加载、链接这些库。
3.4 静态链接 vs 动态链接
| 特性 | 静态链接 | 动态链接 |
|---|---|---|
| 库文件位置 | 嵌入 .exe 内部 |
运行时作为独立 .dll(Windows)或 .so(Linux)文件存在 |
| 可执行文件大小 | 较大 | 较小 |
| 运行时依赖 | 无,独立运行 | 需要 .dll 文件位于合适位置 |
| 启动速度 | 更快(无需加载外部库) | 稍慢(运行时加载) |
| 更新库 | 需重新编译整个程序 | 只需替换 .dll 文件 |
| 编译器优化 | 可执行链接时优化(LTO),性能更佳 | 优化受限 |
| 调试体验 | 完整符号信息可用 | 相对复杂 |
3.5 如何配置静态链接(以 GLFW 库为例)
步骤 1:下载库文件
从 GLFW 官网下载预编译的二进制文件,需根据目标应用程序的架构选择 32 位或 64 位版本(而非根据当前操作系统)。解压后,库通常包含两个目录:
-
include/:头文件(声明函数接口) -
lib/:预编译的二进制库文件(.lib或.dll)
步骤 2:配置包含目录(Include Directories)
在 Visual Studio 项目属性中,找到 C/C++ → 常规 → 附加包含目录 ,添加 include 文件夹的路径。建议使用 $(SolutionDir) 宏 来表示解决方案目录,这样使用相对路径可以避免因项目移动而导致路径失效的问题。
配置完成后,就可以在代码中包含头文件了:
cpp
#include <glfw3.h>
步骤 3:配置库目录(Library Directories)
在项目属性中,找到 链接器 → 常规 → 附加库目录 ,添加 lib 文件夹的路径
步骤 4:指定要链接的库文件
在 链接器 → 输入 → 附加依赖项 中,添加库文件名(如 glfw3.lib)。需要注意,必须包含 .lib 后缀 ,否则链接器可能会尝试打开 .obj 文件导致找不到文件-。
3.6 如何配置静态链接(以 GLFW 库为例)
步骤 1:下载动态库版本
下载 GLFW 预编译包时,选择动态库版本(通常文件名含 dll 字样)。解压后得到:
-
include------ 头文件(与静态版相同) -
lib------ 导入库文件glfw3dll.lib(用于编译链接,很小,只负责引导) -
glfw3.dll------ 真正的动态库(运行时需要)
步骤 2:配置包含目录 (与静态相同)
C/C++ → 附加包含目录 指向 include 文件夹
步骤 3:配置库目录
链接器 → 附加库目录 指向存放 glfw3dll.lib 的文件夹。
步骤 4:指定导入库
链接器 → 附加依赖项 添加 glfw3dll.lib(注意文件名与静态库不同)。
步骤 5:部署 DLL 文件
生成 exe 后,必须将 glfw3.dll 放在 exe 同一目录 ,或放在系统能搜索到的路径(如 C:\Windows\System32,不推荐)。否则运行时会报"找不到 glfw3.dll"错误。