Qt位置服务深度解析:从GPS定位到地理围栏的完整架构设计

副标题:揭秘Qt Location模块的底层实现原理与跨平台定位策略


一、引言:为什么Qt开发者需要掌握位置服务

在移动互联网时代,位置感知能力已成为众多应用的核心功能。从外卖配送的实时轨迹追踪,到车载导航的精准路径规划,再到社交软件的"附近的人"功能,位置服务无处不在。作为Qt开发者,深入理解Qt Location模块的架构设计与底层实现,不仅能提升跨平台开发能力,更能为构建位置感知应用打下坚实基础。

Qt位置服务(Qt Location)模块自Qt 5引入,提供了一套统一的跨平台API,屏蔽了Android、iOS、Windows、Linux等不同平台的定位差异。本文将从源码层面深入剖析其架构设计、核心类层次、关键源码实现,并提供实战代码示例与性能优化策略。


二、Qt Location架构全景图

2.1 模块整体架构

Qt Location采用典型的插件架构,通过抽象接口层屏蔽平台差异,底层依赖各平台原生定位服务:

复制代码
┌─────────────────────────────────────────────────────────────┐
│                    Application Layer                          │
│         (QGeoPositionInfoSource, QGeoServiceProvider)          │
├─────────────────────────────────────────────────────────────┤
│                    Qt Location Core                           │
│    ┌──────────────┐  ┌──────────────┐  ┌──────────────┐     │
│    │ QGeoPosition │  │ QGeoSatellite│  │ QGeoAreaMonitor│     │
│    │ InfoSource    │  │ InfoSource   │  │ Source         │     │
│    └──────────────┘  └──────────────┘  └──────────────┘     │
├─────────────────────────────────────────────────────────────┤
│                   Plugin Interface Layer                      │
│         (QGeoPositionInfoSourceFactory, QGeoServiceProvider)  │
├─────────────────────────────────────────────────────────────┤
│              Platform-Specific Plugins                        │
│  ┌─────────┐ ┌─────────┐ ┌─────────┐ ┌─────────────────┐   │
│  │ Android │ │   iOS   │ │ Windows │ │ Linux/GeoClue2 │   │
│  │ Plugin  │ │ Plugin  │ │ Plugin  │ │    Plugin       │   │
│  └─────────┘ └─────────┘ └─────────┘ └─────────────────┘   │
├─────────────────────────────────────────────────────────────┤
│              Native Platform Services                         │
│   Android Location API   iOS CoreLocation   WinRT Geolocation│
└─────────────────────────────────────────────────────────────┘

2.2 核心类层次结构

Qt Location的核心类继承关系如下(源码路径:qtlocation/src/location/):

cpp 复制代码
// QGeoPositionInfoSource - 位置信息源基类
class Q_LOCATION_EXPORT QGeoPositionInfoSource : public QObject
{
    Q_OBJECT
public:
    enum PositioningMethod {
        NoPositioningMethods = 0x00000000,
        SatellitePositioningMethods = 0x00000001,
        NonSatellitePositioningMethods = 0x00000002,
        AllPositioningMethods = 0xFFFFFFFF
    };
    Q_DECLARE_FLAGS(PositioningMethods, PositioningMethod)
    
    // 核心方法声明...
};

// QGeoSatelliteInfoSource - 卫星信息源基类
class Q_LOCATION_EXPORT QGeoSatelliteInfoSource : public QObject
{
    Q_OBJECT
public:
    // 卫星状态枚举
    enum SatelliteInfoStatus {
        NoError = 0,
        AccessError,
        ClosedError,
        UnknownSourceError
    };
};

// QGeoAreaMonitorSource - 地理围栏监控基类
class Q_LOCATION_EXPORT QGeoAreaMonitorSource : public QObject
{
    Q_OBJECT
public:
    // 区域监控状态
    enum AreaMonitorError {
        NoError,
        AccessError,
        InsufficientAccuracy,
        UnknownSourceError
    };
};

三、源码级核心原理分析

3.1 位置信息源工厂模式实现

Qt Location采用工厂模式创建平台特定的位置源,源码位于qtlocation/src/plugins/position/

