一起学习Boost标准库--Boost.texical_cast&format库

本文介绍Boost库中lexical_cast用于数值与字符串间的转换,及format库实现字符串的格式化输出。lexical_cast支持基本类型转换,而format库提供类似printf的格式化功能。

今天接续介绍有关字符串表示相关的两个boost库:

  • lexical_cast 将数值转换成字符串
  • format 字符串输出格式化

首先,介绍下lexical_cast ,闻其名,知其意。类似C中的atoi 函数,可以进行字符串与整数/浮点数之间的字面转换

Boost::lexical_cast库

前期准备

lexical_cast库位于boost命名空间下,使用需要引入头文件

#include <boost/lexical_cast.hpp>
using namespace boost;

函数声明

lexical_cast使用类似C++标类型操作符的形式进行通用的语法,其声明如下:

// 1. 标准形式,转换数字和字符串
template <typename Target, typename Source>
inline Target lexical_cast(const Source &arg);

// 2. 转换C字符串
template <typename Target>
inline Target lexical_cast(const char* chars, std::size_t count);
inline Target lexical_cast(const unsigned char* chars, std::size_t count);
inline Target lexical_cast(const signed char* chars, std::size_t count);
inline Target lexical_cast(const wchar_t* chars, std::size_t count);
inline Target lexical_cast(const char16_t* chars, std::size_t count);
inline Target lexical_cast(const char32_t* chars, std::size_t count);
  • 第一种形式有两个模版参数,Target 为需要转换的目标类型,通常是数字类型或者std::string,第二个参数Source 则不用写,可以通过函数参数推到出来,调用形式如下
lexical_cast<int>("123");
lexical_cast<double>("234.123");
lexical_cast<std::string>(567.789);
  • 第二种形式主要用来处理C字符串,支持多种类型,只接受一个模板参数Target,指明转换后的目标类型,函数参数charscount 则标记了要转换的字符串范围
const char* double_str = "123.456";
double d1 = lexical_cast<double>(double_str, strlen(double_str));

使用样例

通过上文的介绍,您大概已经知道如何使用了,此处,我们就通过一个简单Demo来使用lexical_cast库

#include <boost/lexical_cast.hpp>
#include <iostream>
#include <string>
#include <iomanip>
using namespace std;
using namespace boost;

int main()
{
    int age = lexical_cast<int>("29");
    int money = lexical_cast<long>("10000000");
    float pai_f = lexical_cast<float>("3.1415926535");
    double pai_d = lexical_cast<double>("3.14159265358979323846264338324990", 20);

    cout << "age: " << age << endl
        << "money: " << money << endl
        << setiosflags(ios::fixed)
        << "pai_f: " << setprecision(8) << pai_f << endl
        << "pai_d: " << setprecision(16) << pai_d << endl;


    cout << lexical_cast<string>(age) << endl
        << lexical_cast<string>(money) << endl
        << lexical_cast<string>(pai_d) << endl;

    return 0;
}

Output:

age: 29
money: 10000000
pai_f: 3.14159274
pai_d: 3.1415926535897931
29
10000000
3.1415926535897931

注意: 使用lexical_cast时要注意,转换成数字的字符串中只能有数字和小数点,不能出现字母或其他非数字字符,同时也不支持高级的格式控制,如果要进行复杂的格式控制可以使用std::stringstramboost::format (后面介绍该库)

错误处理

当lexcial_cast无法执行转换操作的时候会抛出异常bad_lexical_cast ,他是std::bad_cast 的派生类,此处以上文中注意 来说明

    try
    {
        int age = lexical_cast<int>("0x64");
        //int age = lexical_cast<int>("100L");
        //bool f = lexical_cast<bool>("false");
        cout << "age: " << age << endl;
    }
    catch (bad_lexical_cast &e)
    {
        cout << "cast error: " << e.what() << endl;
    }

Output:

cast error: bad lexical cast: source type value could not be interpreted as target

如果每次都通过异常捕获来处理,就比较麻烦了,还好lexical_cast为我们想到了,再命名空间boost::conversion 提供方法try_lexical_convert() 函数来避免抛出异常,通过返回bool值来表示是否转换成功。具体使用如下:

using namespace boost::conversion;
int x = 0;
if (!try_lexical_convert<int>("0x64", x))
    cout << "convert failed" << endl;
else
    cout << "x: " << x << endl;

Boost.format库

C++标准库提供了强大的输入输出流处理,可以通过设置输出各种各样的格式,精度控制、填充、对齐等。但是唯一缺点就是太复杂了,真心记不住这么多,还是怀恋printf() 可以通过规定的样式输出想要的格式。虽然C++中可以继续使用printf() 但它缺乏类型安全检查等其他缺点,重点就是boost.format库实现了类似于printf() 的格式化对象,可以把参数格式化到一个字符串,而且是类型安全的,是一个header-only 的函数库,只要准备好头文件,不用预先编译就可以使用了,最主要的是用着还挺顺手。

前期准备

format库位于boost命名空间中,需引入头文件:

#include <boost/format.hpp>
using namespace boost;

类声明

template <class Ch, 
        class Tr = BOOST_IO_STD char_traits<Ch>, class Alloc = std::allocator<Ch> >
    class basic_format;

typedef basic_format<char >     format;

template<class Ch, class Tr, class Alloc>
class basic_format
{
public:
    explicit basic_format(const Ch* str = NULL);
    explicit basic_format(const string_type& s);
    basic_format(const basic_format& x);
    basic_format& operator= (const basic_format& x);

    basic_format& clear();       // 清空缓存
    basic_format& parse(const string_type&); // 重新格式化

    size_type   size() const;    // 获取字符串长度
    string_type str()  const;    // 获取格式化后的字符串

    template<class T>
    basic_format&   operator%(const T& x); //重载操作符%

    template<class Ch2, class Tr2, class Alloc2>
    friend std::basic_ostream<Ch2, Tr2> &
        operator<<(std::basic_ostream<Ch2, Tr2> &,
        const basic_format<Ch2, Tr2, Alloc2>&); //流输出
}; // class basic_format
  • str: 返回format对象内部已经格式化号的字符串
  • size : 返回format对象格式化号的字符串长度,可以直接从str()返回的string.size()
  • parse :重新格式化,清空format对象内部缓存,改用一个新的格式化字符串,如果只是想清空缓存,则使用clear(),它把format对象恢复到初始化状态
  • operator% : 可以接受待格式化的任意参数,%输入的参数个数必须等于格式化字符串中要求的个数,过多或者过少都会抛出异常
  • operator<<:重载流输入操作符,可以直接输出格式化好的字符串

