## 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); // 链接时找到定义
}
- 模板不能这样分离
// 头文件 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
}
常量
- 常量指针(const pointer)
指针本身是常量,不能改变指向,但可以修改指向的值
int x = 10;
int y = 20;
int* const p = &x; // 常量指针:p 本身是 const
*p = 30; // 正确!可以修改指向的值(x 变成 30)
// p = &y; // 错误!不能改变指针的指向
// 记忆:const 在 * 后面,修饰 p(指针本身)
- 指针常量(pointer to const)
指针指向的值是常量,不能通过指针修改值,但可以改变指向
int x = 10;
int y = 20;
const int* p = &x; // 指针常量:指向的值是 const
// *p = 30; // 错误!不能通过 p 修改 x
p = &y; // 正确!可以改变指针的指向
// 记忆:const 在 * 前面,修饰 *p(指向的值)
- 引用(没有"引用常量")
引用本身就不能改变指向,所以:
int x = 10;
int y = 20;
int& ref = x; // 普通引用
ref = y; // 这是给 x 赋值,不是改变 ref 的指向!
// 引用一旦绑定,就不能重新绑定
// 所以没有"引用常量"这种说法
- 常量引用(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 不是依赖类型(不依赖模板参数)
}
- 直接使用模板参数
template <typename T>
class Box {
T data; // T 是依赖类型
T* ptr; // T* 是依赖类型
T& ref; // T& 是依赖类型
};
- 使用模板参数内部的类型
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 是类型
}
常见依赖类型场景
- 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;
}
}
- 分配器中的类型
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
- 普通 new(最常用)
// 分配内存 + 调用构造函数
Type* p = new Type; // 调用默认构造函数
Type* p = new Type(value); // 调用带参数的构造函数
Type* p = new Type(); // 调用默认构造函数(带括号)
- 数组 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;
- 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);
- 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 的几种形式
- 基本形式
void* operator new(size_t size);
// 分配 size 字节,返回 void*
- nothrow 形式
void* operator new(size_t size, const std::nothrow_t&);
// 失败时返回 nullptr,不抛异常
- 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++ 的作用域解析运算符,用来访问不同作用域里的东西
基本语法 作用域::成员
- 全局作用域
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(); // 调用全局函数
//^^
// 明确指定全局作用域
}
};
- 命名空间作用域
#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;
- 类的静态成员
class MyClass {
public:
static int count; // 静态成员变量
static void func() { // 静态成员函数
cout << "静态函数" << endl;
}
};
// 定义静态成员变量
int MyClass::count = 0;
int main() {
// 访问静态成员
MyClass::count = 10;
MyClass::func();
return 0;
}
- 类内类型
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;
}
- 类外定义成员函数
class MyClass {
public:
void func(); // 声明
static void staticFunc(); // 静态函数声明
};
// 在类外定义成员函数
void MyClass::func() {
// 用 :: 表示这是 MyClass 的 func
cout << "成员函数定义" << endl;
}
void MyClass::staticFunc() {
cout << "静态函数定义" << endl;
}
- 多重作用域
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; // 隐藏真实类型
}
常见句柄类型
- 文件句柄
// Windows 文件句柄
HANDLE fileHandle = CreateFile(...);
// fileHandle 不是文件本身,而是一个标识
// 使用文件
WriteFile(fileHandle, data, size, ...);
// 关闭文件
CloseHandle(fileHandle);
- 窗口句柄
// Windows 窗口句柄
HWND hwnd = CreateWindow(...);
// hwnd 是窗口的标识
// 操作窗口
ShowWindow(hwnd, SW_SHOW);
DestroyWindow(hwnd);
- 数据库连接句柄
// 数据库连接
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++ 可变参数模板的“声明”与“展开”的语法规则
声明参数包时,… 放在变量名前面。
展开参数包时,… 放在表达式后面。
- 声明参数包
template<typename T, typename... Args>
friend T* newElement(Args&&... args);
typename… Args:声明 Args 是一个类型参数包(包含 0 到 N 个类型)。… 写在 Args 前面。
Args&&… args:声明 args 是一个函数参数包(对应 Args 里的类型)。… 写在 args 前面。
这就像告诉编译器:“我要接收一堆参数,打包起来叫 args。”
- 展开参数包(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;
}
2391



被折叠的 条评论
为什么被折叠?



