c++项目

NVIDIA Isaac平台体验:Isaac Sim与Isaac ROS的快速了解 NVIDIA Isaac平台是一个专为机器人和人工智能(AI)打造的加速平台,旨在为开发者提供一个全面的工具集,以加速机器人相关应用的开发和部署。材质定义语言(MDL)是一种用于描述材质属性和行为的编程语言,能够精确地定义材质的光学特性和物理属性。NVIDIA Isaac Sim 通过支持 MDL,进一步提升了仿真环境中材质的真实感和细节表现。精确的材质定义:MDL 允许开发者精确地定义材质的光学特性和物理属性,如反射率、折射率、粗糙度和透明度等。这使得仿真环境中的物体看起来更加真实和自然。动态材质。 阅读详情
## typedef 
typedef T value_type;语法的结构是typedef 原类型 新别名;定义后,value_type 和 T 就是同一个类型,可以互换使用
## 模板
C++ 模板的声明和定义分离有特殊性,和普通函数/类不一样
1. 普通函数可以分离

```cpp
// 头文件 add.h
int add(int a, int b);  // 声明

// 源文件 add.cpp
int add(int a, int b) {  // 定义
    return a + b;
}

// main.cpp 可以正常使用
#include "add.h"
int main() {
    add(1, 2);  // 链接时找到定义
}
  1. 模板不能这样分离
// 头文件 add.h
template <typename T>
T add(T a, T b);  // 声明

// 源文件 add.cpp
template <typename T>
T add(T a, T b) {  // 定义
    return a + b;
}

// main.cpp - 链接错误!
#include "add.h"
int main() {
    add(1, 2);  // 错误:找不到定义
}

原因:编译器看到 add(1, 2) 时需要生成 add(int, int) 的代码,但定义在 .cpp 文件中看不到
解决方案一:全部放在头文件(最常用)

// add.h - 声明和定义都在头文件
template <typename T>
T add(T a, T b);  // 声明

template <typename T>
T add(T a, T b) {  // 定义
    return a + b;
}

// main.cpp - 正常使用
#include "add.h"
int main() {
    add(1, 2);  // 正确
}

解决方案二:显式实例化(放 .cpp)

// add.h
template <typename T>
T add(T a, T b);  // 只声明

// add.cpp
template <typename T>
T add(T a, T b) {  // 定义
    return a + b;
}

// 显式实例化 - 告诉编译器要生成哪些版本
template int add<int>(int, int);
template double add<double>(double, double);

// main.cpp
#include "add.h"
int main() {
    add(1, 2);      // 可以用,因为 int 版本已生成
    add(1.5, 2.5);  // 可以用,因为 double 版本已生成
    // add("a", "b"); // 错误!没有生成 string 版本
}
// 类型模板参数
template <typename T>
class Box {
    T data;
};

// 非类型模板参数
template <int Size>
class Array {
    int data[Size];
};

// bool 模板参数
template <bool flag>
class Config {
    void print() {
        if (flag) cout << "true" << endl;
        else cout << "false" << endl;
    }
};

// 使用
Box<int> box;        // T = int
Array<10> arr;       // Size = 10
Config<true> c1;     // flag = true
Config<false> c2;    // flag = false

类的声明和定义分离

// Box.h
template <typename T>
class Box {
public:
    Box(T val);
    T get();
};

// Box.cpp
template <typename T>
Box<T>::Box(T val) : data(val) {}  // 定义

template <typename T>
T Box<T>::get() { return data; }   // 定义

// main.cpp - 链接错误!

正确做法一:全放头文件

// Box.h
template <typename T>
class Box {
    T data;
public:
    Box(T val) : data(val) {}  // 直接定义
    T get() { return data; }   // 直接定义
};

正确做法二:头文件包含实现文件

// Box.h
template <typename T>
class Box {
    T data;
public:
    Box(T val);
    T get();
};

#include "Box.tpp"  // 包含实现文件

// Box.tpp(实现文件)
template <typename T>
Box<T>::Box(T val) : data(val) {}

template <typename T>
T Box<T>::get() { return data; }

正确做法三:显式实例化

// Box.h
template <typename T>
class Box {
    T data;
public:
    Box(T val);
    T get();
};

// Box.cpp
template <typename T>
Box<T>::Box(T val) : data(val) {}

template <typename T>
T Box<T>::get() { return data; }

// 显式实例化
template class Box<int>;
template class Box<double>;

// main.cpp
#include "Box.h"
int main() {
    Box<int> b1(10);      // 可以用
    Box<double> b2(3.14); // 可以用
    // Box<string> b3("x"); // 错误!没有显式实例化
}

[]重载

template <typename T>
class MyVector {
    T* data_;
    
public:
    T& operator[](size_t i) {
        return data_[i];  // 返回引用,不是副本
    }
};

int main() {
    MyVector<int> vec;
    // 假设 data_ 指向 [1, 2, 3, ...]
    
    // 这行代码发生了什么:
    vec[0] = 10;
    
    // 分解步骤:
    // 1. vec[0] 调用 operator[](0)
    // 2. operator[](0) 返回 data_[0] 的引用(不是副本)
    // 3. 给这个引用赋值 = 10
    // 4. 相当于直接修改 data_[0] = 10
}

常量

  1. 常量指针(const pointer)
    指针本身是常量,不能改变指向,但可以修改指向的值
int x = 10;
int y = 20;

int* const p = &x;  // 常量指针:p 本身是 const

*p = 30;    // 正确!可以修改指向的值(x 变成 30)
// p = &y;  // 错误!不能改变指针的指向

// 记忆:const 在 * 后面,修饰 p(指针本身)
  1. 指针常量(pointer to const)
    指针指向的值是常量,不能通过指针修改值,但可以改变指向
int x = 10;
int y = 20;

const int* p = &x;  // 指针常量:指向的值是 const

// *p = 30;  // 错误!不能通过 p 修改 x
p = &y;      // 正确!可以改变指针的指向

// 记忆:const 在 * 前面,修饰 *p(指向的值)
  1. 引用(没有"引用常量")
    引用本身就不能改变指向,所以:
int x = 10;
int y = 20;

int& ref = x;  // 普通引用
ref = y;       // 这是给 x 赋值,不是改变 ref 的指向!

// 引用一旦绑定,就不能重新绑定
// 所以没有"引用常量"这种说法
  1. 常量引用(reference to const)
    通过引用不能修改值
int x = 10;

const int& ref = x;  // 常量引用

// ref = 30;  // 错误!不能通过 ref 修改 x
int y = ref;  // 正确!可以读取

// 没有 int& const ref = x; 这种写法(引用本身就不能改)

记忆技巧

// 从右往左读
int* const p;        // p is a const pointer to int
const int* p;        // p is a pointer to const int
const int* const p;  // p is a const pointer to const int
const int& ref;      // ref is a reference to const int

class 里面定义 struct

class 里面可以定义 struct,默认是 public 的。也可以定义 class(默认 private)

typedef

typedef typename CHxAllocator::template rebind::other NodeAlloc;
原类型:CHxAllocator::template rebind::other(很长的一串)
新类型(别名):NodeAlloc
意思:给 CHxAllocator::template rebind::other 这个类型起个简单的名字叫 NodeAlloc。
CHxAllocator::template rebind::other
CHxAllocator - 一个分配器类型
::rebind - 访问里面的 rebind 模板
::other - 访问 rebind 里的 other 类型

typedef value_type const* const_pointer;
// 等价于:
typedef const T* const_pointer;
是常量指针(pointer to const),指向的值不能改变,但指针本身可以改变。

// const 在 * 前面:指向的值是常量(常量指针/pointer to const)
typedef const T* const_pointer;
// 不能改值,能改指向

// const 在 * 后面:指针本身是常量(指针常量/const pointer)
typedef T* const pointer_const;
// 能改值,不能改指向

// 两个 const:都是常量
typedef const T* const const_pointer_const;
// 不能改值,不能改指向

::

:: 是作用域解析运算符,用于访问类、命名空间、结构体等作用域里的成员。

namespace MyNamespace {
    int x = 10;
}

class MyClass {
public:
    typedef int value_type;  // 类型成员
    static int count;        // 静态成员
    
    struct Inner {
        int y;
    };
};

int main() {
    // 命名空间
    MyNamespace::x = 20;
    
    // 类的类型成员
    MyClass::value_type val = 10;
    
    // 类的静态成员
    MyClass::count = 5;
    
    // 类里的结构体
    MyClass::Inner inner;
    inner.y = 30;
}

throw std::bad_alloc(0);

throw 异常对象;  // 抛出异常

// std::bad_alloc 是标准库的异常类
// std::bad_alloc(0) 是创建一个 bad_alloc 对象
// throw 把这个异常抛出去

bad_alloc

bad_alloc 是标准库 头文件中的异常类。

#include <new>  // bad_alloc 定义在这里

// 标准库中定义
namespace std {
    class bad_alloc : public exception {
        // ...
    };
}
继承关系
std::exception          // 所有标准异常的基类
    ↑
std::bad_alloc         // 内存分配失败异常
    ↑
std::bad_array_new_length  // 数组长度错误(C++11)

#include <new>
#include <iostream>
using namespace std;

int main() {
    try {
        // 尝试分配巨大的内存
        int* p = new int[1000000000];  // 可能失败
        
        // 或者用 malloc
        void* p2 = malloc(SIZE_MAX);  // 分配失败返回 NULL
        
        if (p2 == NULL) {
            throw bad_alloc();  // 手动抛出
        }
        
    } catch (const bad_alloc& e) {
        cout << "内存分配失败!" << endl;
        cout << e.what() << endl;
    }
    
    return 0;
}

标准异常类层次

#include <exception>   // std::exception(基类)
#include <new>         // std::bad_alloc
#include <stdexcept>   // std::runtime_error, std::logic_error 等

// 常见标准异常:
std::exception          // 基类
├── std::bad_alloc      // 内存分配失败(<new>)
├── std::bad_cast       // 类型转换失败(<typeinfo>)
├── std::logic_error    // 逻辑错误(<stdexcept>)
│   ├── std::invalid_argument
│   ├── std::out_of_range
│   └── std::length_error
└── std::runtime_error  // 运行时错误(<stdexcept>)
    ├── std::overflow_error
    ├── std::underflow_error
    └── std::range_error
// 现代 C++ 推荐写法
throw std::bad_alloc();

// 旧代码可能的写法(你的代码中)
throw std::bad_alloc(0);

// 两者效果一样,但后者是老习惯

依赖类型

依赖于模板参数的类型。简单说,就是类型里面包含了模板参数 T

template <typename T>
void func() {
    T x;  // T 是依赖类型(依赖模板参数 T)
    
    int y;  // int 不是依赖类型(不依赖模板参数)
}
  1. 直接使用模板参数
template <typename T>
class Box {
    T data;        // T 是依赖类型
    T* ptr;        // T* 是依赖类型
    T& ref;        // T& 是依赖类型
};
  1. 使用模板参数内部的类型
template <typename T>
class MyClass {
    // T::value_type 是依赖类型
    // 因为它依赖 T,而且 T 里面有 value_type
    T::value_type x;  // 这是类型还是变量?编译器不确定!
};

为什么需要 typename

template <typename T>
void func() {
    T::value_type * p;  // 这行有歧义!
}
编译器看到 T::value_type * p 时,有两种理解:
理解1:value_type 是类型
// 如果 T::value_type 是类型
// 那么这是声明一个指针
T::value_type * p;  // 声明 p 是指向 value_type 的指针
理解2:value_type 是变量
// 如果 T::value_type 是静态变量
// 那么这是乘法运算
T::value_type * p;  // T::value_type 乘以 p

用 typename 解决歧义
template <typename T>
void func() {
    typename T::value_type * p;  // 明确告诉编译器:value_type 是类型
}

常见依赖类型场景

  1. STL 容器中的迭代器
template <typename Container>
void print(const Container& c) {
    // Container::iterator 是依赖类型
    typename Container::iterator it;  // 必须用 typename
    
    for (it = c.begin(); it != c.end(); ++it) {
        cout << *it << endl;
    }
}
  1. 分配器中的类型
template <typename Alloc>
class MyVector {
    // Alloc::value_type 是依赖类型
    typename Alloc::value_type* data;  // 必须用 typename
};

std::vector 的 push_back 实现

三个关键指针

template <class T>
class Vector {
    T* first;        // 指向第一个元素
    T* first_free;   // 指向最后一个元素的下一个位置(空闲位置)
    T* end;          // 指向内存的末尾
};

内存布局

[已使用元素][空闲空间]
^           ^           ^
first       first_free  end
功能:在末尾添加一个元素

template <class T>
void Vector<T>::push_back(const T& t)
{
    // 检查是否还有空间
    if (first_free == end)
        reallocate();  // 没空间了,重新分配更大的内存
    
    // 在末尾构造新元素
    alloc.construct(first_free, t);
    
    // 更新末尾指针
    ++first_free;
}

new

  1. 普通 new(最常用)
// 分配内存 + 调用构造函数
Type* p = new Type;           // 调用默认构造函数
Type* p = new Type(value);    // 调用带参数的构造函数
Type* p = new Type();         // 调用默认构造函数(带括号)
  1. 数组 new
// 分配数组
int* arr = new int[10];              // 10个 int,未初始化
int* arr2 = new int[10]();           // 10个 int,初始化为 0
int* arr3 = new int[10]{1,2,3};      // 前3个初始化,其余为0

// 释放数组
delete[] arr;
  1. placement new(在指定内存上构造)
// 在已有内存上构造对象,不分配新内存
new (指针) Type(参数);
#include <new>  // placement new 需要这个头文件

// 1. 先分配内存(不构造)
void* memory = malloc(sizeof(int));

// 2. 在内存上构造对象
int* p = new (memory) int(10);  // 在 memory 处创建 int,值为10

// 3. 使用
cout << *p << endl;  // 输出 10

// 4. 析构(不释放内存)
p->~int();

// 5. 释放内存
free(memory);
  1. nothrow new(不抛异常)
// 普通 new:失败时抛出 bad_alloc 异常
int* p1 = new int[1000000000];  // 可能抛出异常

// nothrow new:失败时返回 nullptr
int* p2 = new (std::nothrow) int[1000000000];  // 失败返回 nullptr

if (p2 == nullptr) {
    cout << "分配失败" << endl;
}

new 的完整过程

MyClass* p = new MyClass(10);

// 等价于:
// 1. 分配内存
void* mem = operator new(sizeof(MyClass));

// 2. 在内存上构造对象
MyClass* p = new (mem) MyClass(10);

operator new

它是一个函数,不是关键字。operator new 就是 C++ 版的 malloc,只分配内存,不构造对象。

// operator new 的声明(简化版)
void* operator new(size_t size);
void* mem = operator new(sizeof(int));  // 分配4字节

和 new 关键字的区别

// new 关键字:分配内存 + 构造对象
MyClass* p = new MyClass(10);
// 做了两件事:
// 1. 调用 operator new 分配内存
// 2. 调用构造函数

// operator new:只分配内存
void* mem = operator new(sizeof(MyClass));
// 只做一件事:
// 1. 分配内存

operator new 的几种形式

  1. 基本形式
void* operator new(size_t size);
// 分配 size 字节,返回 void*
  1. nothrow 形式
void* operator new(size_t size, const std::nothrow_t&);
// 失败时返回 nullptr,不抛异常
  1. placement new 形式
void* operator new(size_t size, void* ptr);
// 不分配内存,直接返回 ptr
// 这是 placement new 的内部实现

// 基本形式
new (place_address) type;
// 在 place_address 处构造 type 对象

// 带初始化
new (place_address) type (initializer-list);
// 在 place_address 处构造 type 对象,用 initializer-list 初始化
// 示例
void* mem = malloc(sizeof(string));
string* s1 = new (mem) string;           // 空字符串
string* s2 = new (mem) string("hello");  // "hello"
string* s3 = new (mem) string(5, 'a');   // "aaaaa"

应用

// 在 vector 中
template <typename T>
class Vector {
    T* first_free;
    allocator<T> alloc;
    
public:
    void push_back(const T& t) {
        // 使用 placement new 在 first_free 处构造对象
        alloc.construct(first_free, t);
        // 等价于:new (first_free) T(t);
    }
};
// operator new(size)
void* mem = operator new(sizeof(MyClass));
// 内存:[未初始化]
//       ^
//       mem

// placement new
MyClass* p = new (mem) MyClass(10);
// 内存:[MyClass对象]
//       ^
//       p(和 mem 同一地址)
first_free指向容器中第一个空闲的块,如果已经没有空闲块了,则通过reallocate函数重新分配。alloc是Alloctor<T>类的一个对象,调用其construct方法可在一个指定的区域构建对象,调用的是类型T的拷贝构造函数。在构造完成之后,让first_free指向下一个空闲块。

std::ptrdiff_t

std::ptrdiff_t 是 C++ 标准库中用来表示两个指针差值的类型。

#include <cstddef>  // ptrdiff_t 定义在这里

// ptrdiff_t 是一个有符号整数类型
// 通常是 long 或 long long
// 在不同平台上可能不同
// 1. 有符号(可以是负数)
// 2. 大小足够容纳两个指针的差值
// 3. 通常是 long 类型
#include <iostream>
#include <cstddef>
using namespace std;

int main() {
    int arr[] = {10, 20, 30, 40, 50};
    
    int* p1 = &arr[0];  // 指向 10
    int* p2 = &arr[4];  // 指向 50
    
    ptrdiff_t diff = p2 - p1;  // diff = 4
    cout << "差值: " << diff << endl;  // 输出 4
    
    // 负数
    ptrdiff_t diff2 = p1 - p2;  // diff2 = -4
    cout << "反向差值: " << diff2 << endl;  // 输出 -4
    
    return 0;
}

allocate

allocate 成员函数的作用:

申请 newcapacity 个元素的空间

每个元素大小是 sizeof(T)

总共申请 newcapacity * sizeof(T) 字节的连续空间

template <typename T>
class Allocator {
public:
    // 分配 n 个 T 类型元素的空间
    T* allocate(size_t n) {
        // 申请 n * sizeof(T) 字节的连续内存
        return static_cast<T*>(operator new[](n * sizeof(T)));
    }
    
    // 释放内存
    void deallocate(T* p, size_t n) {
        operator delete[](p);
    }
};
static_cast<目标类型>(表达式);

allocate(n) 就是申请一块能放下 n 个 T 类型元素的连续内存,并返回指向开头的指针。

::

:: 是 C++ 的作用域解析运算符,用来访问不同作用域里的东西
基本语法 作用域::成员

  1. 全局作用域
int x = 10;  // 全局变量

int main() {
    int x = 20;  // 局部变量
    
    cout << x << endl;    // 输出 20(局部变量)
    cout << ::x << endl;  // 输出 10(全局变量)
    //     ^^
    //     访问全局的 x
}
void func() {
    cout << "全局函数" << endl;
}

class MyClass {
public:
    void func() {
        cout << "成员函数" << endl;
    }
    
    void call() {
        func();      // 调用成员函数
        ::func();    // 调用全局函数
        //^^
        // 明确指定全局作用域
    }
};
  1. 命名空间作用域
#include <iostream>

// 命名空间
namespace MyNamespace {
    int x = 10;
    void func() {
        cout << "MyNamespace::func" << endl;
    }
}

int main() {
    // 访问命名空间里的东西
    cout << MyNamespace::x << endl;  // 输出 10
    MyNamespace::func();             // 调用函数
    
    return 0;
}
std 命名空间
// 标准库都在 std 命名空间
std::cout << "hello" << endl;
std::string str = "world";
std::vector<int> vec;
  1. 类的静态成员
class MyClass {
public:
    static int count;  // 静态成员变量
    static void func() {  // 静态成员函数
        cout << "静态函数" << endl;
    }
};

// 定义静态成员变量
int MyClass::count = 0;

int main() {
    // 访问静态成员
    MyClass::count = 10;
    MyClass::func();
    
    return 0;
}
  1. 类内类型
class MyClass {
public:
    typedef int value_type;  // 类型别名
    struct Inner {           // 内部结构体
        int x;
    };
    enum Color { RED, GREEN, BLUE };  // 枚举
};

int main() {
    // 访问类内的类型
    MyClass::value_type x = 10;
    MyClass::Inner inner;
    MyClass::Color color = MyClass::RED;
    
    return 0;
}
  1. 类外定义成员函数
class MyClass {
public:
    void func();  // 声明
    static void staticFunc();  // 静态函数声明
};

// 在类外定义成员函数
void MyClass::func() {
    // 用 :: 表示这是 MyClass 的 func
    cout << "成员函数定义" << endl;
}

void MyClass::staticFunc() {
    cout << "静态函数定义" << endl;
}
  1. 多重作用域
namespace Outer {
    namespace Inner {
        int x = 10;
        
        class MyClass {
        public:
            static int y;
        };
    }
}

int main() {
    // 多层嵌套
    cout << Outer::Inner::x << endl;  // 访问嵌套命名空间
    cout << Outer::Inner::MyClass::y << endl;  // 访问类静态成员
    
    return 0;
}

namespace

namespace(命名空间)
作用:组织代码,避免名字冲突。

// 命名空间:只是给代码分组
namespace MyNamespace {
    int x = 10;
    void func() { }
    class MyClass { };
}

// 使用
MyNamespace::x;
MyNamespace::func();
MyNamespace::MyClass obj;

浅拷贝 vs 深拷贝

// 浅拷贝(默认):只拷贝指针
class ShallowCopy {
public:
    int* data;
    
    ShallowCopy(int v) : data(new int(v)) {}
    
    // 默认拷贝构造(浅拷贝)
    ShallowCopy(const ShallowCopy& other) : data(other.data) {}
    // 两个对象指向同一块内存!
};

// 深拷贝:拷贝指针指向的内容
class DeepCopy {
public:
    int* data;
    
    DeepCopy(int v) : data(new int(v)) {}
    
    // 深拷贝构造
    DeepCopy(const DeepCopy& other) : data(new int(*other.data)) {}
    // 创建新内存,拷贝值
    // 两个对象指向不同的内存!
};

const MyClass& other 中的 & 是引用(不是取地址)。

copy

普通 copy 作用:把元素拷贝到已初始化的内存。

// 内部实现(简化版)
template <class InputIterator, class OutputIterator>
OutputIterator copy(InputIterator first, InputIterator last, 
                    OutputIterator result) {
    while (first != last) {
        *result = *first;  // 使用赋值运算符
        ++first;
        ++result;
    }
    return result;
}

uninitialized_copy 作用:把元素拷贝到未初始化的内存。

template <class InputIterator, class ForwardIterator>
ForwardIterator uninitialized_copy(InputIterator first, InputIterator last,
                                   ForwardIterator result) {
    ForwardIterator current = result;  // 保存当前位置
    
    try {
        while (first != last) {  // 遍历源范围
            // placement new:在 current 处构造对象
            ::new (static_cast<void*>(&*current)) 
                typename iterator_traits<ForwardIterator>::value_type(*first);
            // 解释:
            // 1. &*current:获取 current 指向的地址
            // 2. static_cast<void*>(...):转成 void*
            // 3. ::new (void*):placement new,在指定地址构造
            // 4. value_type(*first):构造 value_type 类型,用 *first 初始化
            
            ++first;    // 移动源迭代器
            ++current;  // 移动目标迭代器
        }
        return current;  // 返回构造后的末尾位置
    } catch (...) {
        // 如果构造失败,销毁已构造的对象
        while (result != current) {  // 从开始到当前位置
            result->~value_type();   // 析构对象
            ++result;                // 移动到下一个
        }
        throw;  // 重新抛出异常
    }
}
1. &*current

// current 是迭代器
// *current:获取迭代器指向的对象
// &*current:获取对象的地址

// 等价于:
// 如果 current 是指针:&*current = current
// 如果 current 是迭代器:获取指向的地址

2. static_cast<void*>(&*current)

// 把对象地址转成 void*
// 因为 placement new 需要 void* 参数

void* address = static_cast<void*>(&*current);
3. ::new (address) Type(value)
4. // placement new 语法
// 在 address 处构造 Type 对象,值为 value

::new (address) Type(value);
4. typename iterator_traits<ForwardIterator>::value_type
// 获取 ForwardIterator 指向的元素类型
// 用 typename 明确说是类型

// 例如:
// 如果 ForwardIterator = int*
// 则 value_type = int


#include <iostream>
#include <memory>
using namespace std;

template <class InputIterator, class ForwardIterator>
ForwardIterator my_uninitialized_copy(InputIterator first, InputIterator last,
                                      ForwardIterator result) {
    ForwardIterator current = result;
    
    try {
        while (first != last) {
            // 在 current 处构造对象
            ::new (static_cast<void*>(&*current)) 
                typename iterator_traits<ForwardIterator>::value_type(*first);
            ++first;
            ++current;
        }
        return current;
    } catch (...) {
        while (result != current) {
            result->~value_type();
            ++result;
        }
        throw;
    }
}

class MyClass {
public:
    int value;
    MyClass(int v = 0) : value(v) {
        cout << "构造:" << value << endl;
    }
};

int main() {
    int source[] = {1, 2, 3};
    
    // 分配未初始化内存
    void* mem = operator new[](sizeof(int) * 3);
    int* dest = static_cast<int*>(mem);
    
    // 调用 uninitialized_copy
    my_uninitialized_copy(source, source + 3, dest);
    
    // 打印结果
    for (int i = 0; i < 3; i++) {
        cout << dest[i] << " ";
    }
    cout << endl;
    
    operator delete[](mem);
    return 0;
}

placement new,只构造对象,不分配内存。

// 1. operator new:分配内存(不构造)
void* mem = operator new(sizeof(Type));
// 此时 mem 是未初始化的原始内存

// 2. placement new:构造对象(不分配)
Type* p = ::new (mem) Type(value);
// 在 mem 处构造 Type 对象

// 3. 普通 new:分配 + 构造
Type* p2 = new Type(value);
// 相当于:
// void* mem = operator new(sizeof(Type));
// Type* p2 = ::new (mem) Type(value);
placement new 构造的对象必须手动析构

异常

try {
    // 可能抛出异常的代码
} catch (...) {
    throw;  // 重新抛出当前捕获的异常
}
// 普通 throw:抛出新的异常
throw exception_type;

// 特殊 throw:重新抛出当前异常
throw;  // 只能在 catch 块中使用

iterator_traits

iterator_traits 就像一个"类型查询工具",能告诉你迭代器的各种类型信息。

// 想知道某个迭代器指向什么类型?
iterator_traits<ForwardIterator>::value_type
// 告诉你:迭代器指向的元素类型
// 通用模板
template <typename Iterator>
struct iterator_traits {
    typedef typename Iterator::value_type        value_type;         // 元素类型
    typedef typename Iterator::difference_type   difference_type;    // 差值类型
    typedef typename Iterator::pointer           pointer;            // 指针类型
    typedef typename Iterator::reference         reference;          // 引用类型
    typedef typename Iterator::iterator_category iterator_category;  // 迭代器类别
};

// 指针的特化版本
template <typename T>
struct iterator_traits<T*> {
    typedef T          value_type;         // 元素类型
    typedef ptrdiff_t  difference_type;    // 差值类型
    typedef T*         pointer;            // 指针类型
    typedef T&         reference;          // 引用类型
    typedef random_access_iterator_tag iterator_category;  // 迭代器类别
};

为什么需要 iterator_traits?
问题:指针也是迭代器
// 迭代器可以是类
vector::iterator it; // 类类型
// 可以通过 it::value_type 获取元素类型

// 迭代器也可以是指针
int* ptr; // 指针类型
// 不能通过 ptr::value_type 获取元素类型(指针没有内嵌类型)
解决:iterator_traits 统一处理

// 对于类类型迭代器
iterator_traits<vector<int>::iterator>::value_type
// 获取 vector<int>::iterator 的 value_type = int

// 对于指针
iterator_traits<int*>::value_type
// 特化版本:获取 int* 指向的类型 = int

allocate - 分配内存 ,construct - 构造对象,destroy - 析构对象,deallocate - 释放内存

template <typename T>
class allocator {
public:
    typedef T* pointer;
    typedef size_t size_type;
    
    pointer allocate(size_type n) {
        if (n == 0) return nullptr;
        
        // 第一步:计算需要多少字节
        size_t bytes = n * sizeof(T);
        // 比如:n=3, T=int → 3 * 4 = 12 字节
        
        // 第二步:调用 operator new 分配内存
        void* p = ::operator new(bytes);
        // 只分配内存,不构造对象
        // 返回 void* 指针
        
        // 第三步:转成 T* 返回
        return static_cast<pointer>(p);
    }
};
template <typename T>
class allocator {
public:
    void construct(pointer p, const T& val) {
        // placement new:在 p 指向的内存上构造对象
        
        // 第一步:把 T* 转成 void*(placement new 需要)
        void* address = static_cast<void*>(p);
        
        // 第二步:在 address 处构造 T 对象
        ::new (address) T(val);
        // 调用 T 的构造函数
        // 不分配内存,只构造对象
    }
};
template <typename T>
class allocator {
public:
    void destroy(pointer p) {
        // 显式调用析构函数
        
        // p 是 T* 指针
        p->~T();
        // 调用 T 的析构函数
        // 只析构对象,不释放内存
    }
};
template <typename T>
class allocator {
public:
    void deallocate(pointer p, size_type n) {
        // 释放内存
        
        // 调用 operator delete 释放内存
        ::operator delete(p);
        // 只释放内存,不析构对象
    }
};

句柄(Handle)

句柄(Handle) 是一个抽象概念,简单说就是用来间接引用资源的"凭证"或"编号"。
句柄就像存包处的号码牌:你把包(资源)交给存包处(系统),系统给你一个号码牌(句柄),你不需要直接接触包,用号码牌就能取包
解决:用句柄间接引用

// 用句柄(比如整数ID)
int handle = createResource();  // 系统返回句柄
useResource(handle);            // 通过句柄使用资源
destroyResource(handle);        // 通过句柄销毁资源

句柄的实现方式
方式1:整数索引

class ResourceManager {
    vector<Resource*> resources;
    
public:
    // 句柄就是索引
    int createResource() {
        resources.push_back(new Resource());
        return resources.size() - 1;  // 返回索引
    }
    
    Resource* getResource(int handle) {
        return resources[handle];  // 通过索引获取
    }
    
    void destroyResource(int handle) {
        delete resources[handle];
        resources[handle] = nullptr;
    }
};
class Handle {
    void* ptr;  // 内部指针
    
public:
    Handle(void* p) : ptr(p) {}
    void* get() { return ptr; }
};

// 使用
Handle h = createResource();
useResource(h.get());

方式3:不透明指针

// 对外隐藏实现
typedef void* HANDLE;

// 内部实现
struct RealResource {
    int data;
    // ...
};

HANDLE createResource() {
    RealResource* r = new RealResource();
    return (HANDLE)r;  // 隐藏真实类型
}

常见句柄类型

  1. 文件句柄
// Windows 文件句柄
HANDLE fileHandle = CreateFile(...);
// fileHandle 不是文件本身,而是一个标识

// 使用文件
WriteFile(fileHandle, data, size, ...);

// 关闭文件
CloseHandle(fileHandle);
  1. 窗口句柄
// Windows 窗口句柄
HWND hwnd = CreateWindow(...);
// hwnd 是窗口的标识

// 操作窗口
ShowWindow(hwnd, SW_SHOW);
DestroyWindow(hwnd);
  1. 数据库连接句柄
// 数据库连接
ConnectionHandle conn = db.connect(...);
// conn 是连接的标识

db.query(conn, "SELECT * FROM users");
db.close(conn);

句柄的好处 1. 安全性 2. 封装性 3. 资源管理

#include <iostream>
#include <vector>
using namespace std;

// 资源管理器
class ResourceManager {
    struct Resource {
        int id;
        string name;
    };
    
    vector<Resource*> resources;
    
public:
    // 创建资源,返回句柄
    int createResource(const string& name) {
        static int nextId = 1;
        Resource* r = new Resource{nextId++, name};
        resources.push_back(r);
        return r->id;  // 返回句柄(ID)
    }
    
    // 通过句柄获取资源
    Resource* getResource(int handle) {
        for (auto* r : resources) {
            if (r && r->id == handle) {
                return r;
            }
        }
        return nullptr;
    }
    
    // 销毁资源
    void destroyResource(int handle) {
        for (auto& r : resources) {
            if (r && r->id == handle) {
                delete r;
                r = nullptr;
                cout << "资源 " << handle << " 已销毁" << endl;
                return;
            }
        }
        cout << "句柄 " << handle << " 无效" << endl;
    }
    
    // 打印资源信息
    void printResource(int handle) {
        Resource* r = getResource(handle);
        if (r) {
            cout << "句柄 " << handle << ": " << r->name << endl;
        } else {
            cout << "句柄 " << handle << " 无效" << endl;
        }
    }
};

int main() {
    ResourceManager manager;
    
    // 创建资源
    int h1 = manager.createResource("资源1");
    int h2 = manager.createResource("资源2");
    int h3 = manager.createResource("资源3");
    
    // 使用句柄
    manager.printResource(h1);
    manager.printResource(h2);
    manager.printResource(h3);
    
    // 销毁资源
    manager.destroyResource(h2);
    
    // 尝试使用已销毁的句柄
    manager.printResource(h2);  // 无效
    
    return 0;
}

C++ 函数指针

void(*)() 是函数指针类型
void:函数返回值 void
(*):代表这是指针
():函数参数列表为空
含义:指向一个 返回 void,无参数 的函数的指针类型。
void *p():这是返回 void * 的函数声明,不是指针!重点:(*p) 括号必须包起来。

// 普通函数
void func(){}

// p 是函数指针变量,类型 void(*)()
void (*p)();

p = func;   // 存函数地址,&func 也可以
p();        // 通过指针调用函数
// 老式typedef
typedef void (*FuncPtr)();

// C++11 using,更推荐
using FuncPtr = void (*)();

FuncPtr fp = func;
fp();

void(*)() 最常见场景:回调、传函数做参数

// 参数接收一个 void(*)() 类型的函数指针
void test(void (*cb)()){
    cb(); // 执行回调
}

void hello(){
    // ...
}

int main(){
    test(hello);
    return 0;
}

静态成员

普通静态成员:不能类内初始化

// 静态成员属于类,不属于任何对象
// 需要在全局内存中分配空间
// 类内只是声明,类外才是定义(分配内存)


class MyClass {
    static int x;  // 声明:告诉编译器有这个成员
};
int MyClass::x = 0;  // 定义:实际分配内存
// C++17 开始,可以用 inline
class MyClass {
public:
    static inline int x = 10;  // C++17 可以!
};
// 1. 普通静态成员:不能类内初始化
class A {
    static int x;  // 声明
    // static int x = 0;  // 错误!
};
int A::x = 0;  // 定义

// 2. const 整数静态成员:可以类内初始化
class B {
    static const int x = 10;  // 可以!
};
// 如果不需要取地址,可以不在类外定义

// 3. constexpr 静态成员:可以类内初始化
class C {
    static constexpr int x = 10;  // 可以!
};

// 4. 函数指针静态成员:必须类外定义
class D {
    static void (*func)();  // 声明
};
void (*D::func)() = nullptr;  // 定义

typedef void(*MALLOCALLOC)();

// MALLOCALLOC 是函数指针类型

// 声明两个函数指针变量
MALLOCALLOC func1;
MALLOCALLOC func2;

// 函数指针之间可以互相赋值
func1 = func2; // 合法!把 func2 的值(函数地址)赋给 func1

枚举

枚举默认从0开始,每个成员递增1。
枚举的本质

// 枚举就是给整数起名字
enum Color { RED, GREEN, BLUE };

// 等价于:
const int RED = 0;
const int GREEN = 1;
const int BLUE = 2;

// 使用:
Color c = RED;  // c 就是 0
enum Color { RED, GREEN, BLUE };
// RED = 0(默认从0开始)
// GREEN = 1(前一个+1)
// BLUE = 2(前一个+1)
enum Color { RED = 10, GREEN = 20, BLUE = 30 };
// RED = 10
// GREEN = 20
// BLUE = 30

enum Color2 { A = 5, B, C };
// A = 5
// B = 6(前一个+1)
// C = 7(前一个+1)
枚举的几种形式
// 1. 匿名枚举(定义常量)
enum { A = 1, B = 2, C = 3 };
// A=1, B=2, C=3

// 2. 命名枚举
enum Color { RED, GREEN, BLUE };
// RED=0, GREEN=1, BLUE=2

// 3. 指定值
enum Size { SMALL = 8, MEDIUM = 16, LARGE = 32 };
// SMALL=8, MEDIUM=16, LARGE=32

volatile 关键字

告诉编译器:这个变量可能被意外修改,每次都要重新从内存读取。

static _Obj* volatile _freeList[_NFREELISTS];
// volatile:每次访问都从内存读取,不优化
// 多线程环境下,其他线程可能修改 _freeList
// volatile 防止编译器优化,确保每次读取最新值

// 不用 volatile:
// 编译器可能把 _freeList[0] 缓存在寄存器里
// 如果其他线程修改了,可能读不到最新值

// 用 volatile:
// 每次访问都从内存读取,保证最新
// 场景1:硬件寄存器
volatile int* statusRegister = (int*)0x1000;
// 硬件可能随时改变这个值
// 每次都要重新读取

// 场景2:多线程共享变量
volatile bool flag = false;
// 其他线程可能修改 flag
// 每次都要重新读取

// 场景3:信号处理
volatile int signalReceived = 0;
// 信号处理函数可能修改
// 每次都要重新读取

volatile 的不同位置

// 1. volatile 在指针后面:指针本身是 volatile
_Obj* volatile p1;
// p1 是 volatile 指针(指针值可能变)

// 2. volatile 在指针前面:指向的对象是 volatile
volatile _Obj* p2;
// p2 指向 volatile 对象(对象内容可能变)

// 3. 两者都是 volatile
volatile _Obj* volatile p3;
// 指针和对象都是 volatile

位运算

_ALIGN = 8 = 00001000(二进制)

_ALIGN - 1 = 7 = 00000111(二进制)
~(_ALIGN - 1) = ~7 = 11111000(二进制)

// 这个操作相当于:把低3位清零
// 结果就是8的倍数

// 例如:
bytes = 1
bytes + 7 = 8 = 00001000
8 & ~7 = 00001000 & 11111000 = 00001000 = 8

bytes = 9
bytes + 7 = 16 = 00010000
16 & ~7 = 00010000 & 11111000 = 00010000 = 16

bytes = 15
bytes + 7 = 22 = 00010110
22 & ~7 = 00010110 & 11111000 = 00010000 = 16

等价写法

// 方式1:位运算(高效)
(bytes + 7) & (~7)

// 方式2:算术运算(易懂)
((bytes + 7) / 8) * 8

// 两者结果一样

模板参数可以是类型,也可以是值

// 1. 类型模板参数(常见)
template <typename T>  // T 是类型
class MyClass { };

// 2. 非类型模板参数(值)
template <int N>  // N 是整数
class Array { };

// 3. 混合
template <typename T, int N>  // T 是类型,N 是整数
class MyArray { };
// 类型模板参数
template <typename T>
class Box {
    T data;
};

// 非类型模板参数
template <int Size>
class Array {
    int data[Size];
};

// bool 模板参数
template <bool flag>
class Config {
    void print() {
        if (flag) cout << "true" << endl;
        else cout << "false" << endl;
    }
};

// 使用
Box<int> box;        // T = int
Array<10> arr;       // Size = 10
Config<true> c1;     // flag = true
Config<false> c2;    // flag = false

指针数组,数组指针

#include <iostream>
using namespace std;

struct Node {
    int value;
};

int main() {
    // 1. 指针数组(数组里存指针)
    Node* ptrArray[3];  // 数组,有3个指针
    Node n1{1}, n2{2}, n3{3};
    ptrArray[0] = &n1;
    ptrArray[1] = &n2;
    ptrArray[2] = &n3;
    
    cout << "=== 指针数组 ===" << endl;
    for (int i = 0; i < 3; i++) {
        cout << ptrArray[i]->value << " ";
    }
    cout << endl;
    
    // 2. 数组指针(指针指向数组)
    Node* (*arrayPtr)[3];  // 指针,指向3元素的数组
    
    // 需要取整个数组的地址
    arrayPtr = &ptrArray;
    
    cout << "\n=== 数组指针 ===" << endl;
    for (int i = 0; i < 3; i++) {
        cout << (*arrayPtr)[i]->value << " ";
    }
    cout << endl;
    
    return 0;
}

锁

atomic_flag是 C++ 中最简单的原子布尔类型
特点
// 1. 无锁(lock-free)
// 2. 只有两种状态:set(true)和 clear(false)
// 3. 只有两个操作:test_and_set 和 clear
// 4. 是其他原子类型的基础
ATOMIC_FLAG_INIT 是初始化 atomic_flag 的宏。
std::atomic_flag flag = ATOMIC_FLAG_INIT;
// 初始化为 clear 状态(false)

// 等价于:
std::atomic_flag flag; // 但状态明确是 clear
test_and_set:设置标志为 true,并返回之前的值。
// 伪代码:
bool test_and_set() {
bool old = flag; // 保存旧值
flag = true; // 设置为 true
return old; // 返回旧值
}

// 原子操作:整个过程不可分割

内存序:控制原子操作的内存可见性。
内存序类型
// 1. memory_order_relaxed:无限制
// 2. memory_order_acquire:获取(读操作)
// 3. memory_order_release:释放(写操作)
// 4. memory_order_acq_rel:获取+释放
// 5. memory_order_seq_cst:顺序一致(最严格)
内存序类型是枚举(enum)

void lock() {
    while (flag.test_and_set(std::memory_order_acquire)) {
        // 自旋
    }
}

// 执行过程:
// 第一次:flag = false
// test_and_set() 返回 false,flag 变为 true
// while(false) → 退出循环,获取锁成功

// 第二次:flag = true(锁被占用)
// test_and_set() 返回 true,flag 还是 true
// while(true) → 继续循环

// 锁释放后:flag = false
// test_and_set() 返回 false,flag 变为 true
// while(false) → 退出循环,获取锁成功

原子类型

原子类型:操作不可分割的类型。

// 普通 int 的 counter++ 不是原子的
int counter = 0;
counter++;
// 实际是三步:
// 1. 读取 counter
// 2. counter + 1
// 3. 写回 counter
// 这三步之间可能被其他线程打断

// 原子类型的 counter++ 是原子的
atomic<int> counter = 0;
counter++;
// 整个过程不可分割
// 其他线程看不到中间状态

原子布尔类型 vs 普通布尔类型

// 普通 bool
bool flag = false;
flag = true;  // 不是原子操作
// 多线程可能出问题

// 原子 bool(atomic<bool>)
atomic<bool> flag = false;
flag = true;  // 原子操作
// 多线程安全

atomic_flag flag = ATOMIC_FLAG_INIT;
// flag 是变量
// set 和 clear 是 flag 的两种状态(值)

atomic_flag flag = ATOMIC_FLAG_INIT;
// 初始状态:clear(false)

flag.test_and_set();
// 状态变为:set(true)

flag.clear();
// 状态变为:clear(false)

flag.test_and_set(memory_order_acquire);
//   ^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^^^^^^
//   |             |
//   函数调用      内存序参数

// 含义:
// 1. 调用 test_and_set 函数
// 2. 使用 memory_order_acquire 内存序
// 3. 设置 flag 为 true,返回旧值
// 4. 保证后续操作不会被重排到之前

release 和 acquire 是配对使用的 release 用于"写"操作,acquire 用于"读"操作。

// clear 是"释放锁"操作
// 释放锁意味着:我写完了,别人可以来读了
// 所以用 release(释放)

// release 的含义:
// "我之前的所有写操作,都已经完成了"
// "别人获取锁后,一定能看到我的修改"

data = 42;                        // 写数据
flag.clear(memory_order_release); // 释放锁
// release 保证:data = 42 一定在 clear 之前完成

// test_and_set 是"获取锁"操作
// 获取锁意味着:我要开始读了
// 所以用 acquire(获取)

// acquire 的含义:
// "我之后的所有读操作,都在获取锁之后"
// "我一定能看到别人释放锁之前的修改"

while (flag.test_and_set(memory_order_acquire)) {}  // 获取锁
cout << data;  // 读数据
// acquire 保证:cout << data 一定在 test_and_set 之后
// 线程1:
data = 42;                        // 1. 写数据
flag.clear(memory_order_release); // 2. 释放锁
// release 保证:1 在 2 之前

// 线程2:
while (flag.test_and_set(memory_order_acquire)) {}  // 3. 获取锁
cout << data;                                        // 4. 读数据
// acquire 保证:4 在 3 之后

// 关键:
// release 保证 1 在 2 之前
// acquire 保证 4 在 3 之后
// 如果线程2看到 flag 被清除(3在2之后)
// 那么 4 一定在 1 之后
// 所以线程2一定看到 data = 42
// 初始状态:flag = false(锁空闲)

while (flag.test_and_set()) {
    // 1. 调用 test_and_set
    //    旧值 = false
    //    flag 变为 true
    //    返回 false
    
    // 2. while(false) → 条件为假,不进入循环体
    //    直接退出循环
}
// 退出循环,获取锁成功!

结论:第一次如果 flag 是 false,返回值是 false,不进入循环,直接获取锁。

// 状态:flag = true(锁被其他线程占用)

while (flag.test_and_set()) {
    // 1. 调用 test_and_set
    //    旧值 = true
    //    flag 还是 true
    //    返回 true
    
    // 2. while(true) → 条件为真,进入循环体
    //    执行空循环(自旋)
}
// 一直循环,直到 flag 变为 false

线程1先获取锁:
┌─────────────────────────────────────┐
│ flag = false │
│ test_and_set() → 返回 false │
│ flag 变为 true │
│ while(false) → 退出循环 │
│ 获取锁成功! │
│ 执行临界区… │
│ flag.clear() → flag 变为 false │
└─────────────────────────────────────┘

线程2同时尝试获取锁:
┌─────────────────────────────────────┐
│ flag = true(线程1持有) │
│ test_and_set() → 返回 true │
│ flag 还是 true │
│ while(true) → 进入循环(自旋) │
│ │
│ 自旋… │
│ 自旋… │
│ 自旋… │
│ │
│ 线程1释放锁:flag 变为 false │
│ test_and_set() → 返回 false │
│ flag 变为 true │
│ while(false) → 退出循环 │
│ 获取锁成功! │
└─────────────────────────────────────┘

自旋锁

#include <atomic>// 包含 atomic_flag 和内存序// 包含 atomic_flag 和内存序

class SpinLock {
    std::atomic_flag flag = ATOMIC_FLAG_INIT;
    // std::atomic_flag:原子标志类型
// ATOMIC_FLAG_INIT:初始化宏,初始为 clear
// 等价于:flag = false(锁空闲)
public:
    void lock() {
        while (flag.test_and_set(std::memory_order_acquire)) {
            // 自旋等待
        }
    }
    
    void unlock() {
        flag.clear(std::memory_order_release);
    }
};

方式1:手动加锁解锁

SpinLock spinlock;

spinlock.lock();      // 加锁
// 临界区代码
spinlock.unlock();    // 解锁

方式2:RAII(推荐)

// 用 lock_guard
{
    std::lock_guard<SpinLock> guard(spinlock);
    // 临界区代码
}  // 自动解锁

方式3:自定义 RAII

class SpinLockGuard {
    SpinLock& lock_;
public:
    SpinLockGuard(SpinLock& lock) : lock_(lock) {
        lock_.lock();
    }
    ~SpinLockGuard() {
        lock_.unlock();
    }
};

// 使用
{
    SpinLockGuard guard(spinlock);
    // 临界区
}  // 自动解锁
class SpinLockGuard {
    SpinLock& lock_;  // 持有锁的引用
    
public:
    // 构造函数:加锁
    SpinLockGuard(SpinLock& lock) : lock_(lock) {
        lock_.lock();
    }
    
    // 析构函数:解锁
    ~SpinLockGuard() {
        lock_.unlock();
    }
};

// 使用:
{
    SpinLockGuard guard(spinlock);
    // 构造时自动加锁
    // 离开作用域时自动解锁
}

为什么用引用

// 用引用:操作的是同一个锁
SpinLock spinlock;
{
    SpinLockGuard guard(spinlock);
    // guard.lock_ 就是 spinlock
    // guard 析构时,解锁的是 spinlock
}

// 用对象:操作的是副本!
SpinLock spinlock;
{
    SpinLockGuard guard(spinlock);
    // guard.lock_ 是 spinlock 的副本
    // guard 析构时,解锁的是副本,不是 spinlock!
    // spinlock 永远锁着!
}
// SpinLock 就像一把锁
class SpinLock {
    // 锁的状态
    // 开锁/关锁的方法
};

// SpinLockGuard 就像一个自动锁
class SpinLockGuard {
    // 拿着锁的引用
    // 创建时自动关锁
    // 销毁时自动开锁
};

// 使用:
{
    SpinLockGuard guard(spinlock);
    // 自动关锁
    // 做事情
}  // 自动开锁

线程

一、线程的基本语法
头文件
#include <thread>
创建线程
std::thread t(可调用对象, 参数1, 参数2, ...);
等待/分离
t.join();      // 等待线程结束
t.detach();    // 分离线程
t.joinable();  // 检查是否可 join
二、thread 的参数详解
参数1:可调用对象(必需)
// 可以是:
// 1. 普通函数
// 2. 函数指针
// 3. Lambda 表达式
// 4. 函数对象(仿函数)
// 5. 成员函数
参数2~N:传给函数的参数(可选)
thread t(func, arg1, arg2, ...);
//        ^^^^  ^^^^^^^^^^^^^^^
//        函数   传给函数的参数
三、五种可调用对象
1. 普通函数
void func() {
    cout << "普通函数" << endl;
}

thread t(func);
t.join();
2. 带参数的函数
void func(int a, string b) {
    cout << a << " " << b << endl;
}

thread t(func, 10, "hello");
t.join();
3. Lambda 表达式
// 无参数
thread t1([]() {
    cout << "Lambda" << endl;
});

// 带参数
thread t2([](int x) {
    cout << "x = " << x << endl;
}, 10);

t1.join();
t2.join();
4. 函数对象(仿函数)
class MyFunctor {
public:
    void operator()() {
        cout << "函数对象" << endl;
    }
    
    void operator()(int x) {
        cout << "函数对象: " << x << endl;
    }
};

MyFunctor f;
thread t1(f);
thread t2(f, 10);

t1.join();
t2.join();
5. 成员函数
class MyClass {
public:
    void func() {
        cout << "成员函数" << endl;
    }
    
    void func2(int x) {
        cout << "成员函数2: " << x << endl;
    }
};

MyClass obj;

// 无参数成员函数
thread t1(&MyClass::func, &obj);

// 带参数成员函数
thread t2(&MyClass::func2, &obj, 10);

t1.join();
t2.join();
四、参数传递详解
1. 默认:拷贝传递
void func(int x) {
    x = 100;  // 修改的是副本
}

int a = 10;
thread t(func, a);  // 拷贝 a
t.join();
cout << a << endl;  // 还是 10
2. 传引用:用 std::ref
#include <functional>

void func(int& x) {
    x = 100;  // 修改原变量
}

int a = 10;
thread t(func, std::ref(a));  // 传引用
t.join();
cout << a << endl;  // 100
3. 传指针
void func(int* x) {
    *x = 100;
}

int a = 10;
thread t(func, &a);
t.join();
cout << a << endl;  // 100
4. 传常量引用
void func(const string& s) {
    cout << s << endl;
}

thread t(func, "hello");  // 临时字符串
t.join();
5. 传多个参数
void func(int a, double b, string c) {
    cout << a << " " << b << " " << c << endl;
}

thread t(func, 10, 3.14, "hello");
t.join();
6. 传对象
class MyClass {
public:
    int value;
    MyClass(int v) : value(v) {}
};

void func(MyClass obj) {
    cout << obj.value << endl;
}

MyClass m(10);
thread t(func, m);  // 拷贝对象
t.join();
五、等待线程的两种方式
1. join():等待线程结束
thread t(func);
t.join();  // 阻塞,直到 t 结束
// 主线程继续
2. detach():分离线程
thread t(func);
t.detach();  // 分离,主线程继续
// 线程在后台运行
3. joinable():检查
thread t(func);

if (t.joinable()) {
    t.join();
}
注意事项
1. 必须 join 或 detach
// 错误:程序崩溃
void bad() {
    thread t(func);
    // 离开作用域,t 销毁
    // 如果 t 还在运行,程序崩溃!
}

// 正确
void good() {
    thread t(func);
    t.join();  // 等待结束
}
2. 不能拷贝
thread t1(func);
thread t2 = t1;  // 错误!

thread t3 = std::move(t1);  // 正确
3. 数据竞争4,用互斥锁保护

// 错误:数据竞争
int counter = 0;

void increment() {
    for (int i = 0; i < 100000; i++) {
        counter++;  // 不是原子操作!
    }
}

int main() {
    thread t1(increment);
    thread t2(increment);
    t1.join();
    t2.join();
    cout << counter << endl;  // 不是 200000!
}
#include <mutex>

int counter = 0;
mutex mtx;

void increment() {
    for (int i = 0; i < 100000; i++) {
        lock_guard<mutex> guard(mtx);
        counter++;
    }
}

join():等待线程结束,阻塞当前线程直到子线程执行完毕。

thread t(func);  // 创建线程
t.join();        // 等待线程 t 结束
// 主线程在这里阻塞,直到 t 执行完、

// join:等待,汇合
thread t(func);
t.join();  // 主线程等待子线程

// detach:分离,各走各的
thread t(func);
t.detach();  // 主线程不等,子线程独立运行

thread t(&MyClass::func, &obj);

&MyClass::func
// 取成员函数的地址
// 类型:void (MyClass::*)()
&obj
// 取对象的地址
// 类型:MyClass*
class MyClass {
public:
    void func() {  // 成员函数
        cout << "成员函数" << endl;
    }
};

MyClass obj;  // 对象

// 创建线程执行 obj.func()
thread t(&MyClass::func, &obj);
//       ^^^^^^^^^^^^^^  ^^^^
//       |               |
//       成员函数指针     对象指针
//       (用 & 取地址)  (用 & 取地址)
// 普通函数:
void func() { }
thread t(func);  // 只需要函数地址

// 成员函数:
class MyClass {
public:
    void func() { }  // 成员函数属于对象
};

// 需要两个信息:
// 1. 哪个成员函数:&MyClass::func
// 2. 哪个对象:&obj
thread t(&MyClass::func, &obj);

主线程:                    子线程:
─────────────────────────────────────
创建 thread t(func)  ──→  开始执行 func
    ↓
继续执行 "主线程:继续执行"
    ↓
调用 t.join()  ──→  等待
    ↓                      ↓
    ↓                  func 结束
    ↓                      ↓
继续执行                   结束
    ↓
"主线程:结束"

std::ref 创建一个引用包装器,让函数能接收引用参数。

thread 默认拷贝参数

#include <functional>  // std::ref 需要这个头文件

void func(int& x) {
    x = 20;
}

int b = 10;
thread t(func, std::ref(b));  // 正确!传递引用
t.join();
cout << b << endl;  // 输出 20
#include <iostream>
#include <thread>
#include <functional>
using namespace std;

void increment(int& x) {
    x++;
}

int main() {
    int a = 10;
    
    // 不用 std::ref:编译错误
    // thread t1(increment, a);  // 错误!
    
    // 用 std::ref:正确
    thread t2(increment, std::ref(a));
    t2.join();
    
    cout << "a = " << a << endl;  // 输出 11
    
    return 0;
}
// std::ref 版本
void func1(int& x) { }  // 引用
thread t1(func1, std::ref(a));  // 匹配

// & 版本
void func2(int* x) { }  // 指针
thread t2(func2, &a);  // 匹配
// std::ref:像普通变量一样用
void func1(int& x) {
    x = 20;  // 直接用
}

// &:需要解引用
void func2(int* x) {
    *x = 20;  // 需要 *
}
#include <iostream>
#include <thread>
#include <functional>
using namespace std;

// 引用版本
void funcRef(int& x) {
    cout << "funcRef: x = " << x << endl;
    x = 20;
}

// 指针版本
void funcPtr(int* x) {
    cout << "funcPtr: *x = " << *x << endl;
    *x = 30;
}

int main() {
    int a = 10;
    int b = 10;
    
    // 1. std::ref 传递引用
    cout << "=== std::ref ===" << endl;
    thread t1(funcRef, std::ref(a));
    t1.join();
    cout << "a = " << a << endl;  // 20
    
    // 2. & 传递指针
    cout << "\n=== & ===" << endl;
    thread t2(funcPtr, &b);
    t2.join();
    cout << "b = " << b << endl;  // 30
    
    return 0;
}

std::move 把左值转换为右值,实现移动语义。

问题:thread 不能拷贝
thread t1(func);
thread t2 = t1; // 错误!thread 不能拷贝
thread t1(func);
thread t2 = std::move(t1); // 正确!移动
// t1 现在为空,t2 接管了线程

lock_guard 、unique_lock、condition_variable

// lock_guard 的接口
template <typename Mutex>
class lock_guard {
public:
    explicit lock_guard(Mutex& m) : mtx(m) { mtx.lock(); }
    ~lock_guard() { mtx.unlock(); }
    
    // 没有 lock() 和 unlock() 成员函数!
    // 构造时加锁,析构时解锁,中间不能操作
    
private:
    Mutex& mtx;
};
template <typename Mutex>
class lock_guard {
    Mutex& mtx;  // 引用成员(不是对象)
    
public:
    explicit lock_guard(Mutex& m) : mtx(m) {
        // m 是引用参数
        // mtx 是引用成员
        // mtx(m):让 mtx 引用 m 引用的对象
        // 没有拷贝!
        mtx.lock();
    }
    
    ~lock_guard() {
        mtx.unlock();
    }
};


// unique_lock 的接口
template <typename Mutex>
class unique_lock {
public:
    void lock();    // 加锁
    void unlock();  // 解锁
    bool owns_lock() const;  // 是否持有锁
    
    // 可以在生命周期内多次加锁/解锁
};
#include <condition_variable>

class condition_variable {
public:
    // 构造函数
    condition_variable();
    ~condition_variable();
    
    // 禁止拷贝
    condition_variable(const condition_variable&) = delete;
    condition_variable& operator=(const condition_variable&) = delete;
    
    // 等待函数
    void wait(unique_lock<mutex>& lock);
    template <typename Predicate>
    void wait(unique_lock<mutex>& lock, Predicate pred);
    
    // 带超时的等待
    template <typename Rep, typename Period>
    cv_status wait_for(unique_lock<mutex>& lock,
                       const chrono::duration<Rep, Period>& rel_time);
    
    template <typename Rep, typename Period, typename Predicate>
    bool wait_for(unique_lock<mutex>& lock,
                  const chrono::duration<Rep, Period>& rel_time,
                  Predicate pred);
    
    // 通知函数
    void notify_one() noexcept;
    void notify_all() noexcept;
};

void increment() {
    for (int i = 0; i < 1000; i++) {
        MyLockGuard guard(m);  // 每次循环都创建一个新对象
        counter++;
    }  // 每次循环结束,guard 析构
}
for (int i = 0; i < 1000; i++) {
    MyLockGuard guard(m);
    // 1. 调用构造函数
    // 2. 构造函数中 mtx_.lock() → 加锁
    // 3. 执行 counter++
    // 4. 循环体结束,guard 析构
    // 5. 析构函数中 mtx_.unlock() → 解锁
    // 6. 下一次循环,创建新的 guard
}

wait

标准库中的实现(简化)

template <typename Predicate>
void condition_variable::wait(unique_lock<mutex>& lock, Predicate pred) {
    while (!pred()) {           // 检查条件
        wait(lock);             // 调用无条件的 wait
    }
}

void condition_variable::wait(unique_lock<mutex>& lock) {
    // 1. 解锁
    lock.unlock();
    
    // 2. 等待通知(阻塞)
    // ... 操作系统层面等待 ...
    
    // 3. 被唤醒后重新加锁
    lock.lock();
}
// wait 必须能:
// 1. 解锁:让其他线程能获取锁,修改条件
// 2. 重新加锁:被唤醒后,安全地检查条件

// lock_guard 做不到:
// 它只能在构造时加锁,析构时解锁
// 中间不能手动操作

// unique_lock 可以:
// 它提供了 lock() 和 unlock() 方法
// 可以在任何时候加锁/解锁

// 消费者等待
unique_lock<mutex> lock(mtx);
cv.wait(lock, []{ return !q.empty(); });

// wait 内部:
// 1. lock.unlock()  ← 释放锁,让生产者能获取
// 2. 等待通知
// 3. lock.lock()    ← 重新获取锁,安全读取队列

// 如果没解锁:
// 消费者持有锁等待
// 生产者无法获取锁,无法添加数据
// 死锁!
消费者                          生产者
    │                              │
    │ unique_lock lock(mtx)        │
    │ 持有锁                       │
    │                              │
    │ cv.wait(lock, ...)           │
    │   ↓                          │
    │   lock.unlock()  ────────→  可以获取锁
    │   等待...                     │
    │                              │ lock_guard lock(mtx)
    │                              │ q.push(data)
    │                              │ cv.notify_one()
    │   ←────────────────────────  │
    │   lock.lock()                │
    │   检查条件                    │
    │   读取数据                    │

哈希桶

基本结构

// 哈希桶:数组 + 链表
template <typename K, typename V>
class HashTable {
    vector<list<pair<K, V>>> buckets;  // 桶数组
    // 每个桶是一个链表
    // 存储哈希值相同的元素
};

哈希桶的工作原理

1. 插入元素
void insert(K key, V value) {
    // 1. 计算哈希值
    size_t hashValue = hash(key);
    
    // 2. 找到对应的桶
    size_t index = hashValue % buckets.size();
    
    // 3. 插入到桶的链表中
    buckets[index].push_back({key, value});
}
2. 查找元素
V find(K key) {
    // 1. 计算哈希值
    size_t hashValue = hash(key);
    
    // 2. 找到对应的桶
    size_t index = hashValue % buckets.size();
    
    // 3. 在链表中查找
    for (auto& pair : buckets[index]) {
        if (pair.first == key) {
            return pair.second;
        }
    }
    return V();  // 没找到
}
3. 删除元素
void erase(K key) {
    size_t index = hash(key) % buckets.size();
    buckets[index].remove_if([key](const pair<K,V>& p) {
        return p.first == key;
    });
}
// 1. std::hash<K> 是一个类型
std::hash<string>  // 类型

// 2. std::hash<K>{} 创建临时对象
std::hash<string>{}  // 临时对象

// 3. std::hash<K>{}(key) 调用 operator()
std::hash<string>{}(key)  // 调用 operator(),返回哈希值
等价写法

// 方式1:创建临时对象
size_t h1 = std::hash<string>{}(key);

// 方式2:创建命名对象
std::hash<string> hasher;
size_t h2 = hasher(key);

// 两种方式等价
std::hash<string>{}  // 创建临时对象(C++11 列表初始化)
std::hash<string>()  // 也可以,但可能被解析为函数声明
class HashTable {
    static const int BUCKET_COUNT = 10;
    vector<list<pair<K, V>>> buckets;
    
public:
    HashTable() : buckets(BUCKET_COUNT) {}
    //          ^^^^^^^^^^^^^^^^^^^^^^^^
    //          用 BUCKET_COUNT 初始化 buckets
    //          buckets 的大小是 10
};
// vector 的构造函数
vector(size_type n);  // 创建 n 个元素

// buckets(BUCKET_COUNT):
// 创建 BUCKET_COUNT 个元素
// 即:创建 10 个 list

初始化利列表

// const 和引用必须用初始化列表
class MyClass {
    const int x;  // const 成员
    int& ref;     // 引用成员
    
public:
    // 错误:不能在函数体内赋值
    MyClass(int a, int& b) {
        x = a;   // 错误!
        ref = b; // 错误!
    }
    
    // 正确:用初始化列表
    MyClass(int a, int& b) : x(a), ref(b) {}
};
class MyClass {
    int a;
    int b;
    
public:
    // 按声明顺序初始化,不是按列表顺序
    MyClass(int x) : b(x), a(b) {}
    // a 先初始化,再 b
    // 但 a(b) 中的 b 还没初始化!
};

reinterpret_cast 是 C++ 中最“暴力”的类型转换,它不改变底层的二进制位,只重新解释这些位。

void* raw = HashBucket::useMemory(sizeof(T)); // 拿到一块裸内存
T* p = reinterpret_cast<T*>(raw);             // 把裸内存地址当作 T* 看待
useMemory 返回的是 void*(或 char*),它只是一块原始内存,没有任何类型信息。reinterpret_cast 负责把“裸内存地址”解释为“T 对象的地址”。
注意:此时 p 只是指向内存,对象还没有构造,所以不能直接访问 p->成员,必须用 placement new 构造。

placement new(定位 new)

new (地址指针) 类型(构造参数)

… C++ 可变参数模板的“声明”与“展开”的语法规则

声明参数包时,… 放在变量名前面。

展开参数包时,… 放在表达式后面。

  1. 声明参数包
template<typename T, typename... Args>
friend T* newElement(Args&&... args);

typename… Args:声明 Args 是一个类型参数包(包含 0 到 N 个类型)。… 写在 Args 前面。

Args&&… args:声明 args 是一个函数参数包(对应 Args 里的类型)。… 写在 args 前面。

这就像告诉编译器:“我要接收一堆参数,打包起来叫 args。”

  1. 展开参数包(std::forward 的情况)
std::forward<Args>(args)...
此时我们要把刚才打包好的 args 拆开,一个一个塞给函数。

展开的语法是:表达式...,即 std::forward<Args>(args)...。

… 的三种位置

// 1. 声明参数包(在类型后面)
template<typename ...Args>   // Args 是类型包
void func(Args... args);     // args 是参数包

// 2. 声明万能引用参数包
void func(Args&&... args);

// 3. 展开参数包(在表达式后面)
func(std::forward<Args>(args)...);  // 展开
func(args...);                       // 展开

… 是参数包展开符号,放在最后表示"展开整个参数包"。

// 假设 Args = {int, double, string}
//      args = {a,    b,      c}

std::forward<Args>(args)...
// 展开为:
std::forward<int>(a), std::forward<double>(b), std::forward<string>(c)

std::forward

std::forward 是完美转发函数,保持参数的左值/右值属性。

// 问题:函数参数有名字,就变成了左值
template<typename T>
void func(T&& x) {
    // x 虽然是 T&&,但 x 本身是左值(有名字)
    otherFunc(x);  // 传递的是左值!
}
template<typename T>
void func(T&& x) {
    otherFunc(std::forward<T>(x));  // 保持原始属性
}
// 作用:保持参数的左值/右值属性
std::forward<T>(x)
// 1. 如果 T 是左值引用 → 返回左值
// 2. 如果 T 是右值引用 → 返回右值

左值右值

左值(lvalue)—— 有名字,有持久内存
它是一个持久的对象,有明确的名字,有具体的内存地址。
你可以在后面的代码里通过名字再次找到它。
右值(rvalue)—— 临时,即将销毁
它是一个临时的值,没有名字,通常在表达式结束时就会销毁。
你不能在后面的代码里再次找到它。
左值引用(T&)
只能绑定到左值。
语法:类型& 引用名 = 左值;
右值引用(T&&)—— C++11引入
只能绑定到右值,用来延长临时对象的生命周期,或者实现移动语义(避免深拷贝)。
语法:类型&& 引用名 = 右值;

只有当 T 是一个推导类型(如模板参数)时,T&& 才是万能引用。
它可以绑定左值,也可以绑定右值:

template<typename T>
void func(T&& x) { ... }

int a = 10;
func(a);  // 传入左值,T 被推导为 int&,引用折叠后 x 是 int&
func(20); // 传入右值,T 被推导为 int,x 是 int&&
template<typename T, typename... Args>
friend T* newElement(Args&&... args);
这里的 Args&& 是万能引用。这意味着无论传入的是左值还是右值,它都能接住。
template<typename T, typename... Args>
T* newElement(Args&&... args) {
    void* mem = malloc(sizeof(T));
    // 这里的 std::forward<Args>(args)... 就是展开
    T* p = new(mem) T(std::forward<Args>(args)...);
    return p;
}
std::forward<Args>(args)... 的作用是完美转发:把接收到的左值原封不动保持为左值,右值原封不动保持为右值,传给 T 的构造函数。

情况1:不需要 std::forward(按值传递)

// 按值传递参数
template<typename ...Args>
void func(Args... args) {
    // args 是普通参数(拷贝)
    // 直接展开
    print(args...);
}

// 调用
int a = 10;
func(a, 20, "hello");
// args = {a 的副本, 20, "hello"}

情况2:需要 std::forward(保持左右值)

// 万能引用参数
template<typename ...Args>
void func(Args&&... args) {
    // 需要保持参数的左右值属性
    otherFunc(std::forward<Args>(args)...);
}

情况3:其他展开方式

// 1. 初始化列表展开
template<typename ...Args>
void func(Args... args) {
    int arr[] = {args...};  // 展开到数组
}

// 2. 逗号表达式展开
template<typename ...Args>
void func(Args... args) {
    (cout << ... << args);  // C++17 折叠表达式
}

// 3. 递归展开
template<typename T>
void print(T value) {
    cout << value << endl;
}

template<typename T, typename ...Args>
void print(T value, Args... args) {
    cout << value << endl;
    print(args...);  // 递归展开
}

friend 友元语法

friend 允许其他函数或类访问自己的私有成员。

形式1:友元类

class A {
private:
    int data;
    
    friend class B;  // B 可以访问 A 的私有成员
};

class B {
public:
    void show(A& a) {
        cout << a.data << endl;  // 可以访问私有成员
    }
};

形式2:友元函数(全局函数)

class A {
private:
    int data;
    
    friend void show(A& a);  // 友元函数
};

void show(A& a) {
    cout << a.data << endl;  // 可以访问私有成员
}

形式3:友元成员函数

class B;  // 前向声明

class A {
private:
    int data;
    
    friend void B::show(A& a);  // B 的成员函数是友元
};

class B {
public:
    void show(A& a);  // 声明
};

void B::show(A& a) {
    cout << a.data << endl;  // 可以访问
}

特点1:不是成员

class A {
    friend void func();  // func 不是 A 的成员函数
};

// 不能这样调用
// A a;
// a.func();  // 错误!func 不是成员

特点2:单向

class A {
    friend class B;  // B 可以访问 A
    // 但 A 不能访问 B 的私有成员
};

class B {
    int data;  // A 不能访问
};

特点3:不能继承

class A {
    friend class B;
};

class C : public B {
    // C 不是 A 的友元
    // 即使 B 是 A 的友元
};

特点4:不能传递

class A {
    friend class B;  // B 是 A 的友元
};

class B {
    friend class C;  // C 是 B 的友元
};

// C 不是 A 的友元!

select 版本 poll 版本 epoll 版本

fd(文件描述符):在 Linux 里,所有的东西(键盘、屏幕、网络、文件)都是用数字编号的。

#include <stdio.h>
#include <sys/select.h>
#include <unistd.h>
#include <string.h>

int main() {
    fd_set readfds;          // 【1. 拿一张空白的名册】
    int max_fd = 0;          // 【2. 记录一下最大的门牌号】
    char buffer[1024];       // 【3. 准备一个筐,用来装读出来的字】

    while (1) {
        FD_ZERO(&readfds);           // 【4. 把名册上的勾全部擦掉】
        
        // 【5. 在名册上给 0 号(键盘)打个勾】
        FD_SET(STDIN_FILENO, &readfds); // STDIN_FILENO 就是 0
        max_fd = STDIN_FILENO;       // 最大的门牌号是 0

        // 【6. 把名册交给内核(老板),让他去查房】
        // 参数拆解:
        // max_fd + 1:告诉老板要查哪几个门牌(0 到 max_fd,所以是 max+1)
        // &readfds:名册的地址
        // 后面三个 NULL:暂时不管写操作,也不管异常
        int n = select(max_fd + 1, &readfds, NULL, NULL, NULL);
        
        if (n > 0) { // n 是老板报给你的,有几个门牌有动静
            // 【7. 拿回名册,看看 0 号(键盘)的格子里,是不是还是勾】
            if (FD_ISSET(STDIN_FILENO, &readfds)) {
                // 【8. 有勾!说明键盘有字,从 0 号门牌把字拿出来放进筐里】
                // 参数拆解:
                // STDIN_FILENO:从 0 号门牌拿
                // buffer:放到哪个筐里
                // sizeof(buffer) - 1:最多拿多少,留 1 个字节给结束符
                int len = read(STDIN_FILENO, buffer, sizeof(buffer) - 1);
                
                if (len > 0) {
                    buffer[len] = '\0'; // 筐里装完字后,尾部打个结(字符串结束符)
                    printf("【select 读取到数据】: %s\n", buffer);
                }
            }
        }
    }
    return 0;
}

STDIN_FILENO:宏,其实就是数字 0,代表标准输入(键盘)。

read():Linux 系统调用,从 fd 里读数据到 buffer 里。

max_fd + 1:select 规定第一个参数必须是“最大 fd 编号 + 1”,因为内核会遍历 0 到 max_fd 的所有位。
d_set 通常是一个包含 32 个 long 的数组(32 * 32 = 1024 位)。

你要监视 fd = 3。

FD_SET 会在第 0 个 long 的第 3 个二进制位写上 1。

本质上全是极其底层的位运算(位移和掩码)。

poll 版本

#include <stdio.h>
#include <poll.h>
#include <unistd.h>
#include <string.h>

int main() {
    // 【1. 准备一张登记表,表上只有 1 行,专门登记键盘】
    struct pollfd fds[1]; 
    
    fds[0].fd = STDIN_FILENO; // 【2. 填门牌号:0 号】
    fds[0].events = POLLIN;   // 【3. 填我想查什么:POLLIN 意思是“有数据可读”】
    
    char buffer[1024];

    while (1) {
        // 【4. 把登记表交给老板,老板你去查吧】
        // 参数拆解:
        // fds:登记表
        // 1:表里有 1 行
        // -1:如果没动静,老板就一直等(死等)
        int n = poll(fds, 1, -1); 
        
        if (n > 0) {
            // 【5. 老板查完回来了,他会把结果写在 fds[0].revents 里】
            // &(按位与):用来检查 revents 里面是不是包含了 POLLIN(有数据)
            if (fds[0].revents & POLLIN) {
                // 【6. 有数据!把字拿出来】
                int len = read(STDIN_FILENO, buffer, sizeof(buffer) - 1);
                if (len > 0) {
                    buffer[len] = '\0';
                    printf("【poll 读取到数据】: %s\n", buffer);
                }
            }
        }
    }
    return 0;
}

epoll 版本

#include <stdio.h>
#include <sys/epoll.h>
#include <unistd.h>
#include <string.h>

int main() {
    // 【1. 买一个高级总机,返回一个专门管理这个总机的编号(epfd)】
    // 参数 0:默认配置
    int epfd = epoll_create1(0); 

    // 【2. 拿一张 VIP 登记卡】
    struct epoll_event ev;
    ev.events = EPOLLIN;          // 【3. 我要办的服务:EPOLLIN(有数据)】
    ev.data.fd = STDIN_FILENO;    // 【4. 绑定 0 号门牌(键盘)】

    // 【5. 把 VIP 卡录入总机】
    // 参数拆解:
    // epfd:总机编号
    // EPOLL_CTL_ADD:动作是“添加”(表示我要新增监视)
    // STDIN_FILENO:门牌号 0
    // &ev:VIP 卡的地址
    epoll_ctl(epfd, EPOLL_CTL_ADD, STDIN_FILENO, &ev);

    // 【6. 准备一个篮子,装老板报上来的就绪名单】
    struct epoll_event events[10]; 
    char buffer[1024];

    while (1) {
        // 【7. 等老板的通知】
        // 参数拆解:
        // epfd:总机编号
        // events:装通知的篮子
        // 10:篮子最多装 10 条通知
        // -1:无限期等
        int n = epoll_wait(epfd, events, 10, -1);
        
        // 【8. 老板只报有动静的!n 是几,就说明有几个有动静】
        // 如果只有键盘有动静,n 就是 1,循环只跑 1 次!
        for (int i = 0; i < n; i++) {
            int fd = events[i].data.fd; // 把门牌号拿出来
            if (fd == STDIN_FILENO) {
                int len = read(fd, buffer, sizeof(buffer) - 1);
                if (len > 0) {
                    buffer[len] = '\0';
                    printf("【epoll 读取到数据】: %s\n", buffer);
                }
            }
        }
    }
    return 0;
}
避开CPU瓶颈:手把手教你用RK3588的DMA-BUF实现V4L2到NPU的零拷贝数据流 本文详细介绍了如何在RK3588平台上利用DMA-BUF实现V4L2到NPU的零拷贝数据流,显著提升异构计算性能。通过实战指南展示了如何配置摄像头采集、RGA图像预处理和NPU推理优化,最终实现CPU占用率降低60%、延迟减少60%的效果,特别适合YOLOv5等AI推理应用的高效部署。 阅读详情

相关推荐

vcs覆盖率相关

选择要查看的覆盖率点,右键选择 show xxx tests;

qq_40571921的博客 2391

推荐开源项目:CPP C++ Papyrus

推荐开源项目:CPP C++ Papyrus 项目介绍 CPP C++ Papyrus 是一个全面的C++编程资源库,由Caio Rodrigues Soares精心编撰和维护。这个开源项目旨在为开发者提供C++语言的核心概念、标准库、工具以及各种应用领域的实例代码和示例。无论你是初学者还是经验丰富的程序员,都能在这个项目中找到你需要的知识点和实践案例。 项目技术分析 该项目涵盖了C++的各个方面...

gitblog_00003的博客 926

中国传媒大学计算机考研初复试经验贴

中传的计算机专业考研的话主要分为两大学院:计网学院和国重,大部分专业在计网学院。

DoKiDoK的博客 1561

该死!GitHub上这些C++项目真香

在知乎上看到了个问题,说,在用github搜学习资料时,总是一些很大型且成熟的项目,根本看不懂。 做为工作近十年的经验老鸟,github上的资源非常丰富,通过github学习一定要循序渐进。 其实,不光有大型项目,还有很多学习型资源。 这里帮大家就C++方向罗列出了一些热度很高的从入门到小实战项目再到大型项目,供大家学习吧。 学习篇: 一、一个项目入门C++足以:CPlusPlusThings CPlusPlusThings 是国人开源一个 C++ 学习项目。它系统地将

启舰 5万+

5分钟搞定!用OpenCV实现全景图像柱面展开的保姆级教程(附完整代码)

本文提供了一份使用OpenCV实现全景图像柱面展开的详细教程。通过清晰的数学原理讲解和分步代码演示,指导读者快速掌握从等距柱状投影全景图到平面展开图的核心转换方法,并涵盖性能优化、参数调整及透视视图扩展等实用技巧,适合图像处理爱好者和开发者实践。

sony5的博客 573

DP_AUX辅助通道介绍

DisplayPort(简称DP)是一个由PC及芯片制造商联盟开发,视频电子标准协会(VESA)标准化的数字式视频接口标准。其中辅助通道(AUX)是一条独立双向半双工的传输通道,它也是一对差分信号线。其数据传输速率1Mbps,用来传输配置参数与指令。

cjie221的博客 5353

C++开源项目—2048.cpp

2048.cpp是一个适合C++入门学习的开源游戏项目,包含了基本的C++知识如lambda表达式、容器(vector/map)和模板等。该项目适合已阅读《C++Primer》的初学者,能帮助巩固知识并积累代码量。游戏在4x4方格中进行,通过WASD键移动合并数字方块,最终目标是合成2048。源码分析显示项目使用了大量C++11特性,推荐学习后可继续阅读jsoncpp项目。该项目支持克隆、CMake构建和修改调试,是理想的C++实践案例。

Amelio Ming 944

五.卡尔曼滤波器(EKF)开发实践之五: 编写自己的EKF替换robot_pose_ekf中EKF滤波器

本系列文章主要介绍如何在工程实践中使用卡尔曼滤波器,分五个小节介绍: 一.卡尔曼滤波器开发实践之一: 五大公式 二.卡尔曼滤波器开发实践之二: 一个简单的位置估计卡尔曼滤波器 三.卡尔曼滤波器(EKF)开发实践之三: 基于三个传感器的海拔高度数据融合 四.卡尔曼滤波器(EKF)开发实践之四: ROS系统位姿估计包robot_pose_ekf详解 五.卡尔曼滤波器(EKF)开发实践之五: 编写自己的EKF替换robot_pose_ekf中EKF滤波器 也就是本文 ...

okgwf的博客 4138

推荐项目:h5cpp - 现代C++的HDF5接口

推荐项目:h5cpp - 现代C++的HDF5接口 在数据存储的世界里,HDF5 是一个重量级选手,以其强大而灵活的数据模型著称。然而,想要充分利用其功能,开发者往往不得不面对不那么“现代”的C API。这一切,随着 h5cpp 的出现而改变了。 项目介绍 h5cpp —— 一个旨在提供现代C++风格封装的HDF5库。它不仅仅是一个简单的绑定或包装器,而是为开发人员打开了一扇通往HDF5高级特性的...

gitblog_00336的博客 658

uni-app - 修改默认端口,H5 项目控制台运行出来的 IP 地址的默认端口号(H5 项目运行后控制台,修改项目运行到内置浏览器默认 8080 端口号)

uni-app运行到H5修改默认端口号,uni-app如何修改默认端口,uni-app修改默认端口,uni-app本地运行修改调试已上线微信H5项目,请问在UNIAPP的H5运行调试时候的IP是否可以自己设置固定,uni-app开发环境,如何设置h5服务器的端口,uni-app如何修改默认端,uni-app修改默认端口,uni-app指定端口号,uni-app-修改H5项目控制台运行出来的IP地址的默认端口号(H5项目运行后控制台,修改项目运行到内置浏览器默认8080端口号)

🏆 前端领域优质创作者 9887

coze 的插件输入飞书多维表格 app_token 后一直显示错误,如何解决?

🏆本文收录于《优快云问答解惑-专业版》专栏,主要记录项目实战过程中的Bug之前因后果及提供真实有效的解决方案,希望能够助你一臂之力,帮你早日登顶实现财富自由🚀;同时,欢迎大家关注&&收藏&&订阅!持续更新中,up!up!up!!

**My Coding Family** 2669

visual studio c++单项目中存在多个cpp,怎么单独运行某个cpp

c++单项目中存在多个cpp,怎么单独运行某个cpp

代码的星辰大海 1万+

GIS数据处理-使用PostGIS+QGIS

GIS数据处理1-使用PostGIS+QGIS 介绍 这里我遇到了一个需求,需要将SQLite的.db数据库中GIS数据导出成shapefile文件 数据库的表结构如下所示,这里存储的是wkt格式的矢量数据 WKT与WKB格式介绍 OpenGIS 规范定义了两种表示空间对象的标准方式:Well-Know Text (WKT) 形式和Well-Known Binary(WKB) 形式。WKT 和 WKB 都包含矢量几何要素的对象类型和构成对象的坐标的信息。WKT可读性强,更方便阅读识别,而在实际数据传输和存

孙霸天的专栏 2528

Cppfront项目解析:探索下一代C++语法Cpp2的实验性编译器

C++作为一门历史悠久的编程语言,其语法复杂性一直是开发者们讨论的焦点。Cppfront项目正是为了解决这一问题而诞生的实验性项目,它提出了"C++语法2"(简称Cpp2)的概念,并提供了首个实现这一语法的编译器cppfront。 ## 项目核心概念 ### 什么是Cpp2? Cpp2是C++的一种替代语法设计,旨在提供: - 更简洁的代码表达方式 - 更强的类型安全性 - 更直观的语义表达...

gitblog_00137的博客 743

精雕软件(JDpaint) 5.5 绿色版

精雕软件(JDpaint) 5.5 绿色版精雕软件(JDpaint) 5.5 绿色版精雕软件(JDpaint) 5.5 绿色版

怀旧版电脑GBC模拟器

再现原版GBC掌机的界面,电脑运行连游戏界面都一样大小,很怀旧,很真实,(英文版)强烈推荐,很小的文件,很难才找到的。

探索C++编程的无限可能:Learn CPP项目推荐

探索C++编程的无限可能:Learn CPP项目推荐 项目介绍 Learn CPP 是一个专注于C++编程语言的开源项目,旨在为初学者和有经验的开发者提供一个全面的学习和实践平台。无论你是刚刚接触编程,还是希望深入了解C++的高级特性,这个项目都能满足你的需求。通过丰富的代码示例和详细的文档,Learn CPP帮助你逐步掌握C++的核心概念和应用技巧。 项目技术分析 语言特性 面向对象编程:C+...

gitblog_00317的博客 801

IEM模型,地表粗糙度模拟

利用微波反演土壤水分时候,利用IEM模型可以实现对随机地表的模拟

探索现代C++编程的艺术:Modern-CPP-Programming项目详解

在编程的世界里,C++始终以其强大的性能和灵活性吸引着众多开发者。然而,随着语言的发展,现代C++已经进化出了许多新的特性和最佳实践。是一个致力于展示这些新特性的开源项目,它将帮助你充分利用C++11、C++14、C++17乃至最新的C++20标准。 ## 项目简介 该项目由Federico Busato创建,它不仅是一份学习指南,更是一个实践平台。它涵盖了现代C++的各种主题,包括智能指针、

gitblog_00090的博客 763

LinePhone3.2.7(android端最新版本) sip客户端

SIP客户端For android:LinePhone3.2.7(最新版本),官网地址:http://www.linphone.org/

探索Go与C++的桥梁:go2cpp项目介绍

探索Go与C++的桥梁:go2cpp项目介绍 在编程语言的世界中,Go和C++各自占据着重要的位置。Go以其简洁和高效的并发处理能力著称,而C++则以其强大的性能和灵活性受到青睐。然而,将Go代码转换为C++代码一直是一个挑战。今天,我们将介绍一个令人兴奋的开源项目——go2cpp,它旨在通过C++20将Go代码编译为本地可执行文件,为开发者提供了一个全新的视角。 项目介绍 go2cpp是一个创新...

gitblog_00499的博客 674

C++算法学习库:algorithm-cpp 项目指南

C++算法学习库:algorithm-cpp 项目指南 项目概述 本教程旨在引导您深入了解 algorithm-cpp 开源项目,该项目位于 GitHub,它提供了一系列用C++编写的算法学习示例代码,覆盖了从基础到进阶的多个方面,如基本的编译流程管理、文件解析、基本语法教学、矩阵运算、机器学习算法实现等。 1. 项目目录结构及介绍 algorithm-cpp 的目录结构精心设计,便于学习者按...

gitblog_00047的博客 734

适合 C++ 新手学习的项目

适合 C++ 新手学习的项目

hw5230的博客 2407

CPP-Terminal 项目使用教程

CPP-Terminal 项目使用教程 1. 项目目录结构及介绍 CPP-Terminal 是一个用于编写跨平台终端应用程序的 C++ 库。以下是项目的目录结构及其主要文件的介绍: cpp-terminal/ ├── cmake/ ├── cpp-terminal/ │ ├── input.hpp │ ├── prompt.hpp │ ├── window.hpp │ ├── ve...

gitblog_00517的博客 999

CPP-Terminal 项目教程

CPP-Terminal 项目教程 1. 项目介绍 CPP-Terminal 是一个用于编写跨平台终端应用程序的 C++ 库。它遵循“零开销原则”,并限制外部包含文件为 C++ STL。CPP-Terminal 支持 Windows、Linux 和 MacOS,并提供统一的 API 跨所有平台。其主要功能包括颜色、键盘输入、终端大小调整处理以及其他常见的终端功能。此外,它还支持从 Windows ...

gitblog_00441的博客 788

Cpp2Uml是一个能够为C++项目生成UML模型文件的Eclipse插件

Cpp2Uml是一个能够为C++项目生成UML模型文件的Eclipse插件。

石头 2031

如何在CPP项目中使用CUDA

如何在CPP项目中使用CUDA1.创建一个CPP项目2.添加.cu/.cuh文件3.修改.cu文件4.工程配置修改5.配置成功 很多时候我们需要对一个cpp项目中某一部分进行GPU加速,如下步骤能够实现这一目的。 1.创建一个CPP项目 直接新建或者打开已有的cpp项目 2.添加.cu/.cuh文件 右键项目,新建或者添加已有的.cu/.cuh文件 3.修改.cu文件 注意:.cpp文件不能直接调用kernel函数(添加了cuda的头文件也不行) 于是我们可以以函数调用的方式,从.cpp文件调用一个.cu里

GLinttsd的博客 4398

Cpp_Empty_Project 项目使用教程

Cpp_Empty_Project 项目使用教程 1. 项目目录结构及介绍 Cpp_Empty_Project 是一个跨平台的基于 CMake 工具的 C++17 空项目。以下是项目的目录结构及其介绍: Cpp_Empty_Project/ ├── CMakeLists.txt ├── CMakeSettings.json ├── LICENSE ├── README.md ├── deps/ ├...

gitblog_00283的博客 973

CPP-Learning 项目教程

CPP-Learning 项目教程 1. 项目介绍 CPP-Learning 是一个专注于 C++ 编程语言学习的开源项目,旨在帮助初学者和中级开发者掌握 C++ 的核心概念和高级特性。项目由 ratansingh98 创建,提供了丰富的代码示例、练习题和教程,帮助用户通过实践来提升 C++ 编程技能。 2. 项目快速启动 2.1 环境准备 在开始之前,请确保你已经安装了以下工具: C++ 编译...

gitblog_00597的博客 916

Godot-CPP 项目教程

Godot-CPP 项目的目录结构如下: ``` godot-cpp/ ├── include/ │ └── godot/ ├── src/ ├── examples/ ├── misc/ ├── test/ ├── CMakeLists.txt ├── README.md ├── LICENSE.md └── SConstruct ``` ### 目录介绍: - **include/*...

gitblog_00694的博客 1106
上一篇: 刷题catcatcatcat
下一篇: C++八股
一只QAQ
博客等级 码龄6年 91粉丝 34原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值