• 面试题目记录


    推荐几款实用的C++ 在线工具
    C++、Qt基础面试题

    C++

    unordered_map桶增长策略

    swap的实现

    C++中的swap函数使用move移动语义实现交换,节省了临时拷贝的开销

    template inline
    void swap(T& a, T& b)
    {
    	T temp = move(a);
    	a = move(b);
    	b = move(temp);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    如何归还vector容器中的内存给系统

    如果有一个vector a;的对象,一直往其中push_back元素,达到1G之多,现在有没有一个方法可以把a这个vector容器中的元素所占用的内存交还给操作系统。

    在 C++ 中,对象的 swap 操作通常会交换它们的内部状态,包括指向堆内存的指针、资源句柄等等。在 std::vector 的情况下,调用 swap 函数会将两个 std::vector 对象的元素以及其内存空间进行交换,而不是进行拷贝或移动操作。因此,如果将一个非空的 std::vector 和一个空的 std::vector 进行 swap 操作,就可以让原先非空的 std::vector 占用的内存空间被空的 std::vector 所接管,从而实现内存的释放。具体来说,swap 函数会将两个 std::vector 对象内部的指针进行交换,而不会进行内存的分配和释放操作。

    需要注意的是,如果一个 std::vector 对象中的元素是指针或引用等类型,而这些指针或引用指向的对象仍然存在于程序中,那么在 swap 操作之后,虽然 std::vector 对象所占用的内存已经被释放,但是指针或引用指向的对象仍然会存在于程序中。因此,在进行 swap 操作之前,需要确保 std::vector 对象中的元素不再被程序所使用。

    template 
    void swap(std::vector& a, std::vector& b)
    {
        // 交换 vector 的元素
        a.swap(b);
    
        // 交换 vector 内部的大小、容量等信息
        std::swap(a._M_impl, b._M_impl);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    定义一个空的vector,占多大的内存

    定义一个空的 vector 不占用任何元素的内存空间,但是在内存中仍然需要为 vector 对象本身分配一些空间来存储其内部的元素指针、元素数量、容量等信息。

    具体来说,一个空的 vector 对象在内存中通常需要占用以下空间:

    在 32 位系统上,vector 对象通常需要占用 12 字节的空间;
    在 64 位系统上,vector 对象通常需要占用 24 字节的空间。
    需要注意的是,这只是一个大致的估计,不同的实现可能会有所不同。另外,vector 对象的大小还会受到内存对齐等因素的影响,因此具体的空间占用大小可能会有所差异。

    多线程

    使用多线程及其同步的方法,写出一个多线程打印0,1,0,1序列的功能。或者写出一个生产者消费者的功能。
    条件变量的wait为何有第二个参数?

    多线程的通信
    • 共享全局变量
    • 消息队列(也是一个全局共享的队列)
    • 创建线程时,传参
    多线程的同步
    控制多线程的顺序执行

    生产者、消费者
    关键:

    • 一个互斥量,用于保护生产者和消费者共用的队列
    • 一个条件变量,用于及时通知
      while { cv.wati() },防止虚假唤醒。
    #include 
    #include //多线程的头文件
    #include //互斥锁的头文件
    #include //条件变量的头文件
    #include //C++ STL所有的容器都不是线程安全
    using namespace std;
    
    std::mutex mtx;//定义互斥锁,做线程间的互斥操作
    std::condition_variable cv;//定义条件变量,做线程间的同步通信操作
    
    //生产者生产一个物品,通知消费者消费一个;消费完了,消费者再通知生产者继续生产物品
    class Queue
    {
    public:
        void put(int val)//生产物品
        {
            unique_lock lck(mtx);
            while (!que.empty())
            {
                //que不为空,生产者应该通知消费者去消费,消费者消费完了,生产者再继续生产
                //生产者线程进入#1等待状态,并且#2把mtx互斥锁释放掉
                cv.wait(lck);//传入一个互斥锁,当前线程挂起,处于等待状态,并且释放当前锁 lck.lock()  lck.unlock
            }
            que.push(val);
            cout<<"produce "< guard(mtx);
            unique_lock lck(mtx);
            while (que.empty())
            {
                //消费者线程发现que是空的,通知生产者线程先生产物品
                //#1 挂起,进入等待状态 #2 把互斥锁mutex释放
                cv.wait(lck);
            }//如果其他线程执行notify了,当前线程就会从等待状态 =》到就绪状态 =》但是要获取互斥锁才能继续向下执行
    
            int val = que.front();
            cout<<"consume: "< que;
    };
    
    //这里模拟生产者生产10个物品,消费者消费10个物品
    void producer(Queue* que)//生产者线程
    {
        for (int i = 1; i <= 10; ++i)
        {
            que->put(i);
            std::this_thread::sleep_for(std::chrono::milliseconds(100));//睡眠100毫秒
        }
    }
    
    void consumer(Queue* que)//消费者线程
    {
        for (int i = 1; i <= 10; ++i)
        {
            que->get();
            std::this_thread::sleep_for(std::chrono::milliseconds(100));//睡眠100毫秒
        }
    }
    
    int main()
    {
        Queue que;	//两个线程共享的队列
        std::thread t1(producer, &que);//开启生产者线程
        std::thread t2(consumer, &que);//开启消费者线程
        //主线程等待两个子线程都执行完再结束。
        t1.join();
        t2.join();
        return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85

    什么是野指针、悬空指针

    拷贝构造、移动构造、赋值运算符、移动赋值运算符

    • 拷贝构造、赋值运算符:注意深拷贝资源的指针。
      通常在类中自定义移动构造函数的同时,会再为其自定义一个适当的拷贝构造函数,由此当用户利用右值初始化类对象时,会调用移动构造函数;使用左值(非右值)初始化类对象时,会调用拷贝构造函数。
    构造函数内调用虚函数
    class A
    {
    public:
        A() {
            cout<<"A sturct"<
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
        A a;	// A sturct  A fun
        B b; // A sturct  A fun B sturct B fun
        A* a1 = new B(); // A sturct  A fun B sturct B fun
    
    • 1
    • 2
    • 3

    返回值

    Test拥有默认构造函数、析构函数、拷贝构造、移动构造、移动赋值、拷贝赋值运算符。看fun函数对Test这些函数的调用时机。

    Test fun() {
    	Test t;
    	return t;
    }
    
    • 1
    • 2
    • 3
    • 4

    有移动构造和没有移动构造,这里的不一样:

    • 没有移动构造:Test默认构造 -》用t去构造一个临时变量,Test的拷贝构造 -》t变量析构,析构函数 -》返回临时变量
    • 有移动构造:Test默认构造 -》用t去构造一个临时变量,Test的移动构造 -》t变量析构,析构函数 -》返回临时变量
    class Demo {
    public:
        Demo() : num(new int(0)) {
            cout << "construct!" << endl;
        }
    
        Demo(const Demo &d) : num(new int(*d.num)) {
            cout << "copy construct!" << endl;
        }
    
        Demo(Demo &&d) : num(d.num) {
            d.num = nullptr;
            cout << "move construct!" << endl;
        }
    
        ~Demo() {
            cout << "class destruct!" << endl;
        }
    
    private:
        int *num;
    };
    
    Demo getDemo() {
        Demo a;
        return a;
    }
    
    int main()
    {
        auto b = getDemo();
        return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33

    i++/++i

        int i = 1;
        int a = i++;
        i = 1;
        int b = ++i;
    
        i = 1;
        int c = ++i + ++i;
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    a = 1 b = 2 c = 6

    操作符重载

    面试题:实现一个++i,i++的重载。
    参考:C++操作符重载

    emplace

    • 解释vector.push_back("xyzzy");的过程
      1、"xyzzy"调用string构造函数,得到一个临时右值变量temp
      2、调用push_back的右值重载函数,把temp实参传入给其形参x,然后内部调用vector的移动构造函数,得到一个副本,插入到vector容器中。
      3、temp临时变量析构
      而采用emplace_back则没有第一步,只有第二步,那么当然的第三步也没有。
      参考:https://cntransgroup.github.io/EffectiveModernCppChinese/8.Tweaks/item42.html

    右值引用

    • std::move的实现
    template                            //在std命名空间
    typename remove_reference::type&&
    move(T&& param)
    {
        using ReturnType =                          //别名声明,见条款9
            typename remove_reference::type&&;
    
        return static_cast(param);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 解释下面的代码value(std::move(text)) 是否调用了string的移动构造函数?
    class Annotation {
    public:
        explicit Annotation(const std::string text)
        :value(std::move(text)) 
        { … }                       
        …
    private:
        std::string value;
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    参考:https://cntransgroup.github.io/EffectiveModernCppChinese/5.RRefMovSemPerfForw/item23.html
    记住:
    第一,不要在你希望能移动对象的时候,声明他们为const。对const对象的移动请求会悄无声息的被转化为拷贝操作。第二点,std::move不仅不移动任何东西,而且它也不保证它执行转换的对象可以被移动。

    • std::forward是怎么知道它的实参是否是被一个右值初始化的?
      std::forward只对实参是右值时才做转换,把形参转为右值(所有的函数参数,都是左值,当实参是左值时,没必要转换)。

    智能指针

    • shared_ptr是否线程安全?
      多线程操作同一个shared_ptr对象时,不是线程安全的。多线程操作多个shared_ptr对象(指向一块内存地址),是线程安全的,不需要去管。踏马的,这个不是正常吗,都不是一个对象了,多线程当然是安全的。
      另一个层面就是:shared_ptr对象所管理的那块内存是不是线程安全的。答,不是。多个shared_ptr对象,在多线程中,共同所指的那块内存地址的操作是不安全的。但也不一定都无脑的加锁去判断。有一种解决方式就是,用一个原子变量代表是否要对这块内存做处理,而在回到顺序处理的流程中,通过判断这个原子变量是否是true,来做最终的处理。
      另一个层面就是:shared_ptr初始化时的线程安全性。make_shared是线程安全的。
    processWidget(std::shared_ptr(new Widget),  //潜在的资源泄漏!
                  computePriority());
    // 解决方法1
    processWidget(std::make_shared(),   //没有潜在的资源泄漏
                  computePriority());
    // 解决方法2
    std::shared_ptr spw(new Widget);
    processWidget(spw, computePriority());  // 正确,但是没优化,因为这里spw是一个左值,方法1中是一个右值参数
    // 解决方法3
    processWidget(std::move(spw), computePriority());   //高效且异常安全
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    因为std::shared_ptr(new Widget),是两个步骤:1、new Widget 2、shared_ptr的构造函数,而std::make_shared()是一步操作。参考:条款二十一:优先考虑使用std::make_unique和std::make_shared,而非直接使用new
    参考:https://blog.csdn.net/bureau123/article/details/121300979
    https://zhuanlan.zhihu.com/p/416289479

    • make_shared
    // 两次分配内存,一次new Widget,一次shared_ptr构造函数中包含引用计数的控制块
    std::shared_ptr spw(new Widget);
    // 一次分配,分配的内存同时容纳了Widget对象和控制块
    auto spw = std::make_shared();
    
    • 1
    • 2
    • 3
    • 4
    • make函数的缺点
      make函数都不允许指定自定义删除器。
      和直接使用new相比,make函数消除了代码重复,提高了异常安全性。对于std::make_shared和std::allocate_shared,生成的代码更小更快。
      不适合使用make函数的情况包括需要指定自定义删除器和希望用花括号初始化。
      对于std::shared_ptrs,其他不建议使用make函数的情况包括(1)有自定义内存管理的类;(2)特别关注内存的系统,非常大的对象,以及std::weak_ptrs比对应的std::shared_ptrs活得更久。

    Lambda表达式

    Lambda实现原理:参考https://www.zhihu.com/question/57241113/answer/2440288161
    Lambda捕获原理,在定义时捕获还是运行时捕获? 运行时,如果是定义时,那么某个捕获的变量如果变化了,那这个变化的值就得不到了。
    下面这段代码有什么问题:

        int i = 10;
        auto f = [=]() {
            i = 9;
        };
    
    • 1
    • 2
    • 3
    • 4
    Lambda使用问题
    • 引用捕获外部的局部变量,此时可能在执行Lambda表达式时,该捕获的局部变量早已脱离作用域而析构了,导致悬空引用。此时需要改用值捕获来解决。
    • 值捕获一个指针变量。需要注意这个指针变量所指的内存是否已经被外面释放了。此时最好值捕获一个shared_ptr,或者使用深拷贝(即重写赋值运算符,里进行深拷贝)。
    • 捕获this问题。如果使用默认捕获,或者值捕获类对象的成员变量,都会存在潜在的问题。即类对象可能会析构了,而lambda表达式中的对成员变量的使用还在继续,这会导致崩溃。
      解决办法:1、捕获一个对这个成员变量的临时赋值的变量,而不是这个成员变量本身。2、使用C++14的广义Lambda 3、C++17增加了新特性可以捕获*this,不持有this指针,而是持有对象的拷贝,这样生命周期就与对象的生命周期不相关,使用上就安全一些。
      参考:Effective Modern C++ 条款31
      参考:c++的lambda使用注意事项,可能导致的崩溃问题分析

    智能指针的实现

    // 定义一个引用计数类,封装接口
    class SharedCount
    {
    public:
        SharedCount() : m_count(1) {}
    
        void Add() { m_count++; }
        auto Reduce() { return --m_count; }
        auto GetCount() { return m_count; }
    
    private:
        unsigned long m_count;
    };
    
    
    template 
    class SharedPtr
    {
    public:
        // 默认构造
        SharedPtr() : m_ptr(nullptr), m_count(nullptr) {}
        // 普通指针初始化
        SharedPtr(T* t = nullptr) : m_ptr(t) {
            if (t != nullptr) {
                m_count = new SharedCount;
            }
        }
        ~SharedPtr() {
            if (m_ptr && m_count->Reduce() == 0) {
                delete m_ptr;
                m_ptr = nullptr;
                delete m_count;
                m_count = nullptr;
            }
        }
    
        // 拷贝构造
        SharedPtr(const SharedPtr &other) :
            m_count(other.m_count), m_ptr(other.m_ptr) {
            if (other.m_count) {
                other.m_count->Add();
            }
        }
    
        // 移动构造
        SharedPtr(SharedPtr &&other)
    //        : m_count(std::move(other.m_count)),
    //        m_ptr(std::move(other.m_ptr))
        {
            this->m_count = other.m_count;
            this->m_ptr = other.m_ptr;
            other.m_ptr = nullptr;
        }
    
        // 赋值运算
        SharedPtr& operator= (const SharedPtr& other) {
            if (m_ptr == other.m_ptr) {
                return *this;
            }
            // 目的对象不空
            if (m_ptr != nullptr) {
                if (m_count->Reduce() == 0) {
                    delete m_ptr;
                }
            }
            // 赋值
            this->m_ptr = other.m_ptr;
            other.m_count->Add();
            this->m_count = other.m_count;
            return *this;
        }
    
        // 移动赋值运算符
        SharedPtr& operator=(const SharedPtr &&other) {
            if (m_ptr == other.m_ptr) {
                return *this;
            }
    
            if (m_ptr != nullptr) {
                if (m_count->Reduce() == 0) {
                    delete m_ptr;
                }
            }
    
            m_ptr = other.m_ptr;
            m_count = other.m_count;
            other.m_ptr = nullptr;
            return *this;
        }
    
        // 解引用
        T* operator-> () {
            return m_ptr;
        }
    
        T& operator* () {
            return *m_ptr;
        }
    
        SharedPtr& swap(SharedPtr &other) {
    
        }
    
        auto Get() { return m_ptr; }
        auto GetCount() { return m_count ? m_count->GetCount() : 0; }
    private:
        T* m_ptr = nullptr;
        SharedCount* m_count = nullptr;
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109

    编译和内存知识

    这部分需要完整看完【程序员的自我修养】

    QT

    使用qml有哪些问题

    • 避免定时轮询,采用信号槽机制通知。
    • 避免频繁的属性访问,如果必要,可以先用一个变量存储这个属性,再去频繁范围这个变量即可。尽量用临时变量替代对属性的访问。
    • 属性绑定时,避免复杂的计算。简单的计算,QML可以直接得出结果,效率比较快。
    • 尽量少用属性绑定(因为被绑定的值一旦变化,属性表达式将重新计算)。用锚布局。
    • 属性设置为异步加载,asynchronous异步属性设置为true,在组件实例化时可以提高流畅性。
    • 避免var的声明,声明为具体的类型,局部变量声明未let
      https://www.jianshu.com/p/e6fcb575f916

    Linux

    fork进程

    在这里插入图片描述
    fork会拷贝当前进程的内存,并创建一个新的进程。如上图,fork函数会将整个进程的内存镜像拷贝到新的内存地址,包括代码段、数据段、堆栈以及寄存器内容。之后,我们就有了两个拥有完全一样内存的进程。
    fork系统调用在两个进程中都会返回,在父进程中,fork系统调用会返回子进程的pid。而在新创建的进程中,fork系统调用会返回0。所以即使两个进程的内存是完全一样的,我们还是可以通过fork的返回值区分旧进程和新进程。
    某种程度上来说这里的拷贝操作浪费了,因为所有拷贝的内存都被丢弃并被exec替换。在大型程序中这里的影响会比较明显。实际上操作系统会对其进行优化。(比如使用COW(copy on write)技术)
    fork创建的新进程从fork语句后开始执行,因为新进程也继承了父进程的PC程序计数器。
    参考:fork函数详解(附代码)

    Windows

    Windows如何定位卡死问题?
    如何定位崩溃问题?
    Windows下如何监听其他应用的一些系统调用?(安全客户端)

    Windows内存泄漏定位

    1、crt提供的接口:_CrtDumpMemoryLeaks
    2、在两段代码直接建立内存快照(_CrtMemCheckpoint),然后比较一前一后两个内存快照的信息(_CrtMemDifference
    3、使用WinDbg,通过heap命令查看代码前后的堆的信息,通过WinDbg的输出信息,查看那个堆地址的内存增长过多。然后通过!heap -p -a 000001e134546ce0,来输出一下它的调用堆栈

    WINDBG定位内存泄漏

    查看Dump文件,帮助我们定位问题。这个过程主要分为以下几步:

    准备工具:需要安装一个Windows调试工具,比如WinDbg。

    加载Dump文件:使用WinDbg打开Dump文件,在命令行输入“.loadby sos clr”,以加载托管代码的调试信息。

    分析堆栈:使用命令“!clrstack”,以获取当前线程的堆栈,进而找出内存泄漏的代码。

    检查对象:使用命令“!dumpheap -type [类型名称]”,检查该类型的对象,以找出是否有对象未被正确回收。

    检查引用:使用命令“!gcroot [对象地址]”,检查该对象是否被其他对象引用,如果被引用,则需要递归检查引用链。

    通过以上步骤,我们可以找到内存泄漏的原因,并通过修改代码来解决问题。

    win32窗口从点击到相应的过程

    参考:https://leetcode.cn/circle/discuss/g4YxE2/

    Windows抓dump的原理

    Windows 的 dump 抓取是一个捕捉程序异常信息的过程。当程序发生异常时,Windows 会将程序的当前状态保存到一个文件中,这个文件就是 dump 文件。

    Windows 在抓取 dump 文件的过程中,会捕捉到程序的内存状态、系统状态、线程状态等信息。用户可以使用调试工具(例如 WinDBG)来分析 dump 文件,从而找出程序异常的原因。

    Windows 抓取 dump 文件的方法有多种,如程序崩溃时系统会自动生成一个 mini dump,用户也可以通过命令行工具(例如 Procdump)手动生成一个 full dump。

    总体来说,Windows 抓取 dump 的原理是通过捕捉程序的运行状态,来帮助用户分析程序的异常情况。

    dump生产的原因有哪些

    Windows dump 文件可以由多种原因生成。一些常见的原因包括:

    程序崩溃:当程序发生异常时,Windows 会自动生成一个 mini dump。

    内存泄漏:当程序在运行时不断分配内存,但没有释放内存,导致内存泄漏。用户可以通过 dump 文件找出内存泄漏的原因。

    假死:当程序没有响应时,用户可以生成一个 dump 文件,以便分析程序的假死原因。

    手动生成:用户可以通过命令行工具(例如 Procdump)手动生成一个 dump 文件,以便分析程序的异常情况。

    这些只是一些常见的原因,实际原因还有很多。使用 dump 文件分析程序的异常情况是一项非常有用的工具,可以帮助用户更快地定位和解决程序的问题。

    dll的入口函数

    参考:https://blog.csdn.net/qq_33757398/article/details/82230360

    设计模式

    观察者-监听者模式

    观察者:接口类提供一个接口handle,每个具体的观察者实现这个接口,并拥有自己独特的处理方法。
    监听者:提供一个注册接口,接收观察者实例,并记录到内部的数据结构中。
    另外提供一个处理的接口,从外部接收不同类型的数据,从内部匹配具体的观察者,然后调用对应的观察者的handle接口完成。

    两个观察者,实现handle接口:

    class Observer {
    public:
    	virtual void handle(int msg_id) = 0;
    };
    
    class Observer1 : public Observer {
    public:
    	void handle(int msg_id) {
    		switch (msg_id) {
    		case 1:
    			cout << "Observer1 recv msg 1" << endl;
    			break;
    		case 2:
    			cout << "Observer1 recv msg 2" << endl;
    			break;
    		default:
    			break;
    		}
    	}
    };
    
    class Observer2 : public Observer {
    public:
    	void handle(int msg_id) {
    		switch (msg_id) {
    		case 2:
    			cout << "Observer2 recv msg 2" << endl;
    			break;
    		default:
    			break;
    		}
    	}
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33

    监听者:

    class Subject {
    public:
    	void add_observer(int msg_id, Observer* observer) {
    		_sub_map[msg_id].push_back(observer);
    	}
    
    	// 发布消息,通知对msg_id感兴趣的观察者处理该事件
    	void publish(int msg_id) {
    		auto iter = _sub_map.find(msg_id);
    		if (iter != _sub_map.end()) {
    			for (Observer* obser : iter->second) {
    				obser->handle(msg_id);
    			}
    		}
    	}
    private:
    	// 存放对事件感兴趣的观察者们
    	unordered_map> _sub_map;
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19

    共享内存

    共享内存上创建C++对象的问题

    参考:https://blog.csdn.net/dickyjyang/article/details/21403451
    https://www.cnblogs.com/yangru/p/3805192.html

  • 相关阅读:
    Django admin后台添加自定义菜单和功能页面
    Linux C语言开发-D9输入输出
    采购数字化提升企业竞争壁垒,供应商系统助力冷链生鲜企业强化供应商管理能力
    Arch挂载错误
    扩展WiFi是什么意思
    [EIS 2019]EzPOP
    Tomcat搭建&JSP&Servlet
    vue 中 style 里面使用 scoped + @import 引入 css 后 scoped 属性不生效导致全局作用域
    Redis之时间轮机制(五)
    我只是还没有全力以赴
  • 原文地址:https://blog.csdn.net/tanxuan231/article/details/128039664