不过要注意的是,透过operator%传给boost::format对象的变量是会储存在对象内部的,所以可以分批的传入变数;但是如果变量的数量不符合的话,在编译阶段虽然不会出现错误,可是到了执行阶段还是会让程序崩溃,所以在使用上必须小心一点。 不过,在有输出后,是可以再重新传入新的变量、重复使用同一个boost::format 对象的。

格式化语法

format基本继承了printf的格式化语法,格式化选项以%开始,后面是格式规则

%[标志][输出最少宽度][.精度][长度]类型

详细请参考printf格式输出

除了支持printf格式化外,还新增了格式:

  • %|spec| :与printf格式选项功能相同,但是两边增加竖线分隔,更好的区分格式化选项与普通字符
  • %N% :标记第N个参数,相当于占位符,不带任何其他格式化的选项

通过以下使用竖线分隔,更加清楚明了格式化参数

format fmt("%05d\n%-8.3f\n% 10s\n%05X\n");
format fmt("%|05d|\n%|-8.3f|\n%| 10s|\n%|05X|\n");

使用样例

#include <boost/format.hpp>
#include <iostream>
using namespace std;
using namespace boost;

int main()
{
    cout << "-------------"<<endl;
    format fmt("%d + %d = %d");
    fmt % 2 % 3 % 5;
    cout << fmt.str() << endl;

    cout << "-------------"<<endl;
    format fmt2("%05d\n%-8.3f\n% 10s\n%05X\n");
    fmt2 % 123;
    fmt2 % 456.7;
    fmt2 %"boost" % 100;
    cout << fmt2 << endl;

    cout << "-------------"<<endl;
    cout << boost::format("x=%1%,  y=%2% ,z= %3%") % "format" % 40.2 % 134 << endl;
    cout << "-------------"<<endl;
    return 0;
}

Output:

-------------
2 + 3 = 5
-------------
00123
456.700
     boost
00064

