QObject这个class是QT对象模型的核心,绝大部分的 QT 类都是从这个类继承而来。这个模型的中心特征就是一个叫做信号和槽(signal and slot)的机制来实现对象间的通讯,你可以把一个信号和另一个槽通过 connect(„) 方法连接起来,并可以使用disconnect(„) 方法来断开这种连接,你还可以通过调用blockSignal(„) 这个方法来临时的阻塞信号. 
    QObject 把它们自己组织在对象树中。当你创建一个 QObject 并使用其它对象作为父对象时,这个对象会自动添加到父对象的 
children() list 中。父对象拥有这个对象,比如,它将在它的析构函数中自动删除它所有的 child 对象。你可以通过 findChild() 或者 findChildren()函数来查找一个对象。 
    每个对象都有一个对象名称(objectName())和类名称(class name), 他们都可以通过相应的 metaObject 对象来获得。你还可以通过inherits()方法来判断一个对象的类是不是从另一个类继承而来。 
    当对象被删除时,它发出destroyed()信号。你可以捕获这个信号来避免对QObject的无效引用。 
    QObject可以通过event()接收事件并且过滤其它对象的事件。详细情况请参考installEventFilter()和 eventFilter()。     对于每一个实现了信号、槽和属性的对象来说,Q_OBJECT 宏都是必须要加上的。

QObject 实现了这么多功能,那么,它是如何做到的呢?让我们通过它的 Source Code 来解开这个秘密吧。 QObject类的实现文件一共有四个.

1、qobject.h

QObject class 的基本定义,也是我们一般定义一个类的头文件.

2、qobject.cpp

QObject class 的实现代码基本上都在这个文件.

3、 qobjectdefs.h

这个文件中最重要的东西就是定义了 QMetaObject class,这个class是为了实现 signal、slot、properties,的核心部分。

4、qobject_p.h

这个文件中的 code 是辅助实现 QObject class 的,这里面最重要的东西是定义了一个 QObjectPrivate 类来存储 QOjbect 对象的成员数据。

理解这个 QObjectPrivate class 又是我们理解 QT kernel source code 的基础,这个对象包含了每一个 QT 对象中的数据成员,好了,让我们首先从理解 QObject 的数据存储代码开始我们的 QT Kernel Source Code 之旅。

二:元对象系统(Meta-Object System) 



从本节开始,我们讲解 QT Meta-Object System 的功能,以及实现。

在使用 QT 开发的过程中,大量的使用了 signal 和 slot. 比如,响应一个 button 的 click 事件,我们一般都写如下的代码:

class MyWindow : public QWidget { Q_OBJECT
public: MyWindow(QWidget* parent) : QWidget(parent)   { QPushButton* btnStart = new QPushButton(“start”, this); connect(btnStart, SIGNAL(clicked()), this, SLOT(slotStartClicked()));   }
private slots: void slotStartClicked(); };  void MyWindow:: slotStartClicked() { // 省略 }

在这段代码中,我们把 btnStart 这个 button 的clicked() 信号和 MyWindow 的 slotStartClicked() 这个槽相连接,当 btnStart 这个 button 被用户按下(click)的时候,就会发出一个 clicked() 的信号,然后,MyWindow:: slotStartClicked() 这个 slot 函数就会被调用用来响应 button 的 click 事件。  
    这段代码是最为典型的 signal/slot 的应用实例,在实际的工作过程中,signal/slot 还有更为广泛的应用。准确的说,signal/slot 是QT提供的一种在对象间进行通讯的技术,那么,这个技术在QT 中是如何实现的呢?

这就是 QT 中的元对象系统(Meta Object System)的作用,为了更好的理解它,让我先来对它的功能做一个回顾,让我们一起来揭开它神秘的面纱。

三、Meta-Object System 的基本功能

Meta Object System 的设计基于以下几个基础设施:

1、 QObject 类 
作为每一个需要利用元对象系统的类的基类 ,Q_OBJECT 宏, 定义在每一个类的私有数据段,用来启用元对象功能,比如,动态属性,信号和槽 。元对象编译器moc (the Meta Object Complier), moc 分析C++源文件,如果它发现在一个头文件(header file)中包含Q_OBJECT 宏定义,然后动态的生成另外一个C++源文件,这个新的源文件包含 Q_OBJECT 的实现代码,这个新的 C++ 源文件也会被编译、链接到这个类的二进制代码中去,因为它也是这个类的完整的一部分。通常,这个新的C++ 源文件会在以前的C++ 源文件名前面加上 moc_ 作为新文件的文件名。其具体过程如下图所示:

Moc分析时,需要判断源代码中的Q_OBJECT、Q_PROPERTY、Q_ENUMS、Q_CLASSINFO、slots、slot、emit等信息,并生成元对象;但这些关键字大部分C++编译器并不认识(Q_OBJECT除外),他们都被define为空。

Qt的信号槽机制其实就是按照名称查表

除了提供在对象间进行通讯的机制外,元对象系统还包含以下几种功能:

QObject::metaObject() 方法 
它获得与一个类相关联的 meta-object

QMetaObject::className() 方法 ,在运行期间返回一个对象的类名,它不需要本地C++编译器的RTTI(run- time type information)支持

QObject::inherits() 方法 
它用来判断生成一个对象类是不是从一个特定的类继承出来,当然,这必须是在QObject 类的直接或者间接派生类当中

QObject::tr() and QObject::trUtf8() 这两个方法为软件的国际化翻译字符串

QObject::setProperty() and QObject::property() 这两个方法根据属性名动态的设置和获取属性值 
 
除了以上这些功能外,它还使用qobject_cast()方法在QObject类之间提供动态转换,qobject_cast()方法的功能类似于标准 C++的dynamic_cast(),但是qobject_cast()不需要RTTI的支持, 
 
    在一个QObject类或者它的派生类中,我们可以不定义Q_OBJECT宏。如果我们在一个类中没有定义Q_OBJECT宏,那么在这里所提到的相应的功能在这个类中也不能使用,从meta-object的观点来说,一个没有定义Q_OBJECT宏的类与它最接近的那个祖先类是相同的,那就是说,QMetaObject::className()方法所返回的名字并不是这个类的名字,而是与它最接近的那个祖先类的名字。所以,我们强烈建议,任何从QObject继承出来的类都定义Q_OBJECT 宏。

三:元对象编译器 - Meta Object Compiler (moc)

元对象编译器用来处理QT的C++扩展,moc 分析C++源文件,如果它发现在一个头文件(header file)中包含Q_OBJECT 宏定义,然后动态的生成另外一个C++源文件,这个新的源文件包含 Q_OBJECT 的实现代码,这个新的 C++源文件也会被编译、链接到这个类的二进制代码中去,因为它也是这个类的完整的一部分。通常,这个新的C++ 源文件会在以前的C++源文件名前面加上 moc_ 作为新文件的文件名。     如果使用qmake工具来生成Makefile文件,所有需要使用moc的编译规则都会给自动的包含到Makefile文件中,所以对程序员来说不需要直接的使用moc

除了处理信号和槽之外,moc还处理属性信息,Q_PROPERTY()宏定义类的属性信息,而Q_ENUMS()宏则定义在一个类中的枚举类型列表。 Q_FLAGS()宏定义在一个类中的flag枚举类型列表,Q_CLASSINFO()宏则允许你在一个类的meta信息中插入name/value 对。     由moc所生成的文件必须被编译和链接,就象你自己写的另外一个C++文件一样,否则,在链接的过程中就会失败。

Code example:

class MyClass : public QObject { Q_OBJECT Q_PROPERTY(Priority priority READ priority WRITE setPriority)     Q_ENUMS(Priority) Q_CLASSINFO("Author", "Oscar Peterson")     Q_CLASSINFO("Status", "Active")
public: enum Priority { High, Low, VeryHigh, VeryLow };MyClass(QObject *parent = 0);     virtual ~MyClass();  void setPriority(Priority priority);     Priority priority() const; };

四:Signal; Slot

信号和槽是用来在对象间通讯的方法,当一个特定事件发生的时候,signal会被 emit 出来,slot 调用是用来响应相应的 signal 的。

QT 对象已经包含了许多预定义的 signal,但我们总是可以在派生类中添加新的 signal。

QT 对象中也已经包含了许多预定义的 slog,但我们可以在派生类中添加新的 slot 来处理我们感兴趣的 signal     signal 和 slot 机制是类型安全的,signal 和 slot必须互相匹配(实际上,一个solt的参数可以比对应的signal的参数少,因为它可以忽略多余的参数)。signal 和 slot是松散的配对关系,发出signal的对象不关心是那个对象链接了这个signal,也不关心是那个或者有多少slot链接到了这个 signal。QT的signal 和 slot机制保证了,如果一个signal和slot相链接,slot会在正确的时机被调用,并且是使用正确的参数。Signal和slot都可以携带任何数量和类型的参数,他们都是类型安全的。

所有从QObject直接或者间接继承出来的类都能包含信号和槽,当一个对象的状态发生变化的时候,信号就可以被emit出来,这可能是某个其它的对象所关心的。这个对象并不关心有那个对象或者多少个对象链接到这个信号了,这是真实的信息封装,它保证了这个对象可以作为一个软件组件来被使用。

槽(slot)是用来接收信号的,但同时他们也是一个普通的类成员函数,就象一个对象不关心有多少个槽链接到了它的某个信号,一个对象也不关心一个槽链接了多少个信号。这保证了用QT创建的对象是一个真实的独立的软件组件。 
    一个信号可以链接到多个槽,一个槽也可以链接多个信号。同时,一个信号也可以链接到另外一个信号。

所有使用了信号和槽的类都必须包含 Q_OBJECT 宏,而且这个类必须从QObject类派生(直接或者间接派生)出来, 当一个signal被emit出来的时候,链接到这个signal的slot会立刻被调用,就好像是一个函数调用一样。当这件事情发生的时候,signal和slot机制与GUI的事件循环完全没有关系,当所有链接到这个signal的slot执行完成之后,在 emit 代码行之后的代码会立刻被执行。当有多个slot链接到一个signal的时候,这些slot会一个接着一个的、以随机的顺序被执行。

Signal代码会由 moc 自动生成,开发人员一定不能在自己的C++代码中实现它,并且,它永远都不能有返回值。     Slot其实就是一个普通的类函数,并且可以被直接调用,唯一特殊的地方是它可以与signal相链接。

C++的预处理器更改或者删除 signal, slot, emit 关键字,所以,对于C++编译器来说,它处理的是标准的C++源文件。

五:Meta Object Class 
前面我们介绍了Meta Object的基本功能,和它支持的最重要的特性之一:Signal & Slot的基本功能。现在让我们来进入 Meta Object 的内部,看看它是如何支持这些能力的。

Meta Object 的所有数据和方法都封装在一个叫QMetaObject 的类中。它用来查询一个 QT 类的 meta 信息,meta信息包含以下几种,:

1、信号表(signal table),其中有这个对应的 QT 类的所有Signal的名字

2、 槽表(slot table),其中有这个对应的QT类中的所有Slot的名字。

3、类信息表(class info table),包含这个QT类的类型信息 
4、属性表(property table),其中有这个对应的QT类中的所有属性的名字。

5、指向parent meta object的指针(pointers to parent meta object)

QMetaObject 对象与 QT 类之间的关系:

每一个QMetaObject对象包含了与之相对应的一个QT类的元信息

每一个QT类(QObject 以及它的派生类) 都有一个与之相关联的静态的(static) QMetaObject 对象(注:class的定义中必须有 Q_OBJECT 宏,否则就没有这个Meta Object)

每一个 QMetaObject 对象保存了与它相对应的 QT 类的父类的 QMetaObject 对象的指针。或者,我们可以这样说:“每一个QMetaObject对象都保存了一个其父亲(parent)的指针”.注意:严格来说,这种说法是不正确的,最起码是不严谨的。

Q_OBJECT宏

Meta Object 的功能实现,这个宏立下了汗马功劳。首先,让我们来看看这个宏是如何定义的:

#define Q_OBJECT \
public: \static const QMetaObject staticMetaObject; \virtual const QMetaObject *metaObject() const; \virtual void *qt_metacast(const char *); \virtual int qt_metacall(QMetaObject::Call, int, void **); \QT_TR_FUNCTIONS \
private: \static void qt_static_metacall(QObject *, QMetaObject::Call, int, void **); \struct QPrivateSignal {}; \

这里,我们先忽略Q_OBJECT_CHECK 和QT_TR_FUNCTIONS 这两个宏。

我们看到,首先定义了一个静态类型的类变量staticMetaObject,然后有一个获取这个对象指针的方法metaObject()。这里最重要的就是类变量staticMetaObject 的定义。这说明所有的 QObject 的对象都会共享这一个staticMetaObject 类变量,靠它来完成所有信号和槽的功能,所以我们就有必要来仔细的看看它是怎么回事了。 
六:QMetaObject class data members 
我们来看一下QMetaObject的定义,我们先看一下QMetaObject对象中包含的成员数据。


struct Q_CORE_EXPORT QMetaObject
{//...................struct { // private dataconst QMetaObject *superdata;const QByteArrayData *stringdata;const uint *data;typedef void (*StaticMetacallFunction)(QObject *, QMetaObject::Call, int, void **);StaticMetacallFunction static_metacall;const QMetaObject * const *relatedMetaObjects;void *extradata; //reserved for future use} d;}

上面的代码就是QMetaObject类所定义的全部数据成员。就是这些成员记录了所有signal,slot,property,class information这么多的信息。下面让我们来逐一解释这些成员变量:  
const QMetaObject *superdata: 
这个变量指向与之对应的QObject类的父类,或者是祖先类的QMetaObject对象。  如何理解这一句话呢?我们知道,每一个QMetaObject对象,一定有一个与之相对应的QObject类(或者由其直接或间接派生出的子类),注意:这里是类,不是对象。

那么每一个QObject类(或其派生类)可能有一个父类,或者父类的父类,或者很多的继承层次之前的祖先类。或者没有父类(QObject)。那么 superdata 这个变量就是指向与其最接近的祖先类中的QMetaObject对象。对于QObject类QMetaObject对象来说,这是一个NULL指针,因为 QObject没有父类。

下面,让我们来举例说明:

class Animal : public QObject
{
   Q_OBJECT  //.............
};
class Cat : public Animal  
{  Q_OBJECT  //.............  
}  

那么,Cat::staticMetaObject.d.superdata 这个指针变量指向的对象是Animal::staticMetaObject,

Animal::staticMetaObject.d.superdata个指针变量指向的对象是QObject::staticMetaObject

QObject::staticMetaObject.d.superdat 这个指针变量的值为 NULL

但如果我们把上面class的定义修改为下面的定义,就不一样了:

class Animal : public QObject
{
// Q_OBJECT,这个 class 不定义这个
//.............
};
class Cat : public Animal
{   Q_OBJECT
//............. 
}

那么,Cat::staticMetaObject.d.superdata 这个指针变量指向的对象是 QObject::staticMetaObject 因为 Animal::staticMetaObject 这个对象是不存在的

const char *stringdata:

顾名思义,这是一个指向string data的指针。但它和我们平时所使用的一般的字符串指针却很不一样,我们平时使用的字符串指针只是指向一个字符串的指针,而这个指针却指向的是很多个字符串。那么它不就是字符串数组吗?哈哈,也不是。因为C++的字符串数组要求数组中的每一个字符串拥有相同的长度,这样才能组成一个数组。那它是不是一个字符串指针数组呢?也不是,那它到底是什么呢?让我们来看一看它的具体值,还是让我们以QObject这个class的QMetaObject为例来说明 吧。

下面是QObject::staticMetaObject.d.stringdata指针所指向的多个字符串数组,其实它就是指向一个连续的内存区,而这个连续的内存区中保存了若干个字符串。

static const char qt_meta_stringdata_QObject[] =
{  "QObject\0\0destroyed(QObject*)\0destroyed()\0"
"deleteLater()\0_q_reregisterTimers(void*)\0"
"QString\0objectName\0parent\0QObject(QObject*)\0"
"QObject()\0"
};

这个字符串都是些什么内容呀?有,Class Name, Signal Name, Slot Name, Property Name。看到这些大家是不是觉得很熟悉呀,对啦,他们就是Meta System所支持的最核心的功能属性了。

既然他们都是不等长的字符串,那么Qt是如何来索引这些字符串,以便于在需要的时候能正确的找到他们呢?第三个成员正式登场了

const uint *data;  

这个指针本质上就是指向一个正整数数组,只不过在不同的object中数组的长度都不尽相同,这取决于与之相对应的class中定义了多少 signal,slot,property。  
这个整数数组的的值,有一部分指出了前一个变量(stringdata)中不同字符串的索引值,但是这里有一点需要注意的是,这里面的数值并不是直接标明了每一个字符串的索引值,这个数值还需要通过一个相应的算法计算之后,才能获得正确的字符串的索引值。

下面是QObject::staticMetaObject.d.data指针所指向的正整数数组的值。

static const uint qt_meta_data_QObject[] =
{
content:  2,       // revision0,       // classname0,    0, // classinfo4,   12, // methods1,   32, // properties0,    0, // enums/sets2,   35, // constructors  //signals: signature, parameters, type, tag, flags9,    8,    8,    8, 0x05,29,    8,    8,    8, 0x25,
// slots: signature, parameters, type, tag, flags  41,    8,    8,    8, 0x0a,55,    8,    8,    8, 0x08,
// properties: name, type, flags  90,   82, 0x0a095103,
// constructors: signature, parameters, type, tag, flags  108,  101,    8,    8, 0x0e, 126,    8,    8,    8, 0x2e,0        // eod

简单的说明一下,  第一个section,就是 //content 区域的整数值,这一块区域在每一个QMetaObject的实体对象中数量都是相同的,含义也相同,但具体的值就不同了。专门有一个struct定义了这 个section,其含义在上面的注释中已经说的很清楚了。

struct QMetaObjectPrivate
{int revision;int className;int classInfoCount, classInfoData;int methodCount, methodData;int propertyCount, propertyData;int enumeratorCount, enumeratorData;int constructorCount, constructorData; //since revision 2int flags; //since revision 3int signalCount; //since revision 4// revision 5 introduces changes in normalized signatures, no new members// revision 6 added qt_static_metacall as a member of each Q_OBJECT and inside QMetaObject itself
};

这个 struct 就是定义第一个secton的,和上面的数值对照一下,很清晰,是吧?

第二个section,以 // signals 开头的这段。这个section中的数值指明了QObject这个class包含了两个signal,  
第三个section,以 // slots 开头的这段。这个section中的数值指明了QObject这个class包含了两个slot。  
第四个section,以 // properties 开头的这段。这个section中的数值指明了QObject这个class包含有一个属性定义。  
第五个section,以 // constructors 开头的这段,指明了QObject这个class有两个constructor。

const void *extradata;

这是一个指向QMetaObjectExtraData数据结构的指针,关于这个指针,这里先略过。

对于每一个具体的整数值与其所指向的实体数据之间的对应算法,实在是有点儿麻烦,这里就不讲解细节了,有兴趣的朋友自己去

读一下源代码,一定会有很多发现。

七:connect, 幕后的故事

我们都知道,把一个signal和slot连接起来,需要使用QObject类的connect方法,它的作用就是把一个object的 signal和

另外一个object的slot连接起来,以达到对象间通讯的目的。 connect 在幕后到底都做了些什么事情?为什么emit一个signal后,

相应的slot都会被调用?好了,让我们来逐一解开其中的谜团.

SIGNAL 和 SLOT 宏定义  
我们在调用connect方法的时候,一般都会这样写: 
obj.connect(&obj, SIGNAL(destroyed()), &app, SLOT(aboutQt())); 
我们看到,在这里signal和slot的名字都被包含在了两个大写的SIGNAL和SLOT中,这两个是什么呢?原来SIGNAL 和 SLOT

是Qt定义的两个宏。好了,让我们先来看看这两个宏都做了写什么事情,这里是这两个宏的定义:

# define SLOT(a)      "1"#a #

#define SIGNAL(a)   "2"#a  
原来Qt把signal和slot都转化成了字符串,并且还在这个字符串的前面加上了附加的符号,signal前面加了’2’,slot前面加了’1’。 也就

是说,我们前面写了下面的connect调用,在经过编译器预处理之后,

就便成了: obj.connect(&obj, "2destroyed()", &app, "1aboutQt()”));

当connect函数被调用了之后,都会去检查这两个参数是否是使用这两个宏正确的转换而来的,它检查的根据就是这两个前置数

字,是否等于1或者是2,如果不是,connect函数当然就会失败啦!

然后,会去检查发送signal的对象是否有这个signal,方法就是查找这个对象的class所对应的staticMetaObject对象中所包含 的
d.stringdata所指向的字符串中是否包含这个signal的名字,在这个检查过程中,就会用到d.data所指向的那一串整数,通过这些整数

值来计算每一个具体字符串的起始地址。同理,还会使用同样的方法去检查slot,看响应这个signal的对象是否包含有相应的slot。

这两个检查的任何一个如果失败的话,connect函数就失败了,返回false. 
前面的步骤都是在做一些必要的检查工作,下一步,就是要把发送signal的对象和响应signal的对象关联起来。

QT信号和槽机制分析相关推荐

  1. Qt 信号和槽机制 优点 效率的详解

    一.信号和槽机制 Qt提供了信号和槽机制用于完成界面操作的响应,是完成任意两个Qt对象之间的通信机制. 其中,信号会在某个特定情况或动作下被触发,槽是等同于接收并处理信号的函数. 二..信号和槽机制的 ...

  2. QT信号与槽机制需要注意的问题

    1.信号与槽的效率是非常高的,但是同真正的回调函数比较起来,由于增加了灵活性,因此在速度上还是有所损失.当然这种损失相对来说是比较小的,但是要追求高效率的话,比如实时系统,就要尽可能避免. 2.信号与 ...

  3. Qt基础之四:Qt信号与槽机制原理及优缺点

    目录 一.简介 二.信号和槽 三.信号(signals) 四.槽(slots) 五.在Qt中使用第三方的Signals和Slots 信号

  4. c++基础学习之QT 信号和槽机制的底层实现

    Qt 信号槽的实现 - DevBean Tech World Qt 的信号槽和属性系统基于在运行时进行内省的能力,内省意味着,我们可以列出对象的方法和属性列表,并且能够获取有关它们的所有信息,例如其参 ...

  5. Qt信号与槽机制详解1-创建一个带信号和槽的例子

    目录 一.编译一个例子 1.hello.h中的内容 2.hello.cpp的内容 3.main.cpp中的内容 4.tutorial.pro内容 二.moc_hello.cpp文件 1.Q_OBJEC ...

  6. QT从入门到入土(三)——信号和槽机制

    摘要 信号槽是 Qt 框架引以为豪的机制之一.所谓信号槽,实际就是观察者模式.当某个事件发生之后,比如,按钮检测到自己被点击了一下,它就会发出一个信号 (signal).这种发出是没有目的的,类似广播 ...

  7. QT信号与槽——观察者模式——回调函数

    QT信号与槽--观察者模式--回调函数 1.QT信号与槽机制 1.1信号本质 信号是由于用户对窗口或控件进行了某些操作,导致窗口或控件产生了某个特定事件,这时候 Qt 对应的窗口类会发出某个信号.比如 ...

  8. QT5开发及实例学习之二信号和槽机制

    文章目录 前言 一.信号与槽机制的连接方式 二.信号与槽机制的优点 三.信号与槽机制的效率 前言 Qt提供了信号和槽机制用于完成界面操作的响应,信号和槽机制是完成任意两个Qt对象之间的通信机制.其中, ...

  9. Qt源码分析之信号和槽机制

    Qt的信号和槽机制是Qt的一大特点,实际上这是和MFC中的消息映射机制相似的东西,要完成的事情也差不多,就是发送一个消息然后让其它窗口响应,当然,这里的消息是广义的 说法,简单点说就是如何在一个类的一 ...

最新文章

  1. 让VB菜鸟最快写出自己的外挂.通杀所有游戏
  2. python下载文件到指定目录-python – 如何将文件下载到特定目录?
  3. 题目1549:货币问题
  4. 怒爬某破 Hub 站资源,只为撸这个鉴黄平台!
  5. Spring Cloud微服务系列文,服务调用框架Feign
  6. c语言 返回函数是结构体指针变量,一个函数返回值为指向结构体的指针的问题...
  7. java previous_java – 在枚举类型上实现`next`和`previous`的最好方法是什么?
  8. c语言 一元二次函数,计算一元二次函数的根,大家看看那里有错了。。。。
  9. 软工导论 12-13-2 实验任务一
  10. 操作系统对的IIS版本
  11. Mysql 时间转换 时间函数
  12. PyTorch 算法加速指南
  13. 快速从入门到精通!黑马java课程大纲
  14. android 自定义相册选择,Android图片选择器,支持拍照、从相册选择、裁剪、自定义主题...
  15. 百度“算盘”logo引领国风来袭
  16. 【直播回顾】战码先锋首期8节直播完美落幕,下期敬请期待!
  17. 博物馆场馆智能化展览展示解决方案
  18. RDB和AOF的区别
  19. FPGA学习之路-ZCU106板子点亮PS侧LED
  20. android 输入法弹出 标题栏不被顶出去

热门文章

  1. c++ overload 、override、overwrite
  2. 共模电压你了解多少?陈老师带你搞清楚
  3. Linux协议栈(1)——协议介绍
  4. 点云配准论文复现:Robust generalized point cloud registration with orientational data based on expectation ma
  5. android近期任务栏图片生成过程
  6. LG30刷小米系统_微信支付宝运动刷步数,IOS安卓皆可使用
  7. 【内网安全】——msf木马生成教程
  8. 几种最小二乘法及python代码:ELS、TLS、RLS
  9. 程序员年底好找工作吗?
  10. 《人类简史》六、融合统一(上)——历史的方向、金钱的味道