深入理解Qt属性系统[Q_PROPERTY]

news2024/11/16 3:44:37

Qt 属性系统是 Qt 框架中一个非常核心和强大的部分,它提供了一种标准化的方法来访问对象的属性。这一系统不仅使得开发者能够以一致的方式处理各种数据类型,还为动态属性的管理提供了支持,并与 Qt 的元对象系统紧密集成。在这篇文章中,我们将详细介绍 Qt 属性系统的概念、实现机制和使用方式,以及它如何帮助开发者提升应用程序的灵活性和可维护性。

Q_PROPERTY

声明属性的类必须要继承QObject,并且使用Q_OBJECT宏,然后通过Q_PROPERTY宏来声明属性。

源码解释

Q_PROPERTY(type name
           (READ getFunction [WRITE setFunction] |
            MEMBER memberName [(READ getFunction | WRITE setFunction)])
           [RESET resetFunction]
           [NOTIFY notifySignal]
           [REVISION int | REVISION(int[, int])]
           [DESIGNABLE bool]
           [SCRIPTABLE bool]
           [STORED bool]
           [USER bool]
           [CONSTANT]
           [FINAL]
           [REQUIRED])

基本结构

  • type:属性的数据类型,支持以下类型:
    1. Qt内建类型:常见的Qt数据类型如QStringQDateQTimeQDateTimeQUrlQListQMapQStringListQCharQByteArray等。
    2. 标准C++类型intbooldoublefloatchar*等。
    3. 枚举类型
    4. 自定义类型:自定义的类也可以作为属性的类型,但是要满足以下条件:
      1. 类必须被Qt的元对象系统所知晓,这通常意味着类需要使用Q_DECLARE_METATYPE宏声明。
      2. 类需要提供公共的构造函数、拷贝构造函数和赋值运算符,以便Qt能够在内部处理属性的复制和赋值。
      3. 如果要通过QVariant进行存储或传递,类必须注册到Qt的类型系统中,使用qRegisterMetaType<ClassName>()
  • name:属性的名称

注意:这里的name和具体的成员变量名可以不同,因为Q_PROPERTY声明的属性不是具体的成员,属性是用来关联对应某个成员对象的。

访问函数

  • READ:READ访问器函数,即读取属性值的函数。函数的返回值必须是属性对应类型或对该类型的const引用,函数体必须带const修饰。函数示例如下:

type getNameFunction() const;
const type& getNameFunction() const;

  • WRITE:WRITE访问器函数,即修改属性值的函数。函数的返回值必须是void类型,并且只接受一个参数,可以是属性对应的类型,也可以是指向该类型的指针或引用。函数示例如下:

void setNameFunction(type val);
void setNameFunction(const type& val);

  • MEMBER:直接指定类中的成员变量作为属性的存储,代替使用单独的读写方法。可以与READWRITE搭配使用来覆盖默认的成员访问。

可选修饰符

  • RESET:指定一个函数用于重置属性到默认的初始状态。通常不带参数,没有返回值。
  • NOTIFY:指定当属性值改变时要发出的信号。这在绑定和数据驱动的界面更新中非常有用。
  • REVISION:用于版本控制,指明属性从哪个Qt版本开始可用。这主要用于与QML集成时的版本控制。
  • DESIGNABLE:指定属性是否应该在Qt设计器中显示,默认为true
  • SCRIPTABLE:指定属性是否可以通过脚本访问,默认为true
  • STORED:指明属性是否应该被序列号,默认为true。如果属性是计算出来的,并不需要存储,可以设置为false
  • USER:指明这个属性是否被指定为该类的面向用户的属性或用户可编辑的属性。通常,每个类只有一个USER属性,默认为false
  • CONSTANT:标记属性为常量,一旦在构造函数中设置后不能改变。常用于只读的配置值。
  • FINAL:指明这个属性在派生类中不能被重写。
  • REQUIRED:属性的存在表明该属性应由该类的用户设置。这不是由 moc 强制执行的,并且对于暴露于 QML 的类来说最有用。在 QML 中,除非设置了所有必需属性,否则无法实例化具有必需属性的类。

使用示例

注意

  1. 使用READWRITE两个访问器函数在未指定MEMBER时需要显式定义
  2. 在指定MEMBER时可以不用显式定义READWRITE两个访问器函数
  3. READWRITE两个访问器函数主要的作用不是显式调用相应的函数,而是通过对象的property()setProperty()来调用

只读属性

