C++知识积累

说明:这里记录自己平时在学习的过程中积累的未知的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及以后)的核心,它主要是为了解决:手动管理内存导致的内存泄露和野指针。在现代开发中,我们几乎不再使用newdelete,而是全面使用智能指针

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;
  }
}

文末附加内容
暂无评论

发送评论 编辑评论


				
|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇
下一篇