Qt基础 | 创建和使用静态链接库 | 创建和使用动态链接库


  这一节介绍 Qt 编写和使用静态链接库和共享库(动态链接库)的方法。

一、创建和使用静态链接库

1.创建静态链接库

静态库编译与动态库编译不同的是:

  release 和 debug 模式下编译生成的都是相同的文件名,并不会 为 debug 版本自动添加一个字母"d", 但是在 release 和 debug 模式下编译应用程序时, 需要使用相应版本的库文件。

  创建一个静态链接库项目,设计各种需要导出的类,包括具有 UI 的窗体类、对话框类,编译后可以生成一个 lib 文件(MSVC 编译器生成后缀为 “.lib" 的文件, MinGW 编译器生成后缀为 ”.a" 的文件),在另一个应用程序里使用这个 lib 文件和类的头文件(不需要 cpp 源文件),就可以静态编译到应用程序里。这种方式适合于在小组开发时,每个人负责自己的部分,使用其他人设计的代码时只能使用而不能看到或修改源代码,便于项目代码的管理。

  创建静态链接库项目,单击 Qt Creator 的 “File” --> “New File or Project” 菜单项,选择 “Library”,在右侧的具体类别中再选择 C++ Library,

image-20240725210409484

单击“Choose…”按钮后出现如下向导对话框

image-20240725210632068

在“类型”下拉列表中选择“Statically Linked Library”,输入类的名称

image-20240725210804746

选择编译器

image-20240725210936956

这样生成的静态库项目 myStaticLib 包含3个文件:myStaticLib.pro、qwdialogpen.h 和 qwdialogpen.cpp。如果需要 ui 文件,则可以进行添加。

myStaticLib.pro

QT +=   widgets
TARGET = myStaticLib
TEMPLATE = lib
CONFIG += staticlib

CONFIG += c++17

# You can make your code fail to compile if it uses deprecated APIs.
# In order to do so, uncomment the following line.
#DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0x060000    # disables all the APIs deprecated before Qt 6.0.0

SOURCES += \
    qwdialogpen.cpp

HEADERS += \
    qwdialogpen.h

FORMS += \
    qwdialogpen.ui

# Default rules for deployment.
unix {
    target.path = $$[QT_INSTALL_PLUGINS]/generic
}
!isEmpty(target.path): INSTALLS += target
  • TEMPLATE = lib:定义项目模板是库,而不是应用程序
  • CONFIG += staticlib:配置项目为静态库
  • TARGET = myStaticLib:定义项目编译后生成的目标文件名称是 myStaticLib

qwdialogpen.h:

#ifndef QWDIALOGPEN_H
#define QWDIALOGPEN_H

#include    <QDialog>
#include    <QPen>

namespace Ui {
class QWDialogPen;
}

class QWDialogPen : public QDialog
{ //QPen属性设置对话框
    Q_OBJECT
private:
    QPen    m_pen; //成员变量
public:
    explicit QWDialogPen(QWidget *parent = 0);
    ~QWDialogPen();

    void    setPen(QPen pen); //设置QPen,用于对话框的界面显示
    QPen    getPen(); //获取对话框设置的QPen的属性
    static  QPen    getPen(QPen  iniPen, bool &ok);  //静态函数

private slots:
    void on_btnColor_clicked();
private:
    Ui::QWDialogPen *ui;
};

#endif // QWDIALOGPEN_H

qwdialogpen.cpp:

#include "qwdialogpen.h"
#include "ui_qwdialogpen.h"

#include    <QColorDialog>

QWDialogPen::QWDialogPen(QWidget *parent) :
    QDialog(parent),
    ui(new Ui::QWDialogPen)
{
    ui->setupUi(this);

//“线型”ComboBox的选择项设置
    ui->comboPenStyle->clear();
    ui->comboPenStyle->addItem("NoPen",0);
    ui->comboPenStyle->addItem("SolidLine",1);
    ui->comboPenStyle->addItem("DashLine",2);
    ui->comboPenStyle->addItem("DotLine",3);
    ui->comboPenStyle->addItem("DashDotLine",4);
    ui->comboPenStyle->addItem("DashDotDotLine",5);
    ui->comboPenStyle->addItem("CustomDashLine",6);

    ui->comboPenStyle->setCurrentIndex(1);
}

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

