Qt 如何实现的 Meta Object

2009-11-01 11:57 741人阅读 评论(0) 收藏 举报
qtsignalcallbackobjectclasstable

(文章转贴自guiliblearning.blogspot.com)

前面提到了 Qt 实现的一个关键性技术,signal/slot。由于 Qt 使用了一个 moc 预处理,因此我们肯定会想知道 Qt 如何通过 moc 实现前面所说对于程序员的优点的呢?我们先通过一个简单的例子看看如何使用 QObject 这一些概念实现最基本的调用过程。然后我们探讨 Qt 实现这种扩展的方式是什么,后面因为要与 Gtkmm 的实现互相比较,而 Gtkmm 使用的是 libsigc++ 实现的对象通信机制,而且不使用预编译,因此可以发现很多有意思的东西。 下面这个例子声明了一个简单的 QObject 的子类,counter.hpp 文件

view plaincopy to clipboardprint?
  1. #ifndef COUNTER_HPP
  2. #define COUNTER_HPP
  3. #include <iostream>
  4. #include <qobject.h>
  5. class Counter : public QObject {
  6. Q_OBJECT
  7. int value ;
  8. signals:
  9. void valueChanged( int ) ;
  10. public slots:
  11. void setValue( int ) ;
  12. public:
  13. Counter( int = 0 ) ;
  14. friend std::ostream& operator<< ( std::ostream&, const Counter& ) ;
  15. } ;
  16. #endif

我们可以看见我们需要通过继承 QObject 类,并且在 private 段声明 Q_OBJECT,signal 和 slots 其实对应的可以理解为 signal 是一个 callback 函数的链表(因为可以一个 signal 激发多个 slots),而 slots 就是常意的函数,因此也分 public/protected/private,可以继承、可以重载。下面是以上 Counter 类的实现,counter.cpp 文件

view plaincopy to clipboardprint?
  1. #include <iostream>
  2. #include "counter.hpp"
  3. void
  4. Counter::setValue( int val )
  5. {
  6. value = val ;
  7. // emit the signal
  8. emit valueChanged( value ) ;
  9. }
  10. Counter::Counter( int val ) : value( val ) {}
  11. std::ostream&
  12. operator<< ( std::ostream& os, const Counter& c )
  13. {
  14. return os << c.value ;
  15. }

然后下面是调用的主程序 main.cpp 文件,

view plaincopy to clipboardprint?
  1. #include <iostream>
  2. #include <iomanip>
  3. #include "counter.hpp"
  4. using namespace std ;
  5. int
  6. main( int argc, char *argv[] )
  7. {
  8. Counter a, b( 10 ) ;
  9. cout << "We have two counters, " << a
  10. << " and " << b << endl ;
  11. QObject::connect( &a, SIGNAL( valueChanged(int) ),
  12. &b, SLOT( setValue(int) ) ) ;
  13. a.setValue( 12 ) ;
  14. cout << "We have two counters, " << a
  15. << " and " << b << endl ;
  16. b.setValue( 5 ) ;
  17. cout << "We have two counters, " << a
  18. << " and " << b << endl ;
  19. return 0 ;
  20. }

我们需要首先在该目录中使用 qmake -project 生成一个 .pro 文件,该文件含有工程细节,然后使用 qmake 产生 Makefile,最后 make 就可以产生可执行文件了。我们看到在主程序中调用 QObject::connect 将一个 signal 和一个 slot 连接,这导致我们触发 a.setValue() 的时候 b 的相关函数也被调用了。 那么我们继续看看 make 之后出现了什么。除了目标代码和执行文件以外,还有一个 moc_counter.cpp,这是使用 moc 产生的一个中间文件,稍微研究该文件,我们就不难发现 Qt 实现这一过程的要点: 继承 QObject 是为了使用 QObject 里面定义的信号方面的函数,如 QObject::connect() 和 QObject::disconnect()、判定继承 QObject::inherits()、支持国际化 QObject::tr() 和 QObject::trUtf8()、属性 QObject::setProperty() 和 QObject::property(),而 Q_OBJECT 宏(定义在 [include]/qt4/QtCore/qobjectdefs.h)是为了在原来这个类中重新插入一个 const static 的 QMetaObject,这个将覆盖父类对应的 QMetaObject,该 QMetaObject 里面含有实现比 C++ 的 RTTI 更丰富功能以及 qobject_cast 等等的一个对象,因为该对象创建后不需要修改,整个 class 公用,所以是 const static 的,那个 moc_*.cpp 里面实际上就含有该 MetaObject 的初始化代码,编译的时候和自己实现的 .o 一起编译,最后连接的时候加入到可执行文件里面。 这个 MetaObject 的初始化 code 是

