当前位置: 首页 > news >正文

QT 中的元对象系统(五):QMetaObject::invokeMethod的使用和实现原理

目录

1.简介

2.原理概述

3.实现分析

3.1.通过方法名调用方法的实现分析

3.2.通过可调用对象调用方法的实现分析

4.使用场景

5.总结


1.简介

        QMetaObject::invokeMethod 是 Qt 框架中的一个静态方法,用于在运行时调用对象的成员函数。这个方法提供了一种动态调用方法的方式,不需要在编译时知道具体的方法名或参数。QMetaObject::invokeMethod 可以用于调用任何对象的任何可调用方法,包括信号、槽和普通成员函数,只要它们符合一定的条件。

        当使用 invokeMethod 时,还需要注意以下几点:

  • 确保对象 object 是有效的,并且其类使用了 Q_OBJECT 宏。
  • 方法 method 必须是可调用的,这通常意味着它是一个槽或使用了 Q_INVOKABLE 宏。
  • 被 Q_INVOKABLE 标记的函数必须是公开的(public),因为元对象系统无法访问私有或受保护的成员函数
  • 如果方法需要参数,确保提供的参数与方法的期望类型匹配。
  • 如果方法返回值,确保正确处理这个返回值。

函数原型为:

QMetaObject::invokeMethod 有几种重载形式,但最常用的一种是:

bool QMetaObject::invokeMethod(QObject *object, const char *method,  QGenericArgument val0 = QGenericArgument(nullptr),  QGenericArgument val1 = QGenericArgument(nullptr),  QGenericArgument val2 = QGenericArgument(nullptr),  QGenericArgument val3 = QGenericArgument(nullptr),  QGenericArgument val4 = QGenericArgument(nullptr),  QGenericArgument val5 = QGenericArgument(nullptr),  QGenericArgument val6 = QGenericArgument(nullptr),  QGenericArgument val7 = QGenericArgument(nullptr),  QGenericArgument val8 = QGenericArgument(nullptr),  QGenericArgument val9 = QGenericArgument(nullptr))
  • object:要调用方法的对象。
  • method:要调用的方法的名称。
  • val0 - val9:方法的参数,最多支持10个。使用 QGenericArgument 类型封装参数。

   invokeMethod 返回一个布尔值,表示方法是否成功调用。如果方法成功被调用,返回 true;如果方法不存在、对象无法找到、参数类型不匹配或方法不是可调用的,返回 false

        假设有一个类 MyClass,它有一个槽 mySlot,可以接受两个整数作为参数:

#include <QCoreApplication>
#include <QObject>
#include <QDebug>class MyObject : public QObject
{Q_OBJECT
public:explicit MyObject(QObject *parent = nullptr) : QObject(parent) {}public slots:void mySlot(int value){qDebug() << "Received value:" << value;}
};#include "main.moc"int main(int argc, char *argv[])
{QCoreApplication a(argc, argv);MyObject obj;int param = 42;// 同步调用bool result = QMetaObject::invokeMethod(&obj, "mySlot", Qt::DirectConnection, Q_ARG(int, param));if (result) {qDebug() << "Method called successfully.";} else {qDebug() << "Method call failed.";}return a.exec();
}

        在这个示例中,我们使用 QMetaObject::invokeMethod 动态调用 MyObject 的 mySlot 方法。Qt::DirectConnection 表示直接调用该方法,Q_ARG(int, param) 用于传递参数。

        QMetaObject::invokeMethod的几种重载 

   QMetaObject::invokeMethod 在 Qt 框架中是一个强大的静态方法,它提供了几种重载形式来适应不同的调用需求。以下是 QMetaObject::invokeMethod 的几种常见重载形式:

        1)基础重载

bool QMetaObject::invokeMethod(QObject *object, const char *method,  Qt::ConnectionType type = Qt::DirectConnection,  QGenericReturnArgument ret = QGenericReturnArgument(nullptr),  QGenericArgument val0 = QGenericArgument(nullptr),  QGenericArgument val1 = QGenericArgument(nullptr),  ... // 最多到 val9  )

        这是最常用的重载形式。它允许你指定要调用的对象、方法名、连接类型(同步或异步)、返回值以及最多10个参数。

        2)无返回值重载

bool QMetaObject::invokeMethod(QObject *object, const char *method,  Qt::ConnectionType type = Qt::DirectConnection,  QGenericArgument val0 = QGenericArgument(nullptr),  ... // 最多到 val9  )

        这个重载与上一个类似,但它不期望方法返回任何值。这在你只关心方法是否被成功调用,而不关心其返回值时很有用。   

       3)带返回值的重载(简化版)

bool QMetaObject::invokeMethod(QObject *object, const char *method,  Qt::ConnectionType type,  QGenericReturnArgument ret)

        这个重载允许你指定一个返回值,但不支持传递参数给方法。它适用于那些不需要参数但期望返回值的场景。

        4)异步调用重载

        虽然这不是一个完全独立的重载,但值得注意的是,invokeMethod 支持异步调用。你可以通过指定 Qt::QueuedConnection 作为连接类型来实现这一点。当使用异步调用时,方法将在事件循环的下一个迭代中被调用,这允许你在不阻塞当前线程的情况下调用方法。

        5)模板重载(C++11及更高版本)

        在 C++11 及更高版本中,Qt 提供了模板化的 invokeMethod,它允许你更直接地传递参数而不需要使用 QGenericArgument。这个重载在编译时根据提供的参数类型自动推断,并调用相应的方法。然而,这个模板重载在 Qt 的某些版本中可能不是直接作为 QMetaObject 的静态成员函数提供的,而是作为 QMetaObject::invokeMethod 的一个帮助器函数或模板特化存在的。

2.原理概述

   QMetaObject::invokeMethod 的核心原理基于 Qt 的元对象系统。元对象系统是 Qt 实现信号槽机制、属性系统和动态调用的基础。每个继承自 QObject 且包含 Q_OBJECT 宏的类都有一个与之关联的 QMetaObject 对象,该对象存储了类的元数据,如类名、信号、槽、属性等信息。

   QMetaObject::invokeMethod 函数利用这些元数据,通过方法的名称或索引来查找并调用对象的方法。它可以在不同线程之间安全地调用方法,并且支持同步和异步调用。

        调用流程

        当调用 QMetaObject::invokeMethod 时,大致会经历以下步骤:

1)查找元对象信息

  • 首先,函数会获取调用对象的 QMetaObject 对象。通过 QObject 的 metaObject() 方法可以获取该对象的元数据。
  • 然后,根据传入的方法名称或索引,在 QMetaObject 中查找对应的方法信息。

2)检查方法是否存在

  • 检查查找结果,如果方法不存在,函数会根据 Qt::ConnectionType 参数的设置返回相应的结果。通常,如果方法不存在,同步调用会返回 false,异步调用会忽略该调用。

3)处理连接类型

QMetaObject::invokeMethod 支持多种连接类型,不同的连接类型会影响方法的调用方式:

  • Qt::DirectConnection:直接调用目标方法,就像直接调用普通函数一样。这种方式适用于调用对象和被调用对象在同一线程的情况。
  • Qt::QueuedConnection:将方法调用封装成一个 QMetaCallEvent 对象,并将其放入目标对象所在线程的事件队列中。当目标线程的事件循环处理到该事件时,会调用目标方法。这种方式适用于跨线程调用。
  • Qt::BlockingQueuedConnection:与 Qt::QueuedConnection 类似,但会阻塞当前线程,直到目标方法调用完成并返回结果。使用时要注意避免在同一线程中使用,否则会导致死锁。
  • Qt::AutoConnection:根据调用对象和被调用对象所在的线程自动选择合适的连接方式。如果在同一线程,使用 Qt::DirectConnection;否则使用 Qt::QueuedConnection

4)调用目标方法

  • 如果是直接调用(Qt::DirectConnection),函数会直接调用目标方法,并将传入的参数传递给该方法。
  • 如果是队列调用(Qt::QueuedConnection 或 Qt::BlockingQueuedConnection),函数会创建一个 QMetaCallEvent 对象,将方法调用的信息(如方法索引、参数等)封装在该事件中,然后将事件发送到目标对象所在线程的事件队列中。

3.实现分析

3.1.通过方法名调用方法的实现分析

   //.\Qt\Qt5.12.12\5.12.12\Src\qtbase\src\corelib\kernel\qobjectdefs.hstatic bool invokeMethod(QObject *obj, const char *member,Qt::ConnectionType,QGenericReturnArgument ret,QGenericArgument val0 = QGenericArgument(nullptr),QGenericArgument val1 = QGenericArgument(),QGenericArgument val2 = QGenericArgument(),QGenericArgument val3 = QGenericArgument(),QGenericArgument val4 = QGenericArgument(),QGenericArgument val5 = QGenericArgument(),QGenericArgument val6 = QGenericArgument(),QGenericArgument val7 = QGenericArgument(),QGenericArgument val8 = QGenericArgument(),QGenericArgument val9 = QGenericArgument());static inline bool invokeMethod(QObject *obj, const char *member,QGenericReturnArgument ret,QGenericArgument val0 = QGenericArgument(nullptr),QGenericArgument val1 = QGenericArgument(),QGenericArgument val2 = QGenericArgument(),QGenericArgument val3 = QGenericArgument(),QGenericArgument val4 = QGenericArgument(),QGenericArgument val5 = QGenericArgument(),QGenericArgument val6 = QGenericArgument(),QGenericArgument val7 = QGenericArgument(),QGenericArgument val8 = QGenericArgument(),QGenericArgument val9 = QGenericArgument()){return invokeMethod(obj, member, Qt::AutoConnection, ret, val0, val1, val2, val3,val4, val5, val6, val7, val8, val9);}static inline bool invokeMethod(QObject *obj, const char *member,Qt::ConnectionType type,QGenericArgument val0 = QGenericArgument(nullptr),QGenericArgument val1 = QGenericArgument(),QGenericArgument val2 = QGenericArgument(),QGenericArgument val3 = QGenericArgument(),QGenericArgument val4 = QGenericArgument(),QGenericArgument val5 = QGenericArgument(),QGenericArgument val6 = QGenericArgument(),QGenericArgument val7 = QGenericArgument(),QGenericArgument val8 = QGenericArgument(),QGenericArgument val9 = QGenericArgument()){return invokeMethod(obj, member, type, QGenericReturnArgument(), val0, val1, val2,val3, val4, val5, val6, val7, val8, val9);}static inline bool invokeMethod(QObject *obj, const char *member,QGenericArgument val0 = QGenericArgument(nullptr),QGenericArgument val1 = QGenericArgument(),QGenericArgument val2 = QGenericArgument(),QGenericArgument val3 = QGenericArgument(),QGenericArgument val4 = QGenericArgument(),QGenericArgument val5 = QGenericArgument(),QGenericArgument val6 = QGenericArgument(),QGenericArgument val7 = QGenericArgument(),QGenericArgument val8 = QGenericArgument(),QGenericArgument val9 = QGenericArgument()){return invokeMethod(obj, member, Qt::AutoConnection, QGenericReturnArgument(), val0,val1, val2, val3, val4, val5, val6, val7, val8, val9);}

几个不同参数的invokeMethod最终调用了下面的参数接口:

bool QMetaObject::invokeMethod(QObject *obj,const char *member,Qt::ConnectionType type,QGenericReturnArgument ret,QGenericArgument val0,QGenericArgument val1,QGenericArgument val2,QGenericArgument val3,QGenericArgument val4,QGenericArgument val5,QGenericArgument val6,QGenericArgument val7,QGenericArgument val8,QGenericArgument val9)
{if (!obj)return false;//1.把函数和函数的参数组合成这种形式:mySlot(int)QVarLengthArray<char, 512> sig;int len = qstrlen(member);if (len <= 0)return false;sig.append(member, len);sig.append('(');const char *typeNames[] = {ret.name(), val0.name(), val1.name(), val2.name(), val3.name(),val4.name(), val5.name(), val6.name(), val7.name(), val8.name(),val9.name()};int paramCount;for (paramCount = 1; paramCount < MaximumParamCount; ++paramCount) {len = qstrlen(typeNames[paramCount]);if (len <= 0)break;sig.append(typeNames[paramCount], len);sig.append(',');}if (paramCount == 1)sig.append(')'); // no parameterselsesig[sig.size() - 1] = ')';sig.append('\0');//2.从元数据中获取函数mySlot(int)的idxconst QMetaObject *meta = obj->metaObject();int idx = meta->indexOfMethod(sig.constData());if (idx < 0) {QByteArray norm = QMetaObject::normalizedSignature(sig.constData());idx = meta->indexOfMethod(norm.constData());}if (idx < 0 || idx >= meta->methodCount()) {// This method doesn't belong to us; print out a nice warning with candidates.qWarning("QMetaObject::invokeMethod: No such method %s::%s%s",meta->className(), sig.constData(), findMethodCandidates(meta, member).constData());return false;}//3.调用QMetaMethod的invoke方法QMetaMethod method = meta->method(idx);return method.invoke(obj, type, ret,val0, val1, val2, val3, val4, val5, val6, val7, val8, val9);
}