void QWDialogPen::setPen(QPen pen)
{ //设置QPen,并刷新显示界面
    m_pen=pen;

    ui->spinWidth->setValue(pen.width()); //线宽
    int i=static_cast<int>(pen.style());  //枚举类型转换为整型
    ui->comboPenStyle->setCurrentIndex(i);

    QColor  color=pen.color();
    ui->btnColor->setAutoFillBackground(true); //设置颜色按钮的背景色
    QString str=QString::asprintf("background-color: rgb(%d, %d, %d);",
                                  color.red(),color.green(),color.blue());
    ui->btnColor->setStyleSheet(str);
}

QPen QWDialogPen::getPen()
{//获得设置的属性
    m_pen.setStyle(Qt::PenStyle(ui->comboPenStyle->currentIndex())); //线型
    m_pen.setWidth(ui->spinWidth->value()); //线宽

    QColor  color;
    color=ui->btnColor->palette().color(QPalette::Button);
    m_pen.setColor(color); //颜色
    return  m_pen;
}

QPen QWDialogPen::getPen(QPen iniPen,bool &ok)
{ //静态函数,获取QPen
    QWDialogPen *Dlg=new QWDialogPen; //创建一个对话框
    Dlg->setPen(iniPen); //设置初始化QPen

    QPen    pen;
    int ret=Dlg->exec(); //弹出对话框
    if (ret==QDialog::Accepted)
    {
        pen=Dlg->getPen(); //获取
        ok=true;    }
    else
    {
        pen=iniPen;
        ok=false;   }

    delete  Dlg; //删除对话框对象
    return  pen; //返回设置的QPen对象
}

void QWDialogPen::on_btnColor_clicked()
{//设置颜色
    QColor  color=QColorDialog::getColor();
    if (color.isValid())
    { //用样式表设置QPushButton的背景色
        QString str=QString::asprintf("background-color: rgb(%d, %d, %d);",
                                      color.red(),color.green(),color.blue());
        ui->btnColor->setStyleSheet(str);
    }
}

ui

image-20240725212907271

2.静态链接库的使用

本实例将一个可视化设计的对话框 QWDialogPen 封装到一个静态库里,也可以将任何 C++类、函数封装到静态库,其实现方法是一样的。

  创建一个基于 QMainWindow 的应用程序 LibUser,在项目源程序目录下新建一个 include 目录,根据使用的编译器复制不同文件

  • 若使用 MSVC 编译器,将静态库项目 myStaticLib 下的 qwdialogpen.h 和 release 版本的 myStaticLib.lib 复制到这个 include 目录下,将 debug 版本的 myStaticLib.lib 更名 myStaticLibd.lib 复制到这个 include 目录下。
  • 若使用 MinGW 编译器,就将其编译的 “.a” 文件复制到 include 目录里。

  在 LibUser项目中,添加外部静态库,

image-20240725214546524

设置完成后,Qt Creator 将自动更改项目配置文件 LibUser.pro,增加以下内容,主要是设置了包含文件和依赖项的路径,增加了 LIBS 设置。

win32:CONFIG(release, debug|release): LIBS += -L$$PWD/include/ -lmyStaticLib
else:win32:CONFIG(debug, debug|release): LIBS += -L$$PWD/include/ -lmyStaticLibd

INCLUDEPATH += $$PWD/include
DEPENDPATH += $$PWD/include

win32-g++:CONFIG(release, debug|release): PRE_TARGETDEPS += $$PWD/include/libmyStaticLib.a
else:win32-g++:CONFIG(debug, debug|release): PRE_TARGETDEPS += $$PWD/include/libmyStaticLibd.a
else:win32:!win32-g++:CONFIG(release, debug|release): PRE_TARGETDEPS += $$PWD/include/myStaticLib.lib
else:win32:!win32-g++:CONFIG(debug, debug|release): PRE_TARGETDEPS += $$PWD/include/myStaticLibd.lib

编译应用程序 LibUser, 使用 MSVC 编译器,在 release 或 debug 模式下都可以编译,运行效果如下所示。

image-20240725220845338

