Qt 框架深度解析与性能优化

Qt 框架深度解析与性能优化实践文档

1. 概述

1.1 文档目的

本文档系统总结 Qt 框架核心机制的深度理解与实践经验,涵盖:

  • Qt 核心机制原理(信号槽、事件循环、对象模型)
  • Qt 内存管理机制与内存泄漏预防
  • 代码质量与可维护性实践
  • 大数据文件加载性能优化方案
  • 框架级问题解决典型案例

2. Qt 核心机制深度解析

2.1 信号槽机制

2.1.1 原理深度理解

核心架构

复制代码
信号发送 → QMetaObject::activate() → 连接查找 → 槽函数调用

关键组件

组件 作用 源码位置
QMetaObject 存储 meta-data(信号、槽、属性) qmetaobject.cpp
QObjectPrivate::ConnectionList 存储连接关系 qobject.cpp
QMetaCallEvent 跨线程信号事件 qobject.cpp

连接类型对比

类型 触发方式 适用场景 性能
DirectConnection 直接调用(同步) 同线程、实时响应 最高
QueuedConnection 事件队列(异步) 跨线程、避免阻塞 中等
AutoConnection 自动选择 默认推荐 动态
BlockingQueuedConnection 阻塞等待 跨线程同步返回 最慢
2.1.2 深入源码分析

信号发射流程

cpp 复制代码
// moc 生成的信号函数
void MyObject::valueChanged(int newValue)
{
    // 调用 QMetaObject::activate
    QMetaObject::activate(this, &staticMetaObject, 
                          0, // 信号索引
                          &newValue);
}

// activate 内部逻辑
void QMetaObject::activate(QObject *sender, ...)
{
    // 1. 查找连接列表
    QObjectPrivate::ConnectionList *list = 
        sender->d_func()->connectionLists;
    
    // 2. 遍历连接
    for (Connection *c : list) {
        if (c->connectionType == Qt::QueuedConnection) {
            // 跨线程:创建 QMetaCallEvent,投递到事件队列
            QMetaCallEvent *event = new QMetaCallEvent(...);
            QCoreApplication::postEvent(receiver, event);
        } else {
            // 同线程:直接调用槽函数
            c->call(sender, argv);
        }
    }
}

关键发现

  • 信号槽连接存储在 QObjectPrivate::ConnectionList
  • 连接查找基于信号索引,O(n) 复杂度
  • 跨线程信号通过 QMetaCallEvent 实现,依赖事件循环
  • 连接断开时需从列表移除,避免悬空指针
2.1.3 框架级问题案例

案例 1:跨线程信号槽连接失效

项目 内容
现象 子线程发射信号,主线程槽函数不触发
根因 子线程未启动事件循环,QMetaCallEvent 无法投递和分发
分析过程 1. 检查连接类型确认为 AutoConnection 2. 分析线程结构发现子线程仅有 QThread 对象 3. 深入源码定位 QThread::run() 未调用 exec() 4. 确认事件循环缺失导致事件无法处理
解决方案 在 QThread::run() 中调用 exec() 启动事件循环
效果 问题彻底解决,槽函数正常触发
cpp 复制代码
// 问题代码
class MyThread : public QThread {
    void run() override {
        // 只执行任务,未启动事件循环
        doWork();
    }
};

// 解决方案
class MyThread : public QThread {
    void run() override {
        doWork();
        exec();  // 启动事件循环,处理跨线程信号
    }
};

案例 2:信号槽连接内存泄漏

项目 内容
现象 频繁创建销毁对象,内存持续增长不释放
根因 连接未正确断开,ConnectionList 中残留悬空连接
分析过程 1. 内存分析工具定位泄漏点在 QObjectPrivate 2. 检查代码发现 connect() 后未调用 disconnect() 3. 深入源码确认连接列表未清理导致引用残留
解决方案 对象销毁前显式 disconnect() 或使用 Qt::UniqueConnection
效果 内存泄漏问题解决,内存正常释放
cpp 复制代码
// 问题代码
void createObject() {
    MyObject *obj = new MyObject();
    connect(obj, &MyObject::signal, this, &Main::slot);
    obj->deleteLater();  // 连接未断开,残留悬空指针
}

// 解决方案 1:显式断开
void createObject() {
    MyObject *obj = new MyObject();
    QMetaObject::Connection conn = connect(obj, &MyObject::signal, 
                                           this, &Main::slot);
    obj->deleteLater();
    connect(obj, &QObject::destroyed, [conn]() { 
        disconnect(conn); 
    });
}