cpp 复制代码
// 源码路径: qtlocation/src/plugins/position/android/src/qgeopositioninfosourcefactory_android.cpp
class QGeoPositionInfoSourceFactoryAndroid : public QObject,
                                              public QGeoPositionInfoSourceFactory
{
    Q_OBJECT
    Q_PLUGIN_METADATA(IID "org.qt-project.qt.position.sourcefactory/6.0"
                       FILE "plugin.json")
    Q_INTERFACES(QGeoPositionInfoSourceFactory)
public:
    QGeoPositionInfoSource *create(QGeoPositionInfoSource::PositioningMethods methods) override
    {
        Q_UNUSED(methods);
        return new QGeoPositionInfoSourceAndroid();
    }
    
    QGeoPositionInfoSource::PositioningMethods supportedPositioningMethods() const override
    {
        return QGeoPositionInfoSource::AllPositioningMethods;
    }
};

设计亮点分析

  • 使用Qt插件元数据系统,实现运行时动态加载
  • 通过Q_INTERFACES宏确保接口正确实现
  • 支持多种定位方法的灵活配置

3.2 Android平台定位核心实现

Android插件的核心定位逻辑源码解析:

cpp 复制代码
// 源码路径: qtlocation/src/plugins/position/android/src/qgeopositioninfosource_android.cpp
void QGeoPositionInfoSourceAndroid::startUpdates()
{
    if (!m_javaProvider.isValid()) {
        // 初始化Java层定位服务
        QAndroidJniObject context = QtAndroid::androidActivity();
        m_javaProvider = QAndroidJniObject(
            "org/qtproject/qt/android/positioning/QtPositioning",
            "(Landroid/content/Context;)V",
            context.object<jobject>()
        );
    }
    
    // 调用Android LocationManager请求位置更新
    QAndroidJniObject::callMethod<void>(
        m_javaProvider.object<jobject>(),
        "requestLocationUpdates",
        "(JF)V",
        static_cast<jlong>(m_updateInterval),
        static_cast<jfloat>(m_minimumDistance)
    );
    
    m_running = true;
}

关键技术点

  1. 使用JNI桥接层连接Qt与Android原生API
  2. 支持updateIntervalminimumDistance双重过滤,减少电量消耗
  3. 通过Qt的Android Extras模块简化JNI调用

3.3 位置信息数据结构解析

cpp 复制代码
// 源码路径: qtlocation/src/location/qgeopositioninfo.h
class Q_LOCATION_EXPORT QGeoPositionInfo
{
public:
    QGeoPositionInfo();
    QGeoPositionInfo(const QGeoCoordinate &coordinate, const QDateTime &timestamp);
    
    // 核心属性
    QGeoCoordinate coordinate() const;      // 经纬度坐标
    QDateTime timestamp() const;            // UTC时间戳
    qreal attribute(Attribute attribute) const;
    
    // 属性枚举 - 包含丰富的定位元数据
    enum Attribute {
        Direction,                    // 航向角(度)
        GroundSpeed,                  // 地面速度(米/秒)
        VerticalSpeed,                // 垂直速度(米/秒)
        MagneticVariation,            // 磁偏角(度)
        HorizontalAccuracy,          // 水平精度(米)
        VerticalAccuracy,            // 垂直精度(米)
        DirectionAccuracy,           // 航向精度(度)
        SpeedAccuracy                // 速度精度(米/秒)
    };
    
private:
    QSharedDataPointer<QGeoPositionInfoPrivate> d;
};

源码深挖:QGeoPositionInfo使用**隐式共享(Copy-on-Write)**技术:

cpp 复制代码
// 源码路径: qtlocation/src/location/qgeopositioninfo.cpp
QGeoPositionInfo::QGeoPositionInfo(const QGeoPositionInfo &other)
    : d(other.d)
{
    // 浅拷贝,引用计数+1,性能优化
}

QGeoPositionInfo &QGeoPositionInfo::operator=(const QGeoPositionInfo &other)
{
    d = other.d;  // QSharedDataPointer自动处理引用计数
    return *this;
}

四、地理围栏(Geofencing)原理与实现

4.1 QGeoAreaMonitor核心机制

地理围栏是位置服务的高级功能,用于监控设备进入/离开特定地理区域:

cpp 复制代码
// 源码路径: qtlocation/src/location/qgeoareamonitor.cpp
class QGeoAreaMonitorSourcePrivate
{
public:
    QGeoAreaMonitorSource *q_ptr;
    QGeoAreaMonitorSource::AreaMonitorError m_error;
    QList<QGeoAreaMonitorInfo> m_activeMonitors;
    
