Qt的信号和槽是如何工作的

2023-11-07

用Qt做过开发的朋友,不知道是否曾为下面这些问题疑惑过:
我们知道Qt是基于C++的,Qt写的代码最终还是要由C++编译器来编译,但是我们的Qt代码中有很多C++里没有的关键字,比如slots\signals\Q_OBJECT等,为什么C++编译器会放过他们呢?
Qt的槽函数跟普通的成员函数有何区别?一个信号发出后,与之关联的槽函数是什么时候由谁来调用的?
Qt的信号定义跟函数定义有相同的形式,那信号跟普通函数有何区别?一个信号被emit时我们的程序到底在干什么?
信号和槽的连接是怎么建立的?又是如何断开的?
本篇博文里对这些问题作了详尽的解释,初学Qt或者用了很长时间Qt却很少思考过这些问题的朋友,可以在下面的段落中找到答案。

下文出自- original:http://woboq.com/blog/how-qt-signals-slots-work.html

译:NewThinker_Jiwey NewThinker_wei @CSDN

【译】Qt的信号-槽机制是如何工作的
(译注:如果阅读过程中有感觉疑惑的地方,可以参考一下本文的原文-译文对照版: http://blog.csdn.net/newthinker_wei/article/details/22701695


很多人都知道Qt的信号和槽机制,但它究竟是如何工作的呢?在本篇博文中,我们会探索QObject 和 QMetaObject 内部的一些具体实现,揭开信号-槽的神秘面纱。

在本篇博文中,我会贴出部分Qt5的源码,有时候会根据需要改变一下格式并做一些简化。


首先,我们看一个官方例程,借此回顾一下信号和槽的使用方法。

头文件是这样的:

***********************************************

class Counter :publicQObject
{
    Q_OBJECT
    int m_value;
public:
    int value()const {returnm_value; }
public slots:
    void setValue(intvalue);
signals:
    void valueChanged(intnewValue);
};

***********************************************




在源文件的某个地方,可以找到setValue()函数的实现:

***********************************************

voidCounter::setValue(intvalue)

{

   if (value !=m_value){

       m_value =valueemitvalueChanged(value);

    }

}

***********************************************




然后开发者可以像下面这样使用Counter的对象:

***********************************************

  Counter ab;
  QObject::connect(&a,SIGNAL(valueChanged(int)),
                   &bSLOT(setValue(int)));

  a.setValue(12); // a.value() == 12, b.value() == 12

***********************************************

这是从1992年Qt最初阶段开始就沿用下来而几乎没有变过的原始语法。

虽然基本的API并没有怎么变过,但它的实现方式却变了几次。很多新特性被添加了进来,底层的实现也发生了很多变化。不过这里面并没有什么神奇的难以理解的东西,本篇博文会向你展示这究竟是如何工作的,


MOC,元对象编译器


Qt的信号/槽和属性系统都基于其能在运行时刻对对象进行实时考察的功能。实时考察意味着即使在运行过程中也可以列出一个对象有哪些方法(成员函数)和属性,以及关于它们的各种信息(比如参数类型)。如果没有实时考察这个功能,QtScript 和 QML 就基本不可能实现了。

C++本身不提供对实时考察的支持,所以Qt就推出了一个工具来提供这个支持。这个工具就是MOC。注意,它是一个代码生成器,而不是很多人说的“预编译器”。

MOC会解析头文件并为每一个含有Q_OBJECT宏的头文件生成一个对应的C++文件(这个文件会跟工程中的其他代码一块参与编译)。这个生成的C++文件包含了实时考察功能所需的全部信息(文件一般被命名为moc_HeaderName。cpp)。

因为这个额外的代码生成器,Qt有时对语言会有很严格的要求。 这里我就让这篇Qt文档来解释这个严格的要求。代码生成器没有什么错误,MOC起到了很大的作用。

几个神奇的宏

你能看出这几个关键字并不是标准C++的关键字吗?signals, slots, Q_OBJECT, emit, SIGNAL, SLOT. 这些都是Qt对C++的扩展。这几个关键字其实都是很简单的宏定义而已,在qobjectdefs.h 头文件中可以找到他们的定义。

[cpp]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. #define signals public  
  2. #define slots /* nothing */  


没错,信号和槽都是很简单的功能:编译器会将他们与其他任何宏一样处理。不过这些宏还有一个特殊的作用:MOC会发现他们。

Signals在Qt4之前都是protected类型的,他们在Qt5中变为了public,这样就可以使用一些新的语法了。

[cpp]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. #define Q_OBJECT \  
  2. public: \  
  3.     static const QMetaObject staticMetaObject; \  
  4.     virtual const QMetaObject *metaObject() const; \  
  5.     virtual void *qt_metacast(const char *); \  
  6.     virtual int qt_metacall(QMetaObject::Call, intvoid **); \  
  7.     QT_TR_FUNCTIONS /* translations helper */ \  
  8. private: \  
  9.     Q_DECL_HIDDEN static void qt_static_metacall(QObject *, QMetaObject::Call, intvoid **);  
  10.   
  11.   
  12. Q_OBJECT defines a bunch of functions and a static QMetaObject Those functions are implemented in the file generated by MOC.  
  13. #define emit /* nothing */  


emit是个空的宏定义,而且MOC也不会对它进行解析。也就是,emit完全是可有可无的,他没有任何意义(除了对开发者有提示作用)。

[cpp]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. Q_CORE_EXPORT const char *qFlagLocation(const char *method);  
  2. #ifndef QT_NO_DEBUG  
  3. # define QLOCATION "\0" __FILE__ ":" QTOSTRING(__LINE__)  
  4. # define SLOT(a)     qFlagLocation("1"#a QLOCATION)  
  5. # define SIGNAL(a)   qFlagLocation("2"#a QLOCATION)  
  6. #else  
  7. # define SLOT(a)     "1"#a  
  8. # define SIGNAL(a)   "2"#a  
  9. #endif  


(译注:对于#define的一些高级用法,参见我整理的一片文章:http://blog.csdn.net/newthinker_wei/article/details/8893407

上面这些宏,会利用预编译器将一些参数转化成字符串,并且在前面添加上编码。

在调试模式中,如果singnal的连接出现问题,我们提示警告信息的时候还会注明对应的文件位置。这是在Qt4.5之后以兼容方式添加进来的功能。为了知道代码对应的行信息,我们可以用qFlagLocation ,它会将对应代码的地址信息注册到一个有两个入口的表里。

MOC生成的代码

我们现在就来看看Qt5的moc生成的部分代码。

The QMetaObject

***********************************************

const QMetaObject Counter::staticMetaObject = {
    { &QObject::staticMetaObject,qt_meta_stringdata_Counter.data,
      qt_meta_data_Counterqt_static_metacall,0,0}
};


const QMetaObject *Counter::metaObject()const
{
    return QObject::d_ptr->metaObject ? QObject::d_ptr->dynamicMetaObject() : &staticMetaObject;
}

*********************************************** 

我们在这里可以看到Counter::metaObject()和 Counter::staticMetaObject 的实现。他们都是在Q_OBJECT宏中被声明的。

QObject::d_ptr->metaObject 只被用于动态元对象(QML对象),随意通常虚函数metaObject() 只是返回类的staticMetaObject 。

staticMetaObject被创建为只读数据。qobjectdefs.h 文件中QMetaObject的定义如下:

 

[cpp]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. struct QMetaObject  
  2. {  
  3.     /* ... Skiped all the public functions ... */  
  4.   
  5.     enum Call { InvokeMetaMethod, ReadProperty, WriteProperty, /*...*/ };  
  6.   
  7.     struct { // private data  
  8.         const QMetaObject *superdata;  
  9.         const QByteArrayData *stringdata;  
  10.         const uint *data;  
  11.         typedef void (*StaticMetacallFunction)(QObject *, QMetaObject::Call, intvoid **);  
  12.         StaticMetacallFunction static_metacall;  
  13.         const QMetaObject **relatedMetaObjects;  
  14.         void *extradata; //reserved for future use  
  15.     } d;  
  16. };  


代码中用的d是为了表明那些数据都本应为私有的。然而他们并没有成为私有的是为了保持它为POD和允许静态初始化。(译注:在C++中,我们把传统的C风格的struct叫做POD(Plain Old Data),字面意思古老的普通的结构体)。

这里会用父类对象的元对象(此处就是指QObject::staticMetaObject )来初始化QMetaObject的superdata,而它的stringdata 和 data 这两个成员则会用之后要讲到的数据初始化。static_metacall是一个被初始化为 Counter::qt_static_metacall的函数指针。

实时考察功能用到的数据表

首先,我们来分析 QMetaObject的这个整型数组:

***********************************************

static const uint qt_meta_data_Counter[] = {

// content:
       7,       // revision
       0,       // classname
       0,    0// classinfo
       2,   14// methods
       0,    0// properties
       0,    0// enums/sets
       0,    0// constructors
       0,       // flags
       1,       // signalCount

// signals: name, argc, parameters, tag, flags

       1,    1,   24,    20x05,

// slots: name, argc, parameters, tag, flags
       4,    1,   27,    20x0a,

// signals: parameters
    QMetaType::Void,QMetaType::Int,   3,

// slots: parameters
    QMetaType::Void,QMetaType::Int,   5,

       0        // eod
};

***********************************************

开头的13个整型数组成了结构体的头信息。对于有两列的那些数据,第一列表示某一类项目的个数,第二列表示这一类项目的描述信息开始于这个数组中的哪个位置(索引值)。

这里,我们的Counter类有两个方法,并且关于方法的描述信息开始于第14个int数据。

每个方法的描述信息由5个int型数据组成。第一个整型数代表方法名,它的值是该方法名(译注:方法名就是个字符串)在字符串表中的索引位置(之后会介绍字符串表)。第二个整数表示该方法所需参数的个数,后面紧跟的第三个数就是关于参数的描述(译注:它表示与参数相关的描述信息开始于本数组中的哪个位置,也是个索引)。我们现在先忽略掉tag和flags。对于每个函数,Moc还会保存它的返回类型、每个参数的类型、以及参数的名称。


字符串表

***********************************************

struct qt_meta_stringdata_Counter_t {
    QByteArrayData data[6];
    char stringdata[47];
};
#define QT_MOC_LITERAL(idx, ofs, len) \
Q_STATIC_BYTE_ARRAY_DATA_HEADER_INITIALIZER_WITH_OFFSET(len, \
offsetof(qt_meta_stringdata_Counter_t, stringdata) + ofs \
- idx * sizeof(QByteArrayData) \
)

static const qt_meta_stringdata_Counter_tqt_meta_stringdata_Counter = {
    {
QT_MOC_LITERAL(0,0,7),
QT_MOC_LITERAL(1,8,12),
QT_MOC_LITERAL(2,21,0),
QT_MOC_LITERAL(3,22,8),
QT_MOC_LITERAL(4,31,8),
QT_MOC_LITERAL(5,40,5)
    },
    "Counter\0valueChanged\0\0newValue\0setValue\0"
    "value\0"
};
#undef QT_MOC_LITERAL

***********************************************

这主要就是一个QByteArray的静态数组。QT_MOC_LITERAL 这个宏可以创建一个静态的QByteArray ,其数据就是参考的在它下面的对应索引处的字符串。

信号

MOC还实现了信号signals(译注:根据前面的介绍我们已经知道signals其实就是publib,因此所有被定义的信号其实都必须有具体的函数定义,这样才能C++编译器编译,而我们在开发中并不曾写过信号的定义,这是因为这些都由MOC来完成)。所有的信号都是很简单的函数而已,他们只是为参数创建一个指针数组并传递给 QMetaObject::activate函数。指针数组的第一个元素是属于返回值的。在我们的例子中将它设置为了0,这是因为我们的返回类型是void。
传递给activate 函数的第3个参数是信号的索引(在这里,该索引为0)。
***********************************************
// SIGNAL 0
void
  Counter:: valueChanged( int _t1)
{
     void * _a[] = {  0, const_cast< void*>( reinterpret_cast< const void*>(& _t1)) };
     QMetaObject:: activate( this, & staticMetaObject, 0, _a);
}
***********************************************



调用槽
通过一个槽的索引来调用一个槽函数也是行得通的,这要借助 qt_static_metacall这个函数:
***********************************************
void  Counter:: qt_static_metacall( QObject * _oQMetaObject:: Call _c, int _id, void ** _a)
{
     if ( _c ==  QMetaObject:: InvokeMetaMethod) {
         Counter * _t = static_cast< Counter *>( _o);
         switch ( _id) {
         case  0_t-> valueChanged((* reinterpret_cast< int(*)>( _a[ 1]))); break;
         case  1_t-> setValue((* reinterpret_cast< int(*)>( _a[ 1]))); break;
         default: ;
        }
***********************************************
函数中的指针数组与在上面介绍signal时的那个函数中的指针数组格式相同。只不过这里没有用到_a[0],因为这里所有的函数都是返回void。
关于索引需要注意的地方
在每个 QMetaObject中,对象的槽、信号和其他一些被涉及到的方法都被分配了一个索引,这些索引值从0开始。他们是按照“先信号、再槽、最后其他方法”的顺序排列。这样得到的索引被称为相对索引( relative index),相对索引与该类的父类(就是基类)和祖宗类中的方法数量无关。
但是通常,我们不是想知道相对索引,而是想知道在包含了从父类和祖宗类中继承来的所有方法后的绝对索引。为了得到这个索引,我们只需要在相关索引( relative index)上加上一个偏移量就可以得到绝对索引 absolute index了。这个绝对索引就是在Qt的API中使用的, 像 QMetaObject::indexOf{Signal,Slot,Method}这样的函数返回的就是绝对索引。
另外,在信号槽的连接机制中还要用到一个关于信号的向量索引。这样的索引表中如果把槽也包含进来的话槽会造成向量的浪费,而一般槽的数量又要比信号多。所以从Qt4.6开始,Qt内部又多出了一个专门的信号索引signal index ,它是一个只包含了信号的索引表。
在用Qt开发的时候,我们只需要关心绝对索引就行。不过在浏览Qt源码的时候,要留意这三种索引的不同。
连接是如何进行的

在进行信号和槽的连接时Qt做的一件事就是找出要连接的信号和槽的索引。Qt会在meta object的字符串表中查找对应的索引。

然后会创建一个QObjectPrivate::Connection对象并添加到内部的链表中。

一个 connection 中需要存储哪些数据呢?我们需要一种能根据信号索引signal index快速访问到对应的connection的方法。因为可能会同时有不止一个槽连接到同一个信号上,所以每一个信号都要有一个槽列表;每个connection必须包含接收对象的指针以及被连接的槽的索引;我们还想让一个connection能在与之对应的接收者被销毁时自动取消连接,所以每个接收者对象也需要知道谁与他建立了连接这样才能在析构时将connection清理掉。

下面是定义在 qobject_p.h 中的QObjectPrivate::Connection :

(译注:只认识QObject不认识QObjectPrivate?看这里:http://blog.csdn.net/newthinker_wei/article/details/22789885)


***********************************************

struct QObjectPrivate::Connection
{
    QObject *sender;
    QObject *receiver;
    union {
        StaticMetaCallFunction callFunction;
        QtPrivate::QSlotObjectBase *slotObj;
    };
    // The next pointer for the singly-linked ConnectionList
    Connection *nextConnectionList;
    //senders linked list
    Connection *next;
    Connection **prev;
    QAtomicPointer<const intargumentTypes;
    QAtomicInt ref_;
    ushort method_offset;
    ushort method_relative;
    uint signal_index : 27// In signal range (see QObjectPrivate::signalIndex())
    ushort connectionType : 3// 0 == auto, 1 == direct, 2 == queued, 4 == blocking
    ushort isSlotObject : 1;
    ushort ownArgumentTypes : 1;
    Connection() : nextConnectionList(0), ref_(2), ownArgumentTypes(true) {
        //ref_ is 2 for the use in the internal lists, and for the use in QMetaObject::Connection
    }
    ~Connection();
    int method() const { return method_offset + method_relative; }
    void ref() { ref_.ref(); }
    void deref() {
        if (!ref_.deref()) {
            Q_ASSERT(!receiver);
            delete this;
        }
    }
};

***********************************************

每一个对象有一个connection vector:每一个信号有一个 QObjectPrivate::Connection的链表,这个vector就是与这些链表相关联的。
每一个对象还有一个反向链表,它包含了这个对象被连接到的所有的 connection,这样可以实现连接的自动清除。而且这个反向链表是一个双重链表。



使用链表是因为它们可以快速地添加和移除对象,它们靠保存在QObjectPrivate::Connection中的next/previous前后节点的指针来实现这些操作。

注意senderListprev指针是一个指针的指针。这是因为我们不是真的要指向前一个节点,而是要指向一个指向前节点的指针。这个指针的指针只有在销毁连接时才用到,而且不要用它重复往回迭代。这样设计可以不用对链表的首结点做特殊处理。


(译注:对连接的建立如果还有疑惑,请参考:http://blog.csdn.net/newthinker_wei/article/details/22791617


信号的发送

我们已经知道当调用一个信号的时候,最终调用的是MOC生成的代码中的QMetaObject::activate函数。

这里是qobject.cpp中这个函数的实现代码,这里贴出来的只是一个注解版本。

***********************************************

void QMetaObject::activate(QObject *senderconst QMetaObject *m,intlocal_signal_index,
                           void **argv)
{
    activate(sender,QMetaObjectPrivate::signalOffset(m),local_signal_index,argv);
    /* We just forward to the next function here. We pass the signal offset of
* the meta object rather than the QMetaObject itself
* It is split into two functions because QML internals will call the later. */

}

void QMetaObject::activate(QObject *senderintsignalOffset,intlocal_signal_index,void **argv)
{
    int signal_index =signalOffset +local_signal_index;

    /* The first thing we do is quickly check a bit-mask of 64 bits. If it is 0,
* we are sure there is nothing connected to this signal, and we can return
* quickly, which means emitting a signal connected to no slot is extremely
* fast. */

    if (!sender->d_func()->isSignalConnected(signal_index))
        return// nothing connected to these signals, and no spy

    ......

    ......


    /*译注:获得当前正在运行的线程的ID*/

    Qt::HANDLE currentThreadId = QThread::currentThreadId();


    /* ... Skipped some debugging and QML hooks, and some sanity check ... */

   /*跳过一些调试代码和完整性检测*/
    /* We lock a mutex because all operations in the connectionLists are thread safe */

    /*这里用一个互斥量锁住,因为在ConnectionList里的所有操作都应是线程安全的*/
    QMutexLocker locker(signalSlotLock(sender));

    /* Get the ConnectionList for this signal. I simplified a bit here. The real code
* also refcount the list and do sanity checks */

   /*得到这个信号的ConnectionList。这里我做了一些简化。原来的代码还有完整性检测等*/
    QObjectConnectionListVector *connectionLists =sender->d_func()->connectionLists;
    const QObjectPrivate::ConnectionList *list =
        &connectionLists->at(signal_index);

    QObjectPrivate::Connection *c = list->first;
    if (!c) continue;
    // We need to check against last here to ensure that signals added
// during the signal emission are not emitted in this emission.

    QObjectPrivate::Connection *last = list->last;

    /* Now iterates, for each slot */
    do {
        if (!c->receiver)
            continue;

        QObject * const receiver = c->receiver;

        /*译注:比较当前正在运行的线程的ID与receiver所属的线程的ID是否相同*/
        const bool receiverInSameThread = QThread::currentThreadId() == receiver->d_func()->threadData->threadId;

        // determine if this connection should be sent immediately or
// put into the event queue

       //译注:注意下面这一段,从这里可以看出对于跨线程的连接,信号发出

       //后槽函数不会立即在当前线程中执行。其执行要等到槽函数所在的线程被

       //激活后。有时间了再研究一下queued_activate这个函数。
        if ((c->connectionType == Qt::AutoConnection && !receiverInSameThread)
            || (c->connectionType == Qt::QueuedConnection)) {
            /* Will basically copy the argument and post an event */
            queued_activate(sender,signal_index,c,argv);
            continue;
        } else if (c->connectionType == Qt::BlockingQueuedConnection) {
            /* ... Skipped ... */
            continue;
        }

        /* Helper struct that sets the sender() (and reset it backs when it
* goes out of scope */

        QConnectionSenderSwitcher sw;
        if (receiverInSameThread)
            sw.switchSender(receiver,sender,signal_index);

        const QObjectPrivate::StaticMetaCallFunctioncallFunction =c->callFunction;
        const int method_relative = c->method_relative;
        if (c->isSlotObject) {
            /* ... Skipped.... Qt5-style connection to function pointer */
        } else if (callFunction &&c->method_offset <=receiver->metaObject()->methodOffset()) {
            /* If we have a callFunction (a pointer to the qt_static_metacall
* generated by moc) we will call it. We also need to check the
* saved metodOffset is still valid (we could be called from the
* destructor) */

            locker.unlock();// We must not keep the lock while calling use code
            callFunction(receiver,QMetaObject::InvokeMetaMethod,method_relative,argv);
            locker.relock();
        } else {
            /* Fallback for dynamic objects */
            const int
 method =method_relative +c->method_offset;
            locker.unlock();
            metacall(receiver,QMetaObject::InvokeMetaMethod,method,argv);
            locker.relock();
        }

        // Check if the object was not deleted by the slot
        if (connectionLists->orphaned)break;
    } while (c != last && (c = c->nextConnectionList) !=0);
}
***********************************************


from :http://blog.csdn.net/newthinker_wei/article/details/22785763



本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

Qt的信号和槽是如何工作的 的相关文章

  • 记一次Swagger页面报错/error 404的排查过程

    记一次Swagger页面报错 error 404的排查过程 使用springfox swagger ui展示的页面如下 Maven引用 使用springfox swagger ui展示的页面如下 说是没有为 error这个路径指明确定的映射
  • Java中的注解和反射

    文章目录 Java中的注解和反射 一 注解 1 1注解Annotation的作用 1 2注解Annotation的格式 1 3注解Annotation在哪里使用 1 4实例 二 内置注解 三 元注解 四 自定义注解 五 静态和动态语言 5
  • 2022年浙江省中职组“网络空间安全”赛项模块B--Windows渗透测试

    2022年中职组浙江省 网络空间安全 赛项 B 1 Windows渗透测试 一 竞赛时间 420分钟 共计7小时 吃饭一小时 二 竞赛阶段 竞赛阶段 任务阶段 竞赛任务 竞赛时间 分值 第 阶段 单兵模式系统渗透测试 任务一 Windows
  • Notepad++ 文件丢失了,找回历史文件方法

    Notepad 文件丢失了 找回历史文件方法 C Users 你当前用户的用户名 AppData Roaming Notepad backup
  • Linux防火墙iptables(二)之SNAT和DNAT

    目录 一 SNAT 1 概述 2 开启SNAT的命令 3 SNAT转换1 固定的公网IP地址 4 SNAT转换2 非固定的公网IP地址 共享动态IP地址 5 实验 二 DNAT 1 DNAT应用环境 2 DNAT原理 3 DNAT转换前提条
  • 解决react-native-image-picker在RN0.6以上上调不起相机问题

    1 需要link react native link react native image picker 2 在android settings gradle文件中添加如下代码 include react native image pick
  • 算法设计与分析-DP习题

    7 1 最小路径和 给定一个m行n列的矩阵 从左上角开始每次只能向右或者向下移动 最后到达右下角的位置 路径上的所有数字累加起来作为这条路径的和 求矩阵的最小路径和 输入格式 输入第一行 两个正整数m和n 1 lt m n lt 1000
  • Altium Designer 学习笔记(原理图库)

    1 AD工程的组成部分 源文件 原理图 PCB图 库文件 原理图库 PCB原件库 一定要建工程 不要只建原理图和PCB 建立了工程才能在原理图和PCB之间建立联系 2 绘制原理图库 电阻容 放置引脚 快捷键 P P 在放置的过程中按TAB可
  • opengl_shader在线教程,GLSL  着色器语言学习入门学习

    下面2个网址对GLSL 着色器语言学习入门学习挺有帮助的 https thebookofshaders com 07 lan ch opengl入门教程 https learnopengl cn github io
  • Qt扫盲-QFile理论总结

    QFile理论总结 1 概述 2 直接操件文件 3 用 流 方式 操作 文件 1 读取文件 2 写文本文件 3 二进制流读写 4 静态函数 5 不同系统存在的问题 1 概述 QFile 类是一种用于读取和写入文本和二进制文件资源的 I O
  • 数据结构(十七) -- 树(九) --B树B+树

    数据结构演示网址 数据结构演示地址 1 出现背景 B树B 树目的 为了硬盘快速读取数据 降低IO操作次数 而设计的一种平衡的多路查找树 二叉查找树 AVL树 红黑树等都属于二叉树的范围 查找的时间复杂度是O log 2N 与树的深度相关 那
  • 不含101的数_200分——二进制数 / 动态规划 / 数位DP_2023A卷

    不含101的数 题目描述 小明在学习二进制时 发现了一类不含 101的数 也就是 将数字用二进制表示 不能出现 101 现在给定一个整数区间 l r 请问这个区间包含了多少个不含 101 的数 输入输出描述 输入描述 输入的唯一一行包含两个
  • 零基础入门STM32编程(九)——定时器PWM呼吸灯(CUBEMX)

    一 前言 前面章节我们学了如何使用定时器中断点灯 原理为定时器计数达到1s时产生中断 此时单片机调用中断服务函数 执行中断服务函数中的代码 本节我们通过定时器的PWM功能实现呼吸灯的功能 二 定时器PWM功能 2 1 端口复用 定时器的PW
  • 数据挖掘案例实战:利用LDA主题模型提取京东评论数据

    数据挖掘案例实战 利用LDA主题模型提取京东评论数据 网上购物已经成为大众生活的重要组成部分 人们在电商平台上浏览商品和购物 产生了海量的用户行为数据 其中用户对商品的评论数据对商家具有重要的意义 利用好这些碎片化 非结构化的数据 将有利于
  • 3D【10】网格优化:Laplacian Mesh Optimization

    拉普拉斯网格优化与平滑是网格处理的经典算法 其一些基本概念可以作为神经网络预测3D mesh的一些约束 如平滑 我们先来看看一些基本概念 基本概念 首先 我们用 G V E G V E G V E 来表示一个网格 其中 V vT1 vT
  • 如何解决长对话摘要生成问题?

    主要参考论文 DYLE Dynamic Latent Extraction for Abstractive Long Input Summarization 摘要 基于transformer的模型已经在短输入的摘要提取方面取得了先进的性能
  • 数据库系统工程师考点笔记

    目录 第1章 计算机系统知识 1 1 计算机硬件基础知识 1 1 1 1 中央处理单元 1 1 1 2 存储器 4 1 1 3 总线 7 1 1 4 输入输出控制 10 1 2 计算机体系结构 14 1 2 1 CISC和RISC 15 1

随机推荐

  • linux下文件的mtime

    利用find命令按文件修改时间对文件进行清理时 预想中应该被清理的文件没有被清理掉 所以专门测试了下mtime的使用规则 测试时间为 wang wmy test date 2020年 10月 24日 星期六 17 55 50 CST wan
  • mybatie+spring+mvc使用反射遇到的问题

    问题1 使用反射调用 serviceImpl时 使用注解的 Dao对象是空 解决办法在 serviceImpl类中 直接去spring容器获取bean 问题2 在一个类TestServiceImple的方法中 使用事务控制 中调用另外一个类
  • 延锋安道拓:简化工作流程 实现研发数据外发安全可控

    客户简介 延锋安道拓座椅有限公司成立于1997年 是由延锋伟世通汽车饰件系统有限公司 隶属于上汽集团华域汽车SH 600741 和美国江森自控国际有限公司 NYSE JCI 共同投资组建的合资企业 拥有70余家分子公司和2个海外制造基地 为
  • 加密货币市值、股市市值、房地产价值

    加密货币市值 股市市值 房地产价值 全球加密货币市值共0 85万亿美元 统计时间2022年12月6日 比特币 时间2022年12月6日价值0 33万亿美元 其他 时间2022年12月6日价值0 52万亿美元 全球流通货币价值共8万亿美元 统
  • @【 ENVI】“应用程序无法正常启动0x0000007b”问题

    ENVI 应用程序无法正常启动0x0000007b 问题 ENVI5 3 百度网盘 链接 https pan baidu com s 1P1nI9fKEGeNbSsMt9D3mMA 提取码 zely 记得安装目录里面不能有中文 idlrt
  • 无线鼠标计算机不识别,教你笔记本电脑检测不到无线鼠标如何解决

    无线鼠标由于没有线的牵绊 受到很多网友的喜爱 特别是笔记本电脑用户 不过最近有网友说自己的笔记本电脑检测不到无线鼠标怎么办 无线鼠标失灵了 其实这个是很常见的问题 造成的原因也比较多样 下面小编就给大家分享下笔记本电脑识别不了无线鼠标的解决
  • 从零开始学nginx

    1 nginx简介 nginx 发音同engine x 是一款轻量级的Web服务器 反向代理服务器及电子邮件 IMAP POP3 代理服务器 并在一个BSD like协议下发行 nginx由俄罗斯的程序设计师Igor Sysoev所开发 最
  • Ubuntu下如何用命令行运行deb安装包

    如果ubuntu要安装新软件 已有deb安装包 例如 iptux deb 但是无法登录到桌面环境 那该怎么安装 答案是 使用dpkg命令 dpkg命令常用格式如下 sudo dpkg I iptux deb 查看iptux deb软件包的详
  • 有序充电运营管理平台是基于物联网和大数据技术的充电设施管理系统-安科瑞黄安南

    随着我国能源战略发展以及低碳行动的实施 电动汽车已逐步广泛应用 而电动汽车的应用非常符合当今社会对环保意识的要求 以及有效节省化石燃料的消耗 由于其没有污染排放的优点以及政府部门的关注 电动汽车将成为以后出行的重要交通工具 由于大批的电车作
  • openssl AES加密、解密示例代码

    openssl AES加密 解密 关于加密解密后长度的说明 AES 高级加密标准 是一种对称加密算法 它使用相同的密钥进行加密和解密操作 无论是加密还是解密 输入和输出的字节数保持一致 AES算法操作的数据以字节为单位 输入数据被分成16字
  • TensorFlow是什么

    TensorFlow是一个开源的深度学习框架 由Google开发 用于构建和训练神经网络 它提供了一种简单而灵活的方法来构建各种类型的机器学习模型 包括卷积神经网络 循环神经网络 深度神经网络等 TensorFlow使用图和张量的概念来描述
  • Mysql免安装版的root密码是多少

    免安装版的Mysql在初始化后root是没有密码的 1 下载免安装版Mysql 下载链接 MySQL Download MySQL Community Server 下载后解压 里面的目录是这样的 2 添加配置文件和系统环境 在系统变量中添
  • redis bitmap实现签到(包含工具类)

    很多应用比如签到送积分 签到领取奖励 签到 1 天送 10 积分 连续签到 2 天送 20 积分 3 天送 30 积分 4 天以上均送 50 积分等 如果连续签到中断 则重置计数 每月初重置计数 显示用户某个月的签到次数 在日历控件上展示用
  • Spring 基础--第一个spring项目及IOC

    一 第一个spring项目 1 导入Jar包
  • 准备写本书

    立个flag 写本书 关于数字IC物理设计 为了避免半途而废 本人在此立下flag 今天起 两年为期 写一本关于数字IC物理设计的书 初步想法 利用公众号专辑功能 先从小章节写起 然后汇聚成册 请大家监督 本来年初就想写 但是迟迟没下定决心
  • 服务器被爬虫恶意攻击怎么办?

    在有预算的情况可以采购第三方服务防火墙 没钱就使用开源的WAF进行防护 WAF防火墙的基本防护原理 WAF Web 应用防火墙 可以使用多种技术来防止恶意爬虫攻击 例如 1 黑名单 WAF 可以使用黑名单技术来过滤恶意爬虫的请求 黑名单中包
  • 【机器学习】TF-IDF以及TfidfVectorizer

    TF IDF定义 TF IDF 全称为 词频一逆文档频率 TF 某一给定词语在该文档中出现的频率 T F w 词语 w
  • common-lang包中一些工具类的使用说明

    一 common lang包常用的类有 1 StringUtils类 该类主要提供对字符串的操作 对null是安全的 主要提供了字符串查找 替换 分割 去空白 去掉非法字符等等操作 2 ObjectUtils类 主要是对null进行安全处理
  • vs网站 服务器变量,了解下变量赋值 (值 vs 引用)

    理解 JavaScript 如何给变量赋值可以帮助我们减少一些不必要的 bug 如果你不理解这一点 可能很容易地编写被无意中更改值的代码 JavaScript 总是按照值来给变量赋值 这一部分非常重要 当指定的值是 JavaScript 的
  • Qt的信号和槽是如何工作的

    用Qt做过开发的朋友 不知道是否曾为下面这些问题疑惑过 我们知道Qt是基于C 的 Qt写的代码最终还是要由C 编译器来编译 但是我们的Qt代码中有很多C 里没有的关键字 比如slots signals Q OBJECT等 为什么C 编译器会