Qt源码阅读(四) 事件循环

事件系统

文章为本人理解,如有理解不到位之处,烦请各位指正。

@

Qt的事件循环,应该是所有Qter都避不开的一个点,所以,这篇博客,咱们来了解源码中一些关于Qt中事件循环的部分。
先抛出几个疑问,根据源代码,下面一一进行解析。

  1. 事件循环是什么?
  2. 事件是怎么产生的?
  3. 事件是如何处理的?

什么是事件循环?

对于Qt事件循环个人理解是,事件循环是一个队列去循环处理事件。当队列中有事件时,则去处理事件,如果没有事件时,则会阻塞等待。

事件是如何产生的?

事件的产生可以分为两种:

  1. 程序外部产生
  2. 程序内部产生

程序外部所产生的事件主要是指系统产生的事件,比如说鼠标按下(MouseButtonPress)、按键按下(KeyPress)等,Qt捕捉系统的事件,然后将系统事件封装成自己的QEvent类,再将事件发送出去。

程序内部产生的事件主要指我们在代码里,手动创建一个事件,然后将事件通过sendEvent/postEvent,来发送到事件循环中。而sendEventpostEvent区别又在于一个是阻塞的(sendEvent)一个是非阻塞的(postEvent)。

我们结合源码分析,看一下sendEventpostEvent分别干了什么导致一个是阻塞的一个是非阻塞的。

sendEvent

完整源码如下:

bool QCoreApplication::sendEvent(QObject *receiver, QEvent *event)
{
	// sendEvent是阻塞调用
    Q_TRACE(QCoreApplication_sendEvent, receiver, event, event->type());

    if (event)
        event->spont = false;
    return notifyInternal2(receiver, event);
}

可以看到,sendEvent是调用了notifyInternal2这个函数

bool QCoreApplication::notifyInternal2(QObject *receiver, QEvent *event)
{
	...
    // Qt enforces the rule that events can only be sent to objects in
    // the current thread, so receiver->d_func()->threadData is
    // equivalent to QThreadData::current(), just without the function
    // call overhead.
    // 事件只能在同一个线程被send
    QObjectPrivate *d = receiver->d_func();
    QThreadData *threadData = d->threadData;
    QScopedScopeLevelCounter scopeLevelCounter(threadData);
    if (!selfRequired)
        return doNotify(receiver, event);
    return self->notify(receiver, event);
}

进一步跟踪到其doNotify函数

static bool doNotify(QObject *receiver, QEvent *event)
{
    if (receiver == nullptr) {                        // serious error
        qWarning("QCoreApplication::notify: Unexpected null receiver");
        return true;
    }

#ifndef QT_NO_DEBUG
	// 检查接受线程与当前是否同线程
    QCoreApplicationPrivate::checkReceiverThread(receiver);
#endif

	// QWidget类必须用QApplication
    return receiver->isWidgetType() ? false : QCoreApplicationPrivate::notify_helper(receiver, event);
}

再到QCoreApplicationPrivate::notify_helper

bool QCoreApplicationPrivate::notify_helper(QObject *receiver, QEvent * event)
{
    // Note: when adjusting the tracepoints in here
    // consider adjusting QApplicationPrivate::notify_helper too.
    Q_TRACE(QCoreApplication_notify_entry, receiver, event, event->type());
    bool consumed = false;
    bool filtered = false;
    Q_TRACE_EXIT(QCoreApplication_notify_exit, consumed, filtered);

    // send to all application event filters (only does anything in the main thread)
    if (QCoreApplication::self
        && receiver->d_func()->threadData.loadRelaxed()->thread.loadAcquire() == mainThread()
        && QCoreApplication::self->d_func()->sendThroughApplicationEventFilters(receiver, event)) {
        filtered = true;
        return filtered;
    }
    // send to all receiver event filters
    if (sendThroughObjectEventFilters(receiver, event)) {
        filtered = true;
        return filtered;
    }

    // deliver the event
    // 直接调用对象的event函数,所以是阻塞的
    consumed = receiver->event(event);
    return consumed;
}