    // 信号槽连接管理
    void handleAreaEvent(const QGeoAreaMonitorInfo &monitor, 
                         QGeoAreaMonitorSource::AreaMonitorEvent event)
    {
        Q_Q(QGeoAreaMonitorSource);
        switch (event) {
        case QGeoAreaMonitorSource::AreaMonitorEvent::EnteredArea:
            emit q->areaEntered(monitor);
            break;
        case QGeoAreaMonitorSource::AreaMonitorEvent::ExitedArea:
            emit q->areaExited(monitor);
            break;
        }
    }
};

4.2 实战代码:创建动态地理围栏

cpp 复制代码
#include <QGeoAreaMonitorSource>
#include <QGeoCoordinate>
#include <QDebug>

class GeofenceManager : public QObject
{
    Q_OBJECT
public:
    explicit GeofenceManager(QObject *parent = nullptr) : QObject(parent)
    {
        // 创建地理围栏监控源
        m_monitorSource = QGeoAreaMonitorSource::createDefaultSource(this);
        if (!m_monitorSource) {
            qWarning() << "Failed to create area monitor source";
            return;
        }
        
        // 连接进入/离开区域信号
        connect(m_monitorSource, &QGeoAreaMonitorSource::areaEntered,
                this, &GeofenceManager::onAreaEntered);
        connect(m_monitorSource, &QGeoAreaMonitorSource::areaExited,
                this, &GeofenceManager::onAreaExited);
    }
    
    // 创建圆形地理围栏
    bool createCircularFence(const QString &fenceId, 
                            double latitude, double longitude, 
                            double radiusMeters)
    {
        QGeoAreaMonitorInfo monitor(fenceId);
        monitor.setCenter(QGeoCoordinate(latitude, longitude));
        monitor.setRadius(radiusMeters);
        monitor.setPersistent(true);  // 应用重启后保持有效
        
        // 设置过期时间(可选)
        monitor.setExpiration(QDateTime::currentDateTimeUtc().addDays(30));
        
        return m_monitorSource->startMonitoring(monitor);
    }
    
private slots:
    void onAreaEntered(const QGeoAreaMonitorInfo &monitor)
    {
        qDebug() << "Entered geofence:" << monitor.identifier();
        // 触发业务逻辑:推送通知、记录日志、启动服务等
        emit fenceTriggered(monitor.identifier(), "entered");
    }
    
    void onAreaExited(const QGeoAreaMonitorInfo &monitor)
    {
        qDebug() << "Exited geofence:" << monitor.identifier();
        emit fenceTriggered(monitor.identifier(), "exited");
    }
    
signals:
    void fenceTriggered(const QString &fenceId, const QString &event);
    
private:
    QGeoAreaMonitorSource *m_monitorSource = nullptr;
};

五、卫星信息获取与NMEA解析

5.1 QGeoSatelliteInfo源码解析

cpp 复制代码
// 源码路径: qtlocation/src/location/qgeosatelliteinfo.cpp
class QGeoSatelliteInfoPrivate : public QSharedData
{
public:
    int m_satelliteId = -1;           // 卫星PRN编号
    int m_signalStrength = -1;        // 信号强度(dB-Hz)
    bool m_inUse = false;             // 是否参与定位解算
    QList<int> m_attributes;          // 扩展属性
    
    // 卫星属性枚举
    enum Attribute {
        Elevation,       // 仰角(度)
        Azimuth,         // 方位角(度)
        PseudorangeRate // 伪距变化率
    };
};

5.2 实时卫星状态监控

cpp 复制代码
#include <QGeoSatelliteInfoSource>
#include <QGeoSatelliteInfo>