运行逻辑是:当点击 “设置Pen” Action时,触发槽函数,在槽函数中调用静态库里的 QWDialogPen 的静态函数 getPen 设置画笔属性,即将一个可视化设计的对话框 QWDialogPen 封装到一个静态库里。

void MainWindow::on_action_Pen_triggered()
{//设置Pen
    bool    ok=false;
    QPen    pen=QWDialogPen::getPen(mPen,ok);
    if (ok)
    {
        mPen=pen;
        this->repaint();
    }
}
QPen QWDialogPen::getPen(QPen iniPen,bool &ok)
{ //静态函数,获取QPen
    QWDialogPen *Dlg=new QWDialogPen; //创建一个对话框
    Dlg->setPen(iniPen); //设置初始化QPen

    QPen    pen;
    int ret=Dlg->exec(); //弹出对话框
    if (ret==QDialog::Accepted)
    {
        pen=Dlg->getPen(); //获取
        ok=true;    }
    else
    {
        pen=iniPen;
        ok=false;   }

    delete  Dlg; //删除对话框对象
    return  pen; //返回设置的QPen对象
}

设置完画笔属性后,触发主窗口的重绘事件。

void MainWindow::paintEvent(QPaintEvent *event)
{//绘图
    Q_UNUSED(event);

    QPainter    painter(this);
    QRect rect(0,0,width(),height()); //viewport矩形区
    painter.setViewport(rect);//设置Viewport
    painter.setWindow(0,0,100,50); // 设置窗口大小,逻辑坐标
    painter.setRenderHint(QPainter::Antialiasing);
    painter.setRenderHint(QPainter::TextAntialiasing);

    painter.setPen(mPen);
    painter.drawRect(10,10,80,30);
}

二、创建和使用共享库

  除了静态库,Qt 还可以创建共享库。共享库就是 Windows 平台上的动态链接库。动态链接库项目编译后生成 DLL 文件,DLL 文件在 Windows 平台上应用广泛。DLL 文件是在应用程序运行时加载的,不像静态库那样在编译期间就编到应用程序里。若更新了 DLL 文件版本,只要接口未变,应用程序依然可以调用。

1.创建共享库

  创建动态链接库项目,单击 Qt Creator 的 “File” --> “New File or Project” 菜单项,选择 “Library”,在右侧的具体类别中再选择 C++ Library,

image-20240726131609785

  单击“Choose…”按钮后出现如下向导对话框,并给项目命名

image-20240726131730182

  在“类型”下拉列表中选择“Shared Library”,输入类的名称“QWDialogPen”

image-20240726132027365

  选择编译器

image-20240726132102311

  点击“完成”

image-20240726132339078

这样生成的动态库项目 myStaticLib 包含 4 个文件:mySharedLib.pro、mySharedLib_global.h、qwdialogpen.h 和 qwdialogpen.cpp。

image-20240726132527197

mySharedLib_global.h

#ifndef MYSHAREDLIB_GLOBAL_H
#define MYSHAREDLIB_GLOBAL_H

#include <QtCore/qglobal.h>

#if defined(MYSHAREDLIB_LIBRARY)
#define MYSHAREDLIB_EXPORT Q_DECL_EXPORT
#else
#define MYSHAREDLIB_EXPORT Q_DECL_IMPORT
#endif

#endif // MYSHAREDLIB_GLOBAL_H
  • 定义 MYSHAREDLIB_EXPORT 宏用于替代 Qt 的宏 Q_DECL_EXPORT 或 Q_DECL_IMPORT。
  • 一个共享库导出给用户使用的类、符号、函数等都需要宏 Q_DECL_EXPORT 来定义导出,一个使用共享库的应用程序需要通过 Q_DECL_IMPORT 导入共享库的可用对象。
  • 通常在构建共享库时设置,以区分是在编译库本身还是在编译使用库的应用程序。

mySharedLib.pro

QT += widgets

TEMPLATE = lib
DEFINES += MYSHAREDLIB_LIBRARY

CONFIG += c++17

# You can make your code fail to compile if it uses deprecated APIs.
# In order to do so, uncomment the following line.
#DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0x060000    # disables all the APIs deprecated before Qt 6.0.0

SOURCES += \
    qwdialogpen.cpp