然后我们可以看到主要有几个流程:

  1. 判断QCoreApplication有没有安装事件过滤器,有就把信号发送到事件过滤器里,由事件过滤器对事件进行处理。

    // send to all application event filters (only does anything in the main thread)
    if (QCoreApplication::self
        && receiver->d_func()->threadData.loadRelaxed()->thread.loadAcquire() == mainThread()
        && QCoreApplication::self->d_func()->sendThroughApplicationEventFilters(receiver, event)) {
        filtered = true;
        return filtered;
    }
    
  2. 判断事件接受对象,有没有安装事件过滤器,有就将信号发送到事件过滤器。

    // send to all receiver event filters
    if (sendThroughObjectEventFilters(receiver, event)) {
        filtered = true;
        return filtered;
    }
    

    具体遍历事件接受对象所安装的事件过滤器的代码如下:

    bool QCoreApplicationPrivate::sendThroughObjectEventFilters(QObject *receiver, QEvent *event)
    {
        if (receiver != QCoreApplication::instance() && receiver->d_func()->extraData) {
            for (int i = 0; i < receiver->d_func()->extraData->eventFilters.size(); ++i) {
                QObject *obj = receiver->d_func()->extraData->eventFilters.at(i);
                if (!obj)
                    continue;
                if (obj->d_func()->threadData != receiver->d_func()->threadData) {
                    qWarning("QCoreApplication: Object event filter cannot be in a different thread.");
                    continue;
                }
                if (obj->eventFilter(receiver, event))
                    return true;
            }
        }
        return false;
    }
    

    我们可以看到,只要事件被一个事件过滤器所成功处理,那么后续的事件过滤器就不会被响应。同时,参看Qt帮助手册中有提及到:

    If multiple event filters are installed on a single object, the filter that was installed last is activated first.

    后插入的事件过滤器会被优先响应。 具体安装事件过滤器,我们在后面进行分析。

  3. 直接调用事件接受对象的event函数进行处理。因为是直接调用的对象的event,所以说,sendEvent函数会阻塞等待。

        // deliver the event
        // 直接调用对象的event函数,所以是阻塞的
        consumed = receiver->event(event);
        return consumed
    

postEvent

完整代码如下:

void QCoreApplication::postEvent(QObject *receiver, QEvent *event, int priority)
{
    Q_TRACE_SCOPE(QCoreApplication_postEvent, receiver, event, event->type());

	// 事件的接收者不能为空
    if (receiver == nullptr) {
        qWarning("QCoreApplication::postEvent: Unexpected null receiver");
        delete event;
        return;
    }

	// 对事件接受对象所在线程的事件处理列表上锁
    auto locker = QCoreApplicationPrivate::lockThreadPostEventList(receiver);
    if (!locker.threadData) {
        // posting during destruction? just delete the event to prevent a leak
        delete event;
        return;
    }

    QThreadData *data = locker.threadData;

    // if this is one of the compressible events, do compression
    // 将重复的事件,进行压缩
    if (receiver->d_func()->postedEvents
        && self && self->compressEvent(event, receiver, &data->postEventList)) {
        Q_TRACE(QCoreApplication_postEvent_event_compressed, receiver, event);
        return;
    }

    if (event->type() == QEvent::DeferredDelete)
        receiver->d_ptr->deleteLaterCalled = true;

    if (event->type() == QEvent::DeferredDelete && data == QThreadData::current()) {
        // remember the current running eventloop for DeferredDelete
        // events posted in the receiver's thread.

        // Events sent by non-Qt event handlers (such as glib) may not
        // have the scopeLevel set correctly. The scope level makes sure that
        // code like this:
        //     foo->deleteLater();
        //     qApp->processEvents(); // without passing QEvent::DeferredDelete
        // will not cause "foo" to be deleted before returning to the event loop.

        // If the scope level is 0 while loopLevel != 0, we are called from a
        // non-conformant code path, and our best guess is that the scope level
        // should be 1. (Loop level 0 is special: it means that no event loops
        // are running.)
        int loopLevel = data->loopLevel;
        int scopeLevel = data->scopeLevel;
        if (scopeLevel == 0 && loopLevel != 0)
            scopeLevel = 1;
        static_cast<QDeferredDeleteEvent *>(event)->level = loopLevel + scopeLevel;
    }

    // delete the event on exceptions to protect against memory leaks till the event is
    // properly owned in the postEventList
    QScopedPointer<QEvent> eventDeleter(event);
    Q_TRACE(QCoreApplication_postEvent_event_posted, receiver, event, event->type());
    data->postEventList.addEvent(QPostEvent(receiver, event, priority));
    eventDeleter.take();
    event->posted = true;
    ++receiver->d_func()->postedEvents;
    data->canWait = false;
    locker.unlock();

    QAbstractEventDispatcher* dispatcher = data->eventDispatcher.loadAcquire();
    if (dispatcher)
        dispatcher->wakeUp();
}
  1. 判断事件接收对象是否为空

    // 事件的接收者不能为空
    if (receiver == nullptr) {
        qWarning("QCoreApplication::postEvent: Unexpected null receiver");
        delete event;
        return;
    }
    
  2. 将事件接收对象所在线程的post事件列表上锁,如果已经被锁了,就把事件删除掉,并返回,防止泄露。

    // 对事件接受对象所在线程的事件处理列表上锁
    auto locker = QCoreApplicationPrivate::lockThreadPostEventList(receiver);
    if (!locker.threadData) {
        // posting during destruction? just delete the event to prevent a leak
        delete event;
        return;
    }
    
  3. 将一些可以压缩的事件进行压缩,及多个事件压缩成只推送最后的一个事件。Qt界面的update就是这个操作,为了防止多次刷新导致卡顿,短时间内多次的调用update可能只会刷新一次

    // if this is one of the compressible events, do compression
    // 将重复的事件,进行压缩
    if (receiver->d_func()->postedEvents
        && self && self->compressEvent(event, receiver, &data->postEventList)) {
        Q_TRACE(QCoreApplication_postEvent_event_compressed, receiver, event);
        return;
    }
    
  4. 将事件插入接收对象所在线程的post事件列表中,并唤醒线程的事件调度器,来进行事件的处理。所以postEvent是非阻塞的,因为其只是把事件插入了线程的事件列表,唤醒事件调度器之后便返回

        // delete the event on exceptions to protect against memory leaks till the event is
        // properly owned in the postEventList
        QScopedPointer<QEvent> eventDeleter(event);
        Q_TRACE(QCoreApplication_postEvent_event_posted, receiver, event, event->type());
        data->postEventList.addEvent(QPostEvent(receiver, event, priority));
        eventDeleter.take();
        event->posted = true;
        ++receiver->d_func()->postedEvents;
        data->canWait = false;
        locker.unlock();
    
        QAbstractEventDispatcher* dispatcher = data->eventDispatcher.loadAcquire();
        if (dispatcher)
            dispatcher->wakeUp();
    

事件是如何处理的?

在Qt中,事件的接收者都是QObject,而QObject中事件处理是调用event函数。如果当时对象不处理某个事件,就会将其转发到父类的event进行处理。
而事件的处理,主要分为三个部分:

  1. 先是由事件循环遍历事件
  2. 然后判断事件接受对象有没有安装事件过滤器(installEventFilter),有安装的话,就把事件丢给事件过滤器(eventFilter)进行处理。
  3. 如果没有安装事件过滤器或者事件过滤器对该事件不进行处理的话,那么,事件将会进一步转发到event函数里进行处理。

所以,在这一章节,我们同样一步一步的分析这三个点。

事件循环是怎么遍历的?

int main(int argc, char *argv[])
{
    QApplication a(argc, argv);

    MainWindow w;
    w.show();
    return a.exec();
}

上面是一个经典的QtGUI程序的main函数,调用a.exec()

