QT信号和槽机制分析
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信号和槽机制分析相关推荐
- Qt 信号和槽机制 优点 效率的详解
一.信号和槽机制 Qt提供了信号和槽机制用于完成界面操作的响应,是完成任意两个Qt对象之间的通信机制. 其中,信号会在某个特定情况或动作下被触发,槽是等同于接收并处理信号的函数. 二..信号和槽机制的 ...
- QT信号与槽机制需要注意的问题
1.信号与槽的效率是非常高的,但是同真正的回调函数比较起来,由于增加了灵活性,因此在速度上还是有所损失.当然这种损失相对来说是比较小的,但是要追求高效率的话,比如实时系统,就要尽可能避免. 2.信号与 ...
- Qt基础之四:Qt信号与槽机制原理及优缺点
目录 一.简介 二.信号和槽 三.信号(signals) 四.槽(slots) 五.在Qt中使用第三方的Signals和Slots 信号
- c++基础学习之QT 信号和槽机制的底层实现
Qt 信号槽的实现 - DevBean Tech World Qt 的信号槽和属性系统基于在运行时进行内省的能力,内省意味着,我们可以列出对象的方法和属性列表,并且能够获取有关它们的所有信息,例如其参 ...
- Qt信号与槽机制详解1-创建一个带信号和槽的例子
目录 一.编译一个例子 1.hello.h中的内容 2.hello.cpp的内容 3.main.cpp中的内容 4.tutorial.pro内容 二.moc_hello.cpp文件 1.Q_OBJEC ...
- QT从入门到入土(三)——信号和槽机制
摘要 信号槽是 Qt 框架引以为豪的机制之一.所谓信号槽,实际就是观察者模式.当某个事件发生之后,比如,按钮检测到自己被点击了一下,它就会发出一个信号 (signal).这种发出是没有目的的,类似广播 ...
- QT信号与槽——观察者模式——回调函数
QT信号与槽--观察者模式--回调函数 1.QT信号与槽机制 1.1信号本质 信号是由于用户对窗口或控件进行了某些操作,导致窗口或控件产生了某个特定事件,这时候 Qt 对应的窗口类会发出某个信号.比如 ...
- QT5开发及实例学习之二信号和槽机制
文章目录 前言 一.信号与槽机制的连接方式 二.信号与槽机制的优点 三.信号与槽机制的效率 前言 Qt提供了信号和槽机制用于完成界面操作的响应,信号和槽机制是完成任意两个Qt对象之间的通信机制.其中, ...
- Qt源码分析之信号和槽机制
Qt的信号和槽机制是Qt的一大特点,实际上这是和MFC中的消息映射机制相似的东西,要完成的事情也差不多,就是发送一个消息然后让其它窗口响应,当然,这里的消息是广义的 说法,简单点说就是如何在一个类的一 ...
最新文章
- 让VB菜鸟最快写出自己的外挂.通杀所有游戏
- python下载文件到指定目录-python – 如何将文件下载到特定目录?
- 题目1549:货币问题
- 怒爬某破 Hub 站资源,只为撸这个鉴黄平台!
- Spring Cloud微服务系列文,服务调用框架Feign
- c语言 返回函数是结构体指针变量,一个函数返回值为指向结构体的指针的问题...
- java previous_java – 在枚举类型上实现`next`和`previous`的最好方法是什么?
- c语言 一元二次函数,计算一元二次函数的根,大家看看那里有错了。。。。
- 软工导论 12-13-2 实验任务一
- 操作系统对的IIS版本
- Mysql 时间转换 时间函数
- PyTorch 算法加速指南
- 快速从入门到精通!黑马java课程大纲
- android 自定义相册选择,Android图片选择器,支持拍照、从相册选择、裁剪、自定义主题...
- 百度“算盘”logo引领国风来袭
- 【直播回顾】战码先锋首期8节直播完美落幕,下期敬请期待!
- 博物馆场馆智能化展览展示解决方案
- RDB和AOF的区别
- FPGA学习之路-ZCU106板子点亮PS侧LED
- android 输入法弹出 标题栏不被顶出去
热门文章
- c++ overload 、override、overwrite
- 共模电压你了解多少?陈老师带你搞清楚
- Linux协议栈(1)——协议介绍
- 点云配准论文复现:Robust generalized point cloud registration with orientational data based on expectation ma
- android近期任务栏图片生成过程
- LG30刷小米系统_微信支付宝运动刷步数,IOS安卓皆可使用
- 【内网安全】——msf木马生成教程
- 几种最小二乘法及python代码:ELS、TLS、RLS
- 程序员年底好找工作吗?
- 《人类简史》六、融合统一(上)——历史的方向、金钱的味道