HEADERS += \
    mySharedLib_global.h \
    qwdialogpen.h

FORMS += \
    qwdialogpen.ui

# Default rules for deployment.
unix {
    target.path = /usr/lib
}
!isEmpty(target.path): INSTALLS += target
  • 增加了符号 MYSHAREDLIB_LIBRARY 的定义

qwdialogpen.h

  需要在类名称前使用宏 MYSHAREDLIB_EXPORT,定义 QWDialogPen 类为一个导出的类,并加入 mySharedLib_global.h 的包含语句。

#ifndef QWDIALOGPEN_H
#define QWDIALOGPEN_H

#include  "mySharedLib_global.h"
#include    <QDialog>
#include    <QPen>
//#include    "sharedlib_global.h"

namespace Ui {
class QWDialogPen;
}

class MYSHAREDLIB_EXPORT QWDialogPen : public QDialog
{ //QPen属性设置对话框
    Q_OBJECT
private:
    QPen    m_pen; //成员变量
public:
    explicit QWDialogPen(QWidget *parent = 0);
    ~QWDialogPen();

    void    setPen(QPen pen); //设置QPen,用于对话框的界面显示
    QPen    getPen(); //获取对话框设置的QPen的属性
    static  QPen    getPen(QPen  iniPen, bool &ok);  //静态函数

private slots:
    void on_btnColor_clicked();
private:
    Ui::QWDialogPen *ui;
};

#endif // QWDIALOGPEN_H

qwdialogpen.cpp

#include "qwdialogpen.h"
#include "ui_qwdialogpen.h"

#include    <QColorDialog>

QWDialogPen::QWDialogPen(QWidget *parent) :
    QDialog(parent),
    ui(new Ui::QWDialogPen)
{
    ui->setupUi(this);

    //“线型”ComboBox的选择项设置
    ui->comboPenStyle->clear();
    ui->comboPenStyle->addItem("NoPen",0);
    ui->comboPenStyle->addItem("SolidLine",1);
    ui->comboPenStyle->addItem("DashLine",2);
    ui->comboPenStyle->addItem("DotLine",3);
    ui->comboPenStyle->addItem("DashDotLine",4);
    ui->comboPenStyle->addItem("DashDotDotLine",5);
    ui->comboPenStyle->addItem("CustomDashLine",6);

    ui->comboPenStyle->setCurrentIndex(1);
}

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

void QWDialogPen::setPen(QPen pen)
{ //设置QPen,并刷新显示界面
    m_pen=pen;

    ui->spinWidth->setValue(pen.width()); //线宽
    int i=static_cast<int>(pen.style());  //枚举类型转换为整型
    ui->comboPenStyle->setCurrentIndex(i);

    QColor  color=pen.color();
    ui->btnColor->setAutoFillBackground(true); //设置颜色按钮的背景色
    QString str=QString::asprintf("background-color: rgb(%d, %d, %d);",
                                    color.red(),color.green(),color.blue());
    ui->btnColor->setStyleSheet(str);
}

QPen QWDialogPen::getPen()
{//获得设置的属性
    m_pen.setStyle(Qt::PenStyle(ui->comboPenStyle->currentIndex())); //线型
    m_pen.setWidth(ui->spinWidth->value()); //线宽

    QColor  color;
    color=ui->btnColor->palette().color(QPalette::Button);
    m_pen.setColor(color); //颜色
    return  m_pen;
}

QPen QWDialogPen::getPen(QPen iniPen,bool &ok)
{ //静态函数,获取QPen
    QWDialogPen *Dlg=new QWDialogPen; //创建一个对话框
    Dlg->setPen(iniPen); //设置初始化QPen

    QPen    pen;
    int ret=Dlg->exec(); //弹出对话框
    if (ret==QDialog::Accepted)
    {
        pen=Dlg->getPen(); //获取
        ok=true;    }
    else
    {
        pen=iniPen;
        ok=false;   }

    delete  Dlg; //删除对话框对象
    return  pen; //返回设置的QPen对象
}

void QWDialogPen::on_btnColor_clicked()
{//设置颜色
    QColor  color=QColorDialog::getColor();
    if (color.isValid())
    { //用样式表设置QPushButton的背景色
        QString str=QString::asprintf("background-color: rgb(%d, %d, %d);",
                                        color.red(),color.green(),color.blue());
        ui->btnColor->setStyleSheet(str);
    }
}