view plaincopy to clipboardprint?
  1. const QMetaObject Counter::staticMetaObject = {
  2. { &QObject::staticMetaObject, qt_meta_stringdata_Counter,
  3. qt_meta_data_Counter, 0 }
  4. };

其中 string 的部分就是 slot 等,其实是以字符串形式存储的,当使用 SLOT/SIGNAL 宏的时候,应该是通过查寻字符串(这两个 macro 其实把代码转换成字符串传递给对应的函数)。 在 [include]/qt4/QtCore/qobjectdefs.h 里面有 MetaObject 类的定义,很多 QObject 的函数都是依赖这里面的函数的,比如 QMetaObject::connect()、QMetaObject::className()、QMetaObject::superClass() 等。通过预处理,同时把 signals 替换为 protected 而把 * slots 的 slots 去掉。我们可以通过 g++ -E 获得预处理后的源文件,我们会发现 signal 对应的 protected 部分没有实现代码,但是在生成的执行代码中却有,这部分难道是 template 产生的?

(文章转贴自guiliblearning.blogspot.com)

首先我们看看 Q_OBJECT 展开变成了什么,在 qobjectdefs.h 文件中有

view plaincopy to clipboardprint?
  1. #define Q_OBJECT /
  2. public: /
  3. Q_OBJECT_CHECK /
  4. static const QMetaObject staticMetaObject; /
  5. virtual const QMetaObject *metaObject() const; /
  6. virtual void *qt_metacast(const char *); /
  7. QT_TR_FUNCTIONS /
  8. virtual int qt_metacall(QMetaObject::Call, int, void **); /
  9. private:

首先调用了 Q_OBJECT_CHECK (插入了一个 qt_check_for_QOBJECT_macro 的 template function),然后是全局常量 QMetaObject 对象,因此可以用 QClassname::staticMetaObject 直接访问,另外提供了两个接口函数 metaObject() 用于不同的 class 返回自己的 staticMetaObject、qt_metacast() 用于转换,我们在 moc 产生的文件里面可以找到这两个接口的实现,

view plaincopy to clipboardprint?
  1. const QMetaObject *Counter::metaObject() const
  2. {
  3. return &staticMetaObject;
  4. }
  5. void *Counter::qt_metacast(const char *_clname)
  6. {
  7. if (!_clname) return 0;
  8. if (!strcmp(_clname, qt_meta_stringdata_Counter))
  9. return static_cast<void*>(const_cast< Counter*>(this));
  10. return QObject::qt_metacast(_clname);
  11. }

后者很明显,如果需要转换的名字 _clname 是自己的类名,就把自己的指针通过转换成 void* 传回去,否则调用 QOject::qt_metacast(),其实就是看是不是 QObject 了,否则就返回 0 了。另外 QT_TR_FUNCTIONS 是对应的 i18n 的函数,我们后面再看。最后还有一个 qt_metacall 的接口,实现如下

view plaincopy to clipboardprint?
  1. int Counter::qt_metacall(QMetaObject::Call _c, int _id, void **_a)
  2. {
  3. _id = QObject::qt_metacall(_c, _id, _a);
  4. if (_id < 0)
  5. return _id;
  6. if (_c == QMetaObject::InvokeMetaMethod) {
  7. switch (_id) {
  8. case 0:
  9. valueChanged((*reinterpret_cast< int(*)>(_a[1])));
  10. break;
  11. case 1:
  12. setValue((*reinterpret_cast< int(*)>(_a[1])));
  13. break;
  14. default: ;
  15. }
  16. _id -= 2;
  17. }
  18. return _id;
  19. }

这个函数起到一个中间作用,可以间接的调用成员方法,我们来仔细看看 QMetaObject,同一个文件里面有该结构的定义,我们只看这一部分,

view plaincopy to clipboardprint?
  1. struct Q_CORE_EXPORT QMetaObject
  2. {
  3. const char *className() const;
  4. const QMetaObject *superClass() const;
  5. QObject *cast(QObject *obj) const;
  6. // ...
  7. struct { // private data
  8. const QMetaObject *superdata;
  9. const char *stringdata;
  10. const uint *data;
  11. const void *extradata;
  12. } d;
  13. } ;

注意 Couter 类 QMetaObject 的初始化,

