一、引用与指针的比较
不同点:
1、引用只能在定义时初始化一次,之后不能改变使其指向其它变量,指针变量的值可变。
2、引用必须指向有效的变量,指针可以为空。
3、sizeof指针对象和引用对象的意义不一样。sizeof引用得到的是所指向的变量的大小,而sizeof指针是对象地址的大小。
4、指针和引用自增(++)自减(--)意义不一样。
5、相对而言,引用比指针更安全。
6、指针比引用更为灵活,但是其风险也很大。使用指针时一定要检查指针是否为空,且空间回收后指针最好置零,以免野指针造成的不安全因素。
相同点:
两者都是地址的概念,指针指向一块儿内存,其内容为所指内存的地址; 引用是某块儿内存的别名。
#include <iostream>
using namespace std;
int main() {
int a = 10;
int b = 20;
/* --- 指针 (Pointer) ---
指针变量的值可变,
引用必须指向有效的变量,指针可以为空 。
*/
int* ptr = &a; // 指针初始化:指向 a 的地址
cout << "ptr 指向 a: " << *ptr << endl; // 输出 10
ptr = &b; // ✅ 指针可以改变,现在指向 b
cout << "ptr 改为指向 b: " << *ptr << endl; // 输出 20
ptr = nullptr; // ✅ 指针可以为空
if (ptr == nullptr) {
cout << "ptr 是空指针" << endl;
}
/* --- 引用 (Reference) ---
引用只能在定义时初始化一次,之后不能改变使其指向其它变量。
*/
int& ref = a; // 引用必须在定义时初始化,且只能指向 a
cout << "ref 的值: " << ref << endl; // 输出 10
ref = b; // ❌ 注意!这不是改变引用的指向,而是给 a 赋值!
// 因为 ref 是 a 的别名,所以这等价于 a = b;
cout << "a 被 ref 修改后: " << a << endl; // 输出 20(a 的值变了)
// ref = c; // ❌ 无法让 ref 指向另一个变量 c(语法错误)
// int& ref2; // ❌ 引用必须初始化,不能定义空引用
/* --- sizeof 比较 ---
sizeof指针对象和引用对象的意义不一样。sizeof引用得到的是所指向的变量的大小,
而sizeof指针是对象地址的大小。
*/
cout << "sizeof(int): " << sizeof(int) << endl; // 通常为 4
cout << "sizeof(ptr): " << sizeof(ptr) << endl; // 通常为 8(64位系统地址大小)
cout << "sizeof(ref): " << sizeof(ref) << endl; // 4!因为 ref 是 a 的别名,等价于 sizeof(a)
/* --- 自增(++) 操作比较 ---
指针和引用自增(++)自减(--)意义不一样。相对而言,引用比指针更安全 。
*/
int x = 5;
int y = 5;
int* p = &x;
int& r = y;
p++; // ✅ 指针自增:p 现在指向下一个 int 位置(地址 +4 或 +8)
r++; // ✅ 引用自增:等价于 y++,y 的值变成 6
cout << "x (after p++): " << x << endl; // x 仍是 5(指针移动不影响 x)
cout << "y (after r++): " << y << endl; // y 变成 6
return 0;
}
二、return a > b ? a : b;
- 使用了 三目运算符(条件运算符)含义是:如果
a > b成立,返回a,否则返回b
等价于
if (a > b)
return a;
else
return b;
三、关键字explicit(明确的)/extern(外部的)
explicit作用是阻止不应该允许的经过转换构造函数进行的隐式转换的发生,声明为 explicit 的构造函数不能在隐式转换中使用。C++中,一个参数的构造函数(或者除了第一个参数外其余参数都有默认值的多参构造函数), 承担了两个角色:
一个是用于对象构造,另一个是默认且隐含的类型转换操作符。
在构造函数前面加上 explicit 修饰, 指定这个构造函数只能被明确的调用,不能作为类
型转换操作符被隐含的使用,因而 explicit 构造函数是用来防止隐式转换的。
代码示例
#include <iostream>
using namespace std;
class Test1 {
public:
Test1(int num) : n(num) { } // 普通构造函数
private:
int n;
};
class Test2 {
public:
explicit Test2(int num) : n(num) { } // 加了 explicit
private:
int n;
};
int main() {
// ✅ Test1:允许隐式转换(隐式调用构造函数)
Test1 t1 = 12; // OK:拷贝初始化,隐式调用 Test1(int)
Test1 t1_2(15); // OK:直接初始化
Test1 t1_3{16}; // OK:列表初始化
// ✅ Test2:explicit 禁止隐式转换
Test2 t2(13); // OK:直接初始化
Test2 t2_2{14}; // OK:列表初始化
// ❌ 下面这行会报错(如果取消注释)
// Test2 t3 = 14; // ERROR:隐式转换被 explicit 禁止!
return 0;
}
extern(外部的)声明变量或函数为外部链接,即该变量或函数名在其它文件中可见。定义在其它文件中的函数和变量,可以使用两种方法调用:
一 、使用头文件调用,这时候,函数和变量必须在头文件中定义和声明。
二、使用extern关键字调用,这时候函数和变量在.cpp或者.c文件中定义和声明。
例如,我们首先创建一个文件test_extern.cpp,在其中加入如下的代码:
#include <iostream>
void fun()
{
std::cout << "I am in file test_extem" << std::endl;
}
另外新建一个文件2-6.cpp,在其中想要调用fun函数,可以使用如下的调用方法:
#include <iostream>
extern void fun();
int main()
{
fun();
return 1;
}
char[] → 输出内容 "abc"
char a[] = "abc";
char* ptr = a;
cout << ptr << endl; // 输出: abc
int[] → 输出地址(如 0x7fff...)
int a[] = {1,2,3};
int* ptr = a;
cout << ptr << endl; // 输出: 0x7fffc5a458ac (内存地址)
根本原因:cout 对不同指针类型的重载规则
std::cout 在遇到指针时,会根据指针所指向的数据类型来决定是:
- 输出 内存地址,还是
- 输出 指向的内容(如字符串)
📌 规则如下:
| 指针类型 | cout << ptr 输出什么? | 原因 |
|---|---|---|
char* 或 const char* | 字符串内容(如 "abc") | 被当作 C 风格字符串处理 |
int*, double*, void* 等 | 内存地址(如 0x7fff...) | 输出指针本身的值 |
四、if(v1 == NULL) 可以用 if(!v1)代替
五、什么是野指针?
野指针是指:指向的内存已经被释放或不再有效,但指针本身没有被置空,仍然保留着旧的地址。
⚠️ 危险:如果你通过野指针访问或修改内存,会导致 未定义行为(Undefined Behavior),可能:
- 程序崩溃(段错误)
- 数据被意外修改
- 看似正常,实则埋下隐患
🧨 野指针的常见来源
❌ 1. 指针指向栈内存(局部变量),函数返回后失效
#include <iostream>
using namespace std;
int* getPtr() {
int x = 100;
return &x; // ❌ 危险!x 是局部变量,函数结束时被销毁
}
int main() {
int* p = getPtr();
cout << *p << endl; // ❌ 未定义行为!p 是野指针
return 0;
}
💥 问题:
x是getPtr()函数的局部变量,存储在栈上。函数结束后,x的内存被回收,p指向的地址已无效。
❌ 2. 动态内存释放后,指针未置空
#include <iostream>
using namespace std;
int main() {
int* ptr = new int(42); // 分配内存
cout << *ptr << endl; // 输出 42
delete ptr; // 释放内存
// ptr 现在变成野指针!
// delete ptr; // ❌ 如果再 delete 一次,会崩溃!
cout << *ptr << endl; // ❌ 未定义行为!访问已释放内存
*ptr = 100; // ❌ 更糟:写入已释放内存,可能破坏其他数据
return 0;
}
✅ 正确做法:释放后立即将指针置空
delete ptr;
ptr = nullptr; // 或 NULL
这样后续判断 if(ptr) 就不会误用。
❌ 3. 多个指针指向同一块内存,其中一个释放后,其他变成野指针
#include <iostream>
using namespace std;
int main() {
int* p1 = new int(99);
int* p2 = p1; // p2 也指向同一块内存
delete p1; // 释放内存
p1 = nullptr; // p1 置空
// p2 仍然是野指针!
cout << *p2 << endl; // ❌ 未定义行为!
return 0;
}
💡 这是使用裸指针时的常见陷阱。解决方案:使用智能指针(如
std::shared_ptr)。
❌ 4. 指向临时对象的指针
#include <iostream>
using namespace std;
const char* getString() {
return "hello world"; // ✅ 安全:字符串字面量存储在常量区,不会被销毁
}
const char* getBadString() {
char temp[] = "temporary";
return temp; // ❌ 危险!temp 是局部数组,函数返回后失效
}
int main() {
const char* str = getBadString();
cout << str << endl; // ❌ 野指针:输出乱码或崩溃
return 0;
}
六、void* 指 针
void*指针是一种特殊的指针类型,可用于存放任意对象的地址,但是丢失了类型信息。如果想要完整的提取指向的数据,程序员就必须对这个指针做出正确的类型转换,然后再解引用,编译器不允许直接对void*类型的指针做解指针操作〔〈提示非法的间接寻址)。malloc做内存申请返回的就是 void*指针。
-
void*是“无类型指针”或“通用指针” -
它可以指向任何类型的数据(
int、double、string等) -
但它不携带类型信息,所以:
-
❌ 不能直接解引用(
*ptr) -
❌ 不能进行指针算术(
ptr++) -
✅ 必须先转换为具体类型的指针,才能使用示例代码:
-
-
示例代码:
#include <iostream>
#include <cstdlib> // for malloc, free
using namespace std;
int main() {
int a = 10;
double b = 3.14;
char c = 'X';
// --- 1. void* 可以指向任意类型 ---
void* ptr;
ptr = &a; // 指向 int
cout << "void* 指向 int: " << ptr << endl;
ptr = &b; // 指向 double
cout << "void* 指向 double: " << ptr << endl;
ptr = &c; // 指向 char
cout << "void* 指向 char: " << ptr << endl;
// --- 2. 不能直接解引用 void* ---
// cout << *ptr << endl; // ❌ 编译错误!非法间接寻址
// --- 3. 必须先类型转换,再解引用 ---
ptr = &a; // 让 ptr 指向 a
// 将 void* 转换为 int*,然后解引用
int* intPtr = (int*)ptr; // C 风格转换
// 或者:int* intPtr = static_cast<int*>(ptr); // C++ 风格转换
cout << "通过 int* 访问: " << *intPtr << endl; // 输出 10
// --- 4. malloc 返回 void* ---
int* dynamicArray = (int*)malloc(5 * sizeof(int)); // 分配 5 个 int
if (dynamicArray == nullptr) {
cout << "内存分配失败!" << endl;
return 1;
}
// 初始化数组
for (int i = 0; i < 5; i++) {
dynamicArray[i] = i * 10;
}
// 输出数组
cout << "动态数组内容: ";
for (int i = 0; i < 5; i++) {
cout << dynamicArray[i] << " ";
}
cout << endl;
// 释放内存
free(dynamicArray);
dynamicArray = nullptr; // 避免野指针
return 0;
}
✅ 输出结果
void* 指向 int: 0x7fff5fbff5a4
void* 指向 double: 0x7fff5fbff5a8
void* 指向 char: 0x7fff5fbff5ad
通过 int* 访问: 10
动态数组内容: 0 10 20 30 40
关键点解析
| 代码 | 说明 |
|---|---|
void* ptr; | 声明一个通用指针,可存任何地址 |
ptr = &a; | void* 可自动接收任何类型地址 |
*ptr | ❌ 错误!编译器不知道 ptr 指向的是 int 还是 double |
(int*)ptr | ✅ 类型转换:告诉编译器“我知道它是 int*” |
malloc(...) | 返回 void*,必须转换为实际类型 |
free(ptr) | 释放 malloc 分配的内存 |
七、智能指针
C++11 引入了三种智能指针:std::shared_ptr、std::unique_ptr 和 std::weak_ptr,它们用于自动管理动态内存,避免内存泄漏和野指针。
示例
1. std::unique_ptr —— 独占所有权
特点:一个对象只能被一个
unique_ptr拥有,不能复制,但可以移动。
✅ 使用场景:
- 独占资源(如文件句柄、网络连接)
- 替代裸指针进行动态内存管理
🧪 示例代码:
#include <iostream>
#include <memory>
using namespace std;
int main() {
// 创建 unique_ptr,独占 int 对象
unique_ptr<int> ptr1 = make_unique<int>(42);
cout << *ptr1 << endl; // 输出: 42
// ❌ 不能复制
// unique_ptr<int> ptr2 = ptr1; // 编译错误!
// ✅ 可以移动(转移所有权)
unique_ptr<int> ptr2 = move(ptr1); // ptr1 失去所有权
cout << *ptr2 << endl; // 输出: 42
// cout << *ptr1 << endl; // ❌ 运行时错误!ptr1 为空
// 当 ptr2 离开作用域时,自动 delete 内存
return 0;
}
✅ 优点:轻量、高效,零成本抽象
❌ 限制:不能共享
2. std::shared_ptr——共享所有权
特点:多个
shared_ptr可以共享同一个对象,使用引用计数管理内存。
✅ 使用场景:
- 多个部分需要共享同一个对象
- 对象生命周期不确定
🧪 示例代码:
#include <iostream>
#include <memory>
using namespace std;
int main() {
// 创建 shared_ptr
shared_ptr<int> ptr1 = make_shared<int>(100);
cout << "引用计数: " << ptr1.use_count() << endl; // 1
{
shared_ptr<int> ptr2 = ptr1; // 共享所有权
cout << "引用计数: " << ptr1.use_count() << endl; // 2
cout << "值: " << *ptr2 << endl; // 输出: 100
} // ptr2 离开作用域,引用计数减1
cout << "引用计数: " << ptr1.use_count() << endl; // 1
cout << "值: " << *ptr1 << endl; // 输出: 100
// 当 ptr1 离开作用域时,引用计数变为 0,自动释放内存
return 0;
}
✅ 优点:支持共享,自动管理生命周期
⚠️ 注意:循环引用会导致内存泄漏(见weak_ptr)
3. std::weak_ptr —— 弱引用(不增加引用计数)
特点:观察
shared_ptr管理的对象,但不拥有它,不会增加引用计数。
✅ 使用场景:
- 解决
shared_ptr的循环引用问题 - 缓存、监听器、观察者模式
🧪 示例:循环引用问题
#include <iostream>
#include <memory>
using namespace std;
struct Node {
int data;
shared_ptr<Node> next;
shared_ptr<Node> prev;
Node(int d) : data(d) {
cout << "Node " << data << " created\n";
}
~Node() {
cout << "Node " << data << " destroyed\n";
}
};
int main() {
auto node1 = make_shared<Node>(1);
auto node2 = make_shared<Node>(2);
node1->next = node2;
node2->prev = node1; // ❌ 循环引用!
// 即使 main 结束,node1 和 node2 的引用计数仍为 1
// 导致内存泄漏!析构函数不会被调用
return 0;
}
❌ 输出:
Node 1 created
Node 2 created
// 没有 "destroyed" 输出!内存泄漏!
✅ 使用 weak_ptr 解决循环引用
struct Node {
int data;
shared_ptr<Node> next;
weak_ptr<Node> prev; // 改为 weak_ptr
Node(int d) : data(d) {
cout << "Node " << data << " created\n";
}
~Node() {
cout << "Node " << data << " destroyed\n";
}
};
int main() {
auto node1 = make_shared<Node>(1);
auto node2 = make_shared<Node>(2);
node1->next = node2;
node2->prev = node1; // weak_ptr 不增加引用计数
// main 结束时,引用计数正确归零
return 0;
}
✅ 输出:
Node 1 created
Node 2 created
Node 2 destroyed
Node 1 destroyed
如何访问 weak_ptr 指向的对象?
weak_ptr<Node> wptr = node1->next;
if (auto ptr = wptr.lock()) { // lock() 返回 shared_ptr
cout << ptr->data << endl; // 安全访问
} else {
cout << "对象已被释放\n";
}
三者对比总结
| 特性 | unique_ptr | shared_ptr | weak_ptr |
|---|---|---|---|
| 所有权 | 独占 | 共享 | 无(观察者) |
| 引用计数 | ❌ 无 | ✅ 有 | ✅ 有(但不增加) |
| 可复制 | ❌ | ✅ | ✅(但不增加计数) |
| 性能 | ⚡ 最快 | 中等 | 轻量 |
| 用途 | 独占资源 | 共享资源 | 解决循环引用 |
| 是否释放内存 | ✅ 自动 | ✅ 引用计数为0时 | ❌ 不释放 |
✅ 最佳实践建议
-
优先使用
make_unique和make_shareauto ptr = make_unique<int>(42); auto sptr = make_shared<string>("hello"); -
避免裸
new和delete// ❌ 不要 int* p = new int(10); delete p; // ✅ 推荐 auto p = make_unique<int>(10);
八、什么是 this 指针?
在 C++中,每一个对象都能通过 this 指针来访问自己的地址。this 指针是所有非静态 成员函数的隐含参数。因此,在成员函数内部,它可以用来指向调用对象。
this是一个隐含的指针,存在于每个非静态成员函数中- 它指向调用该成员函数的那个对象
- 它的作用是:让成员函数知道自己在为“谁”工作
this是const指针,不能被修改(即this = nullptr;是非法的)
示例 1:基本用法 —— 区分成员变量和参数
#include <iostream>
using namespace std;
class Person {
private:
string name;
public:
// 构造函数:参数名和成员变量名相同
Person(string name) {
this->name = name; // this->name 是成员变量,右边的 name 是参数
}
void print() {
cout << "Name: " << this->name << endl;
}
// 返回当前对象的地址
Person* getAddress() {
return this; // 返回调用该函数的对象的地址
}
};
int main() {
Person p1("Alice");
Person p2("Bob");
cout << "p1 地址: " << &p1 << endl;
cout << "p2 地址: " << &p2 << endl;
cout << "p1.getAddress(): " << p1.getAddress() << endl; // 应该等于 &p1
cout << "p2.getAddress(): " << p2.getAddress() << endl; // 应该等于 &p2
return 0;
}
输出:
p1 地址: 0x7fff5fbff5a0
p2 地址: 0x7fff5fbff5b0
p1.getAddress(): 0x7fff5fbff5a0
p2.getAddress(): 0x7fff5fbff5b0
✅
this指向了调用getAddress()的对象
示例 2:链式调用(Method Chaining)
this 常用于返回当前对象,实现链式调用(如 cout << a << b)
class Calculator {
private:
int value;
public:
Calculator(int v = 0) : value(v) {}
// 每个函数返回 *this(当前对象的引用)
Calculator& add(int x) {
value += x;
return *this; // 返回当前对象的引用
}
Calculator& multiply(int x) {
value *= x;
return *this;
}
int getResult() const {
return value;
}
void print() const {
cout << "Current value: " << value << endl;
}
};
int main() {
Calculator calc(5);
// 链式调用
calc.add(3).multiply(2).print(); // 相当于 ((calc.add(3)).multiply(2)).print()
cout << "Final result: " << calc.getResult() << endl; // 16
return 0;
}
输出:
Current value: 16
Final result: 16
✅
return *this;实现了流畅的链式 API
示例 3:重载赋值运算符(=)
this 在运算符重载中非常关键
class MyString {
private:
char* data;
public:
MyString(const char* str = "") {
data = new char[strlen(str) + 1];
strcpy(data, str);
}
// 析构函数
~MyString() {
delete[] data;
}
// 赋值运算符重载
MyString& operator=(const MyString& other) {
// 1. 检查自赋值
if (this == &other) { // 比较地址
return *this;
}
// 2. 释放旧内存
delete[] data;
// 3. 分配新内存并复制
data = new char[strlen(other.data) + 1];
strcpy(data, other.data);
// 4. 返回当前对象
return *this;
}
void print() const {
cout << data << endl;
}
};
int main() {
MyString s1("Hello");
MyString s2("World");
s1 = s2; // 触发 operator=
s1.print(); // 输出: World
s1 = s1; // 自赋值,this == &other,安全跳过
s1.print();
return 0;
}
✅
this == &other防止自赋值导致的内存错误
示例 4:this 是隐含参数
class Test {
public:
void func() {
cout << "this 指向的地址: " << this << endl;
}
};
int main() {
Test t1, t2;
cout << "t1 的地址: " << &t1 << endl;
cout << "t2 的地址: " << &t2 << endl;
t1.func(); // 编译器实际调用 func(&t1)
t2.func(); // 编译器实际调用 func(&t2)
return 0;
}
🔥 本质:成员函数的调用是:
t1.func(); // 相当于 func(&t1)
this 指针的关键特性总结
| 特性 | 说明 |
|---|---|
| 隐含参数 | 所有非静态成员函数都有 this |
| 指向调用者 | this 指向调用该函数的对象 |
| 类型 | T* const(不能修改指针本身) |
| 不能用于静态函数 | 静态函数不依赖具体对象 |
| 可用于返回自身 | 实现链式调用 |
| 可用于自赋值检查 | if (this == &other) |
什么时候不能使用 this?
class Test {
public:
static void staticFunc() {
// cout << this; // ❌ 错误!静态函数没有 this 指针
}
};
九、虚函数和虚函数表
用「遥控器控制不同品牌的电视」来讲解 虚函数和虚函数表(vtable)。
场景:你有一个万能遥控器
- 你家有两台电视:一台是 小米电视,一台是 索尼电视
- 你只有一个通用遥控器(比如叫“智能遥控器”)
- 这个遥控器有「开机」、「关机」、「调音量」等按钮
- 虽然遥控器按钮一样,但按下时,不同电视的反应不一样
👉 这就像 C++ 中的 多态(Polymorphism)
用代码模拟这个场景
#include <iostream>
using namespace std;
// 基类:电视
class TV {
public:
// 虚函数:表示“这个行为可以被不同品牌定制”
virtual void turnOn() {
cout << "TV is turning on..." << endl;
}
virtual void turnOff() {
cout << "TV is turning off..." << endl;
}
virtual void adjustVolume(int level) {
cout << "Volume set to " << level << endl;
}
// 析构函数也要是虚的(重要!)
virtual ~TV() {
cout << "TV cleaned up." << endl;
}
};
// 派生类:小米电视
class XiaomiTV : public TV {
public:
void turnOn() override {
cout << "小米电视开机:滴!欢迎回家~" << endl;
}
void turnOff() override {
cout << "小米电视关机:晚安,明天见!" << endl;
}
void adjustVolume(int level) override {
cout << "小米电视:音量调整到 " << level << ",护眼模式已开启" << endl;
}
~XiaomiTV() override {
cout << "小米电视已关闭电源。";
}
};
// 派生类:索尼电视
class SonyTV : public TV {
public:
void turnOn() override {
cout << "索尼电视开机:Hello, 索尼为您服务!" << endl;
}
void turnOff() override {
cout << "索尼电视关机:Goodbye!" << endl;
}
void adjustVolume(int level) override {
cout << "索尼电视:音量调整到 " << level << ",影院模式启动" << endl;
}
~SonyTV() override {
cout << "索尼电视已断电。";
}
};
主函数:用同一个遥控器控制不同电视
int main() {
// 创建两台电视
XiaomiTV xiaomi;
SonyTV sony;
// 遥控器(TV* 类型的指针)
TV* remoteControl;
cout << "=== 用同一个遥控器控制小米电视 ===" << endl;
remoteControl = &xiaomi; // 遥控器对准小米电视
remoteControl->turnOn(); // 按下“开机”按钮
remoteControl->adjustVolume(5); // 调音量
remoteControl->turnOff(); // 关机
cout << "\n=== 用同一个遥控器控制索尼电视 ===" << endl;
remoteControl = &sony; // 遥控器对准索尼电视
remoteControl->turnOn();
remoteControl->adjustVolume(7);
remoteControl->turnOff();
return 0;
}
输出结果:
=== 用同一个遥控器控制小米电视 ===
小米电视开机:滴!欢迎回家~
小米电视:音量调整到 5,护眼模式已开启
小米电视关机:晚安,明天见!
=== 用同一个遥控器控制索尼电视 ===
索尼电视开机:Hello, 索尼为您服务!
索尼电视:音量调整到 7,影院模式启动
索尼电视关机:Goodbye!
✅ 虽然你用的是同一个遥控器(TV*),但它能正确控制不同的电视!
背后发生了什么?—— 虚函数表(vtable)
你可以把每台电视想象成:
| 电视品牌 | 内部有一张“操作说明书”(vtable) |
|---|---|
| 小米电视 | 开机:播放“滴!欢迎回家~”<br>关机:说“晚安” |
| 索尼电视 | 开机:说“Hello, 索尼”<br>关机:说“Goodbye” |
当你按下遥控器的「开机」按钮时:
- 遥控器问:“你现在对着哪台电视?”
- 找到那台电视的 “操作说明书”(vtable)
- 查说明书里的“开机”那一行,执行对应动作
📌 这就是 虚函数表(vtable) 的作用!
如果没有 virtual 会怎样?
如果你去掉 virtual,就像遥控器“死记硬背”了某个电视的操作方式。
void turnOn() { // 没有 virtual
cout << "TV is turning on..." << endl; // 所有电视都这样
}
那么不管你对准小米还是索尼,按“开机”都只会输出:
TV is turning on...
❌ 失去了“智能”和“个性化”能力。
十、虚函数与纯虚函数的区别
核心区别:
| 类型 | 是否必须实现 | 是否允许实例化类 | 用途 |
|---|---|---|---|
| 虚函数 | ✅ 基类可以有默认实现 | ✅ 可以创建对象 | 允许派生类重写(override) |
| 纯虚函数 | ❌ 基类不实现(或可选) | ❌ 不能创建对象(抽象类) | 强制派生类必须实现 |
示例:
动物会叫,但“叫”这个行为每个动物都不同
场景设定:
- 所有动物都会“叫”,但叫声不同
- 我们希望强制每种动物必须自己实现“叫”这个行为
- 同时提供一些通用功能(如吃东西)
1. 虚函数(Virtual Function)
#include <iostream>
using namespace std;
// 基类:动物
class Animal {
public:
// 虚函数:有默认实现
virtual void makeSound() {
cout << "Animal makes a generic sound" << endl;
}
// 普通函数
void eat() {
cout << "Animal is eating" << endl;
}
virtual ~Animal() = default;
};
// 派生类:狗
class Dog : public Animal {
public:
void makeSound() override {
cout << "Woof! Woof!" << endl;
}
};
// 派生类:猫
class Cat : public Animal {
public:
void makeSound() override {
cout << "Meow~" << endl;
}
};
✅ 特点:
makeSound()是虚函数,有默认实现- 派生类可以重写,也可以不重写
- 可以创建
Animal对象
int main() {
Animal a; // ✅ 可以创建对象
Dog dog;
Cat cat;
a.makeSound(); // 输出: Animal makes a generic sound
Animal* ptr;
ptr = &dog;
ptr->makeSound(); // 输出: Woof! Woof!
ptr = &cat;
ptr->makeSound(); // 输出: Meow~
return 0;
}
✅ 虚函数:“你可以重写,但我不强制”
2. 纯虚函数(Pure Virtual Function)
现在我们改一下需求:
“所有动物必须自己定义怎么叫,不允许用默认叫声!”
这就需要用 纯虚函数。
#include <iostream>
using namespace std;
// 抽象基类:动物
class Animal {
public:
// 纯虚函数:没有实现,强制派生类重写
virtual void makeSound() = 0; // = 0 表示纯虚函数
// 普通函数(可以有实现)
void eat() {
cout << "Animal is eating" << endl;
}
// 析构函数也可以是虚的(推荐)
virtual ~Animal() = default;
};
// 派生类:狗
class Dog : public Animal {
public:
void makeSound() override {
cout << "Woof! Woof!" << endl;
}
};
// 派生类:猫
class Cat : public Animal {
public:
void makeSound() override {
cout << "Meow~" << endl;
}
};
❌ 注意:不能创建 Animal 对象!
int main() {
// Animal a; // ❌ 编译错误!Animal 是抽象类,不能实例化
Dog dog;
Cat cat;
Animal* ptr;
ptr = &dog;
ptr->makeSound(); // 输出: Woof! Woof!
ptr = &cat;
ptr->makeSound(); // 输出: Meow~
ptr->eat(); // ✅ 可以调用非纯虚函数
return 0;
}
✅ 纯虚函数:“你必须实现,否则不许出生!”
🚫 如果派生类没实现纯虚函数?
class Bird : public Animal {
// 没有实现 makeSound()
};
// Bird b; // ❌ 编译错误!Bird 也是抽象类,不能创建对象
Bird 因为没实现
makeSound(),所以它也是抽象类,不能实例化。
只有实现了所有纯虚函数的类,才能创建对象。
虚函数 vs 纯虚函数 对比表
| 特性 | 虚函数 | 纯虚函数 |
|---|---|---|
| 语法 | virtual void func(); | virtual void func() = 0; |
| 是否有实现 | ✅ 可以有默认实现 | ❌ 基类无实现(或可选) |
| 派生类是否必须重写 | ❌ 可选 | ✅ 必须重写 |
| 所在类是否可实例化 | ✅ 可以 | ❌ 不能(抽象类) |
| 用途 | 提供可选的多态行为 | 定义接口/契约,强制实现 |
| 类似概念 | 可选插件 | 必须实现的接口 |
十一、继承与虚继承
问题背景:钻石继承(Diamond Problem)
想象一下,你正在设计一个游戏角色系统:
- 所有角色都有
生命值(HP) 战士和法师都是角色- 现在要创建一个
骑士,他既是战士又是法师(又肉又能打) - 但问题来了:如果
战士和法师都继承自角色,那么骑士就会两次继承角色,导致HP出现两份!
这就是著名的 “钻石继承问题”。
❌ 示例 1:普通多继承 → 问题出现
#include <iostream>
using namespace std;
// 基类:角色
class Character {
protected:
int hp;
public:
Character(int h = 100) : hp(h) {
cout << "Character constructor" << endl;
}
void takeDamage(int dmg) {
hp -= dmg;
cout << "HP: " << hp << endl;
}
};
// 战士
class Warrior : public Character {
public:
Warrior() : Character(150) {
cout << "Warrior constructor" << endl;
}
void attack() {
cout << "Warrior attacks with sword!" << endl;
}
};
// 法师
class Mage : public Character {
public:
Mage() : Character(80) {
cout << "Mage constructor" << endl;
}
void castSpell() {
cout << "Mage casts fireball!" << endl;
}
};
// 骑士:既是战士又是法师(多继承)
class Knight : public Warrior, public Mage {
public:
Knight() {
cout << "Knight constructor" << endl;
}
};
❌ 测试代码:问题暴露
int main() {
Knight k;
// k.hp; // ❌ 编译错误!ambiguous:到底是谁的 hp?
// 下面两行也无法确定调用哪个父类的 takeDamage
// k.takeDamage(10); // ❌ 错误!不明确
return 0;
}
💥 问题:
Knight继承了 两份Character(一份来自Warrior,一份来自Mage)hp成员变量有两份,编译器不知道你要访问哪一个- 这就是 二义性(Ambiguity)
解决方案:虚继承(Virtual Inheritance)
我们要告诉编译器:“Warrior 和 Mage 在继承 Character 时,要用共享方式,只保留一份 Character”。
✅ 修改代码:使用 virtual 继承
#include <iostream>
using namespace std;
// 基类:角色
class Character {
protected:
int hp;
public:
Character(int h = 100) : hp(h) {
cout << "Character constructor" << endl;
}
void takeDamage(int dmg) {
hp -= dmg;
cout << "HP: " << hp << endl;
}
};
// 战士(虚继承)
class Warrior : virtual public Character {
public:
Warrior() : Character(150) { // 注意:现在由最派生类负责初始化 Character
cout << "Warrior constructor" << endl;
}
void attack() {
cout << "Warrior attacks with sword!" << endl;
}
};
// 法师(虚继承)
class Mage : virtual public Character {
public:
Mage() : Character(80) { // 这里 Character 构造函数不会被调用
cout << "Mage constructor" << endl;
}
void castSpell() {
cout << "Mage casts fireball!" << endl;
}
};
// 骑士
class Knight : public Warrior, public Mage {
public:
Knight() : Character(200) { // ✅ 最终由 Knight 初始化 Character
cout << "Knight constructor" << endl;
}
};
int main() {
Knight k;
k.takeDamage(30); // ✅ 正确!只有一份 hp
k.attack(); // 输出: Warrior attacks with sword!
k.castSpell(); // 输出: Mage casts fireball!
cout << "Final HP: ";
k.takeDamage(20); // HP: 150
return 0;
}
输出:
Character constructor
Warrior constructor
Mage constructor
Knight constructor
HP: 170
Warrior attacks with sword!
Mage casts fireball!
HP: 150
✅ 成功解决二义性!
Knight只有一份Character!
虚继承的工作原理
| 普通继承 | 虚继承 |
|---|---|
Warrior 有自己的 Character 子对象 | Warrior 不直接拥有 Character,而是通过指针共享 |
Mage 也有自己的 Character 子对象 | Mage 也通过指针共享同一个 Character |
Knight 包含两个 Character | Knight 只包含一个 Character,被 Warrior 和 Mage 共享 |
生活比喻
| 场景 | 类比 |
|---|---|
| 普通多继承 | 你有两个身份证(一个来自爸爸,一个来自妈妈),办事时别人不知道用哪个 |
| 虚继承 | 你只有一个官方身份证,爸爸和妈妈都引用它,信息一致,无冲突 |
总结
| 概念 | 关键点 |
|---|---|
| 多继承 | 一个类继承多个父类,可能引发二义性 |
| 虚继承 | 使用 virtual 关键字,确保公共基类只被继承一次 |
| 钻石问题 | 多继承中公共基类被多次继承,导致成员重复 |
| 解决方案 | 使用虚继承 + 最派生类初始化基类 |
十二、友元函数(friend function) 和 友元类(friend class)
什么是“友元”?
在 C++ 中,类的 private 和 protected 成员是对外隐藏的,外部不能直接访问。
但有时候,你希望某些特定的函数或类能“破例”访问这些私有成员 —— 这就是 友元(friend) 的作用。
💡 友元就像你家的“信任名单”:
“虽然我家门锁着,但你是我的朋友,我允许你进来。”
示例 1:友元函数(Friend Function)
场景:一个 BankAccount 类,想让一个外部函数打印账户余额(即使是私有的)
#include <iostream>
using namespace std;
// 前向声明(因为 AccountPrinter 用到了 BankAccount)
class BankAccount;
// 外部函数:打印账户信息
void printAccountInfo(const BankAccount& account);
// 银行账户类
class BankAccount {
private:
string owner;
double balance;
public:
BankAccount(string o, double b) : owner(o), balance(b) {}
// 声明友元函数:允许它访问 private 成员
friend void printAccountInfo(const BankAccount& account);
// 普通成员函数
void deposit(double amount) {
balance += amount;
}
};
// 实现友元函数
void printAccountInfo(const BankAccount& account) {
// ✅ 可以访问 private 成员!
cout << "Owner: " << account.owner << endl;
cout << "Balance: $" << account.balance << endl;
}
测试代码:
int main() {
BankAccount acc("Alice", 1000.0);
// 调用友元函数
printAccountInfo(acc);
return 0;
}
输出:
Owner: Alice
Balance: $1000
printAccountInfo 不是成员函数,但因为是“友元”,所以能访问 owner 和 balance!
示例 2:友元类(Friend Class)
场景:银行系统中,AccountManager 类需要管理所有账户的私有信息
#include <iostream>
using namespace std;
class BankAccount; // 前向声明
// 账户管理员类
class AccountManager {
public:
void viewBalance(const BankAccount& acc);
void freezeAccount(BankAccount& acc);
};
// 银行账户类
class BankAccount {
private:
string owner;
double balance;
bool isFrozen;
public:
BankAccount(string o, double b)
: owner(o), balance(b), isFrozen(false) {}
void deposit(double amount) {
if (!isFrozen) balance += amount;
}
void withdraw(double amount) {
if (!isFrozen && balance >= amount)
balance -= amount;
}
// 声明友元类:AccountManager 可以访问所有 private 成员
friend class AccountManager;
};
// 实现 AccountManager 的成员函数
void AccountManager::viewBalance(const BankAccount& acc) {
// ✅ 访问 private 成员
cout << "Manager sees: " << acc.owner
<< "'s balance = $" << acc.balance
<< " (Frozen: " << (acc.isFrozen ? "Yes" : "No") << ")"
<< endl;
}
void AccountManager::freezeAccount(BankAccount& acc) {
acc.isFrozen = true; // ✅ 修改 private 成员
cout << "Account frozen!" << endl;
}
测试代码:
int main() {
BankAccount acc("Bob", 5000.0);
AccountManager manager;
manager.viewBalance(acc); // 查看余额
manager.freezeAccount(acc); // 冻结账户
// 尝试取钱(应该失败)
acc.withdraw(100); // withdraw 内部检查 isFrozen
// 不会成功,但不会报错
return 0;
}
输出:
Manager sees: Bob's balance = $5000 (Frozen: No)
Account frozen!
✅
AccountManager类的所有成员函数都可以访问BankAccount的私有成员!
友元的关键特性总结
| 特性 | 说明 |
|---|---|
| 打破封装 | 允许外部函数/类访问 private/protected 成员 |
| 单向信任 | A 把 B 当朋友,不代表 B 也把 A 当朋友 |
| 不传递 | A 是 B 的朋友,B 是 C 的朋友 → A 不是 C 的朋友 |
| 不继承 | 父类的友元,不是子类的友元 |
| 慎用! | 过多使用会破坏封装性,降低安全性 |
友元函数 vs 友元类 对比
| 特性 | 友元函数 | 友元类 |
|---|---|---|
| 类型 | 单个函数(可以是全局函数或其他类的成员函数) | 整个类 |
| 声明方式 | friend void func(...); | friend class ClassName; |
| 访问权限 | 该函数可以访问私有成员 | 该类的所有成员函数都可以访问 |
| 使用场景 | 工具函数、操作符重载(如 <<) | 管理类、配套类 |
十三、拷贝构造函数
什么是拷贝构造函数?
- 它是一个特殊的构造函数
- 当你用一个已存在的对象去初始化另一个新对象时,它会被自动调用
- 形式:
ClassName(const ClassName& other) - 作用:复制对象的内容
示例 1:基本的拷贝构造函数
#include <iostream>
using namespace std;
class Person {
private:
string name;
int age;
public:
// 普通构造函数
Person(string n, int a) : name(n), age(a) {
cout << "普通构造: " << name << endl;
}
// 拷贝构造函数
Person(const Person& other)
: name(other.name), age(other.age) {
cout << "拷贝构造: 从 " << other.name << " 创建新对象" << endl;
}
// 打印信息
void print() const {
cout << "Name: " << name << ", Age: " << age << endl;
}
};
测试代码:
int main() {
Person p1("Alice", 25); // 调用普通构造函数
// 三种会调用拷贝构造函数的情况:
// 1. 用一个对象初始化另一个对象
Person p2 = p1; // ✅ 调用拷贝构造函数
// 2. 传值方式传递对象
auto func = [](Person p) { // 参数是值传递,会拷贝
p.print();
};
func(p1);
// 3. 函数返回一个对象(某些情况下)
// (这里先不展开,现代编译器常优化掉)
cout << "\n所有对象已创建:" << endl;
p1.print();
p2.print();
return 0;
}
输出:
普通构造: Alice
拷贝构造: 从 Alice 创建新对象
拷贝构造: 从 Alice 创建新对象 // func(p1) 时发生拷贝
Name: Alice, Age: 25
所有对象已创建:
Name: Alice, Age: 25
Name: Alice, Age: 25
✅
Person p2 = p1;触发了拷贝构造函数!
示例 2:如果类中有指针成员(深拷贝 vs 浅拷贝)
这是拷贝构造函数最重要的应用场景!
class String {
private:
char* data;
public:
// 普通构造函数
String(const char* str = "") {
data = new char[strlen(str) + 1];
strcpy(data, str);
cout << "构造: " << data << endl;
}
// ❌ 错误:没有自定义拷贝构造函数 → 编译器生成“浅拷贝”
// 如果你不写,C++ 会自动生成一个默认的拷贝构造函数
// 它只是简单地复制指针,导致两个对象指向同一块内存!
// ✅ 正确:自定义拷贝构造函数(深拷贝)
String(const String& other) {
if (other.data) {
data = new char[strlen(other.data) + 1];
strcpy(data, other.data);
cout << "深拷贝: " << data << endl;
} else {
data = nullptr;
}
}
// 析构函数
~String() {
delete[] data;
cout << "析构完成" << endl;
}
void print() const {
cout << "String: " << (data ? data : "null") << endl;
}
};
测试代码:
int main() {
String s1("Hello");
// 调用拷贝构造函数
String s2 = s1; // ✅ 深拷贝,各自有独立的内存
s1.print(); // String: Hello
s2.print(); // String: Hello
return 0;
}
// 程序结束,s2 和 s1 分别析构,各自 delete[] data → 安全!
输出:
构造: Hello
深拷贝: Hello
String: Hello
String: Hello
析构完成
析构完成
✅ 深拷贝避免了重复释放内存的错误!
什么时候会调用拷贝构造函数?
| 场景 | 示例 |
|---|---|
| 1. 用一个对象初始化另一个对象 | Person p2 = p1; |
| 2. 函数参数是值传递 | void func(Person p) |
| 3. 函数返回一个对象(某些情况) | Person create() { return p; } |
| 4. 容器插入对象 | vec.push_back(p); |
总结
| 概念 | 关键点 |
|---|---|
| 拷贝构造函数 | ClassName(const ClassName&) |
| 触发时机 | 用一个对象初始化另一个新对象 |
| 默认行为 | 编译器生成浅拷贝(只复制值) |
| 深拷贝 | 手动分配新内存,复制数据(重要!) |
| 三大法则 | 有指针?记得写析构、拷贝构造、赋值运算符 |
十四、C++ 引用(reference) 的经典问题
给定代码:
int a = 12;
int &b = a;
解释:
-
int a = 12;
定义了一个整型变量a,并初始化为12。 -
int &b = a;
定义了一个 引用b,它是变量a的别名(alias)。
✅ 也就是说:b是a的另一个名字,它们指向同一块内存。
验证示例:
#include <iostream>
using namespace std;
int main() {
int a = 12;
int &b = a; // b 是 a 的引用
cout << "a = " << a << endl; // 输出: a = 12
cout << "b = " << b << endl; // 输出: b = 12
// 修改 b
b = 20;
cout << "a = " << a << endl; // 输出: a = 20 (a 也变了!)
// 修改 a
a = 30;
cout << "b = " << b << endl; // 输出: b = 30 (b 也变了!)
return 0;
}
输出:
a = 12
b = 12
a = 20
b = 30
💡 因为
a和b是同一个变量的两个名字,改一个,另一个也变。
十五、重 载" +" 运 算 符
示例:定义一个模板类 Rectangle(矩形),并重载了 + 运算符,用于将两个矩形的宽和高相加。
#include <iostream>
template <typename T>
class Rectangle {
public:
// 默认构造函数: 当不传参数创建对象时调用;使用初始化列表将 width 和 height 初始化为 0
Rectangle() : width(0), height(0) {}
//带参构造函数:接收两个类型为 T 的参数 w 和 h;初始化矩形的宽和高
Rectangle(T w, T h) : width(w), height(h) {}
// 重载 + 运算符:允许你使用 rect1 + rect2 这样的语法,将两个矩形的 width 相加,height相加,生成一个新的矩形
Rectangle operator+(Rectangle &rc) {
Rectangle r;
r.width = this->width + rc.width;
r.height = this->height + rc.height;
return r;
}
void get_res() {
std::cout << "width: " << width << std::endl;
std::cout << "height: " << height << std::endl;
}
private:
T width;
T height;
};
int main() {
Rectangle<float> rect1(10.2, 11.3);
Rectangle<float> rect2(5.2, 1.3);
Rectangle<float> rect3 = rect1 + rect2;
rect3.get_res();
}
输出结果:
width: 15.4
height: 12.6
十六、深拷贝(Deep Copy) 和 浅拷贝(Shallow Copy)
核心区别:
- 浅拷贝:只复制指针,不复制数据 → 两个对象共享同一块内存
- 深拷贝:不仅复制指针,还重新分配内存并复制数据 → 每个对象有自己的独立数据
示例场景:一个 MyString 类,封装字符数组
#include <iostream>
#include <cstring>
using namespace std;
class MyString {
private:
char* data; // 指向动态分配的字符串
int length;
public:
// 构造函数
MyString(const char* str = "") {
length = strlen(str);
data = new char[length + 1]; // 分配内存
strcpy(data, str);
cout << "构造: \"" << data << "\" 在地址 " << (void*)data << endl;
}
// 析构函数
~MyString() {
cout << "析构: 释放地址 " << (void*)data << " 上的数据 \""
<< (data ? data : "null") << "\"" << endl;
delete[] data;
}
// 打印字符串
void print() const {
cout << "内容: \"" << data << "\", 地址: " << (void*)data << endl;
}
};
❌ 问题来了:没有自定义拷贝构造函数 → 浅拷贝!
C++ 默认会生成一个浅拷贝的拷贝构造函数,它只是简单地复制指针:
MyString s1("Hello");
MyString s2 = s1; // 调用默认拷贝构造函数 → 浅拷贝!
此时内存布局是:
s1.data ──┐
├─→ 指向同一块内存:"Hello"
s2.data ──┘
完整测试代码(浅拷贝问题):
int main() {
MyString s1("Hello");
s1.print();
MyString s2 = s1; // ❌ 浅拷贝
s2.print();
return 0;
}
// 程序结束时,s2 先析构,delete[] data;
// 然后 s1 析构,再次 delete[] data; → ❌ 重复释放!程序崩溃!
输出(可能的结果):
构造: "Hello" 在地址 0x12345678
内容: "Hello", 地址: 0x12345678
内容: "Hello", 地址: 0x12345678 // 地址相同!
析构: 释放地址 0x12345678 上的数据 "Hello"
析构: 释放地址 0x12345678 上的数据 "Hello" // 同一地址被释放两次!
// 💥 崩溃或未定义行为!
❌ 浅拷贝导致“悬空指针”和“重复释放内存”
✅ 解决方案:自定义拷贝构造函数 → 深拷贝
// 添加深拷贝的拷贝构造函数
MyString(const MyString& other) {
length = other.length;
data = new char[length + 1]; // 1. 重新分配新内存
strcpy(data, other.data); // 2. 复制内容
cout << "深拷贝: \"" << data << "\" 在地址 " << (void*)data << endl;
}
修改后的完整类(含深拷贝):
#include <iostream>
#include <cstring>
using namespace std;
class MyString {
private:
char* data;
int length;
public:
// 构造函数
MyString(const char* str = "") {
length = strlen(str);
data = new char[length + 1];
strcpy(data, str);
cout << "构造: \"" << data << "\" 在地址 " << (void*)data << endl;
}
// ✅ 深拷贝构造函数
MyString(const MyString& other) {
length = other.length;
data = new char[length + 1];
strcpy(data, other.data);
cout << "深拷贝: \"" << data << "\" 在地址 " << (void*)data << endl;
}
// 析构函数
~MyString() {
cout << "析构: 释放地址 " << (void*)data << " 上的数据 \""
<< (data ? data : "null") << "\"" << endl;
delete[] data;
}
void print() const {
cout << "内容: \"" << data << "\", 地址: " << (void*)data << endl;
}
};
int main() {
MyString s1("Hello");
s1.print();
MyString s2 = s1; // ✅ 深拷贝
s2.print();
return 0;
}
输出:
构造: "Hello" 在地址 0x12345678
内容: "Hello", 地址: 0x12345678
深拷贝: "Hello" 在地址 0x87654321
内容: "Hello", 地址: 0x87654321 // 地址不同!
析构: 释放地址 0x87654321 上的数据 "Hello"
析构: 释放地址 0x12345678 上的数据 "Hello"
✅ 成功!两个对象有各自的内存,安全析构。
深拷贝 vs 浅拷贝 对比表
| 特性 | 浅拷贝(Shallow Copy) | 深拷贝(Deep Copy) |
|---|---|---|
| 内存分配 | 不分配新内存 | 分配新内存 |
| 数据复制 | 只复制指针值 | 复制指针指向的数据 |
| 内存共享 | 两个对象共享数据 | 每个对象有独立数据 |
| 析构风险 | ❌ 重复释放、崩溃 | ✅ 安全释放 |
| 性能 | ⚡ 快(只复制指针) | 稍慢(需分配+复制) |
| 适用场景 | 成员无动态内存 | 有指针、动态资源 |
总结
| 概念 | 关键点 |
|---|---|
| 浅拷贝 | 编译器默认行为,只复制指针,危险! |
| 深拷贝 | 手动分配新内存,复制数据,安全 |
| 触发时机 | 拷贝构造、赋值操作 |
| 何时需要 | 类中有指针或动态资源 |
| 核心原则 | 谁分配,谁释放;避免共享导致的冲突 |
十七、Lambda 表达式(匿名函数)
代码示例
#include <iostream>
using namespace std;
int main() {
int a = 10, b = 100;
//b 是对原变量 b 的引用,修改 b 就等于修改外部的 b
//捕获列表:<br>- a:按值捕获(拷贝一份)<br>- &b:按引用捕获(共享原变量)
auto f = [a, &b]() -> int {
cout << a << ", " << b << endl;
// a = 200; // ❌ 报错:表达式必须是可修改的左值
b = 200; // ✅ OK
return 0;
};
f();
cout << a << ", " << b << endl;
}
最终输出
10, 100
10, 200
拆解:
| 部分 | 含义 |
|---|---|
auto f = | 用 auto 推导 Lambda 类型并赋值给变量 f |
[a, &b] | 捕获列表:<br>- a:按值捕获(拷贝一份)<br>- &b:按引用捕获(共享原变量) |
() | 无参数 |
-> int | 尾置返回类型:返回 int |
{ ... } | Lambda 函数体 |
关键点:捕获方式决定能否修改
✅ a 是值捕获
- 在 Lambda 创建时,
a的值(10)被拷贝进 Lambda 内部 - 这个拷贝是
const的(除非加mutable) - 所以你不能修改它:
a = 200; // ❌ 错误!无法修改只读变量
💡 编译器错误提示:“表达式必须是可修改的左值” —— 因为
a是只读的副本。
✅ b 是引用捕获
b是对原变量b的引用- 修改
b就等于修改外部的b - 所以:
b = 200; // ✅ 正确!修改的是外部的 b
值捕获 vs 引用捕获 对比
| 特性 | [a] 值捕获 | [&b] 引用捕获 |
|---|---|---|
| 是否共享原变量 | ❌ 否(拷贝) | ✅ 是(引用) |
| 能否修改原变量 | ❌ 不能(除非 mutable) | ✅ 能 |
| 生命周期风险 | ✅ 安全(有副本) | ⚠️ 注意:不要引用已销毁的变量 |
| 性能 | 拷贝小对象 OK | 更快(无拷贝) |
| 默认是否可修改 | ❌ 不可修改 | ✅ 可修改 |
最佳实践建议
- ✅ 优先使用值捕获(如
[a, b]),避免悬空引用 - ✅ 如果需要修改外部变量,才用引用捕获(
[&x]) - ✅ 大对象捕获时,使用引用避免拷贝开销
- ✅ 如果要在 Lambda 中修改值捕获的变量,加
mutable - ❌ 避免
[=]或[&]捕获所有,降低可读性
✅ 总结
| 问题 | 答案 |
|---|---|
为什么 a = 200 报错? | a 是值捕获,默认是 const,不可修改 |
为什么 b = 200 可以? | b 是引用捕获,可以直接修改外部变量 |
mutable 的作用? | 允许修改值捕获的变量(但只改副本) |
最终 a, b 的值? | a = 10, b = 200 |
十八、std: :function
std: :function 是 C++11 的 新 特性 , 包含 在 头 文件 <functional> 中 。 std::function 是 一 个 函数 包装 器 , 该 函数 包装 器 模板 能 包装 任何 类 型 的 可 调用 实体 , 如 普通 函数 ,函数 对 象 ,lamda 表达 式 等 。
什么是 std::function?
💡 它就像一个“万能函数容器”,能装:
- 普通函数
- 函数指针
- 函数对象(仿函数)
- Lambda 表达式
- 成员函数指针
- 绑定器(如
std::bind)
举例说明:std::function 的多种用法
#include <iostream>
#include <functional> // 必须包含
using namespace std;
// 1. 普通函数
int add(int a, int b) {
return a + b;
}
int multiply(int a, int b) {
return a * b;
}
// 2. 函数对象(仿函数)
struct Divide {
int operator()(int a, int b) {
return b != 0 ? a / b : 0;
}
};
// 3. Lambda 表达式(我们稍后赋值)
int main() {
// 声明一个 function 对象,能接受两个 int,返回 int
std::function<int(int, int)> calc;
// ✅ 1. 装普通函数
calc = add;
cout << "add: " << calc(10, 5) << endl; // 输出: 15
// ✅ 2. 装另一个普通函数
calc = multiply;
cout << "multiply: " << calc(10, 5) << endl; // 输出: 50
// ✅ 3. 装函数对象
calc = Divide();
cout << "divide: " << calc(10, 5) << endl; // 输出: 2
// ✅ 4. 装 Lambda 表达式
calc = [](int a, int b) {
return a - b;
};
cout << "lambda (subtract): " << calc(10, 5) << endl; // 输出: 5
return 0;
}
输出:
add: 15
multiply: 50
divide: 2
lambda (subtract): 5
✅ 看!同一个
calc变量,可以先后装不同类型的可调用对象!
std::function 的核心优势
| 优势 | 说明 |
|---|---|
| 统一接口 | 不管是函数、lambda 还是仿函数,都能用相同方式调用 |
| 类型擦除 | 隐藏具体类型,只关心“函数签名” |
| 运行时绑定 | 可以在运行时动态切换函数逻辑 |
| 函数式编程支持 | 非常适合回调、事件处理、策略模式等 |
十九、std::bind
bind是这样一种机制,它可以预先把指定可调用实体的某些参数绑定到已有的变量,产生一个新的可调用实体,这种机制在回调函数的使用过程中用的较多。
bind的思想实际上是一种延迟计算的思想 , 将可调用对象保存起来,然后在需要的时候再调用。而且这种绑定是非常灵活的,不论是普通函数、 函数对象、还是成员函数都可以绑定,而且其参数可以支持占位符,比如你可以这样绑定一个二元函数auto f= bind(&func, 1, _2);,调用的时候通过fl1,2)实现调用。
基本语法
#include <functional>
using namespace std;
auto new_callable = bind(callable, arg1, arg2, ...);
callable:可以是函数、函数对象、成员函数指针等arg1, arg2, ...:参数可以是具体值,也可以是占位符(如_1,_2)- 占位符定义在
std::placeholders命名空间中
💡
std::bind返回一个新的可调用对象(函数包装器)
示例 1:绑定普通函数的参数
#include <iostream>
#include <functional>
using namespace std;
using namespace std::placeholders;
// 普通函数:加法
int add(int a, int b, int c) {
return a + b + c;
}
int main() {
// 绑定第一个参数 a = 10
auto f1 = bind(add, 10, _1, _2);
// f1(b, c) 相当于 add(10, b, c)
cout << f1(20, 30) << endl; // 输出: 60 (10+20+30)
// 绑定第二个参数 b = 5
auto f2 = bind(add, _1, 5, _2);
// f2(a, c) 相当于 add(a, 5, c)
cout << f2(10, 30) << endl; // 输出: 45 (10+5+30)
return 0;
}
✅ 解释:
_1表示调用f1时的第一个参数_2表示第二个参数bind(add, 10, _1, _2)把add的第一个参数固定为10,后两个由调用时传入
示例 2:绑定 Lambda 表达式
auto lambda = [](int x, int y, int z) {
return x * y + z;
};
auto f = bind(lambda, _2, 10, _1); // 调换顺序并固定 y=10
// f(a, b) 相当于 lambda(b, 10, a)
cout << f(3, 5) << endl; // 相当于 lambda(5, 10, 3) → 5*10 + 3 = 53
✅
std::bind可以重新排列参数顺序!
示例 3:绑定成员函数(非常重要!)
class Calculator {
public:
int multiply(int a, int b) {
return a * b;
}
};
int main() {
Calculator calc;
// 绑定成员函数
auto f = bind(&Calculator::multiply, &calc, _1, _2);
// f(a, b) 相当于 calc.multiply(a, b)
cout << f(5, 6) << endl; // 输出: 30
return 0;
}
关键点:
&Calculator::multiply:成员函数指针&calc:绑定到哪个对象实例(可以是对象、指针、智能指针)_1,_2:调用f时传入的参数
也可以绑定到对象副本:
auto f = bind(&Calculator::multiply, calc, _1, _2); // 拷贝 calc
示例 4:预绑定部分参数(柯里化)
int divide(int a, int b) {
return b != 0 ? a / b : 0;
}
int main() {
// 创建一个“除以2”的函数
auto half = bind(divide, _1, 2);
cout << half(10) << endl; // 10 / 2 = 5
// 创建一个“被2除”的函数(a 固定为 100)
auto reciprocal = bind(divide, 100, _1);
cout << reciprocal(4) << endl; // 100 / 4 = 25
return 0;
}
这就是所谓的“柯里化(Currying)”——把一个多参数函数变成一系列单参数函数。
示例 5:结合 std::function 实现回调
#include <iostream>
#include <functional>
#include <vector>
using namespace std;
using namespace std::placeholders;
// 回调函数类型
using Callback = function<void(int)>;
void forEach(const vector<int>& vec, Callback cb) {
for (int x : vec) {
cb(x);
}
}
void printSquare(int x) {
cout << x << "^2 = " << x*x << endl;
}
int main() {
vector<int> nums = {1, 2, 3, 4, 5};
// 绑定一个带偏移的打印函数
auto printWithOffset = bind(printSquare, _1 + 10);
forEach(nums, printWithOffset);
// 输出:
// 11^2 = 121
// 12^2 = 144
// 13^2 = 169
// 14^2 = 196
// 15^2 = 225
return 0;
}
std::bind 对比 Lambda
| 特性 | std::bind | Lambda |
|---|---|---|
| 参数重排 | ✅ 很方便 | ❌ 需手动写 |
| 预绑定参数 | ✅ 天生支持 | ✅ 用捕获实现 |
| 可读性 | ⚠️ 较复杂 | ✅ 更直观 |
| C++11 之前 | ❌ 不支持 | ❌ 不支持 |
| 性能 | 相当 | 相当 |
💡 现代 C++ 更推荐使用 Lambda,因为更直观。但
std::bind在某些复杂场景(如参数重排、绑定成员函数)仍有优势。
✅ 占位符说明
| 占位符 | 含义 |
|---|---|
_1 | 调用新函数时的第 1 个参数 |
_2 | 第 2 个参数 |
_3 | 第 3 个参数 |
| ... | ... |
auto f = bind(func, _2, _1); // 调换前两个参数顺序
f(a, b); // 相当于 func(b, a)
✅ 总结
| 问题 | 回答 |
|---|---|
std::bind 是什么? | 一个能“预绑定参数”的工具,生成新可调用对象 |
| 主要用途? | 参数固定、参数重排、延迟调用、回调 |
| 能绑定哪些? | 普通函数、lambda、成员函数、函数对象 |
占位符 _1, _2 是什么? | 表示调用时传入的第1、第2个参数 |
| 需要包含什么? | #include <functional> + using namespace std::placeholders; |
| 和 Lambda 比? | Lambda 更直观,bind 更灵活(尤其参数重排) |
💡 一句话理解:
“
std::bind就像一个‘函数定制器’,你可以把一个函数的某些参数先‘焊死’,或者调换顺序,生成一个新函数。”
二十、std::chrono::duration
在 C++ 中,std::chrono::duration 是 时间处理的核心组件之一,它定义在 <chrono> 头文件中,是 C++11 引入的现代时间库的重要部分。
什么是 std::chrono::duration?
std::chrono::duration表示一个时间段(time interval),比如:
- 5 秒
- 100 毫秒
- 2 小时
它由两个部分组成:
- 数值(count):表示有多少个时间刻度
- 单位(period):表示每个刻度是多少秒(用分数表示)
常用预定义的时间单位(C++11 提供)
| 类型 | 含义 | 等价于 |
|---|---|---|
std::chrono::nanoseconds | 纳秒 | duration<long long, nano> |
std::chrono::microseconds | 微秒 | duration<long long, micro> |
std::chrono::milliseconds | 毫秒 | duration<long long, milli> |
std::chrono::seconds | 秒 | duration<long long> |
std::chrono::minutes | 分钟 | duration<long long, ratio<60>> |
std::chrono::hours | 小时 | duration<long long, ratio<3600>> |
⚠️ 包含头文件:
#include <chrono>
示例 1:创建和使用 duration
#include <iostream>
#include <chrono>
using namespace std;
using namespace std::chrono;
int main() {
// 创建 duration 对象
seconds s(5); // 5 秒
milliseconds ms(1500); // 1500 毫秒 = 1.5 秒
minutes min(3); // 3 分钟
hours h(2); // 2 小时
// 输出时间值
cout << "秒: " << s.count() << " 秒" << endl; // 5
cout << "毫秒: " << ms.count() << " 毫秒" << endl; // 1500
cout << "分钟: " << min.count() << " 分钟" << endl; // 3
return 0;
}
.count() 返回内部存储的“刻度数量”
示例 2:duration 之间的转换
milliseconds ms(1500);
seconds s = duration_cast<seconds>(ms); // 向下取整
cout << "1500ms = " << s.count() << " 秒" << endl; // 输出: 1
// 如果想保留小数,可以用 double
duration<double, seconds::period> s_fine(1.5);
cout << "精确秒: " << s_fine.count() << " 秒" << endl; // 1.5
✅
duration_cast用于安全转换,会截断小数部分
示例 3:计算时间差(实际应用)
#include <iostream>
#include <chrono>
#include <thread> // 用于 sleep
using namespace std;
using namespace std::chrono;
int main() {
// 记录开始时间
auto start = high_resolution_clock::now();
// 模拟耗时操作
this_thread::sleep_for(milliseconds(1200)); // 睡眠 1.2 秒
// 记录结束时间
auto end = high_resolution_clock::now();
// 计算时间差
auto duration = duration_cast<milliseconds>(end - start);
cout << "耗时: " << duration.count() << " 毫秒" << endl;
// 输出: 耗时: 1200 毫秒(大约)
return 0;
}
✅ 这是
duration最常见的用途:测量代码执行时间
示例 4:自定义 duration 单位
// 定义“半天”为单位(43200 秒 = 12 小时)
using half_days = duration<long long, ratio<43200>>;
half_days hd(3); // 3 个半天 = 36 小时
cout << "3 个半天 = " << hd.count() << " 半天" << endl;
// 转换为小时
hours hrs = duration_cast<hours>(hd);
cout << "等于 " << hrs.count() << " 小时" << endl; // 36
示例 5:duration 的算术运算
seconds s1(10);
seconds s2(5);
seconds total = s1 + s2; // 15 秒
seconds diff = s1 - s2; // 5 秒
milliseconds ms = 2 * s1; // 20 秒 = 20000 毫秒
cout << "总时间: " << total.count() << " 秒" << endl;
cout << "毫秒: " << ms.count() << " 毫秒" << endl;
✅
duration支持 +、-、*、/ 等运算
二十一、多进程 vs 多线程
在 Ubuntu 系统中同时运行多个 C++ 程序(或“节点”),这通常是多进程(multi-process),而不是多线程(multi-threading)。
详细解释:进程 vs 线程
| 概念 | 进程(Process) | 线程(Thread) |
|---|---|---|
| 定义 | 一个正在运行的程序实例 | 进程内的执行单元 |
| 内存空间 | 每个进程有独立的内存空间(堆、栈、代码段等) | 同一进程的所有线程共享内存空间(堆、全局变量) |
| 资源开销 | 较大(创建、销毁、切换成本高) | 较小(轻量级) |
| 通信方式 | 进程间通信(IPC):管道、共享内存、消息队列等 | 直接读写共享变量(需同步) |
| 独立性 | 高:一个进程崩溃不影响其他进程 | 低:一个线程崩溃可能导致整个进程崩溃 |
| 创建方式 | fork() 或启动新程序 | std::thread, pthread |
二十二、标准线程库 <thread>的使用
示例 1:最简单的多线程(函数)
#include <iostream>
#include <thread>
#include <chrono>
using namespace std;
using namespace std::chrono;
void hello() {
std::this_thread::sleep_for(1s); // 睡眠1秒
std::cout << "Hello from thread!" << std::endl;
}
int main() {
std::thread t(hello); // 启动线程执行 hello 函数
std::cout << "Hello from main!" << std::endl;
t.join(); // 等待线程结束
return 0;
}
输出(顺序可能不同):
Hello from main!
Hello from thread!
✅
t.join():主线程等待子线程结束
示例 2:传递参数给线程函数
void printID(int id) {
std::cout << "Thread ID: " << id << std::endl;
}
void printName(std::string name, int waitTime) {
std::this_thread::sleep_for(milliseconds(waitTime));
std::cout << "Name: " << name << std::endl;
}
int main() {
std::thread t1(printID, 10);
std::thread t2(printName, "Alice", 100);
t1.join();
t2.join();
return 0;
}
输出:
Thread ID: 10
Name: Alice
✅ 参数是值传递(会被拷贝)
💡 如果要传引用,必须用std::ref
示例 3:传递引用参数(使用 std::ref)
void updateData(int& data) {
data += 100;
std::cout << "Inside thread: data = " << data << std::endl;
}
int main() {
int value = 42;
std::thread t(updateData, std::ref(value)); // 传引用
t.join();
std::cout << "After thread: value = " << value << std::endl;
// 输出: 142
return 0;
}
✅ std::ref(value) 包装引用,避免被拷贝
示例 4:使用 Lambda 表达式
int main() {
int local = 100;
std::thread t([local]() {
std::cout << "Lambda thread: " << local * 2 << std::endl;
});
t.join();
return 0;
}
✅ Lambda 可以捕获外部变量(值或引用)
示例 5:使用类成员函数
class Worker {
public:
void doWork(std::string task) {
std::cout << "Working on: " << task << std::endl;
}
};
int main() {
Worker worker;
std::thread t(&Worker::doWork, &worker, "Coding"); // 绑定对象和函数
t.join();
return 0;
}
✅
&Worker::doWork:成员函数指针
✅&worker:调用该函数的对象
示例 6:多个线程并发执行
void task(int id) {
for (int i = 0; i < 3; ++i) {
std::cout << "Task " << id << " step " << i << std::endl;
std::this_thread::sleep_for(milliseconds(100));
}
}
int main() {
std::thread t1(task, 1);
std::thread t2(task, 2);
std::thread t3(task, 3);
t1.join();
t2.join();
t3.join();
std::cout << "All tasks done!" << std::endl;
return 0;
}
✅ 多个线程真正并发执行!
示例 7:join() vs detach()
void backgroundTask() {
for (int i = 0; i < 5; ++i) {
std::cout << "Background: " << i << std::endl;
std::this_thread::sleep_for(milliseconds(200));
}
}
int main() {
std::thread t(backgroundTask);
// t.join(); // 主线程等待
t.detach(); // 分离线程,后台运行
std::this_thread::sleep_for(milliseconds(1000)); // 让主线程活久一点
std::cout << "Main thread exiting..." << std::endl;
return 0; // 即使 detached 线程还在运行,程序也可能结束
}
| 方法 | 说明 |
|---|---|
join() | 阻塞主线程,直到子线程结束 |
detach() | 子线程在后台运行,不能再 join,生命周期由系统管理 |
⚠️
detach()后,如果主线程结束,整个程序可能终止,即使 detached 线程还没执行完!
示例 8:获取线程 ID
void printThreadID() {
std::cout << "Thread ID: " << std::this_thread::get_id() << std::endl;
}
int main() {
std::thread t1(printThreadID);
std::thread t2(printThreadID);
std::cout << "Main thread ID: " << std::this_thread::get_id() << std::endl;
t1.join();
t2.join();
return 0;
}
✅ 每个线程有唯一 ID,可用于日志、调试
示例 9:线程局部存储(thread_local)
thread_local int counter = 0; // 每个线程有自己的副本
void increment(int id) {
for (int i = 0; i < 3; ++i) {
++counter;
std::cout << "Thread " << id << ": counter = " << counter << std::endl;
}
}
int main() {
std::thread t1(increment, 1);
std::thread t2(increment, 2);
t1.join();
t2.join();
return 0;
}
输出:
Thread 1: counter = 1
Thread 1: counter = 2
Thread 1: counter = 3
Thread 2: counter = 1
Thread 2: counter = 2
Thread 2: counter = 3
✅
thread_local变量每个线程独有,不会冲突
最佳实践建议
| 场景 | 推荐做法 |
|---|---|
| 线程生命周期可控 | 使用 join() |
| 后台任务(如日志) | 使用 detach()(谨慎) |
| 共享数据 | 配合 std::mutex 使用 |
| 线程池 | 手动管理或使用第三方库 |
| 参数传递 | 优先值传递,引用用 std::ref |
二十三、std::mutex 与 std::atomic 的使用
在 C++ 多线程编程中,数据竞争(Data Race) 是最常见、最危险的问题。为了解决这个问题,C++ 提供了两种主要的同步机制:
std::mutex:互斥锁,保护临界区std::atomic:原子操作,保证单个变量的读写是原子的
基础头文件
#include <iostream>
#include <thread>
#include <mutex>
#include <atomic>
#include <vector>
using namespace std;
场景:多个线程同时对一个变量自增
❌ 问题:不加同步的后果
int counter = 0;
void bad_increment(int n) {
for (int i = 0; i < n; ++i) {
++counter; // 非原子操作:读 → 修改 → 写
}
}
int main() {
const int n = 100000;
thread t1(bad_increment, n);
thread t2(bad_increment, n);
t1.join();
t2.join();
cout << "Expected: " << 2*n << endl;
cout << "Actual: " << counter << endl; // 很可能 < 200000
}
❌ 输出可能是
189234、195678等,结果不确定!
原因:++counter不是原子操作,两个线程可能同时读到同一个值。
方案一:使用 std::mutex(互斥锁)
原理:
- 用锁保护“临界区”
- 同一时间只有一个线程能进入
int counter = 0;
mutex mtx; // 全局互斥锁
void safe_increment_with_mutex(int n) {
for (int i = 0; i < n; ++i) {
lock_guard<mutex> lock(mtx); // RAII 自动加锁/解锁
++counter;
}
}
int main() {
const int n = 100000;
thread t1(safe_increment_with_mutex, n);
thread t2(safe_increment_with_mutex, n);
t1.join();
t2.join();
cout << "Mutex result: " << counter << endl; // 一定是 200000
}
优点:
- 可以保护多个操作或复杂逻辑
- 适合保护一段代码(临界区)
方案二:使用 std::atomic<T>(原子变量)
原理:
atomic变量的读写是原子的- 无需锁,硬件级支持
atomic<int> atomic_counter{0}; // 原子整数
void safe_increment_with_atomic(int n) {
for (int i = 0; i < n; ++i) {
++atomic_counter; // 原子操作,无需锁
}
}
int main() {
const int n = 100000;
thread t1(safe_increment_with_atomic, n);
thread t2(safe_increment_with_atomic, n);
t1.join();
t2.join();
cout << "Atomic result: " << atomic_counter << endl; // 一定是 200000
}
优点:
- 性能高(无锁,lock-free)
- 不会死锁
- 语法简单
std::atomic 的常用操作
#include <iostream>
#include <atomic>
using namespace std;
int main() {
atomic<int> a{10}; // 初始化 atomic 变量 a,值为 10
// 原子写操作
a.store(20); // 设置 a 的值为 20
cout << "After store(20): " << a.load() << endl; // 打印 20
// 原子读操作
int val = a.load(); // 读取 a 的值,val = 20
cout << "After load(): " << val << endl; // 打印 20
// 原子交换(exchange)
int old = a.exchange(30); // 将 a 的值设为 30,返回旧值 20
cout << "After exchange(30): " << old << ", a = " << a.load() << endl; // 打印 old = 20,a = 30
// 比较并交换(CAS)
int expected = 30;
bool success = a.compare_exchange_strong(expected, 40); // 如果 a == expected (30),则将 a 设置为 40
cout << "After compare_exchange_strong: "
<< "expected = " << expected << ", a = " << a.load()
<< ", success = " << (success ? "true" : "false") << endl;
// 输出:a = 40, success = true
// 原子加操作(fetch_add)
int old_val = a.fetch_add(5); // 将 a 的值加 5,返回旧值(40)
cout << "After fetch_add(5): old = " << old_val << ", a = " << a.load() << endl;
// 输出:old = 40, a = 45
// 原子减操作(fetch_sub)
old_val = a.fetch_sub(3); // 将 a 的值减 3,返回旧值(45)
cout << "After fetch_sub(3): old = " << old_val << ", a = " << a.load() << endl;
// 输出:old = 45, a = 42
return 0;
}
结果输出:
After store(20): 20
After load(): 20
After exchange(30): old = 20, a = 30
After compare_exchange_strong: expected = 30, a = 40, success = true
After fetch_add(5): old = 40, a = 45
After fetch_sub(3): old = 45, a = 42
总结:
- 原子操作(如
store,load,exchange,fetch_add,fetch_sub,compare_exchange_strong)保证了在多线程环境下,数据操作是安全的,避免了竞态条件。 - CAS(比较并交换) 是一种常用的原子操作,广泛用于无锁编程中。
std::mutex vs std::atomic 对比
| 特性 | std::mutex | std::atomic<T> |
|---|---|---|
| 适用范围 | 一段代码、多个变量 | 单个变量 |
| 性能 | 较低(加锁开销) | 高(通常 lock-free) |
| 死锁风险 | ✅ 有 | ❌ 无 |
| 复杂逻辑支持 | ✅ 支持 | ❌ 不支持 |
| 内存开销 | 小 | 小 |
| 使用难度 | 中等(注意死锁) | 简单 |
| 典型用途 | 保护临界区、复杂操作 | 计数器、状态标志、指针 |
示例:用 atomic 实现线程安全的标志位
#include <iostream>
#include <thread>
#include <atomic>
#include <chrono> // for milliseconds, seconds
using namespace std;
atomic<bool> ready{false};
void worker() {
while (!ready.load(memory_order_acquire)) { // 等待主线程通知
this_thread::sleep_for(chrono::milliseconds(10));
}
cout << "Worker: Start working!" << endl;
}
int main() {
thread t(worker);
this_thread::sleep_for(chrono::seconds(1));
ready.store(true, memory_order_release); // 通知 worker 开始
t.join();
return 0;
}
用 atomic<bool> 实现“等待-通知”机制,无需锁
二十四、互斥锁(std::mutex)
共同头文件
#include <iostream>
#include <thread>
#include <vector>
#include <chrono>
using namespace std;
using namespace std::chrono;
互斥锁(std::mutex)—— 最基础的锁
🔐 作用:
- 保护临界区,确保同一时间只有一个线程能访问共享资源
- 防止数据竞争
示例:多个线程自增计数器
#include <iostream>
#include <thread>
#include <vector>
#include <chrono>
using namespace std;
using namespace std::chrono;
#include <mutex>
int counter = 0;
std::mutex mtx; // 互斥锁
void safe_increment(int n) {
for (int i = 0; i < n; ++i) {
std::lock_guard<std::mutex> lock(mtx); // RAII 自动加锁/解锁
++counter;
}
}
int main() {
thread t1(safe_increment, 100000);
thread t2(safe_increment, 100000);
t1.join();
t2.join();
cout << "Counter: " << counter << endl; // 一定是 200000
return 0;
}
✅ 特点:
- 简单、高效
- 使用
lock_guard实现 RAII(自动解锁) - 适合保护短小临界区
11万+

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