class SatelliteMonitor : public QObject
{
    Q_OBJECT
public:
    explicit SatelliteMonitor(QObject *parent = nullptr) : QObject(parent)
    {
        m_satSource = QGeoSatelliteInfoSource::createDefaultSource(this);
        if (m_satSource) {
            connect(m_satSource, &QGeoSatelliteInfoSource::satellitesInViewUpdated,
                    this, &SatelliteMonitor::onSatellitesInView);
            connect(m_satSource, &QGeoSatelliteInfoSource::satellitesInUseUpdated,
                    this, &SatelliteMonitor::onSatellitesInUse);
            
            // 设置更新间隔(毫秒)
            m_satSource->setUpdateInterval(1000);
            m_satSource->startUpdates();
        }
    }
    
private slots:
    void onSatellitesInView(const QList<QGeoSatelliteInfo> &satellites)
    {
        qDebug() << "Satellites in view:" << satellites.count();
        
        // 按卫星系统分类
        QMap<QString, int> systemCount;
        for (const auto &sat : satellites) {
            QString system = getSatelliteSystem(sat.satelliteIdentifier());
            systemCount[system]++;
        }
        
        // 输出分类统计
        for (auto it = systemCount.begin(); it != systemCount.end(); ++it) {
            qDebug() << "  " << it.key() << ":" << it.value();
        }
    }
    
    void onSatellitesInUse(const QList<QGeoSatelliteInfo> &satellites)
    {
        qDebug() << "Satellites used for fix:" << satellites.count();
        
        // 计算平均信号强度
        double avgStrength = 0;
        for (const auto &sat : satellites) {
            avgStrength += sat.signalStrength();
        }
        if (!satellites.isEmpty()) {
            avgStrength /= satellites.count();
            qDebug() << "Average signal strength:" << avgStrength << "dB-Hz";
        }
    }
    
private:
    QString getSatelliteSystem(int prn)
    {
        // GPS: 1-32, GLONASS: 65-96, Galileo: 1-36, BeiDou: 201-235
        if (prn >= 1 && prn <= 32) return "GPS";
        if (prn >= 65 && prn <= 96) return "GLONASS";
        if (prn >= 201 && prn <= 235) return "BeiDou";
        return "Other";
    }
    
    QGeoSatelliteInfoSource *m_satSource = nullptr;
};

六、性能优化策略

6.1 定位精度与电量平衡

cpp 复制代码
class LocationOptimizer : public QObject
{
    Q_OBJECT
public:
    enum PowerMode {
        HighAccuracy,    // GPS + Network,耗电高
        Balanced,        // 智能切换
        LowPower         // 仅Network定位
    };
    
    void setPowerMode(PowerMode mode)
    {
        QGeoPositionInfoSource *source = m_positionSource;
        if (!source) return;
        
        switch (mode) {
        case HighAccuracy:
            source->setPreferredPositioningMethods(
                QGeoPositionInfoSource::AllPositioningMethods);
            source->setUpdateInterval(1000);  // 1秒
            break;
        case Balanced:
            source->setPreferredPositioningMethods(
                QGeoPositionInfoSource::AllPositioningMethods);
            source->setUpdateInterval(5000);  // 5秒
            break;
        case LowPower:
            source->setPreferredPositioningMethods(
                QGeoPositionInfoSource::NonSatellitePositioningMethods);
            source->setUpdateInterval(30000);  // 30秒
            break;
        }
    }
    
    // 动态调整策略:根据移动速度调整更新频率
    void onPositionUpdated(const QGeoPositionInfo &info)
    {
        qreal speed = info.attribute(QGeoPositionInfo::GroundSpeed);
        
        if (speed > 10.0) {  // 速度 > 36km/h(乘车状态)
            m_positionSource->setUpdateInterval(2000);
        } else if (speed > 1.0) {  // 步行状态
            m_positionSource->setUpdateInterval(5000);
        } else {  // 静止状态
            m_positionSource->setUpdateInterval(30000);
        }
    }
};

6.2 批量位置缓存与上传

cpp 复制代码
class LocationBatchUploader : public QObject
{
    Q_OBJECT
public:
    LocationBatchUploader(int batchSize = 100, int maxAge = 300)
        : m_batchSize(batchSize), m_maxAge(maxAge)
    {
        m_timer.setInterval(60000);  // 1分钟检查一次
        connect(&m_timer, &QTimer::timeout, this, &LocationBatchUploader::tryUpload);
        m_timer.start();
    }
    