ui文件

image-20240726134218217

  项目的文件准备好之后就可以编译生成 DLL 文件,根据使用编译器的不同,生成的文件有些区别。

  • 若使用 MSVC 编译,编译后会生成 mySharedLib.dll 和 mySharedLib.lib 两个文件,mySharedLib.dll 在运行应用程序时调用,mySharedLib.lib 在应用程序隐式调用动态链接库时使用。

    image-20240726135339721

  • 若使用 MinGW 编译,编译后会生成 mySharedLib.dll 和 libmySharedLib.a 两个文件,mySharedLib.dll 在运行应用程序时调用,libmySharedLib.a 在应用程序隐式调用动态链接库时使用。

采用 debug 和 release 不同模式下生成的文件只能当应用程序在 debug 或 release 模式下编译或调用。

2.使用共享库

  调用动态链接库有两种方式:隐式链接调用和显示链接调用。

2.1 隐式链接调用共享库

  隐式链接调用是在编译应用程序时,有动态库的 lib 文件(或 .a 文件)和 h 头文件,知道 DLL 中有哪些接口类和函数,编译时就隐式地生成必要的链接信息,使用 DLL 中的类或函数时根据 h 头文件中的定义使用即可。应用程序运行时将自动加载 DLL 文件。隐式链接调用主要用于同一种编程软件(如Qt)生成的代码的共享。

  创建一个项目 sharedLibUser,程序功能与 之前的 LibUsre 项目一样,可直接将相关文件进行替换。

image-20240726140457650

  在 项目sharedLibUser 文件目录下新建 include 目录,将 mySharedLib 项目的两个头文件 mySharedLib_global.h 与 qwdialogpen.h 复制到此目录下。我使用的是 MSVC2019 编译器,则将 release 版本的 mySharedLib.lib 复制到此目录下,debug 版本的 mySharedLib.lib 更名为 mySharedLibd.lib 复制到此目录下。

image-20240726141603422

  接下来,为应用程序增加动态链接库

image-20240726141429527

  完成后在 sharedLibUser.pro 文件绘制自动增加下面几行:

win32:CONFIG(release, debug|release): LIBS += -L$$PWD/./release/ -lmySharedLib
else:win32:CONFIG(debug, debug|release): LIBS += -L$$PWD/./debug/ -lmySharedLibd

INCLUDEPATH += $$PWD/include
DEPENDPATH += $$PWD/include

  项目编译时,会根据当前是 release 还是 debug 模式,自动添加相应的库文件。这里添加库文件只是使用了动态库的导出定义,而不是将库的实现代码链接到应用程序的可执行文件里。

  调用共享库里的类 QWDialogPen 只需要包含头文件 qwdialogpen.h 即可。

#include "mainwindow.h"
#include "ui_mainwindow.h"

#include    "qwdialogpen.h"
#include    <QPainter>

MainWindow::MainWindow(QWidget *parent) :
    QMainWindow(parent),
    ui(new Ui::MainWindow)
{
    ui->setupUi(this);
}

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

void MainWindow::paintEvent(QPaintEvent *event)
{//绘图
    Q_UNUSED(event);

    QPainter    painter(this);
    QRect rect(0,0,width(),height()); //viewport矩形区
    painter.setViewport(rect);//设置Viewport
    painter.setWindow(0,0,100,50); // 设置窗口大小,逻辑坐标
    painter.setRenderHint(QPainter::Antialiasing);
    painter.setRenderHint(QPainter::TextAntialiasing);

    painter.setPen(mPen);
    painter.drawRect(10,10,80,30);
}

void MainWindow::on_action_Pen_triggered()
{//设置Pen
    bool    ok=false;
    QPen    pen;
    pen=QWDialogPen::getPen(mPen,ok);
    if (ok)
    {
        mPen=pen;
        this->repaint();
    }
}

  必须将动态链接库文件如mySharedLib.dll 复制到可执行文件的目录下,程序才可以正常运行,mySharedLib.dll 的 debug 和 release 版本分别应用于应用程序的 debug 和 release 版本,否则运行时出错。