class Data : public QObject {
    Q_OBJECT
    Q_PROPERTY(QString name READ name FINAL)
public:
    explicit Data(QObject* parent = nullptr)
        : QObject(parent)
    {
    }

    QString name() const
    {
        return m_name;
    }

private:
    QString m_name { "Data" };
};

int main(int argc, char* argv[])
{
    QCoreApplication a(argc, argv);

    Data data;
    // 使用 property() 来获取属性值
    qDebug() << "Initial name:" << data.property("name").toString();

    // 使用 setProperty() 来修改属性值
    data.setProperty("name", "Updated Name");
    qDebug() << "Updated name:" << data.property("name").toString();

    return a.exec();
}

输出结果:

Initial name: "Data"
Updated name: "Data"

可读写属性

class Data : public QObject {
    Q_OBJECT
    Q_PROPERTY(QString name READ name WRITE setName FINAL)
public:
    explicit Data(QObject* parent = nullptr)
        : QObject(parent)
    {
    }

    QString name() const
    {
        return m_name;
    }
    void setName(const QString& name)
    {
        if (name != m_name)
            m_name = name;
    }

private:
    QString m_name { "Data" };
};

int main(int argc, char* argv[])
{
    QCoreApplication a(argc, argv);

    Data data;
    // 使用 property() 来获取属性值
    qDebug() << "Initial name:" << data.property("name").toString();

    // 使用 setProperty() 来修改属性值
    data.setProperty("name", "Updated Name");
    qDebug() << "Updated name:" << data.property("name").toString();

    return a.exec();
}

输出结果:

Initial name: "Data"
Updated name: "Updated Name"

MEMBER关键字

class Data : public QObject {
    Q_OBJECT
    Q_PROPERTY(QString name MEMBER m_name FINAL)
public:
    explicit Data(QObject* parent = nullptr)
        : QObject(parent)
    {
    }

private:
    QString m_name { "Data" };
};

int main(int argc, char* argv[])
{
    QCoreApplication a(argc, argv);

    Data data;
    // 使用 property() 来获取属性值
    qDebug() << "Initial name:" << data.property("name").toString();

    // 使用 setProperty() 来修改属性值
    data.setProperty("name", "Updated Name");
    qDebug() << "Updated name:" << data.property("name").toString();

    return a.exec();
}

输出结果:

Initial name: "Data"
Updated name: "Updated Name"

使用MEMBER关键字时,用户无法通过实例对象显式调用gettersetter函数,只能通过property()setProperty()来获取和设置。

NOTIFY关键字的使用

class Data : public QObject {
    Q_OBJECT
    Q_PROPERTY(QString name MEMBER m_name NOTIFY nameChanged FINAL)
public:
    explicit Data(QObject* parent = nullptr)
        : QObject(parent)
    {
    }

signals:
    void nameChanged(const QString& name);

private:
    QString m_name { "Data" };
};

int main(int argc, char* argv[])
{
    QCoreApplication a(argc, argv);

    Data data;
    QObject::connect(&data, &Data::nameChanged, [&data](const QString& name) {
        qDebug() << "nameChanged: " << data.property("name");
    });
    // 使用 property() 来获取属性值
    qDebug() << "Initial name:" << data.property("name").toString();

    // 使用 setProperty() 来修改属性值
    data.setProperty("name", "Updated Name");
    qDebug() << "Updated name:" << data.property("name").toString();

    return a.exec();
}

输出结果:

Initial name: "Data"
nameChanged:  QVariant(QString, "Updated Name")
Updated name: "Updated Name"

属性系统的主要应用场景

C++和QML交互

首先,我们需要定义一个包含可观察属性的C++类。此类将发出属性变化的信号,QML界面将响应这些信号更新显示。
CppBackend.h

#ifndef CPPBACKEND_H
#define CPPBACKEND_H

#include <QObject>
#include <QString>

class CppBackend : public QObject {
    Q_OBJECT
    Q_PROPERTY(QString message READ message WRITE setMessage NOTIFY messageChanged)

public:
    explicit CppBackend(QObject *parent = nullptr) : QObject(parent), m_message("Hello from C++!") {}

    QString message() const { return m_message; }

public slots:
    void setMessage(const QString &message) {
        if (m_message != message) {
            m_message = message;
            emit messageChanged();
        }
    }

signals:
    void messageChanged();

private:
    QString m_message;
};

#endif // CPPBACKEND_H

接下来,我们创建一个QML文件来显示message属性,并提供一个按钮来改变它。
main.qml

import QtQuick 2.15
import QtQuick.Window 2.15
import QtQuick.Controls 2.15

