QtDbus的API及示例

摘要:
sessionBus().call(消息);如果(reply.isValid()){QStringvalue=reply.value();“value=”&lt:value;asyncCall()异步调用QDBusPendingCallsync=interface->asyncCall(“setName”;

目录

Qt高级——QtDBus快速入门

DBus for IPC in Qt

1. Client :: Method Call

1.1. 方法1: 直接使用 Message 传递消息(Low-Level-API)

// 传参数
QDBusMessage msg = QDBusMessage::createMethodCall("com.myabc.service",
                             "/", "com.myabc.interface", "setName");
msg << QString("Bright");
QDBusMessage response = QDBusConnection::sessionBus().call(msg);

// 获取返回值
QDBusMessage msg = QDBusMessage::createMethodCall("com.myabc.service",
                             "/", "com.myabc.interface", "name");
QDBusMessage response = QDBusConnection::sessionBus().call(msg);

// 判断 Method 是否被正确返回
if(response.type() == QDBusMessage::ReplyMessage)
{
    // QDBusMessage的arguments不仅可以用来存储发送的参数,也用来存储返回值
    // 这里取得 checkIn 的返回值
    QString name= response.arguments().takeFirst().toString();
}

1.2. 方法2: 通过 DBusInterface 调用方法(同步+异步)

QDBusInterface interface("com.myabc.service", "/",
                         "com.myabc.interface",
                         QDBusConnection::sessionBus());
if(!interface.isValid())
{
    qDebug() << qPrintable(QDBusConnection::sessionBus().lastError().message());
    exit(1);
}

// 调用远程对象的方法 setName()
interface.call("setName", "Bright");

// 调用 name() 并获取返回值
QDBusReply<QString> reply = interface.call("name");
if(reply.isValid())
{
    QString value = reply.value();
    qDebug() << "value = " << value ;
}

interface::asyncCall( ) 异步调用

QDBusPendingCall async = interface->asyncCall("setName", "Brion");
// or use this: QDBusPendingReply<QString> reply = interface->asyncCall("RemoteMethod");
async.waitForFinished ();
QDBusPendingCallWatcher *watcher = new QDBusPendingCallWatcher(async, this);

QObject::connect(watcher, SIGNAL(finished(QDBusPendingCallWatcher*)),
                 this, SLOT(callFinishedSlot(QDBusPendingCallWatcher*)));

void MyClass::callFinishedSlot(QDBusPendingCallWatcher *call)
{
    QDBusPendingReply<QString> reply = *call;
    if (! reply.isError()) {
        QString name= reply.argumentAt<0>();
        qDebug() << "name = " << name;
    }
    call->deleteLater();
}

1.3. 方法3: 从XML导入代理类

  1. 使用工具qdbuscpp2xml从object.h生成XML文件:

    指令: qdbuscpp2xml -M test.h -o com.scorpio.test.xml

    <!DOCTYPE node PUBLIC "-//freedesktop//DTD D-BUS Object Introspection 1.0//EN" "http://www.freedesktop.org/standards/dbus/1.0/introspect.dtd">;;
    <node>
      <interface name="com.scorpio.test.value">
        <method name="maxValue">
          <arg type="i" direction="out"/>
        </method>
        <method name="minValue">
          <arg type="i" direction="out"/>
        </method>
        <method name="value">
          <arg type="i" direction="out"/>
        </method>
      </interface>
    </node>
    
  2. 使用工具qdbusxml2cpp从XML文件生成继承自QDBusInterface的类

    指令: qdbusxml2cpp com.scorpio.test.xml -p valueInterface

    生成两个文件:valueInterface.cpp 和 valueInterface.h & valueInterface.h文件:

    /*
     * This file was generated by qdbusxml2cpp version 0.7
     * Command line was: qdbusxml2cpp com.scorpio.test.xml -p testInterface
     *
     * qdbusxml2cpp is Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies).
     *
     * This is an auto-generated file.
     * Do not edit! All changes made to it will be lost.
     */
    
    #ifndef TESTINTERFACE_H_1526737677
    #define TESTINTERFACE_H_1526737677
    
    #include <QtCore/QObject>
    #include <QtCore/QByteArray>
    #include <QtCore/QList>
    #include <QtCore/QMap>
    #include <QtCore/QString>
    #include <QtCore/QStringList>
    #include <QtCore/QVariant>
    #include <QtDBus/QtDBus>
    
    /*
     * Proxy class for interface com.scorpio.test.value
     */
    class ComScorpioTestValueInterface: public QDBusAbstractInterface
    {
        Q_OBJECT
    public:
        static inline const char *staticInterfaceName()
        { return "com.scorpio.test.value"; }
    
    public:
        ComScorpioTestValueInterface(const QString &service, const QString &path, const QDBusConnection &connection, QObject *parent = 0);
    
        ~ComScorpioTestValueInterface();
    
    public Q_SLOTS: // METHODS
        inline QDBusPendingReply<int> maxValue()
        {
            QList<QVariant> argumentList;
            return asyncCallWithArgumentList(QLatin1String("maxValue"), argumentList);
        }
    
        inline QDBusPendingReply<int> minValue()
        {
            QList<QVariant> argumentList;
            return asyncCallWithArgumentList(QLatin1String("minValue"), argumentList);
        }
    
        inline QDBusPendingReply<int> value()
        {
            QList<QVariant> argumentList;
            return asyncCallWithArgumentList(QLatin1String("value"), argumentList);
        }
    
    Q_SIGNALS: // SIGNALS
    };
    
    namespace com {
      namespace scorpio {
        namespace test {
          typedef ::ComScorpioTestValueInterface value;
        }
      }
    }
    #endif
    
    /*
     * This file was generated by qdbusxml2cpp version 0.7
     * Command line was: qdbusxml2cpp com.scorpio.test.xml -p testInterface
     *
     * qdbusxml2cpp is Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies).
     *
     * This is an auto-generated file.
     * This file may have been hand-edited. Look for HAND-EDIT comments
     * before re-generating it.
     */
    
    #include "testInterface.h"
    
    /*
     * Implementation of interface class ComScorpioTestValueInterface
     */
    
    ComScorpioTestValueInterface::ComScorpioTestValueInterface(const QString &service, const QString &path, const QDBusConnection &connection, QObject *parent)
        : QDBusAbstractInterface(service, path, staticInterfaceName(), connection, parent)
    {
    }
    
    ComScorpioTestValueInterface::~ComScorpioTestValueInterface()
    {
    }
    

    调用Proxy类访问Service如下:

    #include <QCoreApplication>
    #include <QDBusMessage>
    #include <QDBusConnection>
    #include <QDBusReply>
    #include <QDBusInterface>
    #include <QDebug>
    #include "testInterface.h"
    
    int main(int argc, char *argv[])
    {
        QCoreApplication a(argc, argv);
        // 初始化自动生成的Proxy类com::scorpio::test::value
        com::scorpio::test::value test("com.scorpio.test",
                                       "/test/objects",
                                       QDBusConnection::sessionBus());
        // 调用value方法
        QDBusPendingReply<int> reply = test.value();
        //qdbusxml2cpp生成的Proxy类是采用异步的方式来传递Message,
        //所以需要调用waitForFinished来等到Message执行完成
        reply.waitForFinished();
        if (reply.isValid())
        {
            int value = reply.value();
            qDebug() << QString("value =  %1").arg(value);
        }
        else
        {
            qDebug() << "value method called failed!";
        }
    
        return a.exec();
    }
    

2. Subscriber :: Signal Catching

2.1. 方法1:BusConnection捕获信号

QDBusConnection::sessionBus().connect("com.brion.service", "/",
                                      "com.brion.interface",
                                      "ageChanged", this,
                                      SLOT(onAgeChanged(int)));

2.2. 方法2:通过Proxy/Interface捕获信号

QDBusInterface *interface = new QDBusInterface("com.brion.service", "/",
                                               "com.brion.interface",
                                               DBusConnection::sessionBus());

QObject::connect(&interface, SIGNAL(ageChanged(int)),
                 object, SLOT(onAgeChanged(int)));

3. Server/Publisher :: Register Service

3.1. 方式1:编写服务类,并注册服务和接口对象

class Person : public QObject
{
    Q_OBJECT

    Q_CLASSINFO("D-Bus Interface", "com.brion.interface")

public:
    explicit Person(QObject *parent = 0);

signals:
    void nameChanged(QString);
    void ageChanged(int);

public slots:
    QString name() const { return m_name; }
    // can't be reference
    void setName(QString name) {
        m_name = name;
    }

    int age() const { return m_age; }
    void setAge(int age) {
        m_age = age;
    }

private:
    QString m_name;
    int m_age;
};

// main.cpp
#include <QtDBus/QDBusConnection>
#include <person.h>
int main(int argc, char *argv[])
{
    QApplication a(argc, argv);

    QDBusConnection sessionBus = QDBusConnection::sessionBus();
    if (sessionBus.registerService("com.brion.service")) {
        sessionBus.registerObject("/", new Person(),
                                  QDBusConnection::ExportAllContents);
    }
    return a.exec();
}

示例2:PingPong Game

Publisher,直接继承自 QDBusAbstractAdaptor:

class Pong: public QDBusAbstractAdaptor
{
    Q_OBJECT
    Q_CLASSINFO("D-Bus Interface", "org.example.QtDBus.ComplexPong.Pong")
    Q_PROPERTY(QString value READ value WRITE setValue)
public:
    QString m_value;
    QString value() const;
    void setValue(const QString &newValue);

    Pong(QObject *obj) : QDBusAbstractAdaptor(obj)
    { }
signals:
    void aboutToQuit();
public slots:
    QDBusVariant query(const QString &query);
    Q_NOREPLY void quit();
};

//  启动服务
int main(int argc, char **argv)
{
    QCoreApplication app(argc, argv);

    QObject obj;
    Pong *pong = new Pong(&obj);
    QObject::connect(&app, &QCoreApplication::aboutToQuit, pong, &Pong::aboutToQuit);
    pong->setProperty("value", "initial value");
    QDBusConnection::sessionBus().registerObject("/", &obj);

    if (!QDBusConnection::sessionBus().registerService(SERVICE_NAME)) {
        fprintf(stderr, "%s
",
                qPrintable(QDBusConnection::sessionBus().lastError().message()));
        exit(1);
    }

    app.exec();
    return 0;
}

Subscriber端,监听Publisher的信号:

class Ping: public QObject
{
    Q_OBJECT
public slots:
    void start(const QString &);
public:
    QFile qstdin;
    QDBusInterface *iface;
};

void Ping::start(const QString &name)
{
    if (name != SERVICE_NAME)
        return;

    // open stdin for reading
    qstdin.open(stdin, QIODevice::ReadOnly);

    // find our remote
    iface = new QDBusInterface(SERVICE_NAME, "/", "org.example.QtDBus.ComplexPong.Pong",
                               QDBusConnection::sessionBus(), this);
    if (!iface->isValid()) {
        fprintf(stderr, "%s
",
                qPrintable(QDBusConnection::sessionBus().lastError().message()));
        QCoreApplication::instance()->quit();
    }

    connect(iface, SIGNAL(aboutToQuit()), QCoreApplication::instance(), SLOT(quit()));

    while (true) {
        printf("Ask your question: ");

        QString line = QString::fromLocal8Bit(qstdin.readLine()).trimmed();
        if (line.isEmpty()) {
            iface->call("quit");
            return;
        } else if (line == "value") {
            QVariant reply = iface->property("value");
            if (!reply.isNull())
                printf("value = %s
", qPrintable(reply.toString()));
        } else if (line.startsWith("value=")) {
            iface->setProperty("value", line.mid(6));
        } else {
            QDBusReply<QDBusVariant> reply = iface->call("query", line);
            if (reply.isValid())
                printf("Reply was: %s
", qPrintable(reply.value().variant().toString()));
        }

        if (iface->lastError().isValid())
            fprintf(stderr, "Call failed: %s
", qPrintable(iface->lastError().message()));
    }
}


int main(int argc, char **argv)
{
    QCoreApplication app(argc, argv);

    if (!QDBusConnection::sessionBus().isConnected()) {
        fprintf(stderr, "Cannot connect to the D-Bus session bus.
"
                "To start it, run:
"
                "	eval `dbus-launch --auto-syntax`
");
        return 1;
    }

    QDBusServiceWatcher serviceWatcher(SERVICE_NAME, QDBusConnection::sessionBus(),
                                       QDBusServiceWatcher::WatchForRegistration);

    Ping ping;
    QObject::connect(&serviceWatcher, &QDBusServiceWatcher::serviceRegistered,
                     &ping, &Ping::start);

    QProcess pong;
    pong.start("./complexpong");

    app.exec();
}

3.2. 方式2:通过 XML 定义并转换为Adapter对象发布服务

方法4: DBusAdapter 生成Adapter类的流程如下:

  1. 使用工具 qdbuscpp2xml从test.h生成XML文件

    qdbuscpp2xml -M test.h -o com.scorpio.test.xml

  2. 编辑com.scorpio.test.xml,选择需要发布的method,不需要发布的删除。

  3. 使用工具qdbusxml2cpp从XML文件生成继承自QDBusInterface的类

    qdbusxml2cpp com.scorpio.test.xml -i test.h -a valueAdaptor

    生成两个文件:valueAdaptor.cpp和valueAdaptor.h & valueAdaptor.h文件:

    /*
     + This file was generated by qdbusxml2cpp version 0.7
     + Command line was: qdbusxml2cpp com.scorpio.test.xml -i test.h -a valueAdaptor
     *
     + qdbusxml2cpp is Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies).
     *
     + This is an auto-generated file.
     + This file may have been hand-edited. Look for HAND-EDIT comments
     + before re-generating it.
     */
    
    #ifndef VALUEADAPTOR_H_1526742670
    #define VALUEADAPTOR_H_1526742670
    
    #include <QtCore/QObject>
    #include <QtDBus/QtDBus>
    #include "test.h"
    class QByteArray;
    template<class T> class QList;
    template<class Key, class Value> class QMap;
    class QString;
    class QStringList;
    class QVariant;
    
    /*
     + Adaptor class for interface com.scorpio.test.value
     */
    class ValueAdaptor: public QDBusAbstractAdaptor
    {
        Q_OBJECT
        Q_CLASSINFO("D-Bus Interface", "com.scorpio.test.value")
        Q_CLASSINFO("D-Bus Introspection", ""
    "  <interface name="com.scorpio.test.value">
    "
    "    <method name="maxValue">
    "
    "      <arg direction="out" type="i"/>
    "
    "    </method>
    "
    "    <method name="minValue">
    "
    "      <arg direction="out" type="i"/>
    "
    "    </method>
    "
    "  </interface>
    "
            "")
    public:
        ValueAdaptor(QObject *parent);
        virtual ~ValueAdaptor();
    
    public: // PROPERTIES
    public Q_SLOTS: // METHODS
        int maxValue();
        int minValue();
    Q_SIGNALS: // SIGNALS
    };
    
    #endif
    
    /*
     + This file was generated by qdbusxml2cpp version 0.7
     + Command line was: qdbusxml2cpp com.scorpio.test.xml -i test.h -a valueAdaptor
     *
     + qdbusxml2cpp is Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies).
     *
     + This is an auto-generated file.
     + Do not edit! All changes made to it will be lost.
     */
    
    #include "valueAdaptor.h"
    #include <QtCore/QMetaObject>
    #include <QtCore/QByteArray>
    #include <QtCore/QList>
    #include <QtCore/QMap>
    #include <QtCore/QString>
    #include <QtCore/QStringList>
    #include <QtCore/QVariant>
    
    /*
     + Implementation of adaptor class ValueAdaptor
     */
    
    ValueAdaptor::ValueAdaptor(QObject *parent)
        : QDBusAbstractAdaptor(parent)
    {
        // constructor
        setAutoRelaySignals(true);
    }
    
    ValueAdaptor::~ValueAdaptor()
    {
        // destructor
    }
    
    int ValueAdaptor::maxValue()
    {
        // handle method call com.scorpio.test.value.maxValue
        int out0;
        QMetaObject::invokeMethod(parent(), "maxValue", Q_RETURN_ARG(int, out0));
        return out0;
    }
    
    int ValueAdaptor::minValue()
    {
        // handle method call com.scorpio.test.value.minValue
        int out0;
        QMetaObject::invokeMethod(parent(), "minValue", Q_RETURN_ARG(int, out0));
        return out0;
    }
    

    调用Adaptor类注册Object对象如下:

    #include <QCoreApplication>
    #include <QDBusConnection>
    #include <QDebug>
    #include <QDBusError>
    #include "test.h"
    #include "valueAdaptor.h"
    
    int main(int argc, char *argv[]){
        QCoreApplication a(argc, argv);
        QDBusConnection connection = QDBusConnection::sessionBus();
    
        test object(60);
        //ValueAdaptor是qdbusxml2cpp生成的Adaptor类
        ValueAdaptor valueAdaptor(&object);
        if (!connection.registerService("com.scorpio.test"))
        {
            qDebug() << connection.lastError().message();
            exit(1);
        }
        connection.registerObject("/test/objects", &object);
        return a.exec();
    }
    

3.3. 其他:自启动DBus服务项

D-Bus系统提供了一种机制可以在访问某个service时,自动把应用程序运行起来。

需要在/usr/share/dbus-1/services下面建立com.scorpio.test.service文件,文件的内容如下:

**[D-****BUS Service]**
Name=com.scorpio.test
Exec=/path/to/scorpio/test

在访问test的method前,不必手动运行应用程序。

4. QtDBus 在 PyQt5 中的应用

常用类即其方法,请参考脑图

4.1. 定义服务类

这里提供一个实例代码(PingPong在PyQt5中的实现):

import sys

from PyQt5.QtCore import QCoreApplication, pyqtSlot, QObject
from PyQt5.QtDBus import QDBusConnection, QDBusInterface, QDBusReply, QDBusAbstractAdaptor

class Pong(QObject):
    @pyqtSlot(str, result=str)
    def pong_method(self, args):
        print("Get a proxy-method call... the args is: ", args)
        return 'ping("%s") got called.' % args

    @pyqtSlot()
    def pong_method_without_params(self):
        print("Get a proxy-method call...")


if __name__ == "__main__":
    app = QCoreApplication(sys.argv)
    bus = QDBusConnection.sessionBus()  # sessionBus()
    if not bus.isConnected():
        raise Exception("Fail to connect to Session-Bus.")
    # register the service
    if not bus.registerService('org.example.QtDBus.PingPong'):  # 注册 BusName
        raise Exception("Fail to register the service.")

    pong = Pong()  # 创建对象 -> dbus server object
    print("-->> ", isinstance(pong, QDBusAbstractAdaptor))
    bus.registerObject("/",  # this is the object-path, must be start from '/'
                        "pong.anything.namespace",  # this is the interface [choose to name it]
                        pong,  # this is the server obj, but you must prepare it before
                        # QDBusConnection.ExportAllSlots)  # 注册所有slot作为proxy-method
                        QDBusConnection.ExportAllContents)
    sys.exit(app.exec_())

4.2. 结合XML定义Adapter类

class Car(QObject):
    def turn_left(self, degree: int):
        print("The car turn left [{}] degree.".format(degree))

    def turn_right(self, degree: int):
        print("The car turn right [{}] degree.".format(degree))

    def turn_back(self):
        print("The car is turnning back.")


class CarInterface(QDBusAbstractAdaptor):
    Q_CLASSINFO("D-Bus Interface", 'org.HallGarden.Examples.Interface')
    Q_CLASSINFO("D-Bus Introspection", ''
            '  <interface name="org.HallGarden.Examples.Interface">
'
            '    <method name="turn_left">
'
            '      <arg direction="in" type="i"/>
'
            '    </method>
'
            '    <method name="turn_right">
'
            '      <arg name="degree" direction="in" type="i"/>
'
            '      <arg name="any" direction="out" type="i"/>
'
            '    </method>
'
            '    <method name="turn_back"/>
'
            '  </interface>
'
            '')

    def __init__(self, parent):
        super().__init__(parent)
        self.setAutoRelaySignals(True)

    @pyqtSlot(int)
    def turn_left(self, degree):
        self.parent().turn_left(degree)

    @pyqtSlot(int)
    def turn_right(self, degree):
        self.parent().turn_right(degree)
        return 30

    @pyqtSlot()
    def turn_back(self):
        self.parent().turn_back()


if __name__ == "__main__":
    app = QCoreApplication(sys.argv)

    car = Car()
    CarInterface(car)  # 装饰car对象,而新生成的对象并没实际应用

    connection = QDBusConnection.sessionBus()
    connection.registerService('org.example.CarExample')
    connection.registerObject('/Car', car)

    sys.exit(app.exec_())

5. 思考

  1. 在 freedesktop 体系中,将interface至于Object下层。于是对于binding的设计,一般的(比如dbus-python)proxy可以获得object的代理,而interface依旧是对象的namespace,故而在proxy下层。但Qt的命名似乎不太一样——它所谓的 Interface 对象与代理很相似,而Qt概念中的代理,一般是通过 XML 转换生成的。

  2. 一般的binding做到Proxy就可以了,而且一般Proxy确实够用了;而Qt又设计了Adapter类,用于将DBus信号绑定QObject,其意义何在?

  3. 关于dbus消息传递时的载体是二进制流——那么它解码后的内容是中间格式的XML吗?亦或是给底层 lib 库的C语言数据结构?

  4. 关于XML是为了实现跨平台、跨语言。但问题是,流程不应该是将代码的结构,例如根据 QDBusConnection.ExportAllContents 的性质,将 @pyqtSlot 槽函数抽象为中间代码,也就是XML,再将XML发送给daemon读取形成总线服务或信号。但实际上这个过程却颠倒过来了——如果你使用QDBusAdapter,你需要将xml文本写入,或者通过 qdbusxml2cpp 再生成C++代码。除了逻辑上的颠倒,更麻烦的是,没有任何工具提供了静态编译验证XML语法或语义的 debug 调试工具。一旦你不小心在中少写了一个“/”结束符,程序并不报错,只是服务项一直不正常~ 呃!

免责声明:文章转载自《QtDbus的API及示例》仅用于学习参考。如对内容有疑问,请及时联系本站处理。

上篇用vim看代码的常用指令Emacs 安装配置使用教程下篇

宿迁高防,2C2G15M,22元/月;香港BGP,2C5G5M,25元/月 雨云优惠码:MjYwNzM=

相关文章

见到的一篇IOCP流程 自己用demo实现了一下, 简单照抄,改动了一点点

要分析的实例分为两个线程: 分别是主线程(MAIN),还有一个是创建的线程(ServerThread) 1.主函数完成初始化工作:   1.1: (主线程)HANDLE hCompletion = ::CreateIoCompletionPort(INVALID_HANDLE_VALUE, 0, 0, 0);    创建完成端口对象   1.2: (主线程...

jQuery日期弹出选择框Datepicker效果

无论你是一个机票在线预定网站设计师,还是一个工程任务管理者,抑或在你的注册表单上有个生日填写项目;本文即将提到的日历日期选择弹出窗口都将帮助你简化用户操作,提高网站的用户体验和易用性。 教程目标:教会大家如何jQuery的UI插件Datepicker通过短短几行JavaScript代码制作一个日期选择弹出窗口,当用户在弹出的日期选择框中选择一个日期后,该日...

Vue2.0进阶组件 短信倒计时组件

原本我想隔个几天再发文章,刚好今天项目上线,环境有问题,导致只有干等,刚好要为公司打造一套属于公司自己的一系列功能组件,这个使命就交给我了,大家也一直叫我来点干货,说实话我只是一个湿货,肚子里干一点就给你们出点货,那从今天开始不看岛国片系列教程视频,不但自撸,还教你撸............你懂的!!最强vue组件 写之前我只想说如果看到错别字,就别给我点...

C#:总结页面传值几种方法

 小知识点: 1.  W7自带 .NetFrameWork 3.5, 兼容模式为 高版本号兼容低版本号; 2. WF和WPF都是基于XAML的,可是两者的用途不同。 WF是一种开发框架,将工作流嵌入在.NET Framework应用程序中,所主要用于开发创建工作流应用程序。WF:http://msdn.microsoft.com/zh-cn/librar...

WebAPI客户端

封装WebAPI客户端,附赠Nuget打包上传VS拓展工具 一、前言 上篇《 WebAPI使用多个xml文件生成帮助文档 》有提到为什么会出现基于多个xml文件生成帮助文档的解决方案,因为定义的模型可能的用处有: 1:单元测试 2:其他项目引用(可能以Nuget包的形式) 3:WebAPI客户端(封装的HttpClient及WebAPI接口调用,其实包含...

处理精度丢之-如何解决

通过上篇我们了解到计算机是如何存储浮点数,那精度丢失是在哪产生的? 拿0.1 + 0.2举例: 0.1 转二进制后:0.0 0011 0011 0011 0011 0011 0011 0011 0011 0011 0011 0011 0011 0011 01(转化后是以0011无限循环,二进制为满一进一,所以末尾为01) 0.2: 转二进制后:0.0 01...