int QCoreApplication::exec()
{
    ...
    
    threadData->quitNow = false;
    QEventLoop eventLoop;
    self->d_func()->in_exec = true;
    self->d_func()->aboutToQuitEmitted = false;
    int returnCode = eventLoop.exec();
    
    ...
}

而看QApplication::exec的源码,实际上就是开启了一个事件循环(QEventLoop)。同样,我们去看QEventLoop::exec的源码,进一步看处理事件的步骤是什么。

int QEventLoop::exec(ProcessEventsFlags flags)
{
    ...

    while (!d->exit.loadAcquire())
        processEvents(flags | WaitForMoreEvents | EventLoopExec);

    ref.exceptionCaught = false;
    return d->returnCode.loadRelaxed();
}

上面可以看到,QEvenLoop::exec里,是一个while循环,循环的去调用processEvent,而且设置了WaitForMoreEvents就是说,如果没有事件,就阻塞等待。

void QCoreApplication::processEvents(QEventLoop::ProcessEventsFlags flags, int ms)
{
    // ### Qt 6: consider splitting this method into a public and a private
    //           one, so that a user-invoked processEvents can be detected
    //           and handled properly.
    QThreadData *data = QThreadData::current();
    if (!data->hasEventDispatcher())
        return;
    QElapsedTimer start;
    start.start();
    while (data->eventDispatcher.loadRelaxed()->processEvents(flags & ~QEventLoop::WaitForMoreEvents)) {
        if (start.elapsed() > ms)
            break;
    }
}

阅读processEvent,其调用了线程的事件调度器QAbstrctEventDispatcher,而这个类是一个抽象基类,根据不同的平台,有不同的实现,我们以windows下(QEventDispatcherWin32)的为例,接着分析事件处理的流程。

bool QEventDispatcherWin32::processEvents(QEventLoop::ProcessEventsFlags flags)
{
    Q_D(QEventDispatcherWin32);

	...

    // To prevent livelocks, send posted events once per iteration.
    // QCoreApplication::sendPostedEvents() takes care about recursions.
    sendPostedEvents();

    ...
}

void QEventDispatcherWin32::sendPostedEvents()
{
    Q_D(QEventDispatcherWin32);

    if (d->sendPostedEventsTimerId != 0)
        KillTimer(d->internalHwnd, d->sendPostedEventsTimerId);
    d->sendPostedEventsTimerId = 0;

    // Allow posting WM_QT_SENDPOSTEDEVENTS message.
    d->wakeUps.storeRelaxed(0);

    QCoreApplicationPrivate::sendPostedEvents(0, 0, d->threadData.loadRelaxed());
}

可以看到,事件调度器最终还是调用了QCoreApplicationsendPostEvents