Window {
    width: 640
    height: 480
    visible: true
    title: qsTr("Hello World")

    Column {
        anchors.centerIn: parent
        spacing: 20

        Text {
            id: displayText
            text: backend.message
            font.pointSize: 20
        }

        Button {
            text: "Change Message"
            onClicked: {
                backend.message = "Updated by QML!"
            }
        }
    }
}

在主程序中,我们将注册后端对象为QML可访问,并加载QML文件。
main.cpp

#include "CppBackend.h"
#include <QGuiApplication>
#include <QQmlApplicationEngine>
#include <QQmlContext>

int main(int argc, char* argv[])
{
#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
    QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling);
#endif
    QGuiApplication app(argc, argv);

    QQmlApplicationEngine engine;
    CppBackend backend;
    engine.rootContext()->setContextProperty("backend", &backend);

    const QUrl url(QStringLiteral("qrc:/main.qml"));
    QObject::connect(
        &engine,
        &QQmlApplicationEngine::objectCreated,
        &app,
        [url](QObject* obj, const QUrl& objUrl) {
            if (!obj && url == objUrl)
                QCoreApplication::exit(-1);
        },
        Qt::QueuedConnection);
    engine.load(url);

    return app.exec();
}

属性树形控件

属性树形控件的类虽然已经被移除,但QtCreator还是在使用的,可以通过Qt的源码查找qtpropertybrowser
展示使用的代码:
TestData1.h

#ifndef TESTDATA1_H
#define TESTDATA1_H

#include <QColor>
#include <QObject>

class TestData1 : public QObject {
    Q_OBJECT
    Q_PROPERTY(int count READ count WRITE setCount NOTIFY countChanged FINAL)
    Q_PROPERTY(QColor bkColor READ bkColor WRITE setBkColor NOTIFY bkColorChanged FINAL)
    Q_PROPERTY(QString name READ name WRITE setName NOTIFY nameChanged FINAL)
public:
    explicit TestData1(QObject* parent = nullptr)
        : QObject(parent)
    {
    }

    int count() const { return m_count; }
    void setCount(int value)
    {
        if (value != m_count) {
            m_count = value;
            emit countChanged(m_count);
        }
    }

    QColor bkColor() const { return m_bkColor; }
    void setBkColor(const QColor& color)
    {
        if (color != m_bkColor) {
            m_bkColor = color;
            emit bkColorChanged(m_bkColor);
        }
    }

    QString name() const { return m_name; }
    void setName(const QString& value)
    {
        if (value != m_name) {
            m_name = value;
            emit nameChanged(m_name);
        }
    }

signals:
    void countChanged(int value);
    void bkColorChanged(const QColor& color);
    void nameChanged(const QString& name);

private:
    int m_count { 1 };
    QColor m_bkColor { Qt::black };
    QString m_name { "TestData" };
};

#endif // TESTDATA1_H

TestData2.h

#ifndef TESTDATA2_H
#define TESTDATA2_H

#include <QColor>
#include <QObject>

class TestData2 : public QObject {
    Q_OBJECT
    Q_PROPERTY(int number READ count WRITE setCount NOTIFY countChanged FINAL)
    Q_PROPERTY(QColor bkColor READ bkColor WRITE setBkColor NOTIFY bkColorChanged FINAL)
    Q_PROPERTY(QString name READ name WRITE setName NOTIFY nameChanged FINAL)
public:
    explicit TestData2(QObject* parent = nullptr)
        : QObject(parent)
    {
    }

    int count() const { return m_number; }
    void setCount(int value)
    {
        if (value != m_number) {
            m_number = value;
            emit countChanged(m_number);
        }
    }

    QColor bkColor() const { return m_bkColor; }
    void setBkColor(const QColor& color)
    {
        if (color != m_bkColor) {
            m_bkColor = color;
            emit bkColorChanged(m_bkColor);
        }
    }

    QString name() const { return m_name; }
    void setName(const QString& value)
    {
        if (value != m_name) {
            m_name = value;
            emit nameChanged(m_name);
        }
    }

signals:
    void countChanged(int value);
    void bkColorChanged(const QColor& color);
    void nameChanged(const QString& name);

private:
    int m_number { 99 };
    QColor m_bkColor { Qt::blue };
    QString m_name { "TestData2" };
};
#endif // TESTDATA2_H

widget.h

#ifndef WIDGET_H
#define WIDGET_H