view plaincopy to clipboardprint?
  1. const QMetaObject Counter::staticMetaObject = {
  2. { &QObject::staticMetaObject, qt_meta_stringdata_Counter,
  3. qt_meta_data_Counter, 0 }
  4. } ;

下面我们着重看看几个与 signal/slot 相关的代码,首先就是 [qt]/src/corelib/kernel/qobject.cpp 文件中关于 QObject::connect() 函数的代码,

view plaincopy to clipboardprint?
  1. bool QObject::connect(const QObject *sender, const char *signal,
  2. const QObject *receiver, const char *method,
  3. Qt::ConnectionType type)
  4. {
  5. {
  6. const void *cbdata[] = { sender, signal, receiver, method, &type };
  7. if (QInternal::activateCallbacks(QInternal::ConnectCallback, (void **) cbdata))
  8. return true;
  9. }
  10. // checking sender, receiver, compatability of signal and slot
  11. QMetaObject::connect(sender, signal_index, receiver, method_index, type, types);
  12. const_cast<QObject*>(sender)->connectNotify(signal - 1);
  13. return true;
  14. }

这里首先调用了 QInternal 这个 namespace 里面 activateCallbacks 这个函数,然后根据 QMetaObject 信息检查了 sender、receiver 以及对应 signal/slots 的匹配性,此时已经把 signal/slot 字符串转换成为了对应的 index,然后调用 QMetaObject::connect() 完成连接,最后的 QObject::connectNotify() 以及另外的 QObject::disconnectNotify() 其实是一个 signal。QInternal::activaeCallback() 在 [qt]/src/corelib/global/qglobal.cpp 中定义,

view plaincopy to clipboardprint?
  1. bool QInternal::activateCallbacks(Callback cb, void **parameters)
  2. {
  3. Q_ASSERT_X(cb >= 0, "QInternal::activateCallback()", "Callback id must be a valid id");
  4. QInternal_CallBackTable *cbt = global_callback_table();
  5. if (cbt && cb < cbt->callbacks.size()) {
  6. QList<qInternalCallback> callbacks = cbt->callbacks[cb];
  7. bool ret = false;
  8. for (int i=0; i<callbacks.size(); ++i)
  9. ret |= (callbacks.at(i))(parameters);
  10. return ret;
  11. }
  12. return false;
  13. }

这可以看出来调用该函数去检查一个 global_callback_table(),如果查到一个匹配的 signal/slot 就返回 true,否则返回 false,换言之 QObject::connect() 通过这个函数判定需不需要调用 QMetaObject::connect 创建新的连接。这个 callback table 本质是什么呢?同一个文件里面有

view plaincopy to clipboardprint?
  1. struct QInternal_CallBackTable {
  2. QVector<QList<qInternalCallback> > callbacks;
  3. };

所以,这是一个链表的动态数组(Orz... 本来以为就是个链表),而 qInternalCallback 是一个在 [qt]/src/corelib/global/qnamespace.h 中定义的函数指针,

view plaincopy to clipboardprint?
  1. typedef bool (*qInternalCallback)(void **);

现在我们可以猜测到在 QMetaObject::connect() 调用中我们会维护这个 callback table。在 [qt]/src/corelib/kernel/qmetaobject.cpp 我们有幸找到了如下代码:

view plaincopy to clipboardprint?
  1. bool QMetaObject::connect(const QObject *sender, int signal_index,
  2. const QObject *receiver, int method_index, int type, int *types)
  3. {
  4. QObject *s = const_cast<QObject *<(sender);
  5. QObject *r = const_cast<QObject *<(receiver);
  6. QOrderedMutexLocker locker(&s->d_func()->threadData->mutex,
  7. &r->d_func()->threadData->mutex);
  8. #if defined(Q_CC_HPACC) && defined(QT_ARCH_PARISC)
  9. QObjectPrivate::Connection c;
  10. c.receiver = r;
  11. c.method = method_index;
  12. c.connectionType = type;
  13. c.argumentTypes = types;
  14. #else
  15. QObjectPrivate::Connection c = { r, method_index, type, Q_BASIC_ATOMIC_INITIALIZER(types) };
  16. #endif
  17. s->d_func()->addConnection(signal_index, &c);
  18. r->d_func()->refSender(s, signal_index);
  19. if (signal_index < 0)
  20. sender->d_func()->connectedSignals = ~0u;
  21. else if (signal_index < 32)
  22. sender->d_func()->connectedSignals |= (1 << signal_index);
  23. return true;
  24. }