void QCoreApplicationPrivate::sendPostedEvents(QObject *receiver, int event_type,
                                               QThreadData *data)
{
    if (event_type == -1) {
        // we were called by an obsolete event dispatcher.
        event_type = 0;
    }

    if (receiver && receiver->d_func()->threadData != data) {
        qWarning("QCoreApplication::sendPostedEvents: Cannot send "
                 "posted events for objects in another thread");
        return;
    }

    ...

    // Exception-safe cleaning up without the need for a try/catch block
    struct CleanUp {
        QObject *receiver;
        int event_type;
        QThreadData *data;
        bool exceptionCaught;

        inline CleanUp(QObject *receiver, int event_type, QThreadData *data) :
            receiver(receiver), event_type(event_type), data(data), exceptionCaught(true)
        {}
        inline ~CleanUp()
        {
            if (exceptionCaught) {
                // since we were interrupted, we need another pass to make sure we clean everything up
                data->canWait = false;
            }

            --data->postEventList.recursion;
            if (!data->postEventList.recursion && !data->canWait && data->hasEventDispatcher())
                data->eventDispatcher.loadRelaxed()->wakeUp();

            // clear the global list, i.e. remove everything that was
            // delivered.
            if (!event_type && !receiver && data->postEventList.startOffset >= 0) {
                const QPostEventList::iterator it = data->postEventList.begin();
                data->postEventList.erase(it, it + data->postEventList.startOffset);
                data->postEventList.insertionOffset -= data->postEventList.startOffset;
                Q_ASSERT(data->postEventList.insertionOffset >= 0);
                data->postEventList.startOffset = 0;
            }
        }
    };
    CleanUp cleanup(receiver, event_type, data);

    while (i < data->postEventList.size()) {
       ...

        // first, we diddle the event so that we can deliver
        // it, and that no one will try to touch it later.
        pe.event->posted = false;
        QEvent *e = pe.event;
        QObject * r = pe.receiver;

        --r->d_func()->postedEvents;
        Q_ASSERT(r->d_func()->postedEvents >= 0);

        // next, update the data structure so that we're ready
        // for the next event.
        const_cast<QPostEvent &>(pe).event = nullptr;

        locker.unlock();
        const auto relocker = qScopeGuard([&locker] { locker.lock(); });

        QScopedPointer<QEvent> event_deleter(e); // will delete the event (with the mutex unlocked)

        // after all that work, it's time to deliver the event.
        QCoreApplication::sendEvent(r, e);

        // careful when adding anything below this point - the
        // sendEvent() call might invalidate any invariants this
        // function depends on.
    }

    cleanup.exceptionCaught = false;
}

我们一个一个的分块分析:

  1. 判断是否在一个线程

    if (receiver && receiver->d_func()->threadData != data) {
        qWarning("QCoreApplication::sendPostedEvents: Cannot send "
                 "posted events for objects in another thread");
        return;
    }
    
  2. 一个有意思的异常安全的处理,不需要try/catch块

    // Exception-safe cleaning up without the need for a try/catch block
    struct CleanUp {
        QObject *receiver;
        int event_type;
        QThreadData *data;
        bool exceptionCaught;
    
        inline CleanUp(QObject *receiver, int event_type, QThreadData *data) :
            receiver(receiver), event_type(event_type), data(data), exceptionCaught(true)
        {}
        inline ~CleanUp()
        {
            if (exceptionCaught) {
                // since we were interrupted, we need another pass to make sure we clean everything up
                data->canWait = false;
            }
    
            --data->postEventList.recursion;
            if (!data->postEventList.recursion && !data->canWait && data->hasEventDispatcher())
                data->eventDispatcher.loadRelaxed()->wakeUp();
    
            // clear the global list, i.e. remove everything that was
            // delivered.
            if (!event_type && !receiver && data->postEventList.startOffset >= 0) {
                const QPostEventList::iterator it = data->postEventList.begin();
                data->postEventList.erase(it, it + data->postEventList.startOffset);
                data->postEventList.insertionOffset -= data->postEventList.startOffset;
                Q_ASSERT(data->postEventList.insertionOffset >= 0);
                data->postEventList.startOffset = 0;
            }
        }
    };
    CleanUp cleanup(receiver, event_type, data);
    

定义了一个结构体CleanUp,结构体的析构函数(~CleanUp)保存了函数退出时需要执行的清理操作。然后在栈上创建了一个结构体对象,遍历事件列表时,异常退出,那么就会调用自动调用~CleanUp的析构函数。

  1. 将事件发送出去(sendEvent)

    while (i < data->postEventList.size()) {
           ...
    
            // first, we diddle the event so that we can deliver
            // it, and that no one will try to touch it later.
            pe.event->posted = false;
            QEvent *e = pe.event;
            QObject * r = pe.receiver;
    
            --r->d_func()->postedEvents;
            Q_ASSERT(r->d_func()->postedEvents >= 0);
    
            // next, update the data structure so that we're ready
            // for the next event.
            const_cast<QPostEvent &>(pe).event = nullptr;
    
            locker.unlock();
            const auto relocker = qScopeGuard([&locker] { locker.lock(); });
    
            QScopedPointer<QEvent> event_deleter(e); // will delete the event (with the mutex unlocked)
    
            // after all that work, it's time to deliver the event.
            QCoreApplication::sendEvent(r, e);
    
            // careful when adding anything below this point - the
            // sendEvent() call might invalidate any invariants this
            // function depends on.
        }
    