#include "qtpropertybrowser/qteditorfactory.h"
#include "qtpropertybrowser/qttreepropertybrowser.h"
#include "qtpropertybrowser/qtvariantproperty.h"
#include "testdata1.h"
#include "testdata2.h"
#include <QListWidgetItem>
#include <QWidget>

QT_BEGIN_NAMESPACE
namespace Ui {
class Widget;
}
QT_END_NAMESPACE

class Widget : public QWidget {
    Q_OBJECT

public:
    Widget(QWidget* parent = nullptr);
    ~Widget();

private slots:
    void on_listWidget_currentItemChanged(QListWidgetItem* current, QListWidgetItem* previous);

protected:
    void updateProperties(QObject* selectedObject);

private:
    Ui::Widget* ui;

    QtVariantPropertyManager* m_variantManager;
    QtVariantEditorFactory* m_variantFactory;

    TestData1 m_data1;
    TestData1 m_data1_2;
    TestData2 m_data2;
    TestData2 m_data2_2;
};
#endif // WIDGET_H

widget.cpp

#include "widget.h"
#include "./ui_widget.h"
#include <QMetaProperty>

Widget::Widget(QWidget* parent)
    : QWidget(parent)
    , ui(new Ui::Widget)
    , m_variantManager(new QtVariantPropertyManager(this))
    , m_variantFactory(new QtVariantEditorFactory(this))
{
    ui->setupUi(this);
    m_data1.setName("TestData1");
    m_data1.setBkColor(Qt::red);
    m_data1_2.setName("TestData1_2");
    m_data1_2.setBkColor(Qt::yellow);
    m_data2.setCount(60);

    ui->widget->setFactoryForManager(m_variantManager, m_variantFactory);

    QtVariantProperty* item = m_variantManager->addProperty(QVariant::Double, "透明度");
    item->setValue(0.5);

    ui->widget->addProperty(item);
}

Widget::~Widget()
{
    delete ui;
}

void Widget::on_listWidget_currentItemChanged(QListWidgetItem* current, QListWidgetItem* previous)
{
    static int index = 0;
    int i = index % 4;
    if (i == 0)
        updateProperties(&m_data1);
    else if (i == 1)
        updateProperties(&m_data1_2);
    else if (i == 2)
        updateProperties(&m_data2);
    else
        updateProperties(&m_data2_2);

    index++;
}

void Widget::updateProperties(QObject* selectedObject)
{
    ui->widget->clear();

    auto metaObj = selectedObject->metaObject();
    for (int i = 0; i < metaObj->propertyCount(); ++i) {
        auto propertyObj = metaObj->property(i);
        auto propertyName = propertyObj.name();
        auto propertyValue = selectedObject->property(propertyName);
        auto item = m_variantManager->addProperty(propertyValue.type(), propertyName);
        item->setValue(propertyValue);

        ui->widget->addProperty(item);

        connect(m_variantManager, &QtVariantPropertyManager::valueChanged,
            this, [selectedObject](QtProperty* prop, const QVariant& value) {
                selectedObject->setProperty(prop->propertyName().toStdString().c_str(), value);
            });
    }
}

注意
ui->widget就是QtTreePropertyBrowser类。

效果:
image.png

总结

属性系统说到底就是建立在类的成员变量之上,并通过标准化的接口(getter和setter)以及元对象系统来增强这些成员的功能。
在很多UI和数据进行交互的时候,我们通常可以通过属性系统提供的标准化接口来实现,因为其带来以下好处:

  1. 一致性和易用性

提供一个统一的方法来访问和修改对象的属性,这意味着无论你在哪里或如何使用这些对象,访问和修改属性的方式总是相同的。这种一致性减少了学习和使用不同对象时的认知负担,使得开发更直观,同时也减少了代码中可能出现的错误。

  1. 封装和数据保护

通过标准化的 getter 和 setter 方法来访问和修改数据,这增强了封装性,保护了数据不被非法访问和修改。封装是面向对象编程中的一个核心概念,它隐藏了对象的内部状态和实现细节,只暴露有限的接口与外界交互。

  1. 解耦和模块化

标准化接口促进了解耦和模块化设计。开发者可以更轻松地替换或修改内部实现而不影响使用这些对象的代码。这是因为外部代码依赖于接口而非具体实现,从而使得整个系统更加灵活和可维护。

  1. 自动化工具和库的集成

标准化的属性接口使得自动化工具(如 GUI 设计器)和其他库(如序列化库、数据库映射工具)能够更容易地与你的代码集成。例如,一个自动化工具可以通过反射机制查找所有属性并允许用户在图形界面中配置它们。

  1. 动态性和反射能力