这个函数关键实现过程主要有3步:

1)取出方法名和方法参数名,把它们组合成" 函数名(参数1类型,参数2类型,...)",形如:

" mySlot(int) " 的字符串。

2)从元数据中获取此方法在QMetaObject的相对位置信息。

int QMetaObject::indexOfMethod(const char *method) const
{const QMetaObject *m = this;int i;Q_ASSERT(priv(m->d.data)->revision >= 7);QArgumentTypeArray types;QByteArray name = QMetaObjectPrivate::decodeMethodSignature(method, types);i = indexOfMethodRelative<0>(&m, name, types.size(), types.constData());if (i >= 0)i += m->methodOffset();return i;
}

3)最后调用QMetaMethod的invoke方法,此步骤最为关键

bool QMetaMethod::invoke(QObject *object,Qt::ConnectionType connectionType,QGenericReturnArgument returnValue,QGenericArgument val0,QGenericArgument val1,QGenericArgument val2,QGenericArgument val3,QGenericArgument val4,QGenericArgument val5,QGenericArgument val6,QGenericArgument val7,QGenericArgument val8,QGenericArgument val9) const
{if (!object || !mobj)return false;Q_ASSERT(mobj->cast(object));// check return type,检测返回值if (returnValue.data()) {const char *retType = typeName();if (qstrcmp(returnValue.name(), retType) != 0) {// normalize the return value as wellQByteArray normalized = QMetaObject::normalizedType(returnValue.name());if (qstrcmp(normalized.constData(), retType) != 0) {// String comparison failed, try compare the metatype.int t = returnType();if (t == QMetaType::UnknownType || t != QMetaType::type(normalized))return false;}}}// check argument count (we don't allow invoking a method if given too few arguments)// 检查参数个数(如果给定的参数太少,我们不允许调用方法)const char *typeNames[] = {returnValue.name(),val0.name(),val1.name(),val2.name(),val3.name(),val4.name(),val5.name(),val6.name(),val7.name(),val8.name(),val9.name()};int paramCount;for (paramCount = 1; paramCount < MaximumParamCount; ++paramCount) {if (qstrlen(typeNames[paramCount]) <= 0)break;}if (paramCount <= QMetaMethodPrivate::get(this)->parameterCount())return false;// check connection type// 检查连接类型QThread *currentThread = QThread::currentThread();QThread *objectThread = object->thread();if (connectionType == Qt::AutoConnection) {connectionType = currentThread == objectThread? Qt::DirectConnection: Qt::QueuedConnection;}#if !QT_CONFIG(thread)if (connectionType == Qt::BlockingQueuedConnection) {connectionType = Qt::DirectConnection;}
#endif// invoke! //调用void *param[] = {returnValue.data(),val0.data(),val1.data(),val2.data(),val3.data(),val4.data(),val5.data(),val6.data(),val7.data(),val8.data(),val9.data()};int idx_relative = QMetaMethodPrivate::get(this)->ownMethodIndex();int idx_offset =  mobj->methodOffset();Q_ASSERT(QMetaObjectPrivate::get(mobj)->revision >= 6);QObjectPrivate::StaticMetaCallFunction callFunction = mobj->d.static_metacall;if (connectionType == Qt::DirectConnection) {if (callFunction) {callFunction(object, QMetaObject::InvokeMetaMethod, idx_relative, param);return true;} else {return QMetaObject::metacall(object, QMetaObject::InvokeMetaMethod, idx_relative + idx_offset, param) < 0;}} else if (connectionType == Qt::QueuedConnection) {if (returnValue.data()) {qWarning("QMetaMethod::invoke: Unable to invoke methods with return values in ""queued connections");return false;}int nargs = 1; // include return typevoid **args = (void **) malloc(paramCount * sizeof(void *));Q_CHECK_PTR(args);int *types = (int *) malloc(paramCount * sizeof(int));Q_CHECK_PTR(types);types[0] = 0; // return typeargs[0] = 0;for (int i = 1; i < paramCount; ++i) {types[i] = QMetaType::type(typeNames[i]);if (types[i] == QMetaType::UnknownType && param[i]) {// Try to register the type and try again before reporting an error.int index = nargs - 1;void *argv[] = { &types[i], &index };QMetaObject::metacall(object, QMetaObject::RegisterMethodArgumentMetaType,idx_relative + idx_offset, argv);if (types[i] == -1) {qWarning("QMetaMethod::invoke: Unable to handle unregistered datatype '%s'",typeNames[i]);for (int x = 1; x < i; ++x) {if (types[x] && args[x])QMetaType::destroy(types[x], args[x]);}free(types);free(args);return false;}}if (types[i] != QMetaType::UnknownType) {args[i] = QMetaType::create(types[i], param[i]);++nargs;}}QCoreApplication::postEvent(object, new QMetaCallEvent(idx_offset, idx_relative, callFunction,0, -1, nargs, types, args));} else { // blocking queued connection
#if QT_CONFIG(thread)if (currentThread == objectThread) {qWarning("QMetaMethod::invoke: Dead lock detected in ""BlockingQueuedConnection: Receiver is %s(%p)",mobj->className(), object);}QSemaphore semaphore;QCoreApplication::postEvent(object, new QMetaCallEvent(idx_offset, idx_relative, callFunction,0, -1, 0, 0, param, &semaphore));semaphore.acquire();
#endif // QT_CONFIG(thread)}return true;
}
  • 如果是直接调用(Qt::DirectConnection),函数会直接调用目标方法,并将传入的参数传递给该方法。
  • 如果是队列调用(Qt::QueuedConnection 或 Qt::BlockingQueuedConnection),函数会创建一个 QMetaCallEvent 对象,将方法调用的信息(如方法索引、参数等)封装在该事件中,然后将事件发送到目标对象所在线程的事件队列中。

3.2.通过可调用对象调用方法的实现分析

C++ 的 Tag Dispatching(标签派发) 惯用法_c++ tag dispatch-CSDN博客

C++之std::enable_if_std enable if-CSDN博客 

1)invokeMethod() 调用类成员函数指针