可以看到,核心还是调用sendEvent将事件发送出去,而前面我们对sendEvent的源码分析我们可以看到,事件先是经过事件过滤器,再经过对象的event函数,来进行事件的处理。所以就引出我们的下一个话题:事件过滤器

事件过滤器

在实际应用中,我们经常要将某一个窗口部件的某个事件如鼠标滑轮滚动拦截,然后执行我们自己想要的操作。这个时候,我们就可以用到事件过滤器(EventFilter**) **
首先,我们需要自己编写一个eventFilter函数,

bool Class::eventFilter(QObject* watcher, QEvent* event)
{
	//以过滤鼠标滚轮事件为例
    if (object == m_watcherObject && event->type() == QEvent::Wheel) {
    	// do something
        return true;       
    }

    QWidget::eventFilter(watcher, event);
}

然后,我们需要为要拦截的某个窗口部件,安装事件过滤器

void Class::initUI() 
{
	QWidget* m_watcherObject = new QWidget(this);
    // 为对象安装一个事件过滤器
	m_watcherObject->installEventFilterr(this);
}

initUI();

那么一个对象安装的多个事件过滤器,会以什么样的顺序触发呢?我们在前面的讲过,后安装的事件过滤器会先触发,这一点,我们可以在源码里得到佐证:

void QObject::installEventFilter(QObject *obj)
{
    Q_D(QObject);
    if (!obj)
        return;
    if (d->threadData != obj->d_func()->threadData) {
        qWarning("QObject::installEventFilter(): Cannot filter events for objects in a different thread.");
        return;
    }

    if (!d->extraData)
        d->extraData = new QObjectPrivate::ExtraData;

    // clean up unused items in the list
    d->extraData->eventFilters.removeAll((QObject*)nullptr);
    d->extraData->eventFilters.removeAll(obj);
    d->extraData->eventFilters.prepend(obj);
}

可以清楚的看到,事件过滤器,是以prepend的形式被添加进事件过滤器列表的。
那么,当有鼠标滚轮事件触发的时候,我们可以看到sendEvent会优先走到事件过滤器里,如果eventFilter返回一个true,那么事件就不会被继续派发,否则,将会将事件发送到其他的事件过滤器里进行处理,如果其他的事件过滤器均对该事件不进行处理,那么事件将会继续往下派发,走到事件的处理函数event

event

接下来,就到了事件处理的最后一站,event函数,这个函数比较简单,我们可以自己重写这个函数,对事件进行自定义的处理。

bool Class::event(QEvent *e)
{
    switch (e->type()) {
    case QEvent::Whell:
        // do something
        return true;

    default:
        if (e->type() >= QEvent::User) {
            customEvent(e);
            break;
        }
        return false;
    }
    return true;
}

夹带私货时间

  1. 之前有说到processEvent,添加一个小经验。当我们有时候不得不在主线程循环执行很耗时的操作的时候,这个时候,界面就会刷新不过来,就会导致界面卡顿,影响使用。但是,我们可以在这个循环里,手动调用qApp->processEvent(),这样就可以手动调用处理掉所有的事件,就可以解决卡顿的问题

原文链接:https://www.cnblogs.com/codegb/p/17274163.html

本站文章如无特殊说明,均为本站原创,如若转载,请注明出处:Qt源码阅读(四) 事件循环 - Python技术站

(0)
上一篇 2023年4月18日
下一篇 2023年4月18日