若没有添加,则报如下错

image-20240726142927648

添加后,即可正常执行。

image-20240726143318787

  使用动态链接库可以很方便地扩展应用程序的功能,但是 DLL 文件需要随应用程序一起发布,并且编译 DLL 和应用程序的Qt 版本最好保持一致,否则需要考虑二进制兼容问题。

2.2 显示链接调用共享库

  显示链接调用是只有 DLL 文件,知道 DLL 里的函数原型,使用 QLibrary 类对象在应用程序里动态加载 DLL 文件,声明函数原型,并使用 DLL 里的函数。这种方式需要在应用程序里声明函数原型,并解析 DLL 里的函数。

  显式链接调用共享库是在应用程序运行时才加载共享库文件,并调用库里的函数 。应用程序编译时无需共享库的任何文件,只需知道函数名和函数的原型即可。所以,这种方式可以调用其他语 言编写 DLL 文件,例如用 Delphi 生成的 DLL 文件。

  显示链接调用共享库是通过 QLibrary 类实现的。QLibrary 是与平台无关的,用于在运行时载入共享库,一个 QLibrary 对象只对一个共享库进行操作。

  一般在 QLibrary 的构造函数中传递一个文件名 ,可以是带路径的绝对文件名 ,也可以是不带后缀的单独文件名。 QLibrary 会根据运行的平台自动查找不同后缀的共享库文件,例如 Unix 上是" .so",Mac上是 ".dylib ", Windows 上是 “. dll”。

  用 Delphi 编写 一个 DLL 项目,生成 DelphiDLL.dll 文件,这个文件里只有一个函数,函数的原型为:

function triple(N:integer):integer;	//这个函数用于计算传递参数 N 的3倍并返回

在 Qt 中调用动态链接库 DelphiDLL.dll 里的 triple() 函数

void MainWindow::on_pushButton_clicked()
{
    QLibrary myLib("DelphiDLL");

    if (myLib.isLoaded())
        QMessageBox::information(this,"信息","DelphiDLL.DLL已经被载入,第1处");
    typedef int (*FunDef)(int); //函数原定定义
    FunDef myTriple = (FunDef) myLib.resolve("triple"); //解析DLL中的函数
    int V=myTriple(ui->spinInput->value()); //调用函数
    ui->spinOutput->setValue(V);
    if (myLib.isLoaded())
        QMessageBox::information(this,"信息","DelphiDLL.DLL已经被载入,第2处");
}
  • 在定义 QLibrary 对象实例 myLib 时传递了共享库文件 “DelphiDLL”, 这里不需要给出后缀名。 DelphiDLL.dll 文件必须在应用程序同一目录、系统目录或可搜索目录下。这里将 DelphiDLL.dll 文件复制到应用程序目录下。

  • QLibrary 有几个函数用于 DLL 文件的载入与卸载:

    • load() 用于手动载入 DLL 文件到内存里,一般无需手工调用此函数 ,在 DLL 里的函数第一次被使用时,QLibrary 会自动调用此函数
    • isLoaded()用于判断 DLL 是否已经被载入内存;
    • unload() 用于将 DLL 从内存中卸载
  • 一个动态链接库在内存里只能有一个实例,也就是即使有多处调用了这个动态链接库里的函数,它也只会被载入一次,如果不是所有的实例都使用 unload() 卸载它,那么它会在应用程序退出时才卸载

  • 显式调用动态链接库里的函数,需要声明函数原型的类型,即:

    typedef int (*FunDef)(int); //函数原定定义
    

    然后使用 QLibrary 的 resolve() 函数解析需要调用的函数

    FunDef myTriple = (FunDef) myLib.resolve("triple"); //解析DLL中的函数
    

    这样就定义了一个函数 myTriple,用于实现 DLL 文件里的函数 “triple” 的功能,当然重新声明函数名称可以和 DLL 函数名称完全相同。

程序运行结果:

  在运行程序时,第一次点击按钮时,只有第二处信息框显示,说明声明了 QLibrary 对象后 ,动态链接库没有立即被载入内存;第二次单击按钮时,两处信息框会先后显示,说明动态链接库上次载入内存后,还在内存里。

image-20240726151343764

  • 34
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值