    void cachePosition(const QGeoPositionInfo &info)
    {
        CachedPosition pos;
        pos.coordinate = info.coordinate();
        pos.timestamp = info.timestamp();
        pos.accuracy = info.attribute(QGeoPositionInfo::HorizontalAccuracy);
        
        m_cache.append(pos);
        
        if (m_cache.size() >= m_batchSize) {
            tryUpload();
        }
    }
    
private slots:
    void tryUpload()
    {
        if (m_cache.isEmpty()) return;
        
        // 过滤过期数据
        QDateTime now = QDateTime::currentDateTimeUtc();
        auto it = std::remove_if(m_cache.begin(), m_cache.end(),
            [&](const CachedPosition &p) {
                return p.timestamp.secsTo(now) > m_maxAge;
            });
        m_cache.erase(it, m_cache.end());
        
        if (m_cache.isEmpty()) return;
        
        // 批量上传
        QJsonArray positions;
        for (const auto &pos : m_cache) {
            QJsonObject obj;
            obj["lat"] = pos.coordinate.latitude();
            obj["lng"] = pos.coordinate.longitude();
            obj["alt"] = pos.coordinate.altitude();
            obj["time"] = pos.timestamp.toString(Qt::ISODate);
            obj["acc"] = pos.accuracy;
            positions.append(obj);
        }
        
        emit uploadReady(positions);
        m_cache.clear();
    }
    
signals:
    void uploadReady(const QJsonArray &positions);
    
private:
    struct CachedPosition {
        QGeoCoordinate coordinate;
        QDateTime timestamp;
        qreal accuracy;
    };
    
    QList<CachedPosition> m_cache;
    int m_batchSize;
    int m_maxAge;  // 秒
    QTimer m_timer;
};

七、跨平台适配策略

7.1 平台能力检测

cpp 复制代码
#include <QGeoPositionInfoSource>

class PlatformLocationAdapter
{
public:
    static void printPlatformCapabilities()
    {
        QGeoPositionInfoSource *source = QGeoPositionInfoSource::createDefaultSource(nullptr);
        if (!source) {
            qWarning() << "No positioning source available";
            return;
        }
        
        qDebug() << "Available positioning methods:";
        auto methods = source->supportedPositioningMethods();
        if (methods & QGeoPositionInfoSource::SatellitePositioningMethods)
            qDebug() << "  - Satellite (GPS/GLONASS/Galileo)";
        if (methods & QGeoPositionInfoSource::NonSatellitePositioningMethods)
            qDebug() << "  - Network (WiFi/Cell)";
        
        qDebug() << "Source type:" << source->sourceName();
        qDebug() << "Minimum update interval:" << source->minimumUpdateInterval() << "ms";
    }
    
    // 检测GeoClue2可用性(Linux专用)
    static bool isGeoClue2Available()
    {
#ifdef Q_OS_LINUX
        QGeoPositionInfoSource *geoClue = 
            QGeoPositionInfoSource::createSource("geoclue2", nullptr);
        if (geoClue) {
            delete geoClue;
            return true;
        }
#endif
        return false;
    }
};

7.2 权限请求处理(移动平台)

cpp 复制代码
// Android权限处理
#ifdef Q_OS_ANDROID
#include <QtAndroid>
#endif

class LocationPermissionHandler : public QObject
{
    Q_OBJECT
public:
    static bool requestLocationPermission()
    {
#ifdef Q_OS_ANDROID
        // Android 10+ 需要后台位置权限
        QtAndroid::PermissionResult r = QtAndroid::checkPermission(
            "android.permission.ACCESS_FINE_LOCATION");
        
        if (r == QtAndroid::PermissionResult::Denied) {
            QtAndroid::requestPermissionsSync(
                QStringList() << "android.permission.ACCESS_FINE_LOCATION"
                              << "android.permission.ACCESS_COARSE_LOCATION"
                              << "android.permission.ACCESS_BACKGROUND_LOCATION"
            );
            
            r = QtAndroid::checkPermission("android.permission.ACCESS_FINE_LOCATION");
            return (r == QtAndroid::PermissionResult::Granted);
        }
#endif
        return true;
    }
};

八、典型应用场景实战

8.1 实时轨迹追踪系统

cpp 复制代码
class TrajectoryTracker : public QObject
{
    Q_OBJECT
public:
    explicit TrajectoryTracker(QObject *parent = nullptr) : QObject(parent)
    {
        m_positionSource = QGeoPositionInfoSource::createDefaultSource(this);
        if (m_positionSource) {
            m_positionSource->setUpdateInterval(1000);
            connect(m_positionSource, &QGeoPositionInfoSource::positionUpdated,
                    this, &TrajectoryTracker::onPositionUpdated);
        }
    }
    