相关文章

  • MordernC++之左值(引用)与右值(引用)

    左值与右值 C++中左值与右值的概念是从C中继承而来,一种简单的定义是左值能够出现再表达式的左边或者右边,而右值只能出现在表达式的右边。 int a = 5; // a是左值,5是右值 int b = a; // b是左值,a也是左值 int c = a + b; // c是左值,a + b是右值 另一种区分左值和右值的方法是:有名字、能取地址的值是左值,没…

    C++ 2023年4月17日
    00
  • 【Visual Leak Detector】核心源码剖析(VLD 1.0)

    说明 使用 VLD 内存泄漏检测工具辅助开发时整理的学习笔记。本篇对 VLD 1.0 源码做内存泄漏检测的思路进行剖析。同系列文章目录可见 《内存泄漏检测工具》目录 目录 说明 1. 源码获取 2. 源码文件概览 3. 源码剖析 3.1 注册自定义 AllocHook 函数 3.2 存储调用堆栈信息 3.3 生成泄漏检测报告 4. 其他问题 4.1 如何区分…

    C++ 2023年4月27日
    00
  • STL 容器 002 (vector 详解)

    为什么 各方面表现都比较中等, 适用范围广 尾插很快, 查找也比较快 是什么 动态数组 特点: 动态数组, 三个指针控制 两倍增长 扩充的方法: 不能原地扩充, 因为后面可能会有其他的东西, 必须在 其他地方开辟一块更大的内存 提供[] 所有的有连续空间的容器都有[] iterator是class类型的 怎么样 制造 两倍增长 //push_back() 检…

    C++ 2023年4月18日
    00
  • 二分查找算法讲解及其C++代码实现

    二分查找算法是一种常用的查找算法,也被称为折半查找。它可以在有序的数组或列表中快速查找需要的元素。 算法描述: 首先确定数组的中间位置mid=(left+right)/2; 然后将要查找的值key与中间位置的值进行比较; 如果key等于中间位置的值,则查找成功,返回mid; 如果key小于中间位置的值,则在左半部分继续查找; 如果key大于中间位置的值,则在…

    C++ 2023年4月30日
    00
  • C++ 测试框架 GoogleTest 初学者入门篇 甲

    *以下内容为本人的学习笔记,如需要转载,请声明原文链接 微信公众号「ENG八戒」https://mp.weixin.qq.com/s/BS_u9A4EY50y4vDDuxkCAQ 开发者虽然主要负责工程里的开发任务,但是每个开发完毕的功能都是需要开发者自测通过的,所以经常会听到开发者提起单元测试的话题。那么今天我就带大伙一起来看看大名鼎鼎的谷歌 C++ 测试…

    C++ 2023年4月18日
    00
  • 【Visual Leak Detector】配置项 ForceIncludeModules

    说明 使用 VLD 内存泄漏检测工具辅助开发时整理的学习笔记。本篇介绍 VLD 配置文件中配置项 ForceIncludeModules 的使用方法。 同系列文章目录可见 《内存泄漏检测工具》目录 目录 说明 1. 配置文件使用说明 2. 设置需要检测的第三方模块 2.1 测试代码 2.2 ForceIncludeModules 为空时的输出 2.3 For…

    C++ 2023年4月18日
    00
  • day3 函数的定义和调用,练习编写简单的程序(记录1)

    一、函数的定义 可以分为以下两种: 1、函数声明和函数定义分离 这种方法将函数声明和函数定义分开,通常在头文件中先声明函数原型,然后在源文件中实现函数定义。 例如,头文件 example.h 中声明了一个函数 add: #ifndef EXAMPLE_H #define EXAMPLE_H int add(int a, int b); // 声明函数原型 #…

    C++ 2023年4月18日
    00
  • P6818 [PA2013]Działka 题解

    P6818 [PA2013]Działka 前言 我太菜了。。。。 对着 jiangly 大佬的题解研究了一下午研究了一下午才搞出来(泪目。 作为一个蒟蒻,我就详细的讲一下我对与本题的理解。 题意 本题的的题意描述的还是比较明了。 在二维坐标系中,输入 \(n\) 个点 \(m\) 次询问, 每次询问,给出一个矩阵, 求出矩阵内极大凸包的面积。 题解 1.如…

    C++ 2023年4月30日
    00
合作推广
合作推广
分享本页
返回顶部