Qt 的元对象系统依赖于标准化的属性接口来支持反射,即在运行时查询和操作对象的能力。这不仅有助于开发各种动态特性(如动态数据绑定和脚本集成),也使得开发者能够编写通用代码来处理不同类型的对象。

  1. 跨语言和平台兼容性

标准化接口简化了在不同编程语言和平台间的对象交互。由于接口提供了清晰定义的交互点,因此可以更容易地将 Qt 应用与其他系统集成,或者在不同平台间迁移应用。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1857884.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

每日AI资讯-20240622

1. 可灵AI全新功能上线&#xff01; 可灵AI全新功能上线&#xff01;图生视频和视频续写来啦&#xff01; 图生视频&#xff1a;上传任意图片&#xff0c;生成5秒精彩视频。支持添加提示词控制图像运动视频续写&#xff1a;对生成视频一键续写4&#xff5e;5秒&#xff0c;支持…

App推广新突破!Xinstall无注册码方案,一键解决您的获客难题

在互联网的浪潮中&#xff0c;App推广与运营面临着前所未有的挑战。随着流量红利的衰退&#xff0c;如何高效、精准地触达用户&#xff0c;提升用户留存和转化率&#xff0c;成为每个企业都必须面对的问题。在这个关键时刻&#xff0c;Xinstall以其无注册码的解决方案&#xff…

深入测评:ONLYOFFICE 8.1 桌面编辑器究竟有多强大?

ONLYOFFICE 8.1桌面编辑器 文章目录 ONLYOFFICE 8.1桌面编辑器一、ONLYOFFICE的简介二、ONLYOFFICE 8.1新功能和改进2.1 轻松编辑器 PDF 文件2.2 用幻灯片版式快速修改幻灯片2.3 无缝切换文档编辑、审阅和查看模式2.4 改进从右至左语言的支持 & 新的本地化选项2.5 隐藏“连…

《精通嵌入式Linux编程》——解锁嵌入式Linux开发的无限可能

文章目录 &#x1f4d1;前言一、书籍概览与作者风采二、内容详解与特色亮点2.1 嵌入式Linux基础与入门2.2 系统编程与内核探索2.3 驱动开发与实战演练2.4 内存管理与性能优化2.5 系统调试与性能提升2.6 综合项目实践与案例分析 三、书籍价值与应用展望 &#x1f4d1;前言 在当今…

软件测试计划审核表、试运行审核、试运行申请表、开工申请表

1、系统测试计划审核表 2、系统试运行审核表 3、系统试运行申请表 4、开工申请表 5、开工令 6、项目经理授权书 软件全套资料获取&#xff1a;本文末个人名片直接获取或者进主页。 系统测试计划审核表 系统试运行审核表 系统试运行申请表 开工申请表 开工令 项目经理授权书

nginx实现反向代理出现502的解决方法

目录 1. 出现原因 1.1. 防火墙拦截了端口 1.1.1. 使用 iptables 1.1.2. 使用 firewall-cmd&#xff08;适用于 CentOS/RHEL 7&#xff09; 1.2. docker容器中的ip和宿主机ip不一致 1. 出现原因 这里我是用的docker容器来进行nginx的启动的&#xff0c;在我们用nginx的配置…

使用vant4+vue3制作电商购物网站

一、前言 1.本项目基于vant4vue3构建&#xff0c;默认友友们已具备相关知识&#xff0c;如不具备&#xff0c;请友友们先去了解相关该概念 2.项目数据来源于开源框架 新峰商城 在此指出 3.此项目目的在于帮助友友们了解基本的用法&#xff0c;没有涉及太多的逻辑操作。 二、…

医院信息管理系统的设计与实现

你好&#xff0c;我是信息技术领域的研究者。如果你对医院信息管理有兴趣&#xff0c;欢迎交流。 开发语言&#xff1a; Java 数据库&#xff1a; MySQL 技术&#xff1a; Java技术&#xff0c;SpringBoot框架&#xff0c;B/S模式 工具&#xff1a; MyEclipse&#xff0c;M…

测试卡无法仪表注册问题分析

1、问题描述 00101测试卡无法注册LTE网络&#xff0c;modemlog中发现终端未发起Attach请求&#xff0c;对比正常注册非正常注册的版本&#xff0c;发现正常的多出了ims apn。可以通过ATCGDCONT?来查询modem APN参数。 2、问题分析 目前Modem是一套&#xff0c;没有相关修改。因…