// 解决方案 2:UniqueConnection(自动管理)
connect(obj, &MyObject::signal, this, &Main::slot, 
        Qt::UniqueConnection);  // 重复连接自动忽略,对象销毁自动清理

2.2 事件循环机制

2.2.1 原理深度理解

核心架构

复制代码
事件源 → 事件队列 → QCoreApplication::exec() → 事件分发 → 处理器

关键流程

cpp 复制代码
int QCoreApplication::exec()
{
    QEventLoop eventLoop;
    return eventLoop.exec();  // 进入循环
}

int QEventLoop::exec()
{
    while (!quit) {
        // 1. 等待事件
        waitForMoreEvents();
        
        // 2. 从队列取事件
        QEvent *event = QCoreApplication::instance()->d_func()-> 
                        postedEvents->takeFirst();
        
        // 3. 分发事件
        QCoreApplication::sendEvent(receiver, event);
    }
}

事件类型与优先级

类型 来源 优先级 处理方式
QMetaCallEvent 跨线程信号槽 sendEvent
QTimerEvent 定时器 sendEvent
QPaintEvent 绘制请求 sendEvent
QInputEvent 用户输入 sendEvent
2.2.2 深入源码分析

事件投递机制

cpp 复制代码
void QCoreApplication::postEvent(QObject *receiver, QEvent *event, 
                                 int priority)
{
    // 1. 获取事件队列
    QThreadData *data = receiver->d_func()->threadData;
    QPostEventList *list = data->postEventList;
    
    // 2. 按优先级插入队列
    list->append(event);  // 或按优先级位置插入
    
    // 3. 通知事件循环有新事件
    data->eventDispatcher->wakeUp();
}