这段代码中使用了防止多线程操作引起问题的 mutex,sender 和 receiver 双方都有一个结构来保证这个通讯机制,sender 是通过 addConnection,receiver 通过 refSender(),这里并没有我们猜测的 global callback table。现在我们有两个问题,一个是 sender 通过 addConnection() 和 receiver 通过 refSneder() 记录了一些什么,用什么数据结构储存,另一个是 global callback table 是做什么用的。

我们先来看看 global callback table,不难发现,该 table 是 QInternal 类(没有成员,提供了一个接口)的方法维护的,主要有 QInternal::registerCallback()、QInternal::unregisterCallback()、QInternal::activateCallbacks()、QInternal::callFunction(),意思我想都很清楚,可是 grep 了一圈,似乎只在某些调试部分看到了调用该函数的地方,莫非这是用来调试的代码?

我们来看看 QObject->d_func() 返回的是什么。

view plaincopy to clipboardprint?
  1. #define Q_DECLARE_PRIVATE(Class) /
  2. inline Class##Private* d_func() { return reinterpret_cast<Class##Private *>(d_ptr); } /
  3. inline const Class##Private* d_func() const { return reinterpret_cast<const Class##Private *>(d_ptr); } /
  4. friend class Class##Private;
  5. #define Q_DECLARE_PRIVATE_D(Dptr, Class) /
  6. inline Class##Private* d_func() { return reinterpret_cast<Class##Private *>(Dptr); } /
  7. inline const Class##Private* d_func() const { return reinterpret_cast<const Class##Private *>(Dptr); } /
  8. friend class Class##Private;
  9. #define Q_DECLARE_PUBLIC(Class)                                    /
  10. inline Class* q_func() { return static_cast<Class *>(q_ptr); } /
  11. inline const Class* q_func() const { return static_cast<const Class *>(q_ptr); } /
  12. friend class Class;
  13. #define Q_D(Class) Class##Private * const d = d_func()
  14. #define Q_Q(Class) Class * const q = q_func()

记得 QObject 的定义么?

