C++学习

一、引用与指针的比较

不同点:

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

💥 问题:xgetPtr() 函数的局部变量,存储在栈上。函数结束后,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* 是“无类型指针”或“通用指针

  • 它可以指向任何类型的数据intdoublestring 等)

  • 但它不携带类型信息,所以:

    • ❌ 不能直接解引用(*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_ptrstd::unique_ptrstd::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_ptrshared_ptrweak_ptr
所有权独占共享无(观察者)
引用计数❌ 无✅ 有✅ 有(但不增加)
可复制✅(但不增加计数)
性能⚡ 最快中等轻量
用途独占资源共享资源解决循环引用
是否释放内存✅ 自动✅ 引用计数为0时❌ 不释放

✅ 最佳实践建议

  1. 优先使用 make_uniquemake_share

    auto ptr = make_unique<int>(42);
    auto sptr = make_shared<string>("hello");
  2. 避免裸 newdelete

    // ❌ 不要
    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”

当你按下遥控器的「开机」按钮时:

  1. 遥控器问:“你现在对着哪台电视?”
  2. 找到那台电视的 “操作说明书”(vtable)
  3. 查说明书里的“开机”那一行,执行对应动作

📌 这就是 虚函数表(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)

我们要告诉编译器:“WarriorMage 在继承 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 包含两个 CharacterKnight 只包含一个 Character,被 Warrior 和 Mage 共享

 生活比喻

场景类比
普通多继承你有两个身份证(一个来自爸爸,一个来自妈妈),办事时别人不知道用哪个
虚继承你只有一个官方身份证,爸爸和妈妈都引用它,信息一致,无冲突

总结

概念关键点
多继承一个类继承多个父类,可能引发二义性
虚继承使用 virtual 关键字,确保公共基类只被继承一次
钻石问题多继承中公共基类被多次继承,导致成员重复
解决方案使用虚继承 + 最派生类初始化基类

十二、友元函数(friend function)友元类(friend class)

什么是“友元”?

在 C++ 中,类的 privateprotected 成员是对外隐藏的,外部不能直接访问。

但有时候,你希望某些特定的函数或类能“破例”访问这些私有成员 —— 这就是 友元(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 不是成员函数,但因为是“友元”,所以能访问 ownerbalance

 示例 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)。
    ✅ 也就是说:ba 的另一个名字,它们指向同一块内存。

验证示例:

#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

💡 因为 ab 是同一个变量的两个名字,改一个,另一个也变

十五、重 载" +" 运 算 符

示例:定义一个模板类 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更快(无拷贝)
默认是否可修改❌ 不可修改✅ 可修改

最佳实践建议

  1. ✅ 优先使用值捕获(如 [a, b]),避免悬空引用
  2. ✅ 如果需要修改外部变量,才用引用捕获([&x]
  3. ✅ 大对象捕获时,使用引用避免拷贝开销
  4. ✅ 如果要在 Lambda 中修改值捕获的变量,加 mutable
  5. ❌ 避免 [=] 或 [&] 捕获所有,降低可读性

✅ 总结

问题答案
为什么 a = 200 报错?a 是值捕获,默认是 const,不可修改
为什么 b = 200 可以?b 是引用捕获,可以直接修改外部变量
mutable 的作用?允许修改值捕获的变量(但只改副本)
最终 ab 的值?a = 10b = 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::bindLambda
参数重排✅ 很方便❌ 需手动写
预绑定参数✅ 天生支持✅ 用捕获实现
可读性⚠️ 较复杂✅ 更直观
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 小时

它由两个部分组成:

  1. 数值(count):表示有多少个时间刻度
  2. 单位(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::secondsduration<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::threadpthread

二十二、标准线程库 <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
}

❌ 输出可能是 189234195678 等,结果不确定
原因:++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

总结:

  • 原子操作(如 storeloadexchangefetch_addfetch_subcompare_exchange_strong)保证了在多线程环境下,数据操作是安全的,避免了竞态条件。
  • CAS(比较并交换) 是一种常用的原子操作,广泛用于无锁编程中。

std::mutex vs std::atomic 对比

特性std::mutexstd::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(自动解锁)
  • 适合保护短小临界区

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值