-------------
x=format,  y=40.2 ,z= 134
-------------
#pragma once #include "SERializableClassTest.h" #include <QJsonObject> #include <QJsonDocument> #include "MTFDataDefine.h" #include <QCoreApplication> #include <QDebug> #include "LogTools.h" extern Loger m_gLog; class ConfigItem : public SerializableAssist { Q_OBJECT Q_PROPERTY(QString Alias MEMBER m_alias) Q_PROPERTY(QString Description MEMBER m_description) Q_PROPERTY(QString Value MEMBER m_value) public: explicit ConfigItem(QObject* parent = nullptr) : SerializableAssist(parent) {} // 添加 bool 值转换方法 bool toBool() const { return stringToBool(m_value); } void setValue(bool value) { m_value = value ? "true" : "false"; } // 安全的字符串到 bool 转换 static bool stringToBool(const QString& str) { QString cleanStr = str.trimmed().toLower(); if (cleanStr == "true" || cleanStr == "1" || cleanStr == "on" || cleanStr == "yes") { return true; } if (cleanStr == "false" || cleanStr == "0" || cleanStr == "off" || cleanStr == "no") { return false; } qWarning() << "Invalid boolean string:" << str << "Using default value (false)"; return false; } public: QString m_alias; QString m_description; QString m_value; }; // 基础树形结构类 class BasicTree : public SerializableAssist { Q_OBJECT Q_PROPERTY(ConfigItem* Time MEMBER m_time) Q_PROPERTY(ConfigItem* TrayName MEMBER m_trayName) Q_PROPERTY(ConfigItem* ConfigFile MEMBER m_configFile) Q_PROPERTY(ConfigItem* ThroughFocusData MEMBER m_throughFocusData) Q_PROPERTY(ConfigItem* Position MEMBER m_position) Q_PROPERTY(ConfigItem* DriverPos MEMBER m_driverPos) Q_PROPERTY(ConfigItem* Defocus MEMBER m_defocus) public: explicit BasicTree(QObject* parent = nullptr) : SerializableAssist(parent) { initItems(); } // 深拷贝构造函数 BasicTree(const BasicTree& other, QObject* parent = nullptr) : SerializableAssist(parent) { initItems(); copyFrom(other); } BasicTree& operator=(const BasicTree& other) { if (this != &other) { copyFrom(other); } return *this; } ~BasicTree() { clearItems(); } private: void initItems() { m_time = new ConfigItem(this); m_trayName = new ConfigItem(this); m_configFile = new ConfigItem(this); m_throughFocusData = new ConfigItem(this); m_position = new ConfigItem(this); m_driverPos = new ConfigItem(this); m_defocus = new ConfigItem(this); } void clearItems() { // 对象由父对象管理,不需要手动删除 } void copyFrom(const BasicTree& other) { m_time->m_value = other.m_time->m_value; m_trayName->m_value = other.m_trayName->m_value; m_configFile->m_value = other.m_configFile->m_value; m_throughFocusData->m_value = other.m_throughFocusData->m_value; m_position->m_value = other.m_position->m_value; m_driverPos->m_value = other.m_driverPos->m_value; m_defocus->m_value = other.m_defocus->m_value; } public: ConfigItem* m_configFile; ConfigItem* m_defocus; ConfigItem* m_driverPos; ConfigItem* m_position; ConfigItem* m_throughFocusData; ConfigItem* m_time; ConfigItem* m_trayName; }; // 结果树形结构类 class ResultTree : public SerializableAssist { Q_OBJECT Q_PROPERTY(ConfigItem* Final MEMBER m_final) Q_PROPERTY(ConfigItem* MTF MEMBER m_mtf) Q_PROPERTY(ConfigItem* DOF MEMBER m_dof) Q_PROPERTY(ConfigItem* FocalShift MEMBER m_focalShift) Q_PROPERTY(ConfigItem* Astigmatism MEMBER m_astigmatism) Q_PROPERTY(ConfigItem* FOV MEMBER m_fov) Q_PROPERTY(ConfigItem* Average MEMBER m_average) Q_PROPERTY(ConfigItem* Aggregate MEMBER m_aggregate) Q_PROPERTY(ConfigItem* MultiFrqMTF MEMBER m_multiFrqMtf) Q_PROPERTY(ConfigItem* Defocus MEMBER m_defocus) Q_PROPERTY(ConfigItem* FailReason MEMBER m_failReason) public: explicit ResultTree(QObject* parent = nullptr) : SerializableAssist(parent) { initItems(); } ResultTree(const ResultTree& other, QObject* parent = nullptr) : SerializableAssist(parent) { initItems(); copyFrom(other); } ResultTree& operator=(const ResultTree& other) { if (this != &other) { copyFrom(other); } return *this; } ~ResultTree() { clearItems(); } private: void initItems() { m_final = new ConfigItem(this); m_mtf = new ConfigItem(this); m_dof = new ConfigItem(this); m_focalShift = new ConfigItem(this); m_astigmatism = new ConfigItem(this); m_fov = new ConfigItem(this); m_average = new ConfigItem(this); m_aggregate = new ConfigItem(this); m_multiFrqMtf = new ConfigItem(this); m_defocus = new ConfigItem(this); m_failReason = new ConfigItem(this); } void clearItems() { // 对象由父对象管理 } void copyFrom(const ResultTree& other) { m_final->m_value = other.m_final->m_value; m_mtf->m_value = other.m_mtf->m_value; m_dof->m_value = other.m_dof->m_value; m_focalShift->m_value = other.m_focalShift->m_value; m_astigmatism->m_value = other.m_astigmatism->m_value; m_fov->m_value = other.m_fov->m_value; m_average->m_value = other.m_average->m_value; m_aggregate->m_value = other.m_aggregate->m_value; m_multiFrqMtf->m_value = other.m_multiFrqMtf->m_value; m_defocus->m_value = other.m_defocus->m_value; m_failReason->m_value = other.m_failReason->m_value; } public: ConfigItem* m_final; ConfigItem* m_mtf; ConfigItem* m_dof; ConfigItem* m_focalShift; ConfigItem* m_astigmatism; ConfigItem* m_fov; ConfigItem* m_average; ConfigItem* m_aggregate; ConfigItem* m_multiFrqMtf; ConfigItem* m_defocus; ConfigItem* m_failReason; }; // 倾斜树形结构类 class TiltTree : public SerializableAssist { Q_OBJECT Q_PROPERTY(ConfigItem* Azimuth MEMBER m_azimuth) Q_PROPERTY(ConfigItem* Angle MEMBER m_angle) Q_PROPERTY(ConfigItem* Type MEMBER m_type) public: explicit TiltTree(QObject* parent = nullptr) : SerializableAssist(parent) { initItems(); } TiltTree(const TiltTree& other, QObject* parent = nullptr) : SerializableAssist(parent) { initItems(); copyFrom(other); } TiltTree& operator=(const TiltTree& other) { if (this != &other) { copyFrom(other); } return *this; } ~TiltTree() { clearItems(); } private: void initItems() { m_azimuth = new ConfigItem(this); m_angle = new ConfigItem(this); m_type = new ConfigItem(this); } void clearItems() { // 对象由父对象管理 } void copyFrom(const TiltTree& other) { m_azimuth->m_value = other.m_azimuth->m_value; m_angle->m_value = other.m_angle->m_value; m_type->m_value = other.m_type->m_value; } public: ConfigItem* m_azimuth; ConfigItem* m_angle; ConfigItem* m_type; }; // 焦点偏移数据树 class FocalShiftDataTree : public SerializableAssist { Q_OBJECT Q_PROPERTY(ConfigItem* Negative MEMBER m_negative) Q_PROPERTY(ConfigItem* Positive MEMBER m_positive) Q_PROPERTY(ConfigItem* Group MEMBER m_group) Q_PROPERTY(ConfigItem* Cameras MEMBER m_cameras) public: explicit FocalShiftDataTree(QObject* parent = nullptr) : SerializableAssist(parent) { initItems(); } FocalShiftDataTree(const FocalShiftDataTree& other, QObject* parent = nullptr) : SerializableAssist(parent) { initItems(); copyFrom(other); } FocalShiftDataTree& operator=(const FocalShiftDataTree& other) { if (this != &other) { copyFrom(other); } return *this; } ~FocalShiftDataTree() { clearItems(); } private: void initItems() { m_negative = new ConfigItem(this); m_positive = new ConfigItem(this); m_group = new ConfigItem(this); m_cameras = new ConfigItem(this); } void clearItems() { // 对象由父对象管理 } void copyFrom(const FocalShiftDataTree& other) { m_negative->m_value = other.m_negative->m_value; m_positive->m_value = other.m_positive->m_value; m_group->m_value = other.m_group->m_value; m_cameras->m_value = other.m_cameras->m_value; } public: ConfigItem* m_negative; ConfigItem* m_positive; ConfigItem* m_group; ConfigItem* m_cameras; }; // 景深数据树 class DOFDataTree : public SerializableAssist { Q_OBJECT Q_PROPERTY(ConfigItem* DOF MEMBER m_dof) Q_PROPERTY(ConfigItem* Minus MEMBER m_minus) Q_PROPERTY(ConfigItem* Plus MEMBER m_plus) Q_PROPERTY(ConfigItem* Cameras MEMBER m_cameras) Q_PROPERTY(ConfigItem* Group MEMBER m_group) public: explicit DOFDataTree(QObject* parent = nullptr) : SerializableAssist(parent) { initItems(); } DOFDataTree(const DOFDataTree& other, QObject* parent = nullptr) : SerializableAssist(parent) { initItems(); copyFrom(other); } DOFDataTree& operator=(const DOFDataTree& other) { if (this != &other) { copyFrom(other); } return *this; } ~DOFDataTree() { clearItems(); } private: void initItems() { m_dof = new ConfigItem(this); m_minus = new ConfigItem(this); m_plus = new ConfigItem(this); m_cameras = new ConfigItem(this); m_group = new ConfigItem(this); } void clearItems() { // 对象由父对象管理 } void copyFrom(const DOFDataTree& other) { m_dof->m_value = other.m_dof->m_value; m_minus->m_value = other.m_minus->m_value; m_plus->m_value = other.m_plus->m_value; m_cameras->m_value = other.m_cameras->m_value; m_group->m_value = other.m_group->m_value; } public: ConfigItem* m_dof; ConfigItem* m_minus; ConfigItem* m_plus; ConfigItem* m_cameras; ConfigItem* m_group; }; // A2景深数据树 class A2DOFDataTree : public SerializableAssist { Q_OBJECT Q_PROPERTY(ConfigItem* A2DOF MEMBER m_a2dof) Q_PROPERTY(ConfigItem* Minus MEMBER m_minus) Q_PROPERTY(ConfigItem* Plus MEMBER m_plus) Q_PROPERTY(ConfigItem* OnAxis MEMBER m_onAxis) Q_PROPERTY(ConfigItem* OffAxis MEMBER m_offAxis) Q_PROPERTY(ConfigItem* A2DOFAvg MEMBER m_a2dofAvg) Q_PROPERTY(ConfigItem* AvgMinus MEMBER m_avgMinus) Q_PROPERTY(ConfigItem* AvgPlus MEMBER m_avgPlus) Q_PROPERTY(ConfigItem* OnAxisAvg MEMBER m_onAxisAvg) Q_PROPERTY(ConfigItem* OffAxisAvg MEMBER m_offAxisAvg) public: explicit A2DOFDataTree(QObject* parent = nullptr) : SerializableAssist(parent) { initItems(); } A2DOFDataTree(const A2DOFDataTree& other, QObject* parent = nullptr) : SerializableAssist(parent) { initItems(); copyFrom(other); } A2DOFDataTree& operator=(const A2DOFDataTree& other) { if (this != &other) { copyFrom(other); } return *this; } ~A2DOFDataTree() { clearItems(); } private: void initItems() { m_a2dof = new ConfigItem(this); m_minus = new ConfigItem(this); m_plus = new ConfigItem(this); m_onAxis = new ConfigItem(this); m_offAxis = new ConfigItem(this); m_a2dofAvg = new ConfigItem(this); m_avgMinus = new ConfigItem(this); m_avgPlus = new ConfigItem(this); m_onAxisAvg = new ConfigItem(this); m_offAxisAvg = new ConfigItem(this); } void clearItems() { // 对象由父对象管理 } void copyFrom(const A2DOFDataTree& other) { m_a2dof->m_value = other.m_a2dof->m_value; m_minus->m_value = other.m_minus->m_value; m_plus->m_value = other.m_plus->m_value; m_onAxis->m_value = other.m_onAxis->m_value; m_offAxis->m_value = other.m_offAxis->m_value; m_a2dofAvg->m_value = other.m_a2dofAvg->m_value; m_avgMinus->m_value = other.m_avgMinus->m_value; m_avgPlus->m_value = other.m_avgPlus->m_value; m_onAxisAvg->m_value = other.m_onAxisAvg->m_value; m_offAxisAvg->m_value = other.m_offAxisAvg->m_value; } public: ConfigItem* m_a2dof; ConfigItem* m_minus; ConfigItem* m_plus; ConfigItem* m_onAxis; ConfigItem* m_offAxis; ConfigItem* m_a2dofAvg; ConfigItem* m_avgMinus; ConfigItem* m_avgPlus; ConfigItem* m_onAxisAvg; ConfigItem* m_offAxisAvg; }; // 标准树形结构 class NormaltTree : public SerializableAssist { Q_OBJECT Q_PROPERTY(ConfigItem* EFL MEMBER m_efl) Q_PROPERTY(ConfigItem* FFL MEMBER m_ffl) Q_PROPERTY(ConfigItem* MTF MEMBER m_mtf) Q_PROPERTY(ConfigItem* MultiFrqMTF MEMBER m_multiFrqMtf) Q_PROPERTY(ConfigItem* MTFPeaks MEMBER m_mtfPeaks) Q_PROPERTY(ConfigItem* HalfFOV MEMBER m_halfFov) Q_PROPERTY(ConfigItem* FullFOV MEMBER m_fullFov) Q_PROPERTY(TiltTree* Tilt MEMBER m_tilt) Q_PROPERTY(FocalShiftDataTree* FocalShift MEMBER m_focalShift) Q_PROPERTY(ConfigItem* Astigmatism MEMBER m_astigmatism) Q_PROPERTY(ConfigItem* LSFPeaks MEMBER m_lsfPeaks) Q_PROPERTY(DOFDataTree* DOF MEMBER m_dof) Q_PROPERTY(A2DOFDataTree* A2DOF MEMBER m_a2dof) Q_PROPERTY(ConfigItem* SymmetricalGroupMTF MEMBER m_symmetricalGroupMtf) Q_PROPERTY(ConfigItem* SymmetricalFieldMTF MEMBER m_symmetricalFieldMtf) Q_PROPERTY(ConfigItem* MTFAverageAllCameras MEMBER m_mtfAverageAllCameras) Q_PROPERTY(ConfigItem* FieldPositionMTF MEMBER m_fieldPositionMtf) Q_PROPERTY(ConfigItem* FocalShiftSingle MEMBER m_focalShiftSingle) Q_PROPERTY(ConfigItem* FocalShiftGroup MEMBER m_focalShiftGroup) Q_PROPERTY(ConfigItem* FocalShiftAverage MEMBER m_focalShiftAverage) Q_PROPERTY(ConfigItem* PeakRange MEMBER m_peakRange) Q_PROPERTY(ConfigItem* PeakMTF MEMBER m_peakMtf) Q_PROPERTY(ConfigItem* PeakMTFAverage MEMBER m_peakMtfAverage) Q_PROPERTY(ConfigItem* MTFRatio MEMBER m_mtfRatio) public: NormaltTree(QObject* parent = nullptr) : SerializableAssist(parent) { initItems(); } NormaltTree(const NormaltTree& other, QObject* parent = nullptr) : SerializableAssist(parent) { initItems(); copyFrom(other); } NormaltTree& operator=(const NormaltTree& other) { if (this != &other) { copyFrom(other); } return *this; } ~NormaltTree() { clearItems(); } private: void initItems() { m_efl = new ConfigItem(this); m_ffl = new ConfigItem(this); m_mtf = new ConfigItem(this); m_multiFrqMtf = new ConfigItem(this); m_mtfPeaks = new ConfigItem(this); m_halfFov = new ConfigItem(this); m_fullFov = new ConfigItem(this); m_tilt = new TiltTree(this); m_focalShift = new FocalShiftDataTree(this); m_astigmatism = new ConfigItem(this); m_lsfPeaks = new ConfigItem(this); m_dof = new DOFDataTree(this); m_a2dof = new A2DOFDataTree(this); m_symmetricalGroupMtf = new ConfigItem(this); m_symmetricalFieldMtf = new ConfigItem(this); m_mtfAverageAllCameras = new ConfigItem(this); m_fieldPositionMtf = new ConfigItem(this); m_focalShiftSingle = new ConfigItem(this); m_focalShiftGroup = new ConfigItem(this); m_focalShiftAverage = new ConfigItem(this); m_peakRange = new ConfigItem(this); m_peakMtf = new ConfigItem(this); m_peakMtfAverage = new ConfigItem(this); m_mtfRatio = new ConfigItem(this); } void clearItems() { // 所有对象由父对象管理 } void copyFrom(const NormaltTree& other) { // 复制所有简单值 m_efl->m_value = other.m_efl->m_value; m_ffl->m_value = other.m_ffl->m_value; m_mtf->m_value = other.m_mtf->m_value; m_multiFrqMtf->m_value = other.m_multiFrqMtf->m_value; m_mtfPeaks->m_value = other.m_mtfPeaks->m_value; m_halfFov->m_value = other.m_halfFov->m_value; m_fullFov->m_value = other.m_fullFov->m_value; m_astigmatism->m_value = other.m_astigmatism->m_value; m_lsfPeaks->m_value = other.m_lsfPeaks->m_value; m_symmetricalGroupMtf->m_value = other.m_symmetricalGroupMtf->m_value; m_symmetricalFieldMtf->m_value = other.m_symmetricalFieldMtf->m_value; m_mtfAverageAllCameras->m_value = other.m_mtfAverageAllCameras->m_value; m_fieldPositionMtf->m_value = other.m_fieldPositionMtf->m_value; m_focalShiftSingle->m_value = other.m_focalShiftSingle->m_value; m_focalShiftGroup->m_value = other.m_focalShiftGroup->m_value; m_focalShiftAverage->m_value = other.m_focalShiftAverage->m_value; m_peakRange->m_value = other.m_peakRange->m_value; m_peakMtf->m_value = other.m_peakMtf->m_value; m_peakMtfAverage->m_value = other.m_peakMtfAverage->m_value; m_mtfRatio->m_value = other.m_mtfRatio->m_value; // 复制嵌套对象 *m_tilt = *other.m_tilt; *m_focalShift = *other.m_focalShift; *m_dof = *other.m_dof; *m_a2dof = *other.m_a2dof; } public: ConfigItem* m_efl; ConfigItem* m_ffl; ConfigItem* m_mtf; ConfigItem* m_multiFrqMtf; ConfigItem* m_mtfPeaks; ConfigItem* m_halfFov; ConfigItem* m_fullFov; TiltTree* m_tilt; FocalShiftDataTree* m_focalShift; ConfigItem* m_astigmatism; ConfigItem* m_lsfPeaks; DOFDataTree* m_dof; A2DOFDataTree* m_a2dof; ConfigItem* m_symmetricalGroupMtf; ConfigItem* m_symmetricalFieldMtf; ConfigItem* m_mtfAverageAllCameras; ConfigItem* m_fieldPositionMtf; ConfigItem* m_focalShiftSingle; ConfigItem* m_focalShiftGroup; ConfigItem* m_focalShiftAverage; ConfigItem* m_peakRange; ConfigItem* m_peakMtf; ConfigItem* m_peakMtfAverage; ConfigItem* m_mtfRatio; }; // 配置文件项类 class ConfigFileItem : public SerializableAssist { Q_OBJECT Q_PROPERTY(ConfigItem* Barcode MEMBER m_barcode) Q_PROPERTY(ConfigItem* CameraCounts MEMBER m_cameraCounts) Q_PROPERTY(ConfigItem* GroupDefine MEMBER m_groupDefine) Q_PROPERTY(BasicTree* Basic MEMBER m_basic) Q_PROPERTY(ResultTree* Result MEMBER m_result) Q_PROPERTY(NormaltTree* Normal MEMBER m_normal) Q_PROPERTY(ConfigItem* Defocus MEMBER m_defocus) public: ConfigFileItem(QObject* parent = nullptr) : SerializableAssist(parent) { initItems(); } ConfigFileItem(const ConfigFileItem& other) : SerializableAssist(other.parent()) { initItems(); copyFrom(other); } ConfigFileItem& operator=(const ConfigFileItem& other) { if (this != &other) { copyFrom(other); } return *this; } ~ConfigFileItem() { clearItems(); } void serializeToFile(const QString& filePath = "") { QString path = filePath.isEmpty() ? QCoreApplication::applicationDirPath() + "/config.json" : filePath; QJsonObject jsonObject = QJsonObject::fromVariantMap(this->toVariantMap()); QJsonDocument jsonDoc(jsonObject); QByteArray jsonData = jsonDoc.toJson(); QFile file(path); if (file.open(QIODevice::WriteOnly | QIODevice::Text)) { file.write(jsonData); file.close(); } else { qWarning() << "Failed to open file for writing:" << path; m_gLog.RecordLogError("Failed to open file for writing:" + path); } } void deserializeFromFile(const QString& filePath = "") { QString path = filePath.isEmpty() ? QCoreApplication::applicationDirPath() + "/config.json" : filePath; QFile file(path); if (file.open(QIODevice::ReadOnly | QIODevice::Text)) { QByteArray jsonData = file.readAll(); file.close(); QJsonDocument jsonDoc = QJsonDocument::fromJson(jsonData); if (jsonDoc.isNull()) { qWarning() << "Invalid JSON document"; m_gLog.RecordLogError("Invalid JSON document"); return; } QJsonObject jsonObject = jsonDoc.object(); this->fromVariantMap(jsonObject.toVariantMap()); } else { qWarning() << "Failed to open file for reading:" << path; m_gLog.RecordLogError("Failed to open file for reading:" + path); } } void copyToTestData(TestData& testData) { testData.m_bBarcode = m_barcode->toBool(); testData.m_keyBarcode = m_barcode->m_alias; // 复制 BasicTree 数据 testData.Basic.m_bConfigFile = m_basic->m_configFile->toBool(); testData.Basic.m_keyConfigFile = m_basic->m_configFile->m_alias; testData.Basic.m_bDefocus = m_basic->m_defocus->toBool(); testData.Basic.m_keyDefocus = m_basic->m_defocus->m_alias; testData.Basic.m_bDriverPos = m_basic->m_driverPos->toBool(); testData.Basic.m_keyDriverPos = m_basic->m_driverPos->m_alias; testData.Basic.m_bPosition = m_basic->m_position->toBool(); testData.Basic.m_keyPosition = m_basic->m_position->m_alias; testData.Basic.m_bThroughFocusData = m_basic->m_throughFocusData->toBool(); testData.Basic.m_keyThroughFocusData = m_basic->m_throughFocusData->m_alias; testData.Basic.m_bTime = m_basic->m_time->toBool(); testData.Basic.m_keyTime = m_basic->m_time->m_alias; testData.Basic.m_bTrayName = m_basic->m_trayName->toBool(); testData.Basic.m_keyTrayName = m_basic->m_trayName->m_alias; testData.m_bCameraCounts = m_cameraCounts->toBool(); testData.m_keyCameraCounts = m_cameraCounts->m_alias; testData.m_bDefocus = m_defocus->toBool(); testData.m_keyDefocus = m_defocus->m_alias; testData.m_bGroupDefine = m_groupDefine->toBool(); testData.m_keyGroupDefine = m_groupDefine->m_alias; // 复制 NormalTree 数据 testData.Normal.A2DOF.m_bA2DOF = m_normal->m_a2dof->m_a2dof->toBool(); testData.Normal.A2DOF.m_keyA2DOF = m_normal->m_a2dof->m_a2dof->m_alias; testData.Normal.A2DOF.m_bMinus = m_normal->m_a2dof->m_minus->toBool(); testData.Normal.A2DOF.m_keyMinus = m_normal->m_a2dof->m_minus->m_alias; testData.Normal.A2DOF.m_bPlus = m_normal->m_a2dof->m_plus->toBool(); testData.Normal.A2DOF.m_keyPlus = m_normal->m_a2dof->m_plus->m_alias; testData.Normal.A2DOF.m_bOnAxis = m_normal->m_a2dof->m_onAxis->toBool(); testData.Normal.A2DOF.m_keyOnAxis = m_normal->m_a2dof->m_onAxis->m_alias; testData.Normal.A2DOF.m_bOffAxis = m_normal->m_a2dof->m_offAxis->toBool(); testData.Normal.A2DOF.m_keyOffAxis = m_normal->m_a2dof->m_offAxis->m_alias; testData.Normal.A2DOF.m_bA2DOFAvg = m_normal->m_a2dof->m_a2dofAvg->toBool(); testData.Normal.A2DOF.m_keyA2DOFAvg = m_normal->m_a2dof->m_a2dofAvg->m_alias; testData.Normal.A2DOF.m_bAvgMinus = m_normal->m_a2dof->m_avgMinus->toBool(); testData.Normal.A2DOF.m_keyAvgMinus = m_normal->m_a2dof->m_avgMinus->m_alias; testData.Normal.A2DOF.m_bAvgPlus = m_normal->m_a2dof->m_avgPlus->toBool(); testData.Normal.A2DOF.m_keyAvgPlus = m_normal->m_a2dof->m_avgPlus->m_alias; testData.Normal.A2DOF.m_bOnAxisAvg = m_normal->m_a2dof->m_onAxisAvg->toBool(); testData.Normal.A2DOF.m_keyOnAxisAvg = m_normal->m_a2dof->m_onAxisAvg->m_alias; testData.Normal.A2DOF.m_bOffAxisAvg = m_normal->m_a2dof->m_offAxisAvg->toBool(); testData.Normal.A2DOF.m_keyOffAxisAvg = m_normal->m_a2dof->m_offAxisAvg->m_alias; testData.Normal.m_bAstigmatism = m_normal->m_astigmatism->toBool(); testData.Normal.m_keyAstigmatism = m_normal->m_astigmatism->m_alias; testData.Normal.DOF.m_bCameras = m_normal->m_dof->m_cameras->toBool(); testData.Normal.DOF.m_keyCameras = m_normal->m_dof->m_cameras->m_alias; testData.Normal.DOF.m_bDOF = m_normal->m_dof->m_dof->toBool(); testData.Normal.DOF.m_keyDOF = m_normal->m_dof->m_dof->m_alias; testData.Normal.DOF.m_bGroup = m_normal->m_dof->m_group->toBool(); testData.Normal.DOF.m_keyGroup = m_normal->m_dof->m_group->m_alias; testData.Normal.DOF.m_bMinus = m_normal->m_dof->m_minus->toBool(); testData.Normal.DOF.m_keyMinus = m_normal->m_dof->m_minus->m_alias; testData.Normal.DOF.m_bPlus = m_normal->m_dof->m_plus->toBool(); testData.Normal.DOF.m_keyPlus = m_normal->m_dof->m_plus->m_alias; testData.Normal.m_bEFL = m_normal->m_efl->toBool(); testData.Normal.m_keyEFL = m_normal->m_efl->m_alias; testData.Normal.m_bFFL = m_normal->m_ffl->toBool(); testData.Normal.m_keyFFL = m_normal->m_ffl->m_alias; testData.Normal.m_bFieldPositionMTF = m_normal->m_fieldPositionMtf->toBool(); testData.Normal.m_keyFieldPositionMTF = m_normal->m_fieldPositionMtf->m_alias; testData.Normal.FocalShift.m_bCameras = m_normal->m_focalShift->m_cameras->toBool(); testData.Normal.FocalShift.m_keyCameras = m_normal->m_focalShift->m_cameras->m_alias; testData.Normal.FocalShift.m_bGroup = m_normal->m_focalShift->m_group->toBool(); testData.Normal.FocalShift.m_keyGroup = m_normal->m_focalShift->m_group->m_alias; testData.Normal.FocalShift.m_bNegative = m_normal->m_focalShift->m_negative->toBool(); testData.Normal.FocalShift.m_keyNegative = m_normal->m_focalShift->m_negative->m_alias; testData.Normal.FocalShift.m_bPositive = m_normal->m_focalShift->m_positive->toBool(); testData.Normal.FocalShift.m_keyPositive = m_normal->m_focalShift->m_positive->m_alias; testData.Normal.m_bFocalShiftAverage = m_normal->m_focalShiftAverage->toBool(); testData.Normal.m_keyFocalShiftAverage = m_normal->m_focalShiftAverage->m_alias; testData.Normal.m_bFocalShiftGroup = m_normal->m_focalShiftGroup->toBool(); testData.Normal.m_keyFocalShiftGroup = m_normal->m_focalShiftGroup->m_alias; testData.Normal.m_bFocalShiftSingle = m_normal->m_focalShiftSingle->toBool(); testData.Normal.m_keyFocalShiftSingle = m_normal->m_focalShiftSingle->m_alias; testData.Normal.m_bHalfFOV = m_normal->m_halfFov->toBool(); testData.Normal.m_keyHalfFOV = m_normal->m_halfFov->m_alias; testData.Normal.m_bFullFOV = m_normal->m_fullFov->toBool(); testData.Normal.m_keyFullFOV = m_normal->m_fullFov->m_alias; testData.Normal.m_bLSFPeaks = m_normal->m_lsfPeaks->toBool(); testData.Normal.m_keyLSFPeaks = m_normal->m_lsfPeaks->m_alias; testData.Normal.m_bMTF = m_normal->m_mtf->toBool(); testData.Normal.m_keyMTF = m_normal->m_mtf->m_alias; testData.Normal.m_bMTFAverageAllCameras = m_normal->m_mtfAverageAllCameras->toBool(); testData.Normal.m_keyMTFAverageAllCameras = m_normal->m_mtfAverageAllCameras->m_alias; testData.Normal.m_bMTFPeaks = m_normal->m_mtfPeaks->toBool(); testData.Normal.m_keyMTFPeaks = m_normal->m_mtfPeaks->m_alias; testData.Normal.m_bMTFRatio = m_normal->m_mtfRatio->toBool(); testData.Normal.m_keyMTFRatio = m_normal->m_mtfRatio->m_alias; testData.Normal.m_bMultiFrqMTF = m_normal->m_multiFrqMtf->toBool(); testData.Normal.m_keyMultiFrqMTF = m_normal->m_multiFrqMtf->m_alias; testData.Normal.m_bPeakMTF = m_normal->m_peakMtf->toBool(); testData.Normal.m_keyPeakMTF = m_normal->m_peakMtf->m_alias; testData.Normal.m_bPeakMTFAverage = m_normal->m_peakMtfAverage->toBool(); testData.Normal.m_keyPeakMTFAverage = m_normal->m_peakMtfAverage->m_alias; testData.Normal.m_bPeakRange = m_normal->m_peakRange->toBool(); testData.Normal.m_keyPeakRange = m_normal->m_peakRange->m_alias; testData.Normal.m_bSymmetricalFieldMTF = m_normal->m_symmetricalFieldMtf->toBool(); testData.Normal.m_keySymmetricalFieldMTF = m_normal->m_symmetricalFieldMtf->m_alias; testData.Normal.m_bSymmetricalGroupMTF = m_normal->m_symmetricalGroupMtf->toBool(); testData.Normal.m_keySymmetricalGroupMTF = m_normal->m_symmetricalGroupMtf->m_alias; testData.Normal.Tilt.m_bAngle = m_normal->m_tilt->m_angle->toBool(); testData.Normal.Tilt.m_keyAngle = m_normal->m_tilt->m_angle->m_alias; testData.Normal.Tilt.m_bAzimuth = m_normal->m_tilt->m_azimuth->toBool(); testData.Normal.Tilt.m_keyAzimuth = m_normal->m_tilt->m_azimuth->m_alias; testData.Normal.Tilt.m_bType = m_normal->m_tilt->m_type->toBool(); testData.Normal.Tilt.m_keyType = m_normal->m_tilt->m_type->m_alias; // 复制 ResultTree 数据 testData.Result.m_bAggregate = m_result->m_aggregate->toBool(); testData.Result.m_keyAggregate = m_result->m_aggregate->m_alias; testData.Result.m_bAstigmatism = m_result->m_astigmatism->toBool(); testData.Result.m_keyAstigmatism = m_result->m_astigmatism->m_alias; testData.Result.m_bAverage = m_result->m_average->toBool(); testData.Result.m_keyAverage = m_result->m_average->m_alias; testData.Result.m_bDOF = m_result->m_dof->toBool(); testData.Result.m_keyDOF = m_result->m_dof->m_alias; testData.Result.m_bFOV = m_result->m_fov->toBool(); testData.Result.m_keyFOV = m_result->m_fov->m_alias; testData.Result.m_bFailReason = m_result->m_failReason->toBool(); testData.Result.m_keyFailReason = m_result->m_failReason->m_alias; testData.Result.m_bFinal = m_result->m_final->toBool(); testData.Result.m_keyFinal = m_result->m_final->m_alias; testData.Result.m_bFocalShift = m_result->m_focalShift->toBool(); testData.Result.m_keyFocalShift = m_result->m_focalShift->m_alias; testData.Result.m_bMTF = m_result->m_mtf->toBool(); testData.Result.m_keyMTF = m_result->m_mtf->m_alias; testData.Result.m_bMultiFrqMTF = m_result->m_multiFrqMtf->toBool(); testData.Result.m_keyMultiFrqMTF = m_result->m_multiFrqMtf->m_alias; testData.Result.m_bDefocus = m_result->m_defocus->toBool(); testData.Result.m_keyDefocus = m_result->m_defocus->m_alias; } private: void initItems() { m_barcode = new ConfigItem(this); m_cameraCounts = new ConfigItem(this); m_groupDefine = new ConfigItem(this); m_basic = new BasicTree(this); m_result = new ResultTree(this); m_normal = new NormaltTree(this); m_defocus = new ConfigItem(this); } void clearItems() { // 所有对象由父对象管理 } void copyFrom(const ConfigFileItem& other) { // 复制简单值 m_barcode->m_value = other.m_barcode->m_value; m_cameraCounts->m_value = other.m_cameraCounts->m_value; m_groupDefine->m_value = other.m_groupDefine->m_value; m_defocus->m_value = other.m_defocus->m_value; // 复制嵌套对象 *m_basic = *other.m_basic; *m_result = *other.m_result; *m_normal = *other.m_normal; } public: ConfigItem* m_barcode; ConfigItem* m_cameraCounts; ConfigItem* m_groupDefine; BasicTree* m_basic; ResultTree* m_result; NormaltTree* m_normal; ConfigItem* m_defocus; }; #pragma once #include <QObject> #include <QVector> #include <QMap> #include <QJsonValue> #include <QFile> #include <QVariant> #include <QMetaObject> #include <QJsonArray> #include <QMetaProperty> class SerializableAssist : public QObject { Q_OBJECT public: explicit SerializableAssist(QObject *parent = nullptr) : QObject(parent) {} virtual QVariantMap toVariantMap() const { QVariantMap map; const QMetaObject *metaObject = this->metaObject(); for (int i = 0; i < metaObject->propertyCount(); ++i) { QMetaProperty property = metaObject->property(i); QString propName = property.name(); if (propName == "objectName") { continue; // Skip objectName property } QVariant propValue = this->property(property.name()); if (propValue.canConvert<SerializableAssist *>()) { SerializableAssist *serializableProp = propValue.value<SerializableAssist *>(); if (serializableProp) { map[propName] = serializableProp->toVariantMap(); } } else if (propValue.canConvert<QVector<SerializableAssist *>>()) { QVector<SerializableAssist *> serializableList = propValue.value<QVector<SerializableAssist *>>(); QVariantList variantList; for (SerializableAssist *item : serializableList) { if (item) { variantList.append(item->toVariantMap()); } } map[propName] = variantList; } else { map[propName] = propValue; } } return map; } virtual void fromVariantMap(const QVariantMap &map) { const QMetaObject *metaObject = this->metaObject(); for (int i = 0; i < metaObject->propertyCount(); ++i) { QMetaProperty property = metaObject->property(i); QString propName = property.name(); if (map.contains(propName)) { QVariant propValue = map[propName]; if (property.isWritable()) { int f = propValue.type(); if (propValue.type() == QVariant::Map) { SerializableAssist *child = qvariant_cast<SerializableAssist*>(this->property(property.name())); if (!child) { const QMetaObject *childMetaObject = QMetaType::metaObjectForType(property.userType()); if (childMetaObject) { child = qobject_cast<SerializableAssist*>(childMetaObject->newInstance(Q_ARG(QObject*, this))); this->setProperty(property.name(), QVariant::fromValue(child)); } } if (child) { child->fromVariantMap(propValue.toMap()); } } else if (propValue.type() == QVariant::List) { QVariantList list = propValue.toList(); QVariantList newList; for (const QVariant &item : list) { int tempType = item.type(); if (item.type() == QVariant::Map) { SerializableAssist *child = qvariant_cast<SerializableAssist*>(this->property(property.name())); const QMetaObject *childMetaObject = QMetaType::metaObjectForType(property.userType()); if (childMetaObject) { SerializableAssist *child = qobject_cast<SerializableAssist*>(childMetaObject->newInstance(Q_ARG(QObject*, this))); if (child) { child->fromVariantMap(item.toMap()); //newList.append(child); } } } else if (item.type() == QVariant::List) { newList.append(item); } else { // Assuming the item is already a SerializableAssist object newList.append(item); } } //this->setProperty(property.name(), QVariant::fromValue(newList)); property.write(this, newList); } else { this->setProperty(property.name(), propValue); } } } } } template<typename T> static T* fromJsonFile(const QString &filePath, QObject *parent = nullptr) { static_assert(std::is_base_of<SerializableAssist, T>::value, "T must inherit from Serializable"); QFile file(filePath); if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) { qWarning() << "Failed to open file for reading:" << file.errorString(); return nullptr; } QByteArray jsonData = file.readAll(); file.close(); QJsonDocument jsonDoc = QJsonDocument::fromJson(jsonData); if (jsonDoc.isNull() || !jsonDoc.isObject()) { qWarning() << "Invalid JSON format"; return nullptr; } QJsonObject jsonObject = jsonDoc.object(); T* object = new T(parent); QVariantMap tempMap = jsonObject.toVariantMap(); object->fromVariantMap(jsonObject.toVariantMap()); return object; } template<typename T> static bool toJsonFile(const QString &filePath,T object, QObject *parent = nullptr) { static_assert(std::is_base_of<SerializableAssist, T>::value, "T must inherit from Serializable"); QJsonObject jsonObj; QVariantMap variantMap = object.toVariantMap(); // 特殊处理容器类型和嵌套对象 for (const QString& key : variantMap.keys()) { QVariant variant = variantMap[key]; if (variant.type() == QMetaType::QVariantList) { QVariantList list = variant.toList(); jsonObj[key] = QJsonValue::fromVariant(variantListToJsonArrayN(list)); } else if (variant.canConvert<QVariantMap>()) { QVariantMap nestedMap = variant.toMap(); jsonObj[key] = QJsonObject::fromVariantMap(nestedMap); } else { jsonObj[key] = QJsonValue::fromVariant(variant); } } QJsonDocument jsonDoc(jsonObj); QFile file(filePath); if (!file.open(QIODevice::WriteOnly)) { qCritical() << "Failed to open file for writing:" << file.errorString(); return false; } qint64 bytesWritten = file.write(jsonDoc.toJson()); if (bytesWritten == -1) { qCritical() << "Failed to write JSON data to file:" << file.errorString(); file.close(); return false; } else if (bytesWritten != jsonDoc.toJson().size()) { qWarning() << "Failed to write all JSON data to file, only" << bytesWritten << "bytes were written"; } if (!file.flush()) { qCritical() << "Failed to flush data to file:" << file.errorString(); file.close(); return false; } file.close(); return true; } static QJsonArray variantListToJsonArrayN(const QVariantList& list) { QJsonArray array; for (const QVariant& v : list) { array.append(QJsonValue::fromVariant(v)); } return array; } private: SerializableAssist *createSerializableInstance(const QString &typeName) { const QMetaObject *metaObject = QMetaType::metaObjectForType(QMetaType::type(typeName.toUtf8().constData())); if (metaObject) { return qobject_cast<SerializableAssist *>(metaObject->newInstance()); } return nullptr; } }; // 宏定义用于快速定义可序列化的类 #define DECLARE_SERIALIZABLE_CLASS(ClassName) \ class ClassName : public Serializable { \ Q_OBJECT \ Q_PROPERTY(QString name MEMBER m_name) \ Q_PROPERTY(int age MEMBER m_age) \ Q_PROPERTY(QStringList hobbies MEMBER m_hobbies) \ public: \ explicit ClassName(QObject *parent = nullptr) : Serializable(parent) {} \ private: \ QString m_name; \ int m_age; \ QStringList m_hobbies; \ }; // 定义数据类 改写不用Q_OBJECT
最新发布
09-23
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

iot-genius

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值