主食冻干喂猫是真对猫好吗?这些选购指南学会了是真不踩雷!

随着科学养猫知识的普及&#xff0c;主食冻干喂养越来越受到养猫人的青睐。主食冻干不仅符合猫咪的饮食天性&#xff0c;还能提供均衡的营养&#xff0c;有助于维护猫咪的口腔和消化系统健康。虽然许多猫主人看到了主食冻干喂养的诸多好处&#xff0c;但在选择适合的主食冻干产…

6.19长难句打卡

The Flatiron School, where people pay to learn programming, started as one of the many coding bootcamps that’s become popular for adults looking for a career change. 人们在Flatiron学校里花钱学习编程&#xff0c;且Flatiron学校也成为在寻求职业变化的成年人之中…

一文读懂交换机MAC地址表:五大关键点,图解21步

HCIA 新班开课了华为HCIA课程介绍苏州面授班 | 全国直播班循环开班&#xff0c;免费重学前言 什么是MAC地址表?MAC地址表有什么作用&#xff1f;MAC地址表里面包含了哪些要素&#xff1f;今天带你好好唠唠。 我们以一个案例为例&#xff1a; 如上图&#xff1a;PC1和PC2通…

vscode linux项目插件global扩展安装

vscode经常用来编辑linux系统下的项目&#xff1b;但是经常出现一些小问题&#xff1b;例如代码导航&#xff1a; 有时想要跳转到函数定义时会提示&#xff1a;未找到xxx的任何定义&#xff1b;更奇怪的是有的函数可以跳转有的函数不能跳转&#xff1b;理论上来说C/C插件已经具…

脑洞大开!用大模型开卡车,还融了2亿美元

物理生成式AI驾驶平台Waabi在官网宣布获得2亿美元&#xff0c;本次由英伟达&#xff08;NVIDIA&#xff09;、沃尔沃、保时捷、Uber、Khosla等全球知名企业投资。 Waabi仅成立3年便获得4级自主驾驶权限&#xff0c;主要是借助了ChatGPT等生成式AI风口&#xff0c;将其融合在自…

AI风险管理新利器:SAIF CHECK利用Meta Llama 3保障合规与安全

每周跟踪AI热点新闻动向和震撼发展 想要探索生成式人工智能的前沿进展吗&#xff1f;订阅我们的简报&#xff0c;深入解析最新的技术突破、实际应用案例和未来的趋势。与全球数同行一同&#xff0c;从行业内部的深度分析和实用指南中受益。不要错过这个机会&#xff0c;成为AI领…

设计软件有哪些?贴图插件篇(2),渲染100邀请码1a12

这次我们继续介绍一些贴图插件。 1、Texporter Texporter是3ds Max的一个插件&#xff0c;用于快速导出贴图。它允许用户一次性导出多个贴图通道&#xff0c;如漫反射、法线、置换等&#xff0c;以各种格式&#xff0c;如TGA、BMP、JPEG等。Texporter提供了简单易用的界面和灵…

ctr/cvr预估之WideDeep模型

ctr/cvr预估之Wide&Deep模型 在探索点击率&#xff08;CTR&#xff09;和转化率&#xff08;CVR&#xff09;预估的领域中&#xff0c;我们始终追求的是一种既能捕获数据中的线性关系&#xff0c;又能发现复杂模式的模型。因子分解机&#xff08;Factorization Machines, …

6.21长难句打卡

Students also benefit from learning something about coding before they get to college, where introductory computer-science classes are packed to the brim, which can drive the less-experienced or -determined students away. 1.brim v.充满 n.边缘 在上大学之前…

Nature重磅:揭开睡眠不足为什么会损害记忆力

我们的记忆力会在睡眠期间得到增强&#xff0c;海马体是大脑的记忆中心&#xff0c;而海马体尖波涟漪&#xff08;sharp-wave ripple&#xff0c;SWR&#xff09;期间觉醒体验的激活和重放被认为是记忆力得到增强的关键。睡眠不足会损害记忆力&#xff0c;然而&#xff0c;我们…

四川赤橙宏海商务信息咨询有限公司电商服务正规吗?

在数字时代的浪潮下&#xff0c;电商行业正以前所未有的速度蓬勃发展。而在这个风起云涌的电商江湖中&#xff0c;四川赤橙宏海商务信息咨询有限公司以其独特的抖音电商服务策略&#xff0c;迅速崭露头角&#xff0c;成为业界翘楚。今天&#xff0c;我们就来聊聊这家公司如何凭…