关键发现

  • 每个线程有独立事件队列(QThreadData::postEventList
  • 事件优先级影响队列顺序
  • wakeUp() 通知事件循环跳出等待
  • sendEvent 直接处理,postEvent 队列投递
2.2.3 框架级问题案例

案例:事件循环阻塞导致界面卡死

项目 内容
现象 执行长时间任务时界面完全卡死,无响应
根因 同步耗时操作阻塞事件循环,事件无法分发
分析过程 1. 界面卡死时 CPU 占用率高 2. 代码分析发现同步网络请求在主线程 3. 深入源码确认 exec() 循环被阻塞无法返回
解决方案 1. 异步化耗时操作(线程/异步 API) 2. 添加事件处理窗口(processEvents)
效果 界面保持响应,用户体验改善
cpp 复制代码
// 问题代码
void loadData() {
    QNetworkReply *reply = manager.get(request);
    // 同步等待,阻塞事件循环
    while (!reply->isFinished()) {
        QThread::msleep(100);  // 阻塞!事件循环无法执行
    }
    processData(reply);
}

// 解决方案 1:异步化
void loadData() {
    QNetworkReply *reply = manager.get(request);
    connect(reply, &QNetworkReply::finished, [reply]() {
        processData(reply);
        reply->deleteLater();
    });
}

// 解决方案 2:事件处理窗口(临时)
void loadData() {
    QNetworkReply *reply = manager.get(request);
    while (!reply->isFinished()) {
        // 处理积压事件,保持界面响应
        QCoreApplication::processEvents();
        QThread::msleep(50);
    }
    processData(reply);
}

2.3 对象模型

2.3.1 原理深度理解

核心架构

复制代码
QObject → QObjectPrivate (d-pointer) → QMetaObject (meta-data)

关键设计

设计 作用 优势
d-pointer 模式 实现细节隐藏在 QObjectPrivate 二进制兼容、ABI 稳定
QMetaObject 存储 meta-data(信号、槽、属性) 动态类型、反射能力
父子对象树 父对象销毁时自动销毁子对象 内存管理自动化
对象生命周期 deleteLater() 延迟销毁 安全销毁、避免悬空
2.3.2 深入源码分析

父子对象树实现

cpp 复制代码
void QObjectPrivate::setParent(QObject *parent)
{
    // 1. 从旧父对象移除
    if (oldParent) {
        oldParent->d_func()->children.removeOne(this);
    }
    
    // 2. 加入新父对象子列表
    if (newParent) {
        newParent->d_func()->children.append(this);
    }
    
    // 3. 更新父指针
    parent = newParent;
}

void QObject::~QObject()
{
    // 1. 销毁所有子对象
    for (QObject *child : children) {
        delete child;  // 自动递归销毁子对象树
    }
    
    // 2. 从父对象移除自己
    if (parent) {
        parent->d_func()->children.removeOne(this);
    }
    
    // 3. 断开所有连接
    disconnect();
}

关键发现

  • 子对象列表存储在 QObjectPrivate::children
  • 父对象销毁时自动 delete 所有子对象
  • deleteLater() 投递 QEvent::DeferredDelete 事件
  • 对象销毁顺序:子对象 → 断开连接 → 从父对象移除
2.3.3 框架级问题案例

案例:父子对象树误用导致崩溃

项目 内容
现象 程序退出时崩溃,访问已销毁对象
根因 手动 delete 子对象后,父对象再次尝试 delete
分析过程 1. 崩溃定位在 QObject::~QObject() 2. 代码分析发现手动 delete 子对象 3. 深入源码确认父对象析构时二次 delete
解决方案 让 Qt 自动管理子对象,避免手动 delete
效果 崩溃问题彻底解决
cpp 复制代码
// 问题代码
QWidget *parent = new QWidget();
QPushButton *child = new QPushButton(parent);  // 指定父对象

// 手动 delete 子对象(错误!)
delete child;  // parent 的 children 列表仍引用 child

// 程序退出时,parent 析构再次 delete child → 崩溃!

// 解决方案 1:不手动 delete,让父对象管理
QWidget *parent = new QWidget();
QPushButton *child = new QPushButton(parent);
// 不调用 delete child,由 parent 自动管理

// 解决方案 2:不指定父对象,手动管理
QPushButton *child = new QPushButton();  // 无父对象
// 手动管理生命周期
delete child;  // 安全

// 解决方案 3:使用 deleteLater() 安全销毁
child->deleteLater();  // 投递延迟删除事件,避免立即销毁

3. Qt 内存管理机制与内存泄漏预防

3.1 内存管理机制深度理解

3.1.1 核心机制
机制 说明 适用场景
父子对象树 父对象自动销毁子对象 Widget、QObject 子类
deleteLater() 延迟安全销毁 事件循环内对象
QSharedPointer 智能指针引用计数 非 QObject 对象
QScopedPointer 独占智能指针 局部作用域对象
QObjectData d-pointer 内存布局 内部实现隐藏
3.1.2 内存泄漏常见原因
原因 说明 预防方法
连接未断开 connect 后对象销毁未 disconnect Qt::UniqueConnection
循环引用 父子对象互相引用 合理设计对象树
事件未处理 postEvent 后对象销毁事件残留 及时清理事件队列
手动管理错误 混用 Qt 自动管理和手动管理 统一管理策略
静态对象引用 静态变量引用动态对象 避免静态引用动态
3.1.3 内存泄漏排查方法论

排查流程

复制代码
现象观察(内存增长) → 工具分析(Visual Leak Detector) → 
源码定位(泄漏点) → 根因分析(Qt 机制误用) → 
方案设计(修正管理方式) → 验证效果(内存稳定)

工具推荐

工具 功能 适用平台
Visual Leak Detector 检测 C++ 内存泄漏 Windows
Valgrind 内存错误检测 Linux
Qt Creator 内存分析 Qt 对象泄漏检测 跨平台
Heaptrack 堆内存追踪 Linux

3.2 内存泄漏预防最佳实践

3.2.1 对象生命周期管理原则
cpp 复制代码
// ✅ 原则 1:统一管理方式
// Qt 管理的对象:指定父对象,不手动 delete
QWidget *widget = new QWidget(parentWidget);

// 手动管理的对象:不指定父对象,用智能指针
QScopedPointer<MyData> data(new MyData());

// ✅ 原则 2:安全销毁时机
// 立即销毁(确定无引用)
delete object;  // 仅用于无父对象、无连接的对象

// 延迟销毁(事件循环内)
object->deleteLater();  // 推荐,安全

// ✅ 原则 3:连接管理
// 使用 UniqueConnection 自动管理
connect(sender, &Sender::signal, receiver, &Receiver::slot,
        Qt::UniqueConnection);

// 或记录连接,销毁时断开
QMetaObject::Connection conn = connect(...);
connect(receiver, &QObject::destroyed, [conn]() {
    disconnect(conn);
});
3.2.2 典型内存泄漏案例

案例:信号槽连接泄漏

项目 内容
代码 频繁创建临时对象并 connect,未断开连接
泄漏原因 连接列表残留引用,对象销毁后连接仍存在
解决方案 使用 Qt::UniqueConnection 或显式 disconnect
cpp 复制代码
// 问题代码
void processFile(QString path) {
    FileProcessor *processor = new FileProcessor();
    connect(processor, &FileProcessor::finished, 
            this, &Main::onFinished);
    
    processor->process(path);
    processor->deleteLater();  // 对象销毁,但连接残留
}

// 内存持续增长:每次调用创建新连接,销毁后残留

// 解决方案
void processFile(QString path) {
    FileProcessor *processor = new FileProcessor();
    connect(processor, &FileProcessor::finished, 
            this, &Main::onFinished, Qt::UniqueConnection);
    
    processor->process(path);
    processor->deleteLater();
}
// UniqueConnection:对象销毁时自动清理连接

4. 代码质量与可维护性实践

4.1 代码质量标准

4.1.1 Qt 代码规范
规范项 要求 示例
命名规范 类名 PascalCase,函数 camelCase,变量 camelCase MyClass, getValue(), m_data
信号槽命名 信号名描述事件,槽名描述动作 valueChanged(), onValueChanged()
成员变量 私有成员前缀 m_ 或 _ m_name, _value
常量命名 全大写,下划线分隔 MAX_SIZE, DEFAULT_VALUE
注释规范 关键逻辑注释,API 文档注释 /** @brief 获取数据 */
4.1.2 可维护性原则
原则 说明 实践方法
低耦合 模块间依赖最小化 接口隔离、依赖注入
高内聚 模块内功能紧密相关 功能分组、职责单一
代码复用 提取公共组件 工具类、基础组件
文档完善 关键逻辑有文档 设计文档、API 文档
测试覆盖 核心功能有测试 单元测试、集成测试

4.2 代码质量提升实践

4.2.1 模块化设计
cpp 复制代码
// ✅ 模块化设计示例

// 1. 接口抽象
class IDataProcessor {
public:
    virtual ~IDataProcessor() = default;
    virtual void process(const QByteArray& data) = 0;
    virtual QByteArray getResult() = 0;
};

// 2. 实现分离
class Hdf5DataProcessor : public IDataProcessor {
public:
    void process(const QByteArray& data) override { /* ... */ }
    QByteArray getResult() override { /* ... */ }
};

// 3. 工厂模式
class DataProcessorFactory {
public:
    static IDataProcessor* create(FileType type) {
        switch (type) {
            case HDF5: return new Hdf5DataProcessor();
            case JSON: return new JsonDataProcessor();
            default: return nullptr;
        }
    }
};

// 4. 依赖注入
class DataManager {
    IDataProcessor *m_processor;
public:
    void setProcessor(IDataProcessor *processor) {
        m_processor = processor;
    }
    void processFile(QString path) {
        QByteArray data = readFile(path);
        m_processor->process(data);
    }
};
4.2.2 代码评审要点
评审项 检查内容 工具
命名规范 命名是否符合 Qt 标准 人工检查
内存管理 对象生命周期是否正确 VLD、静态分析
信号槽连接 连接是否正确、是否泄漏 代码审查
线程安全 多线程访问是否安全 静态分析
异常处理 错误处理是否完整 代码审查
性能影响 是否有性能瓶颈 性能测试

5. 大数据文件加载性能优化方案

5.1 性能瓶颈分析

5.1.1 问题背景
项目 内容
需求 加载 500MB+ HDF5 科学数据文件
问题 加载耗时 30 秒,界面卡死,用户体验极差
影响 客户投诉,影响产品竞争力
5.1.2 瓶颈定位方法

性能分析流程

复制代码
用户体验反馈 → 性能测试量化 → 瓶颈定位工具 → 
根因分析 → 方案设计 → 实施验证 → 效果对比

工具与方法

方法 工具 发现问题
时间测量 QElapsedTimer 定位耗时函数
性能剖析 Qt Creator Profiler 定位热点代码
内存分析 Heaptrack 定位内存瓶颈
源码分析 Qt 源码阅读 理解底层机制
5.1.3 瓶颈根因
瓶颈 根因 占用时间
全量加载 一次性读取全部数据到内存 15秒
数据解析 HDF5 解析开销大 8秒
UI 更新 大量数据渲染阻塞 5秒
内存拷贝 Qt 容器频繁拷贝 2秒

5.2 优化方案设计

5.2.1 流式加载架构

核心思路:分块读取 + 增量解析 + 增量渲染 + 内存池管理

cpp 复制代码
// 流式加载架构设计
class StreamDataLoader : public QObject {
    Q_OBJECT
public:
    // 配置参数
    struct Config {
        int chunkSize = 10 * 1024 * 1024;  // 10MB 每块
        int maxMemory = 100 * 1024 * 1024; // 100MB 内存上限
        int threadCount = 2;               // 解析线程数
    };
    
    // 启动加载
    void loadFile(QString path, Config config) {
        m_filePath = path;
        m_config = config;
        
        // 启动读取线程
        m_readThread = new ReadThread(this);
        m_parseThreads = createParseThreads(config.threadCount);
        
        connect(m_readThread, &ReadThread::chunkReady,
                this, &StreamDataLoader::onChunkReady);
        
        m_readThread->start();
    }
    
signals:
    void progressChanged(int percent);
    void dataReady(QVector<DataChunk> chunks);
    void loadFinished();
    
private:
    // 读取线程:分块读取文件
    class ReadThread : public QThread {
        void run() override {
            QFile file(m_path);
            file.open(QIODevice::ReadOnly);
            
            qint64 totalSize = file.size();
            qint64 bytesRead = 0;
            
            while (!file.atEnd()) {
                // 读取一个块
                QByteArray chunk = file.read(m_chunkSize);
                bytesRead += chunk.size();
                
                // 发射块数据
                emit chunkReady(chunk, bytesRead, totalSize);
                
                // 等待解析线程处理(避免内存积压)
                m_semaphore.acquire();
            }
        }
    };
    
    // 解析线程池:并行解析数据块
    class ParseThread : public QThread {
        void run() override {
            while (m_running) {
                // 从队列获取待解析块
                QByteArray chunk = m_queue->dequeue();
                if (chunk.isEmpty()) break;
                
                // 解析 HDF5 数据
                QVector<DataItem> items = parseHdf5Chunk(chunk);
                
                // 发射解析结果
                emit parseReady(items);
                
                // 通知读取线程继续
                m_semaphore->release();
            }
        }
    };
    
    // 内存池管理:限制内存使用
    class MemoryPool {
        QVector<QByteArray> m_chunks;
        int m_maxSize;
        
        bool tryAllocate(int size) {
            if (currentSize() + size > m_maxSize) {
                // 释放旧数据
                releaseOldest();
            }
            return true;
        }
    };
};
5.2.2 增量渲染优化
cpp 复制代码
// 增量渲染策略
class IncrementalRenderer : public QObject {
    Q_OBJECT
public:
    void appendData(const QVector<DataItem>& items) {
        // 添加到渲染队列
        m_renderQueue.append(items);
        
        // 启动定时器增量渲染
        if (!m_renderTimer.isActive()) {
            m_renderTimer.start(16);  // 16ms,保持 60fps
        }
    }
    
private slots:
    void onRenderTimeout() {
        // 每帧渲染固定数量(避免阻塞)
        int itemsPerFrame = 100;
        
        for (int i = 0; i < itemsPerFrame && !m_renderQueue.isEmpty(); ++i) {
            DataItem item = m_renderQueue.takeFirst();
            renderItem(item);
        }
        
        // 队列空则停止
        if (m_renderQueue.isEmpty()) {
            m_renderTimer.stop();
            emit renderFinished();
        }
        
        // 更新进度
        emit progressChanged(calculateProgress());
    }
    
private:
    QTimer m_renderTimer;
    QVector<DataItem> m_renderQueue;
};
5.2.3 内存管理优化
cpp 复制代码
// 内存管理策略
class SmartMemoryManager {
public:
    // LRU 缓存策略
    template<typename T>
    class LruCache {
        QMap<QString, QSharedPointer<T>> m_cache;
        QStringList m_accessOrder;  // 访问顺序
        int m_maxSize;
        
        QSharedPointer<T> get(QString key) {
            if (m_cache.contains(key)) {
                // 更新访问顺序
                m_accessOrder.removeAll(key);
                m_accessOrder.append(key);
                return m_cache[key];
            }
            return nullptr;
        }
        
        void put(QString key, QSharedPointer<T> data) {
            // 超出限制则释放最久未访问
            while (m_cache.size() >= m_maxSize) {
                QString oldest = m_accessOrder.takeFirst();
                m_cache.remove(oldest);
            }
            
            m_cache[key] = data;
            m_accessOrder.append(key);
        }
    };
    
    // 内存压力监控
    void monitorMemoryPressure() {
        qint64 currentUsage = getMemoryUsage();
        qint64 threshold = 80 * 1024 * 1024;  // 80MB
        
        if (currentUsage > threshold) {
            // 释放低优先级数据
            releaseLowPriorityData();
            
            // 通知用户
            emit memoryPressureWarning(currentUsage);
        }
    }
};

5.3 优化效果对比

5.3.1 性能数据对比
指标 优化前 优化后 提升
加载时间(500MB) 30秒 5秒 6倍
内存峰值 500MB 100MB 5倍降低
界面响应 卡死 流畅 显著改善
用户体验评分 60分 90分 30分提升
5.3.2 技术方案总结
方案 效果 实现复杂度
流式加载 加载时间降低 6 倍
增量渲染 界面保持流畅
内存池管理 内存峰值降低 5 倍
并行解析 解析效率提升 2 倍
LRU 缓存 减少重复加载

6. 框架级问题解决典型案例

6.1 问题解决方法论

复制代码
问题现象 → 日志分析 → 源码定位 → 根因确认 → 方案设计 → 
实施验证 → 效果评估 → 知识沉淀 → 团队分享

6.2 典型案例汇总

案例 问题类型 解决方法 效果
跨线程信号失效 框架机制误用 启动事件循环 问题彻底解决
事件循环阻塞 同步操作阻塞 异步化改造 界面恢复响应
父子对象崩溃 内存管理错误 统一管理策略 崩溃消失
信号槽内存泄漏 连接管理错误 UniqueConnection 内存稳定
大数据加载性能 性能瓶颈 流式加载架构 性能提升 6 倍
Qt 6 迁移兼容性 API 变化 系统性迁移方案 成功迁移

7. 最佳实践总结

7.1 Qt 核心机制使用原则

原则 说明
信号槽 跨线程用 QueuedConnection,避免循环连接
事件循环 长任务异步化,必要时用 processEvents
对象树 统一管理方式,避免混用自动和手动管理
内存管理 Qt 管理的对象不手动 delete,智能指针管理非 QObject

7.2 性能优化原则

原则 说明
流式处理 大数据分块处理,避免全量加载
异步化 耗时操作异步执行,避免阻塞主线程
内存控制 设置内存上限,及时释放不常用数据
增量渲染 分帧渲染,保持界面流畅

7.3 代码质量原则

原则 说明
模块化 低耦合高内聚,接口抽象
规范化 遵循 Qt 代码规范,命名统一
文档化 关键逻辑有文档,API 有说明
测试化 核心功能有测试,回归验证

8. 附录

8.1 Qt 源码关键位置

模块 源码文件 关键内容
信号槽 qobject.cpp activate、connect、disconnect
事件循环 qcoreapplication.cpp exec、postEvent、sendEvent
对象模型 qobject.cpp 析构、父子关系、d-pointer
内存管理 qsharedpointer.h 智能指针实现
相关推荐
lzhdim4 小时前
C#性能优化技巧
开发语言·性能优化·c#
郝学胜-神的一滴5 小时前
Qt 高级开发 007: 图片查看器案例
开发语言·c++·qt·程序人生·开源软件
林夕075 小时前
Qt集成AI推理引擎:TensorFlow Lite与ONNX Runtime实战
人工智能·qt·neo4j
半途鹅飞、5 小时前
Qt Creator 界面(菜单栏 / 工具栏 / 运行栏)消失解决方法
开发语言·qt
小短腿的代码世界13 小时前
从.qrc到rcc编译器:Qt资源系统的隐秘运作机制与大型项目性能突围
开发语言·qt
我在人间贩卖青春17 小时前
重学Qt——Qt常用界面组件
qt
Larry_Yanan19 小时前
QML面试常见问题(一)QML中组件呈现方式的方法有哪些
开发语言·c++·qt·ui·面试
小蒋聊技术19 小时前
电商系列第七课:售后与物流中心 —— 服务质量提升与智能物流路由
架构·系统架构·电商
王飞飞不会飞19 小时前
iOS卡顿查找和定位-ProFile
ios·性能优化