说明:这里记录自己平时在学习的过程中积累的未知的C++知识。因为我之前学习C++是看的黑马的课程,所以这里记录的C++知识主要是除了黑马之外的C++知识。
std::bind
定义在< functional >头文件,主要用于将可调用对象(如函数、成员函数、函数对象)与其他参数进行绑定,成成一个新的可调用对象。
- 绑定普通函数(固定部分参数)如果有一个需要三个参数的函数,但是在调用的时候传递一个参数,可以使用std::bind固定前两个void printSum(int a, int b, int c) { std::cout << a + b + c; }
// 固定前两个参数为 10 和 20
auto addTenAndTwenty = std::bind(printSum, 10, 20, std::placeholders::_1);
// std::placeholders::_1:占位符,表示新生成的函数在被调用时,接收到的第一个参数应该填在这里
addTenAndTwenty(5); // 相当于执行 printSum(10, 20, 5) -> 输出 35 - 绑定成员函数(最常用的场景)在c++中,非静态成员函数隐含了一个this指针作为第一个参数,直接传递成员函数名是无法调用的,必须绑定到一个具体的对象实例上class Calculator {
public:
void add(int x) { std::cout << “Result: ” << x; }
};
Calculator cap;
// 必须绑定实例指针 cap,否则不知道调用哪个对象的 add
auto boundAdd = std::bind(&Calculator::add, &cap, std::placeholders::_1);
boundAdd(100); // 输出 Result: 100 - 在异步/多线程中经常见到它在多线程编程中,线程通常只接受一个固定的函数签名,例如void()但是,业务函数通常是某个类的成员函数(带有this指针),或者需要复杂的参数std::bind抹平了成员函数和普通函数直接的差异,它将void MyCloss::func(int)适配成了线程池需要的void()
- std::bind && lambda表达式auto f = std::bind(&MyClass::process, worker);auto f = [worker] () { worker->process(); }
std::placeholders::_1
占位符,表示新生成的函数在被调用时,接收到的第一个参数应该填在这里
lambda表达式
lambda表达式语法:[捕获列表] (参数列表) 核心修饰符 -> 返回类型 { 函数体 }
- [捕获列表]:[]:不捕获任何变量;[&]:按引用捕获所有局部变量;[=]:按值捕获所有局部变量(拷贝一份);[x, &y]:特定捕获,x按值捕获,&y按引用捕获
- (参数列表):与普通函数参数一致
- 修饰符:默认情况下,按值捕获的变量在Lambda内部是const的(只读),如果想修改这些拷贝的值,需要加上mutable关键字
- ->返回类型:通常可以省略,编译器会根据return语句自动推导
示例:
#include <vector>
#include <algorithm>
#include <iostream>
int main() {
std::vector<int> nums = {3, 1, 4, 1, 5, 9};
// 使用 Lambda 定义排序规则
std::sort(nums.begin(), nums.end(), [](int a, int b) {
return a > b; // 降序
});
for(int n : nums) std::cout << n << " "; // 输出: 9 5 4 3 1 1
}
int threshold = 5;
std::vector<int> nums = {1, 10, 2, 8};
// 捕获 threshold,统计大于该值的个数
int count = std::count_if(nums.begin(), nums.end(), [threshold](int n) {
return n > threshold;
});
// count 结果为 2
这里面设计了很多STL中的常见算法,要好好学习,道路很长
auto 关键字
变量声明
类型推导,让编译器根据初始化表达式的类型来自动确定变量的类型
auto i = 10; // 推导为 int
auto d = 3.14; // 推导为 double
// 注意:不能直接auto i; 必须要给定赋值
简单复杂类型
std::map<std::string, std::vector<int>> complexMap;
// 不使用 auto
std::map<std::string, std::vector<int>>::iterator it = complexMap.begin();
// 使用 auto
auto it = complexMap.begin();
explicit
主要用于修饰构造函数以及c++11后的类型转换运算符,它的核心作用是:禁止编译器进行隐式类型转换
如果不加explicit,单参数的构造函数会变成一个隐式转换工具
假设你有一个表示“圆”的类,构造函数接收半径:
class Circle {
public:
Circle(double radius) : r(radius) {} // 没有使用 explicit
double r;
};
void printCircle(Circle c) {
// 打印圆的相关信息
}
int main() {
// 正常调用
Circle c1(5.0);
// 隐式转换:发生了什么?
// 编译器看到 printCircle 需要一个 Circle 对象,但你给了一个 double。
// 它发现 Circle 有一个接收 double 的构造函数,于是自动创建了一个临时对象。
printCircle(10.0);
}
问题在于:有时候这种转换逻辑上很怪。比如一个 Buffer 类的构造函数接收 int size,如果你误写了 Buffer b = 10;,编译器能过,但代码意图模糊,极易引发 Bug。
当你加上 explicit,上述的“自动转换”就会被禁止。
class Circle {
public:
explicit Circle(double radius) : r(radius) {}
};
int main() {
// 这种写法依然有效(显式初始化)
Circle c1(5.0);
// 报错!编译器不再允许这种偷偷摸摸的操作
// Error: cannot convert 'double' to 'Circle' in argument passing
// printCircle(10.0);
// 如果非要这么做,必须显式调用
printCircle(Circle(10.0));
}
原则上:对于所有单参数的构造函数,除非你有明确的理由允许隐式转换,否则都应该默认加上explicit
< memory >库
C++的< memory >库是现代C++(C++11及以后)的核心,它主要是为了解决:手动管理内存导致的内存泄露和野指针。在现代开发中,我们几乎不再使用new和delete,而是全面使用智能指针。
1. 核心智能指针
智能指针利用了RAII(资源获取即初始化)机制:当智能指针对象超出作用域被销毁时,它会自动释放所管理的内存。
Unique Pointer(std::unique_ptr)
- 特性:独占所有权。同一时间只能有一个
unique_ptr指向该资源。 - 用法:不能被复制,只能被移动 (
std::move)。 - 场景:最常用的指针,开销几乎等同于原始指针。
Shared Pointer(std::shared_ptr)
- 特性:共享所有权。内部维护一个引用计数,当最后一个指针被销毁时,内存才会被释放。
- 场景:多个对象需要共享同一份资源时使用。
Weak Pointer(std::weak_ptr)
- 特性:不控制资源生命周期的“观察者”。它指向
shared_ptr管理的对象,但不会增加引用计数。 - 场景:用于解决
shared_ptr的循环引用问题。
2. 核心工具函数
除了指针,< memory >还提供了一些及其重要的辅助函数
std::make_unique / std::make_shared
- 保证异常安全(防止在构造函数抛出异常时发生泄漏),且 make_shared 在内存分配上更高效
std::addressof
- 获取对象的真实地址,即使对象重载了operator&,他也能拿到正确的地址
std::allocator
- 将内存分配与对象构造解耦,通常用STL容器(如std::vector)内部使用。
3. 为什么避开循环引用?
如果两个对象相互持有对方的std::shared_ptr,它们的引用计数永远不会降为0,导致内存永远无法释放,这就是std::weak_ptr的用武之地
4. 示例
#include <iostream>
#include <memory>
struct Task {
Task() { std::cout << "Task Created\n"; }
~Task() { std::cout << "Task Destroyed\n"; }
};
int main() {
// 1. 推荐的创建方式
auto up = std::make_unique<Task>();
{
auto sp1 = std::make_shared<Task>();
std::cout << "Count: " << sp1.use_count() << "\n"; // 1
{
auto sp2 = sp1;
std::cout << "Count: " << sp1.use_count() << "\n"; // 2
} // sp2 离开作用域
std::cout << "Count: " << sp1.use_count() << "\n"; // 1
} // sp1 离开作用域,Task 被自动销毁
}
构造函数 = default;
这里的 = default 被称为显示缺陷,是C++11引入的一项特性。
1. 与{}的区别
- 保持“琐碎性” (Triviality): 使用
= default可以让编译器认为这个类是“平凡的”(Trivial)。这意味着编译器可以对这个类进行底层的优化(例如内存拷贝可以使用memcpy而不是逐个调用构造函数)。如果你写了{},编译器会认为你手动定义了构造函数,该类就不再是 Trivial 的了。 - 性能更好: 编译器生成的默认构造函数通常比手动写的空构造函数更高效。
- 意图明确: 它向阅读代码的人明确传达了:“我需要一个默认构造函数,而且我不需要在这个函数里做任何额外的事情。”
2. 为什么要写出来?
情况A:被其他构造函数“顶掉”了
在C++中,如果你定义了任何一个带参数的构造函数,编译器就不再自动生成默认构造函数了。
class YoloPose {
public:
// 因为定义了有参构造,编译器罢工了,不再生成无参构造
YoloPose(int input_size) { ... }
// 强行把默认构造函数“请”回来
YoloPose() = default;
};
YoloPose p1; // 如果没有 = default,这行会报错
情况B:在头文件中声明,在源文件中定义
有时为了减少头文件的耦合(比如使用了 Pimpl 模式),你可能需要在 .h 里声明,在 .cpp 里写 = default。
try-catch
基本语法
try {
// 可能会抛出异常的代码
auto detector = std::make_unique<YOLO11_Pose>("model.engine");
detector->infer(image);
}
catch (const std::exception& e) {
// 捕获异常并处理
std::cerr << "错误发生: " << e.what() << std::endl;
}
catch (...) {
// 捕获所有类型的异常(万能捕获)
std::cerr << "发生了未知类型的异常" << std::endl;
}
enum关键字
enum用于定义枚举类型,它允许为一组整型常量赋予有意义的名称,提高代码的可读性和可维护性。
1. 传统枚举(C++98/03)
enum Color {
RED, // 默认值为 0
GREEN, // 默认值为 1
BLUE // 默认值为 2
};
enum Weekday {
MON = 1,
TUE = 2,
WED = 3,
THU = 4,
FRI = 5,
SAT = 6,
SUN = 7
};
int main() {
Color c = RED;
Weekday w = MON;
// 枚举值可以隐式转换为整数
int colorValue = GREEN; // colorValue = 1
int weekValue = FRI; // weekValue = 5
// 整数也可以赋值给枚举变量(需要显式转换)
// Color c2 = 1; // 错误
Color c2 = static_cast<Color>(1); // 正确
return 0;
}
传统枚举的特点:
- 枚举值默认从0开始递增
- 可以指定任意整数值
- 作用域不限定:枚举成员在包含枚举的作用域内可见(可能引起命名冲突)
- 可以隐式转换为整数
传统枚举的问题:
enum Color { RED, GREEN, BLUE };
enum TrafficLight { RED, YELLOW, GREEN }; // 编译错误:RED 和 GREEN 重复定义
2. 有作用域枚举(C++11引入)
C++11 引入了 enum class(也称为有作用域枚举):
enum class Color {
RED,
GREEN,
BLUE
};
enum class TrafficLight {
RED,
YELLOW,
GREEN
};
int main() {
// Color c = RED; // 错误:RED 不在作用域内
Color c = Color::RED; // 正确:需要使用作用域运算符
TrafficLight t = TrafficLight::RED; // 正确:可以同名,不会冲突
// int value = Color::GREEN; // 错误:不能隐式转换为整数
int value = static_cast<int>(Color::GREEN); // 正确:需要显式转换
// 可以指定底层类型
enum class Permission : unsigned int {
READ = 1,
WRITE = 2,
EXECUTE = 4
};
return 0;
}
有作用域枚举的特点:
- 使用
enum class声明 - 强作用域:必须通过
枚举名::成员名访问 - 无隐式转换:不能隐式转换为整数
- 可指定底层类型(默认是
int)
3. 指定底层类型(C++11)
C++11 允许为传统枚举和有作用域枚举指定底层类型:
// 指定底层类型为 unsigned char
enum class Status : unsigned char {
OK,
ERROR,
PENDING
};
// 传统枚举也可以指定底层类型
enum Flag : long long {
FLAG_A = 1LL << 30,
FLAG_B = 1LL << 31
};
// 获取枚举的底层类型
#include <type_traits>
std::underlying_type_t<Status> value = 42;
4. 枚举的前向声明(C++11)
C++11 支持枚举的前向声明,但需要指定底层类型:
// 前向声明
enum class Color : int; // 正确:指定了底层类型
enum class Day; // 正确:默认底层类型为 int
enum Weekday : short; // 正确:传统枚举也可以前向声明
// 使用前向声明的枚举
void printColor(Color c);
// 定义
enum class Color : int {
RED,
GREEN,
BLUE
};
5. 枚举的实际应用示例
// 1. 状态机
enum class State {
IDLE,
CONNECTING,
CONNECTED,
DISCONNECTED,
ERROR
};
class Connection {
State currentState = State::IDLE;
public:
void connect() {
if (currentState == State::IDLE) {
currentState = State::CONNECTING;
// 连接逻辑...
}
}
};
// 2. 标志位(位掩码)
enum class Permission {
NONE = 0,
READ = 1 << 0,
WRITE = 1 << 1,
EXECUTE = 1 << 2
};
// 重载位运算符
inline Permission operator|(Permission a, Permission b) {
return static_cast<Permission>(
static_cast<int>(a) | static_cast<int>(b)
);
}
inline Permission operator&(Permission a, Permission b) {
return static_cast<Permission>(
static_cast<int>(a) & static_cast<int>(b)
);
}
// 使用
Permission p = Permission::READ | Permission::WRITE;
bool canRead = (p & Permission::READ) != Permission::NONE;
// 3. 选项配置
enum class Color {
RED = 0xFF0000,
GREEN = 0x00FF00,
BLUE = 0x0000FF,
BLACK = 0x000000,
WHITE = 0xFFFFFF
};
std::string getColorName(Color color) {
switch (color) {
case Color::RED: return "红色";
case Color::GREEN: return "绿色";
case Color::BLUE: return "蓝色";
default: return "未知颜色";
}
}
6. C++17/20 的增强
// C++17:可以使用 auto 初始化枚举变量
enum class Color { Red, Green, Blue };
auto myColor = Color::Red; // 类型推导为 Color
// C++20:支持 using enum 引入枚举成员
enum class Direction { North, South, East, West };
void move(Direction dir) {
switch (dir) {
using enum Direction; // 引入枚举成员到当前作用域
case North: /* ... */ break;
case South: /* ... */ break;
case East: /* ... */ break;
case West: /* ... */ break;
}
}