view plaincopy to clipboardprint?
  1. class Q_CORE_EXPORT QObject
  2. {
  3. Q_OBJECT
  4. Q_PROPERTY(QString objectName READ objectName WRITE setObjectName)
  5. Q_DECLARE_PRIVATE(QObject)

这样 QObject 通过 d_func() 返回 d_ptr 这个指针,这是怎么一回事呢?我们知道这个宏在 QObject 内部定义了一个 QObjectPrivate 的类,并且留下了一个指针

view plaincopy to clipboardprint?
  1. protected:
  2. QObjectData *d_ptr;

在 QObject 构造的时候创建了这个对象,并在析构的时候释放,由于是 friend class,可以对 QObject 无限制访问,

view plaincopy to clipboardprint?
  1. QObject::QObject(QObject *parent) : d_ptr(new QObjectPrivate)
  2. QObject::~QObject()
  3. {
  4. Q_D(QObject);
  5. // clear all signal receivers
  6. emit destroyed(this);
  7. // clear all signal senders
  8. delete d;
  9. d_ptr = 0;
  10. }

可见真正管理 signal/slots 的是 QObjectPrivate 类,下面是它的主要成员函数,

view plaincopy to clipboardprint?
  1. //constructor and destructor
  2. QObjectPrivate::QObjectPrivate(int version) ;
  3. QObjectPrivate::~QObjectPrivate() ;
  4. int *QObjectPrivate::setDeleteWatch(QObjectPrivate *d, int *w) ;
  5. void QObjectPrivate::resetDeleteWatch(QObjectPrivate *d, int *oldWatch, int deleteWatch) ;
  6. void QObjectPrivate::sendPendingChildInsertedEvents() ;
  7. void QObjectPrivate::removePendingChildInsertedEvents(QObject *child) ;
  8. bool QObjectPrivate::isSender(const QObject *receiver, const char *signal) const ;
  9. QObjectList QObjectPrivate::receiverList(const char *signal) const ;
  10. QObjectList QObjectPrivate::senderList() ;
  11. // connection list manipulation
  12. void QObjectPrivate::addConnection(int signal, Connection *c) ;
  13. void QObjectPrivate::removeReceiver(int signal, QObject *receiver) ;
  14. void QObjectPrivate::cleanConnectionLists() ;
  15. // sender list manipulation
  16. void QObjectPrivate::refSender(QObject *sender, int signal) ;
  17. void QObjectPrivate::derefSender(QObject *sender, int signal) ;
  18. void QObjectPrivate::removeSender(QObject *sender, int signal) ;
  19. QObjectPrivate::Sender *QObjectPrivate::setCurrentSender(QObject *receiver, Sender *sender) ;
  20. void QObjectPrivate::resetCurrentSender(QObject *receiver, Sender *currentSender, Sender *previousSender) ;
  21. void QObjectPrivate::clearGuards(QObject *object) ;

那么我们如何存储数据的呢?在 [qt]/src/corelib/kernel/qobject_p.h 里,有该类的声明,

view plaincopy to clipboardprint?
  1. class Q_CORE_EXPORT QObjectPrivate : public QObjectData {
  2. // ...
  3. public:
  4. QList<qobject> pendingChildInsertedEvents;
  5. struct Sender {
  6. QObject *sender;
  7. int signal;
  8. int ref;
  9. };
  10. Sender *currentSender;
  11. QList<QPointer<QObject> > eventFilters;
  12. struct Connection {
  13. QObject *receiver;
  14. int method;
  15. uint connectionType : 3; // 0 == auto, 1 == direct, 2 == queued, 4 == blocking
  16. QBasicAtomicPointer<int> argumentTypes;
  17. };
  18. typedef QList<Connection> ConnectionList;
  19. QObjectConnectionListVector *connectionLists;
  20. QList<Sender> senders;
  21. } ;</qobject>

至此,我们已经找到了 Qt 实现 signal/slot 机制的所有需要知道的东西。

记得下面 moc 生成的代码

view plaincopy to clipboardprint?
  1. void Counter::valueChanged(int _t1)
  2. {
  3. void *_a[] = { 0, const_cast<void*>(reinterpret_cast<const void*>(&_t1)) };
  4. QMetaObject::activate(this, &staticMetaObject, 0, _a);
  5. }

这里重要的就是 QMetaObject::activate() 函数,其实它就是依次激活 senders 里面的函数,

view plaincopy to clipboardprint?
  1. void QMetaObject::activate(QObject *sender, int from_signal_index, int to_signal_index, void **argv) ;
  2. void QMetaObject::activate(QObject *sender, int signal_index, void **argv) ;
  3. void QMetaObject::activate(QObject *sender, const QMetaObject *m, int local_signal_index, void **argv) ;
  4. void QMetaObject::activate(QObject *sender, const QMetaObject *m, int from_local_signal_index, int to_local_signal_index, void **argv) ;

这里调用的是第三个,他们最终都是用第一个实现的,下面是实现的基本代码,

view plaincopy to clipboardprint?
  1. void QMetaObject::activate(QObject *sender, int from_signal_index, int to_signal_index, void **argv)
  2. {
  3. if (sender->d_func()->blockSig)
  4. return;
  5. // ...
  6. for (int signal = from_signal_index;
  7. (signal >= from_signal_index && signal <= to_signal_index) || (signal == -2);
  8. (signal == to_signal_index ? signal = -2 : ++signal))
  9. {
  10. int count = connectionLists->at(signal).count();
  11. for (int i = 0; i < count; ++i) {
  12. // signal type and etc
  13. receiver->qt_metacall(QMetaObject::InvokeMetaMethod, method, argv ? argv : empty_argv);
  14. // ...
  15. }
  16. //...
  17. }
  18. // ...
  19. --connectionLists->inUse;
  20. // ...
  21. }

我们来回顾一下,使用 Qt 实现 signal/slots 机制是通过继承 QObject,并在类声明时加入 Q_OBJECT,该宏嵌入一个 QMetaObject 作为整个类实现 Qt 的 RTTI 机制的基础。继承 QObject 的同时使得该类含有一个 d_ptr 指向 QObject 共有的一个友元类 QObjectPrivate,每一个对象都会创建一个 QObjectPrivate 的对象来管理自己 signal/slot。对用户而言,通过声明 signal 其实是实现一个 protected function,它由 moc 生成程序,而 slot 本身就是一般的函数。signal 和 slot 通过调用 QObject::connect()/disconnect() 连接/切断连接,该函数验证连接的合理性(通过 QMetaObject 里面存放的字符串信息,如类名、父类、signal 和 slot 名称和参数),然后调用 QMetaObject::connect() 该方法会使用 mutex 保证操作的线程稳定性,访问两个对象的 d_ptr 指向的 QObjectPrivate 对象,对 sender 添加 connectionLists(一个链表),对 receiver 添加 senders(一个链表)。调用 signal 使用的 emit 其实什么都不是,可以直接调用该 signal 的函数也行,这会调用 QMetaObject::activate() 调用连接上的 slots。

Qt 如何实现的 Meta Object相关推荐

  1. Qt 原理-MOC(1)Meta Object Compiler

    Qt程序编译两个步骤: 1. qmake 生成Makefile文件,Makefile.Debug, Makefile.Release 主要是通过MOC(Meta Object Compiler) 元对 ...

  2. 6、Qt Meta Object system 学习

    原文地址:http://blog.csdn.net/ilvu999/article/details/8049908 使用 meta object system 继承自 QOject 类定义中添加 Q_ ...

  3. MOF (Meta Object Facility) 规范

    一.MOF的定义 MOF (Meta Object Facility) 称为元对象设施或者元对象机制,是OMG 组织为了帮助销售商.开发者和用户更好的使用元模型和元数据技术而制定的. MOF最初是OM ...

  4. Qt文档阅读笔记-Object Model

    Object Model 标准C++对象模型提供了非常有效的运行时的对象范例.但是他的静态属性在某些领域是不灵活的.图形用户界面的编程领域需要运行时的效率和高等级的灵活.Qt提供了这样能力,通过C++ ...

  5. Qt文档阅读笔记-Object Model初步解析

    对象模型 标准C++对象模型提供了对象范式在运行时的高效性.但是他的一些静态性质在某些领域是不灵活的.在GUI程序中不仅要运行时的高效还需要较高的灵活性.Qt对象模型不急结合了C++运行时的高效并且还 ...

  6. Qt文档阅读笔记-Object Trees Ownership解析与实例(为何某些程序在被关闭的时候会崩溃)

    目录 官方解析 Construction/Destruction Order of QObjects 博主栗子 继承里面构造函数与析构函数的顺序 在Qt中,object trees里面的创建与析构 [ ...

  7. Qt warning Pass a context object as connect 3th paramter

    在Qt中使用三个参数重载的connect函数时,槽函数使用Lambda表达式.如果此时Lambda表达式使用了至少一个上下文对象.就会报pass a context object connect 3t ...

  8. QT 的信号与槽机制介绍

    QT 是一个跨平台的 C++ GUI 应用构架,它提供了丰富的窗口部件集,具有面向对象.易于扩展.真正的组件编程等特点,更为引人注目的是目前 Linux 上最为流行的 KDE 桌面环境就是建立在 QT ...

  9. 分析21个 Qt隐藏功能和技巧

    分析21个 Qt 隐藏功能和技巧是本文将要介绍的内容,一一详细的罗列出来,以方便大家阅读方便. Q_GADGET:不需要从QObject继承就可以使用Qt的Meta Object功能 Q_ENUMS: ...

最新文章

  1. 梯度算法求步长的公式_LM(Levenberg-Marquarelt)算法
  2. 【MATLAB】数据分析之数据插值
  3. python的类型化_显式类型化的Python版本?
  4. ASP.NET MVC中如何以ajax的方式在View和Action中传递数据
  5. python set union_python – set.union()抱怨它在传入生成器时没有参数
  6. C++学习——const
  7. TeeChart.Pro.v7.06在BDS2006中的安装(For Win32)
  8. springmvc整合fastjson
  9. React Natvie Fetch工具类
  10. 2021-02-23 根据RNA-seq测序数据判断文库类型和链特异性
  11. 服务器防火墙部分指令
  12. 安装Ceres Solver
  13. HDU 6438Buy and Resell
  14. matlab并行加路径,matlab parfor_matlab 添加到路径_matlab 分布式计算
  15. vue-element-admin安装指南
  16. vrchat合并账号
  17. 单片机控制秒表C语言程序,89C51单片机秒表的设计(全文完整版)
  18. com.googlecode.genericdao
  19. 华为服务器做系统蓝屏,服务器安装2008r2后蓝屏
  20. 求1到n的素数个数C语言,求 1~n 之间素数的个数

热门文章

  1. Others2_谈谈个人常用的软件
  2. eNSP配置无线网络
  3. LeetCode 831. Masking Personal Information【字符串,正则表达式】中等
  4. On Die Termination (ODT) DDR
  5. ThinkPHP5结合云之讯短信验证简单案例
  6. 自编码器(Auto-encoder)的概念和应用
  7. PyQt 记住上次打开路径
  8. mega2560电脑识别不到端口后_mega2560在win7 64位安装驱动成功
  9. Java基于SM4算法实现文件加密 SM4FileUtils
  10. 研究生语音识别课程作业记录(三) 非特定人孤立词识别