// invokeMethod() for member function pointertemplate <typename Func>static typename std::enable_if<QtPrivate::FunctionPointer<Func>::IsPointerToMemberFunction&& !std::is_convertible<Func, const char*>::value&& QtPrivate::FunctionPointer<Func>::ArgumentCount == 0, bool>::typeinvokeMethod(typename QtPrivate::FunctionPointer<Func>::Object *object,Func function,Qt::ConnectionType type = Qt::AutoConnection,typename QtPrivate::FunctionPointer<Func>::ReturnType *ret = nullptr){return invokeMethodImpl(object, new QtPrivate::QSlotObjectWithNoArgs<Func>(function), type, ret);}template <typename Func>static typename std::enable_if<QtPrivate::FunctionPointer<Func>::IsPointerToMemberFunction&& !std::is_convertible<Func, const char*>::value&& QtPrivate::FunctionPointer<Func>::ArgumentCount == 0, bool>::typeinvokeMethod(typename QtPrivate::FunctionPointer<Func>::Object *object,Func function,typename QtPrivate::FunctionPointer<Func>::ReturnType *ret){return invokeMethodImpl(object, new QtPrivate::QSlotObjectWithNoArgs<Func>(function), Qt::AutoConnection, ret);}

2)invokeMethod() 调用函数指针

// invokeMethod() for function pointer (not member)template <typename Func>static typename std::enable_if<!QtPrivate::FunctionPointer<Func>::IsPointerToMemberFunction&& !std::is_convertible<Func, const char*>::value&& QtPrivate::FunctionPointer<Func>::ArgumentCount == 0, bool>::typeinvokeMethod(QObject *context, Func function,Qt::ConnectionType type = Qt::AutoConnection,typename QtPrivate::FunctionPointer<Func>::ReturnType *ret = nullptr){return invokeMethodImpl(context, new QtPrivate::QFunctorSlotObjectWithNoArgsImplicitReturn<Func>(function), type, ret);}template <typename Func>static typename std::enable_if<!QtPrivate::FunctionPointer<Func>::IsPointerToMemberFunction&& !std::is_convertible<Func, const char*>::value&& QtPrivate::FunctionPointer<Func>::ArgumentCount == 0, bool>::typeinvokeMethod(QObject *context, Func function,typename QtPrivate::FunctionPointer<Func>::ReturnType *ret){return invokeMethodImpl(context, new QtPrivate::QFunctorSlotObjectWithNoArgsImplicitReturn<Func>(function), Qt::AutoConnection, ret);}

3)invokeMethod() 调用仿函数或lamdba表达式等可调用对象

// invokeMethod() for Functortemplate <typename Func>static typename std::enable_if<!QtPrivate::FunctionPointer<Func>::IsPointerToMemberFunction&& QtPrivate::FunctionPointer<Func>::ArgumentCount == -1&& !std::is_convertible<Func, const char*>::value, bool>::typeinvokeMethod(QObject *context, Func function,Qt::ConnectionType type = Qt::AutoConnection, decltype(function()) *ret = nullptr){return invokeMethodImpl(context,new QtPrivate::QFunctorSlotObjectWithNoArgs<Func, decltype(function())>(std::move(function)),type,ret);}template <typename Func>static typename std::enable_if<!QtPrivate::FunctionPointer<Func>::IsPointerToMemberFunction&& QtPrivate::FunctionPointer<Func>::ArgumentCount == -1&& !std::is_convertible<Func, const char*>::value, bool>::typeinvokeMethod(QObject *context, Func function, decltype(function()) *ret){return invokeMethodImpl(context,new QtPrivate::QFunctorSlotObjectWithNoArgs<Func, decltype(function())>(std::move(function)),Qt::AutoConnection,ret);}

这3中情况都调用了invokeMethodImpl,invokeMethodImpl的详细实现如下:

bool QMetaObject::invokeMethodImpl(QObject *object, QtPrivate::QSlotObjectBase *slot, Qt::ConnectionType type, void *ret)
{struct Holder {QtPrivate::QSlotObjectBase *obj;~Holder() { obj->destroyIfLastRef(); }} holder = { slot };Q_UNUSED(holder);if (! object)return false;QThread *currentThread = QThread::currentThread();QThread *objectThread = object->thread();if (type == Qt::AutoConnection)type = (currentThread == objectThread) ? Qt::DirectConnection : Qt::QueuedConnection;void *argv[] = { ret };if (type == Qt::DirectConnection) {slot->call(object, argv);} else if (type == Qt::QueuedConnection) {if (argv[0]) {qWarning("QMetaObject::invokeMethod: Unable to invoke methods with return values in ""queued connections");return false;}// args and typesCopy will be deallocated by ~QMetaCallEvent() using free()void **args = static_cast<void **>(calloc(1, sizeof(void *)));Q_CHECK_PTR(args);int *types = static_cast<int *>(calloc(1, sizeof(int)));Q_CHECK_PTR(types);QCoreApplication::postEvent(object, new QMetaCallEvent(slot, 0, -1, 1, types, args));} else if (type == Qt::BlockingQueuedConnection) {
#if QT_CONFIG(thread)if (currentThread == objectThread)qWarning("QMetaObject::invokeMethod: Dead lock detected");QSemaphore semaphore;QCoreApplication::postEvent(object, new QMetaCallEvent(slot, 0, -1, 0, 0, argv, &semaphore));semaphore.acquire();
#endif // QT_CONFIG(thread)} else {qWarning("QMetaObject::invokeMethod: Unknown connection type");return false;}return true;
}

此函数的实现和上面讲的QMetaMethod的invoke方法实现类似,就不在这里赘述了。

4.使用场景

1)Q_INVOKABLE与QMetaObject::invokeMethod均由元对象系统唤起。这一机制在Qt C++/QML混合编程跨线程编程Qt Service Framework 以及 Qt/ HTML5混合编程以及里广泛使用。

        Qt C++/QML混合编程

QML中调用C++方法借助了Qt元对象系统。考虑在QML中使用Qt C++定义的方法,如下代码所示:

import Qt 4.7   
import Shapes 5.0   //自定义模块  
Item {   width: 300; height: 200  Ellipse {   x: 50; y: 35; width: 200; height: 100   color: "blue"   MouseArea {   anchors.fill: parent  // 调用C++中定义的randomColor方法   onClicked: parent.color = parent.randomColor()    }   }  
}  

为了让上述QML代码成功的调用下面这段代码定义的randomColor()函数,最为关键的一点见randomColor方法用Q_INVOKABLE 修饰。

        在跨线程编程中的使用

        我们如何调用驻足在其他线程里的QObject方法呢?Qt提供了一种非常友好而且干净的解决方案:向事件队列post一个事件,事件的处理将以调用我们所感兴趣的方法为主(当然这需要线程有一个正在运行的事件循环)。而触发机制的实现是由moc提供的内省方法实现的。因此,只有信号、槽以及被标记成Q_INVOKABLE的方法才能够被其它线程所触发调用。如果你不想通过跨线程的信号、槽这一方法来实现调用驻足在其他线程里的QObject方法。另一选择就是将方法声明为Q_INVOKABLE,并且在另一线程中用invokeMethod唤起。

        Qt Service Framework

        Qt服务框架是Qt Mobility 1.0.2版本推出的,一个服务(service)是一个独立的组件提供给客户端(client)定义好的操作。客户端可以通过服务的名称,版本号和服务的对象提供的接口来查找服务。 查找到服务后,框架启动服务并返回一个指针。

        服务通过插件(plug-ins)来实现。为了避免客户端依赖某个具体的库,服务必须继承自QObject。这样QMetaObject 系统可以用来提供动态发现和唤醒服务的能力。要使QmetaObject机制充分的工作,服务必须满足,其所有的方法都是通过 signal,slot,property 或invokable methodQ_INVOKEBLE来实现

        其中,最常见的与servicer交互的方法如下:

QServiceManager manager;QObject *storage ;  
storage = manager.loadInterface("com.nokia.qt.examples.FileStorage"); if (storage)     QMetaObject::invokeMethod(storage, "deleteFile", Q_ARG(QString, "/tmp/readme.txt")); 
上面的代码通过service的元对象提供的invokeMethod方法,调用文件存储对象的deleteFile() 方法。客户端不需要知道对象的类型,因此也没有链接到具体的service库。  当然在服务端的deleteFile方法,一定要被标记为Q_INVOKEBLE,才能够被元对象系统识别。

        Qt服务框架的一个亮点是它支持跨进程通信,服务可以接受远程进程。在服务管理器上注册后 进程通过signal,slot,invokable method和property来通信,就像本地对象一样。服务可以设定为在客户端间共享,或针对一个客户端。  请注意,在Qt服务框架推出之前,信号、槽以及invokable method仅支持跨线程。 下图是跨进成的服务/客户段通信示意图(图片来自诺基亚论坛)。这里我们可以清楚的看到,invokable methodQ_INVOKEBLE 是跨进城、跨线程对象之间通信的重要利器。

2)使用 QMetaObject::invokeMethod类外调用私有槽函数

QMetaObject::invokeMethod 可以在运行时动态调用对象的方法,包括私有槽函数。

示例代码:

#include <QObject>
#include <QDebug>class MyClass : public QObject
{Q_OBJECT
private slots:void privateSlot() {qDebug() << "Private slot called.";}
};#include "main.moc"int main(int argc, char *argv[])
{MyClass obj;QMetaObject::invokeMethod(&obj, "privateSlot", Qt::DirectConnection);return 0;
}

QMetaObject::invokeMethod 函数利用 Qt 的元对象系统,依据方法名来查找并调用对象的方法。这里使用 Qt::DirectConnection 直接调用 privateSlot 私有槽函数。不过要注意,使用这种方法时,方法名必须准确无误,而且要保证元对象系统能正确识别该方法。

使用场景总结:

  • 动态调用:在运行时根据不同的条件动态调用对象的方法,而不需要在编译时确定具体的调用方法。例如,根据用户的输入或配置文件中的信息来决定调用哪个方法。
  • 跨线程调用:在多线程应用中,安全地在不同线程之间调用对象的方法。例如,在工作线程中更新 UI 线程的对象状态。由于 Qt 的 UI 类不是线程安全的,不能直接在非 UI 线程中操作 UI 控件,使用 QMetaObject::invokeMethod 可以将 UI 操作封装成事件,放入 UI 线程的事件队列中处理。
  • 反射机制:实现类似于反射的功能,通过方法名来调用对象的方法,提高代码的灵活性和可扩展性。

5.总结

优点

  • 灵活性:可以在运行时动态调用对象的方法,无需在编译时确定具体的调用方法,增强了代码的灵活性和可扩展性。
  • 线程安全:支持跨线程调用,通过合理设置连接类型,可以确保在不同线程之间安全地调用对象的方法。
  • 通用性:不仅可以调用槽函数,还可以调用信号和普通的成员函数,具有很强的通用性。

缺点

  • 性能开销:由于 QMetaObject::invokeMethod 是通过元对象系统进行方法查找和调用的,相比直接调用普通函数会有一定的性能开销。因此,在性能敏感的场景中要谨慎使用。
  • 类型安全问题:在使用 QGenericArgument 和 QGenericReturnArgument 传递参数和接收返回值时,需要手动管理类型,容易出现类型不匹配的问题,导致运行时错误。

注意事项

  • 方法名的准确性:传递给 invokeMethod 的方法名必须准确无误,包括大小写和参数列表。如果方法名错误,调用将失败。
  • 参数类型和数量:传递的参数类型和数量必须与被调用方法的定义一致,否则可能会导致调用失败或产生未定义行为。
  • 线程安全:在使用 Qt::BlockingQueuedConnection 时,要注意避免死锁问题,确保调用对象和被调用对象不在同一线程。
  • 异常处理:由于方法调用可能是异步的(如使用 Qt::QueuedConnection),调用线程无法直接捕获被调用方法中抛出的异常。因此,在被调用方法中要做好异常处理,避免异常导致程序崩溃。

        综上所述,QMetaObject::invokeMethod 是一个非常有用的函数,但在使用时需要根据具体情况权衡其优缺点,并注意相关的注意事项,以确保代码的正确性和性能。

相关文章:

QT 中的元对象系统(五):QMetaObject::invokeMethod的使用和实现原理

目录 1.简介 2.原理概述 3.实现分析 3.1.通过方法名调用方法的实现分析 3.2.通过可调用对象调用方法的实现分析 4.使用场景 5.总结 1.简介 QMetaObject::invokeMethod 是 Qt 框架中的一个静态方法&#xff0c;用于在运行时调用对象的成员函数。这个方法提供了一种动态调…...

基数排序算法解析与TypeScript实现

基数排序&#xff08;Radix Sort&#xff09;是一种高效的非比较型整数排序算法&#xff0c;通过逐位分配与收集的方式实现排序。本文将深入解析其工作原理&#xff0c;并给出完整的TypeScript实现。 一、算法原理 1. 核心思想 多关键字排序&#xff1a;将整数按位数切割成不同…...

oracle asm 相关命令和查询视图

有关asm磁盘的命令 添加磁盘 alter diskgroup data1 add disk /devices/diska*;---runs with a rebalance power of 5 , and dose not return until the rebalance operation is completealter diskgroup data1 add disk /devices/diskd* rebalance power 5 wait;查询 select …...

THUNLP_Multimodal_Excercise

背景 多模态大模型&#xff08;Multimodal Large Language Models, MLLM&#xff09;的构建过程中&#xff0c;模型结构、模型预测、指令微调以及偏好对齐训练是其中重要的组成部分。本次任务中&#xff0c;将提供一个不完整的多模态大模型结构及微调代码&#xff0c;请根据要求…...

AIGC6——AI的哲学困境:主体性、认知边界与“天人智一“的再思考

引言&#xff1a;当机器开始"思考" 2023年&#xff0c;Google工程师Blake Lemoine声称对话AI LaMDA具有"自我意识"&#xff0c;引发轩然大波。这一事件将古老的哲学问题重新抛回公众视野&#xff1a;​**机器能否拥有主体性&#xff1f;**从东方"天人…...

主题(topic)中使用键(key)来区分同一主题下的多个数据实例

在Fast DDS中&#xff0c;通过在主题&#xff08;topic&#xff09;中使用键&#xff08;key&#xff09;来区分同一主题下的多个数据实例&#xff0c;具体含义如下&#xff1a; 1. **主题&#xff08;Topic&#xff09;**&#xff1a;在DDS中&#xff0c;主题是数据的类型或类…...

[王阳明代数讲义]琴语言类型系统工程特性

琴语言类型系统工程特性 层展物理学组织实务与艺术与琴生生.物机.械科.技工.业研究.所软凝聚态物理开发工具包社会科学气质砥砺学人生意气场社群成员魅力场与心气微积分社会关系力学 意气实体过程图论信息编码&#xff0c;如来码导引 注意力机制道装Transformer架构的发展标度律…...

蜜蜡是什么?蜜蜡与琥珀的区别以及蜜蜡的收藏价值一览

蜜蜡是琥珀的一种&#xff0c;在物理成分和化学成分上都和琥珀没有区别&#xff0c;只是因其“色如蜜&#xff0c;光如蜡”而得名。蜜蜡形成于白垩纪时期&#xff0c;因形成时间较长&#xff0c;形成过程比较复杂等原因&#xff0c;种类较其他产地要多。 蜜蜡是有机矿物&#x…...

第五课:高清修复和放大算法

文章目录 Part.01 高清修复(Hi-Res Fix)Part.02 SD放大(SD Upscale)Part.03 附加功能放大Part.01 高清修复(Hi-Res Fix) 文生图中的高清修复/高分辨率修复/超分辨率修复先低分辨率抽卡,再高分辨率修复。不能突破显存限制放大重绘幅度安全范围是0.3-0.5,如果想让AI更有想象力0…...

SpringSecurity6.0 通过JWTtoken进行认证授权

之前写过一个文章&#xff0c;从SpringSecurity 5.x升级到6.0&#xff0c;当时是为了配合公司的大版本升级做的&#xff0c;里面的各项配置都是前人留下来的&#xff0c;其实没有花时间进行研究SpringSecurity的工作机制。现在新东家有一个简单的系统要搭建&#xff0c;用户的认…...

TypeScript工程集成

以下是关于 TypeScript 工程集成 的系统梳理,涵盖基础配置、进阶优化、开发规范及实际场景的注意事项,帮助我们构建高效可靠的企业级 TypeScript 项目: 一、基础知识点 1. 项目初始化与配置 tsconfig.json 核心配置:{"compilerOptions": {"target": &…...

网络空间安全(51)邮件函数漏洞

前言 邮件函数漏洞&#xff0c;特别是在PHP环境中使用mail()函数时&#xff0c;是一个重要的安全问题。 一、概述 在PHP中&#xff0c;mail()函数是一个用于发送电子邮件的内置函数。其函数原型为&#xff1a; bool mail ( string $to , string $subject , string $message [, …...

怎么让一台云IPPBX实现多家酒店相同分机号码一起使用

下面用到的IPPBX是我们二次开发后的成品&#xff0c;支持各种云服务器一键安装&#xff0c;已经写好了一键安装包&#xff0c;自动识别系统环境&#xff0c;安装教程这里就不再陈述了&#xff01; 前言需求 今天又遇到了一个客户咨询&#xff0c;关于部署一台云IPPBX&#xf…...

qt tcpsocket编程遇到的并发问题

1. 单个socket中接收消息的方法要使用局部变量而非全局&#xff0c;避免消息频发时产生脏数据 优化后的关键代码 recieveInfo() 方法通过返回内部处理后的 msg 进行传递if (data.indexOf("0103") -1) { 这里增加了判断, 对数据&#xff08;非注册和心跳&#xff0…...

U盘实现——BOT 常用命令

文章目录 U盘实现——BOT 常用命令命令格式CBWCSW数据传输条件命令传输数据传输状态传输命令汇总INQUIRY Command:12h数据格式抓包READ FORMAT CAPACITIES Command: 23h数据格式抓包READ CAPACITY Command: 25h数据格式抓包TEST UNIT READY Command: 00h数据格式抓包WRITE(10) …...

JavaScript DOM 节点操作

目录 一、DOM 节点 节点类型&#xff08;Node Types&#xff09; 二、查找节点 1.查找父节点 1. parentNode 2. parentElement 2.查找子节点 1. childNodes 2. children 3. firstChild / lastChild 4. firstElementChild / lastElementChild 3.查找兄弟节点 1. pre…...

JDBC常用的接口

一、什么是JDBC JDBC是Java语言连接数据库的接口规范。 二、JDBC的体系 1、Java官方提供一个操作数据库的抽象接口 抽象接口有很多的接口和抽象类。 例如&#xff1a;Driver、Connection、Statement。 2、各个数据库厂商提供各自的Java实现类 需要各自实现具体的细节。 例如&am…...

【DLI】Generative AI with Diffusion Models通关秘籍

Generative AI with Diffusion Models&#xff0c;加载时间在20分钟左右&#xff0c;耐心等待。 6.2TODO 这里是在设置扩散模型的参数&#xff0c;代码里的FIXME部分需要根据上下文进行替换。以下是各个FIXME的替换说明&#xff1a; 1.a_bar 是 a 的累积乘积&#xff0c;在 …...

TP6图片操作 Image::open 调用->save()方法时候报错Type is not supported

错误提示&#xff1a; { "code": 0, "msg": "Type is not supported", "data": { "code": 0, "line": 50, "file": "/www/wwwroot/ytems/vendor/topthink/framework/src/think…...

11_常用函数

文章目录 一、概述二、字符函数2.1、获取字符串所占字节数2.2、获取字符个数2.3、拼接字符串2.4、大小写转换2.5、获取子串2.6、获取子串第一次出现的索引2.7、去除字符串前后子字符串2.7.1、去掉左侧空格2.7.2、去掉右侧空格 2.8、左右填充2.9、字符串替换 三、数学函数3.1、四…...

《inZOI(云族裔)》50+MOD整合包

载具 RebelCore - 年龄和时间 mod启动器 优化补丁 去除雾气 坦克模型 菜单 前置 跳过启动 更好性能 等 共计50MOD整合 在游戏的世界里&#xff0c;追求更丰富、更优质的体验是玩家们永恒的主题。RebelCore 这款游戏通过精心打造的 50MOD 整合&#xff0c;为玩家带来了前所未有的…...

【技术报告】GPT-4o 原生图像生成的应用与分析

【技术报告】GPT-4o 原生图像生成的应用与分析 1. GPT-4o 原生图像生成简介1.1 文本渲染能力1.2 多轮对话迭代1.3 指令遵循能力1.4 上下文学习能力1.5 跨模态知识调用1.6 逼真画质与多元风格1.7 局限性与安全性 2. GPT-4o 技术报告2.1 引言2.2 安全挑战、评估与缓解措施2.2.1 安…...

拼多多延迟发货解答2

三、延迟发货处理标准 延迟发货极大地影响了消费者的购物体验&#xff0c;平台对延迟发货行为也有相应的处理标准&#xff0c;因此各位商家一定不要以为延迟发货是小事儿。延迟发货处理标准具体可查看《拼多多发货规则》第3条。 商家发生延迟发货的&#xff0c;拼多多平台有权…...

RTOS基础 -- NXP M4小核的RPMsg-lite与端点机制回顾

一、RPMsg-lite与端点机制回顾 在RPMsg协议框架中&#xff1a; Endpoint&#xff08;端点&#xff09; 是一个逻辑通信端口&#xff0c;由本地地址&#xff08;local addr&#xff09;、远程地址&#xff08;remote addr&#xff09;和回调函数组成。每个消息都会发送到特定的…...

25.4.3学习总结【Java】

又是一道错题&#xff1a; 1. 班级活动https://www.lanqiao.cn/problems/17153/learning/?page1&first_category_id1&sortdifficulty&asc1&second_category_id3 问题描述 小明的老师准备组织一次班级活动。班上一共有 n 名 (n 为偶数) 同学&#xff0c;老师…...

市场交易策略优化与波动管理

市场交易策略优化与波动管理 在市场交易中&#xff0c;策略的优化和波动的管理至关重要。市场价格的变化受多种因素影响&#xff0c;交易者需要根据市场环境动态调整策略&#xff0c;以提高交易的稳定性&#xff0c;并有效规避市场风险。 一、市场交易策略的优化方法 趋势交易策…...

TypeScript 元数据操作 API 及示例

TypeScript 元数据操作 API 及示例 1. 配置环境 安装依赖 npm install reflect-metadatatsconfig.json 配置 {"compilerOptions": {"experimentalDecorators": true,"emitDecoratorMetadata": true,"target": "ES6"} }2…...

蓝桥杯刷题记录【并查集001】(2024)

主要内容&#xff1a;并查集 并查集 并查集的题目感觉大部分都是模板题&#xff0c;上板子&#xff01;&#xff01; class UnionFind:def __init__(self, n):self.pa list(range(n))self.size [1]*n self.cnt ndef find(self, x):if self.pa[x] ! x:self.pa[x] self.fi…...

搜广推校招面经六十六

高德推荐算法 一、介绍Transformer中的位置编码&#xff08;Positional Encoding&#xff09; 在 Transformer 结构中&#xff0c;由于模型没有内置的序列信息&#xff08;不像 RNN 那样有时间步的顺序依赖&#xff09;&#xff0c;需要通过**位置编码&#xff08;Positional…...

Java高频面试题2:集合框架

一、集合框架概述 1. 常见的集合框架有哪些&#xff1f; Collection&#xff1a;存储单个元素的集合。 List&#xff08;有序、可重复&#xff09;&#xff1a;ArrayList&#xff08;动态数组&#xff09;、LinkedList&#xff08;双向链表&#xff09;。Set&#xff08;无序…...

06-公寓租赁项目-后台管理-公寓管理篇

尚庭公寓项目/公寓管理模块 https://www.yuque.com/pkqzyh/qg2yge/5ba67653b51379d18df61b9c14c3e946 一、属性管理 属性管理页面包含公寓和房间各种可选的属性信息&#xff0c;其中包括房间的可选支付方式、房间的可选租期、房间的配套、公寓的配套等等。其所需接口如下 1.1…...

目前主流OCR/语义理解/ASR

OCR 基于多篇专业评测的结果&#xff0c;以下是目前免费开源OCR工具的推荐排名&#xff08;侧重中文场景&#xff09;&#xff1a; 1. RapidOCR 优势&#xff1a;基于PaddleOCR优化&#xff0c;在印刷中文、自然场景文字识别中综合评分第一&#xff0c;支持180度旋转和低对比…...

Selenium 元素定位方法详解

Selenium 提供了多种元素定位方式&#xff0c;掌握这些方法是进行 Web 自动化测试的基础。以下是主要的元素定位方法及其使用示例&#xff1a; 1. 基本定位方法 1.1 通过 ID 定位 element driver.find_element(By.ID, "element_id") 1.2 通过 Name 定位 element …...

fastGPT—前端开发获取api密钥调用机器人对话接口(HTML实现)

官网文档链接&#xff1a;OpenAPI 介绍 | FastGPT 首先按照文档说明创建api密钥 <!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><meta name"viewport" content"widthdevice-width, initial-sca…...

c语言数据结构--------拓扑排序和逆拓扑排序(Kahn算法和DFS算法实现)

#include <stdio.h> #include <string.h> #include <stdbool.h> #include <stdlib.h>//使用卡恩算法(Kahn)和深度优先算法(DFS)实现//拓扑排序和逆拓扑排序//拓扑排序和逆拓扑排序顶点顺序相反//图&#xff0c;邻接矩阵存储 #define MaxVertexNum 100 …...

日期类的实现

本文运用c类和对象中的构造函数&#xff0c; 析构函数 &#xff0c;拷贝构造函数 &#xff0c; 赋值运算符重载等为大家模拟实现日期类的操作 #define _CRT_SECURE_NO_WARNINGS 1 #include"date.h" void Date:: showinfo() {cout << _year << "年&…...

3dgs通俗讲解

3d gaussian splatting&#xff1a;基于splatting和机器学习的三维重建方法。 特点&#xff1a; 无深度学习简单的机器学习大量的CG知识复杂的线性代数对GPU的高性能编程 一、什么是splatting 1、选择“雪球”&#xff1b; 为什么使用核&#xff08;雪球&#xff09; 各向…...

源码分析之Leaflet比例尺控件Control.Scale实现原理

概述 Control.Scale 是一个用于显示地图比例尺的控件&#xff0c;是 Leaflet 中实现比例尺控件的核心逻辑&#xff0c;用于在地图上动态显示公制&#xff08;米/千米&#xff09;和英制&#xff08;英尺/英里&#xff09;的比例尺。 源码分析 源码实现 Control.Scale的源码…...

【无标题 langsmith

【GPT入门】第32课 langsmith介绍与实战 1.lang smith作用2.lang smith配置方法3. 上手第一个lang smith3.1 可运行代码3.2 lang smith 官网&#xff0c;个人项目下 1.lang smith作用 LangSmith是由LangChain开发的一个平台&#xff0c;主要用于构建生产级LLM应用程序&#xf…...

智能建造新范式:装配式建筑 4.0 的数字化进阶

在全球数字化与可持续发展的浪潮中&#xff0c;建筑业正经历着第四次工业革命的深刻变革。装配式建筑4.0的出现&#xff0c;标志着建筑行业从传统的“钢筋水泥时代”迈向“数据驱动时代”&#xff0c;其核心在于通过技术融合重构建筑全生命周期的生产方式&#xff0c;实现从设计…...

从标准输入中读取所有内容sys.stdin.read()

sys.stdin.read().strip() 用于从标准输入中读取所有内容并去除首尾的空白字符。 1. sys.stdin.read() 作用&#xff1a;从标准输入流中读取所有内容&#xff0c;直到遇到文件结束符&#xff08;EOF&#xff09;。在命令行中&#xff0c;EOF 可以通过 CtrlD&#xff08;Linux…...

网络:华为数通HCIA学习:静态路由基础

文章目录 前言静态路由基础静态路由应用场景 静态路由配置静态路由在串行网络的配置静态路由在以太网中的配置 负载分担配置验证 路由备份&#xff08;浮动静态路由&#xff09;配置验证 缺省路由配置验证 总结 华为HCIA 基础实验&#xff0d;静态路由 & eNSP静态路由 基础…...

DAY 35 leetcode 202--哈希表.快乐数

题号202 编写一个算法来判断一个数 n 是不是快乐数。 「快乐数」 定义为&#xff1a; 对于一个正整数&#xff0c;每一次将该数替换为它每个位置上的数字的平方和。然后重复这个过程直到这个数变为 1&#xff0c;也可能是 无限循环 但始终变不到 1。如果这个过程 结果为 1&a…...

Linux Command nmap 网络扫描

tags: 网络 文章目录 简介原理端口状态选项基本扫描发现扫描禁用DNS名称解析无ping扫描 端口扫描版本检测防火墙规避技术故障排除和调试NMAP 脚本 简介 Nmap&#xff08;“ Network Mapper ”&#xff09;是一个用于网络探索和安全审计的开源工具。它旨在快速扫描大型网络&…...

根据源码分析vue中nextTick的实现原理

根据源码分析vue中nextTick的实现原理 一. ‌核心变量定义‌‌二. 异步策略选择&#xff08;降级处理&#xff09;‌1. 微任务优先‌2. 降级到 MutationObserver‌3. 降级到宏任务‌ 三、回调执行逻辑‌四、 ‌nextTick 函数实现‌五、 ‌与 Vue 更新流程的结合‌六‌、关键设计…...

Linux内核TCP/IP协议栈中的设计模式:从面向对象到系统级软件的跨界实践

引言 设计模式(Design Patterns)自GoF(Gang of Four)在1994年提出以来,已成为软件工程领域的核心概念。尽管其经典定义基于面向对象编程(OOP),但设计模式的本质是解决复杂问题的经验总结,而非局限于特定编程范式。本文以Linux内核的TCP/IP协议栈为例,探讨设计模式在…...

风云可测:华为AI天气大模型将暴雨预测误差缩至3公里内

华为云正式发布全球首个气象专用人工智能大模型"盘古气象"&#xff0c;实现台风路径24小时预测误差<30公里、暴雨落区72小时精度91%&#xff0c;较传统数值预报效率提升10000倍。本文基于对西北太平洋10个台风回溯测试、全国2360个气象站验证数据&#xff0c;解析…...

DeepSeek-R1 面试题汇总

Deepseek-r1 面试宝典 原文地址&#xff1a;https://articles.zsxq.com/id_91kirfu15qxw.html DeepSeek-R1 面试题汇总 DeepSeek-R1 面试题汇总 GRPO&#xff08;Group Relative Policy Optimization&#xff09;常见面试题汇总篇 DeepSeek-R1 DeepSeek-R1-Zero 常见面试题汇总…...

ASM1042A型CANFD芯片通信可靠性研究

摘要 本文旨在深入探讨ASM1042A型CAN-FD芯片在多节点通信中的可靠性表现。通过对芯片的电气特性、测试环境、多节点通信测试结果等多方面进行分析&#xff0c;结合实验数据与理论研究&#xff0c;全面评估其在复杂通信场景下的性能与可靠性。研究结果表明&#xff0c;ASM1042A…...

Java8 到 Java21 系列之 Stream API:数据处理的新方式(Java 8)

Java 8 到 Java 21 系列之 Stream API&#xff1a;数据处理的新方式&#xff08;Java 8&#xff09; 系列目录 Java8 到 Java21 系列之 Lambda 表达式&#xff1a;函数式编程的开端&#xff08;Java 8&#xff09;Java 8 到 Java 21 系列之 Stream API&#xff1a;数据处理的…...