    void startTracking(const QString &sessionId)
    {
        m_sessionId = sessionId;
        m_trackPoints.clear();
        m_startTime = QDateTime::currentDateTimeUtc();
        m_positionSource->startUpdates();
    }
    
    void stopTracking()
    {
        m_positionSource->stopUpdates();
        saveTrajectory();
    }
    
private slots:
    void onPositionUpdated(const QGeoPositionInfo &info)
    {
        TrackPoint point;
        point.coordinate = info.coordinate();
        point.timestamp = info.timestamp();
        point.speed = info.attribute(QGeoPositionInfo::GroundSpeed);
        point.heading = info.attribute(QGeoPositionInfo::Direction);
        point.accuracy = info.attribute(QGeoPositionInfo::HorizontalAccuracy);
        
        // 轨迹简化:距离太近的点过滤掉
        if (!m_trackPoints.isEmpty()) {
            QGeoCoordinate last = m_trackPoints.last().coordinate;
            qreal distance = last.distanceTo(point.coordinate);
            if (distance < 5.0) {  // 5米阈值
                return;
            }
        }
        
        m_trackPoints.append(point);
        emit trackPointAdded(point);
    }
    
private:
    void saveTrajectory()
    {
        QString filename = QString("trajectory_%1_%2.json")
            .arg(m_sessionId)
            .arg(m_startTime.toString("yyyyMMdd_HHmmss"));
        
        QJsonArray points;
        for (const auto &pt : m_trackPoints) {
            QJsonObject obj;
            obj["lat"] = pt.coordinate.latitude();
            obj["lng"] = pt.coordinate.longitude();
            obj["alt"] = pt.coordinate.altitude();
            obj["time"] = pt.timestamp.toString(Qt::ISODate);
            obj["speed"] = pt.speed;
            obj["heading"] = pt.heading;
            points.append(obj);
        }
        
        QJsonDocument doc(points);
        QFile file(filename);
        if (file.open(QIODevice::WriteOnly)) {
            file.write(doc.toJson());
            file.close();
        }
    }
    
signals:
    void trackPointAdded(const TrackPoint &point);
    
private:
    struct TrackPoint {
        QGeoCoordinate coordinate;
        QDateTime timestamp;
        qreal speed;
        qreal heading;
        qreal accuracy;
    };
    
    QGeoPositionInfoSource *m_positionSource = nullptr;
    QList<TrackPoint> m_trackPoints;
    QString m_sessionId;
    QDateTime m_startTime;
};

九、总结与展望

Qt位置服务模块通过插件架构实现了跨平台定位能力的统一封装,其核心优势在于:

  1. 抽象层设计精良:通过工厂模式、策略模式等GO设计模式,实现了平台差异的透明化
  2. 性能优化到位:隐式共享、批量缓存、动态调整等机制确保低功耗运行
  3. 功能覆盖全面:从基础定位到地理围栏,从卫星信息到轨迹追踪,满足各类需求

未来,随着北斗三号全球组网完成、Galileo系统成熟,多GNSS融合定位将成为趋势。Qt Location模块也在持续演进,为开发者提供更精准、更高效的位置服务能力。


《注:若有发现问题欢迎大家提出来纠正》

相关推荐
Lucky_ldy1 小时前
C语言学习:数据在内存中的存储
c语言·开发语言·学习
钱多多_qdd1 小时前
基于mac环境,升级python环境问题解决
开发语言·python·macos
boonya1 小时前
Python 量化金融框架及技术落地方案
开发语言·python·金融
Ulyanov1 小时前
《从质点到位姿:基于Python与PyVista的导弹制导控制全栈仿真》: 基石——3-DOF质点弹道的高保真建模与数值稳定性分析
开发语言·python·算法·ui·系统仿真
学习中.........1 小时前
Java 并发容器深度解析:从早期遗留类到现代高并发架构
java·开发语言·架构
加号31 小时前
【C#】 实现程序最小化后重新拉起并强制置顶显示的技术指南
开发语言·c#
wangl_921 小时前
C# / .NET 在工业环境中的优势
开发语言·c#·.net·.netcore·.net core·visual studio
史迪仔01121 小时前
[QML] Qt5/6图像色彩空间处理
开发语言·前端·c++·qt
北冥湖畔的燕雀1 小时前
C++日志系统:从原理到实战实现
java·开发语言