gflags中宏定义和模板高手用法

预备知识

宏定义__STDC_FORMAT_MACROS

  • 64位整数,在32位系统中是long long int,在64位系统中是long int
  • 64位整数printf打印,在32位系统中是lld,在64位系统中是ld
  • 跨平台打印方案,printf("%" PRId64 "\n", value) \\long int value(64位) long long int value(32位),PRId64在inttypes.h中定义
  • 这个设计是给c用的,c++定义一个__STDC_FORMAT_MACROS宏显示打开它

宏定义__attribute__((visibility("default")))

https://blog.csdn.net/mutourenzhang/article/details/47803803

  • 用于控制链接库总函数对外是否可见
  • windows下#define GFLAGS_DLL_DECLARE_FLAG __declspec(dllimport)

预处理结果查看

  • gcc -E只预处理不编译

利用宏和模板存储值类型

  • 假设类value_buffer_字段可以存储值;type_字段存储值对应bool,int,string等类型,如何实现
  • 自己经常用的实现方式(构造时显示传入值类型,只有一个构造函数)
#include <iostream>
#include <string>
#include <stdlib.h>
#include <string.h>
#include <assert.h>
#include <stdint.h>
#include <inttypes.h>

using std::cout;
using std::string;

typedef signed char int8;
typedef unsigned char uint8;
typedef int32_t int32;
typedef uint32_t uint32;
typedef int64_t int64;
typedef uint64_t uint64;
typedef std::string clstring;

enum ValueType {
  FV_BOOL = 0,
  FV_INT32 = 1,
  FV_UINT32 = 2,
  FV_INT64 = 3,
  FV_UINT64 = 4,
  FV_DOUBLE = 5,
  FV_STRING = 6,
  FV_MAX_INDEX = 6,
};

#define strto64 strtoll
#define strtou64 strtoull

#define VALUE_AS(type) *reinterpret_cast<type *>(value_buffer_)
#define OTHER_VALUE_AS(fv, type) *reinterpret_cast<type *>(fv.value_buffer_)
#define SET_VALUE_AS(type, value) VALUE_AS(type) = (value)

#ifndef __STDC_FORMAT_MACROS
#define __STDC_FORMAT_MACROS
#endif

class FlagValue {
public:
  FlagValue(void *const valbuf, int8 type, bool transfer_ownership_of_value)
      : value_buffer_(valbuf), type_(type),
        owns_value_(transfer_ownership_of_value) {}
  ~FlagValue() {
    if (!owns_value_) {
      return;
    }
    switch (type_) {
    case FV_BOOL:
      delete reinterpret_cast<bool *>(value_buffer_);
      break;
    case FV_INT32:
      delete reinterpret_cast<int32 *>(value_buffer_);
      break;
    case FV_UINT32:
      delete reinterpret_cast<uint32 *>(value_buffer_);
      break;
    case FV_INT64:
      delete reinterpret_cast<int64 *>(value_buffer_);
      break;
    case FV_UINT64:
      delete reinterpret_cast<uint64 *>(value_buffer_);
      break;
    case FV_DOUBLE:
      delete reinterpret_cast<double *>(value_buffer_);
      break;
    case FV_STRING:
      delete reinterpret_cast<string *>(value_buffer_);
      break;
    }
  }

  bool ParseFrom(const char *value) {
    if (type_ == FV_BOOL) {
      const char *kTrue[] = {"1", "t", "true", "y", "yes"};
      const char *kFalse[] = {"0", "f", "false", "n", "no"};
      for (size_t i = 0; i < sizeof(kTrue) / sizeof(*kTrue); ++i) {
        if (strcasecmp(value, kTrue[i]) == 0) {
          SET_VALUE_AS(bool, true);
          return true;
        } else if (strcasecmp(value, kFalse[i]) == 0) {
          SET_VALUE_AS(bool, false);
          return true;
        }
      }
      return false; // didn't match a legal input

    } else if (type_ == FV_STRING) {
      SET_VALUE_AS(string, value);
      return true;
    }

    // OK, it's likely to be numeric, and we'll be using a strtoXXX method.
    if (value[0] == '\0') // empty-string is only allowed for string type.
      return false;
    char *end;
    // Leading 0x puts us in base 16.  But leading 0 does not put us in base 8!
    // It caused too many bugs when we had that behavior.
    int base = 10; // by default
    if (value[0] == '0' && (value[1] == 'x' || value[1] == 'X'))
      base = 16;
    errno = 0;

    switch (type_) {
    case FV_INT32: {
      const int64 r = strto64(value, &end, base);
      if (errno || end != value + strlen(value))
        return false;                 // bad parse
      if (static_cast<int32>(r) != r) // worked, but number out of range
        return false;
      SET_VALUE_AS(int32, static_cast<int32>(r));
      return true;
    }
    case FV_UINT32: {
      while (*value == ' ')
        value++;
      if (*value == '-')
        return false; // negative number
      const uint64 r = strtou64(value, &end, base);
      if (errno || end != value + strlen(value))
        return false;                  // bad parse
      if (static_cast<uint32>(r) != r) // worked, but number out of range
        return false;
      SET_VALUE_AS(uint32, static_cast<uint32>(r));
      return true;
    }
    case FV_INT64: {
      const int64 r = strto64(value, &end, base);
      if (errno || end != value + strlen(value))
        return false; // bad parse
      SET_VALUE_AS(int64, r);
      return true;
    }
    case FV_UINT64: {
      while (*value == ' ')
        value++;
      if (*value == '-')
        return false; // negative number
      const uint64 r = strtou64(value, &end, base);
      if (errno || end != value + strlen(value))
        return false; // bad parse
      SET_VALUE_AS(uint64, r);
      return true;
    }
    case FV_DOUBLE: {
      const double r = strtod(value, &end);
      if (errno || end != value + strlen(value))
        return false; // bad parse
      SET_VALUE_AS(double, r);
      return true;
    }
    default: {
      assert(false); // unknown type
      return false;
    }
    }
  }

  string ToString() const {
    char intbuf[64]; // enough to hold even the biggest number
    switch (type_) {
    case FV_BOOL:
      return VALUE_AS(bool) ? "true" : "false";
    case FV_INT32:
      snprintf(intbuf, sizeof(intbuf), "%" PRId32, VALUE_AS(int32));
      return intbuf;
    case FV_UINT32:
      snprintf(intbuf, sizeof(intbuf), "%" PRIu32, VALUE_AS(uint32));
      return intbuf;
    case FV_INT64:
      snprintf(intbuf, sizeof(intbuf), "%" PRId64, VALUE_AS(int64));
      return intbuf;
    case FV_UINT64:
      snprintf(intbuf, sizeof(intbuf), "%" PRIu64, VALUE_AS(uint64));
      return intbuf;
    case FV_DOUBLE:
      snprintf(intbuf, sizeof(intbuf), "%.17g", VALUE_AS(double));
      return intbuf;
    case FV_STRING:
      return VALUE_AS(string);
    default:
      assert(false);
      return ""; // unknown type
    }
  }

  ValueType Type() const { return static_cast<ValueType>(type_); }

private:
  void *const value_buffer_; // points to the buffer holding our data
  const int8 type_;          // how to interpret value_
  const bool owns_value_;    // whether to free value on destruct
};

int main(int argc, char **argv) {
  int val1 = 30;
  FlagValue value1(&val1, FV_INT32, false);
  std::cout << value1.ToString() << std::endl;

  string val2 = "test!!!";
  FlagValue value2(&val2, FV_STRING, false);
  std::cout << value2.ToString() << std::endl;
    
  return 0;
}
  • 看Google的gflags源码种利用构造函数模板实现
#include <iostream>
#include <string>
#include <stdlib.h>
#include <string.h>
#include <assert.h>
#include <stdint.h>
#include <inttypes.h>

using std::cout;
using std::string;

typedef signed char int8;
typedef unsigned char uint8;
typedef int32_t int32;
typedef uint32_t uint32;
typedef int64_t int64;
typedef uint64_t uint64;
typedef std::string clstring;

enum ValueType {
  FV_BOOL = 0,
  FV_INT32 = 1,
  FV_UINT32 = 2,
  FV_INT64 = 3,
  FV_UINT64 = 4,
  FV_DOUBLE = 5,
  FV_STRING = 6,
  FV_MAX_INDEX = 6,
};

#define strto64 strtoll
#define strtou64 strtoull

#define VALUE_AS(type) *reinterpret_cast<type *>(value_buffer_)
#define OTHER_VALUE_AS(fv, type) *reinterpret_cast<type *>(fv.value_buffer_)
#define SET_VALUE_AS(type, value) VALUE_AS(type) = (value)

#ifndef __STDC_FORMAT_MACROS
#define __STDC_FORMAT_MACROS
#endif

template <typename FlagType> struct FlagValueTraits;

// Map the given C++ type to a value of the ValueType enum at compile time.
#define DEFINE_FLAG_TRAITS(type, value)                                        \
  template <> struct FlagValueTraits<type> {                                   \
    static const ValueType kValueType = value;                                 \
  }

DEFINE_FLAG_TRAITS(bool, ValueType::FV_BOOL);
DEFINE_FLAG_TRAITS(int32, ValueType::FV_INT32);
DEFINE_FLAG_TRAITS(uint32, ValueType::FV_UINT32);
DEFINE_FLAG_TRAITS(int64, ValueType::FV_INT64);
DEFINE_FLAG_TRAITS(uint64, ValueType::FV_UINT64);
DEFINE_FLAG_TRAITS(double, ValueType::FV_DOUBLE);
DEFINE_FLAG_TRAITS(std::string, ValueType::FV_STRING);

#undef DEFINE_FLAG_TRAITS

class FlagValue {
public:
  template <typename FlagType>
  FlagValue(FlagType *valbuf, bool transfer_ownership_of_value)
      : value_buffer_(valbuf), type_(FlagValueTraits<FlagType>::kValueType),
        owns_value_(transfer_ownership_of_value) {}

  ~FlagValue() {
    if (!owns_value_) {
      return;
    }
    switch (type_) {
    case FV_BOOL:
      delete reinterpret_cast<bool *>(value_buffer_);
      break;
    case FV_INT32:
      delete reinterpret_cast<int32 *>(value_buffer_);
      break;
    case FV_UINT32:
      delete reinterpret_cast<uint32 *>(value_buffer_);
      break;
    case FV_INT64:
      delete reinterpret_cast<int64 *>(value_buffer_);
      break;
    case FV_UINT64:
      delete reinterpret_cast<uint64 *>(value_buffer_);
      break;
    case FV_DOUBLE:
      delete reinterpret_cast<double *>(value_buffer_);
      break;
    case FV_STRING:
      delete reinterpret_cast<string *>(value_buffer_);
      break;
    }
  }

  bool ParseFrom(const char *value) {
    if (type_ == FV_BOOL) {
      const char *kTrue[] = {"1", "t", "true", "y", "yes"};
      const char *kFalse[] = {"0", "f", "false", "n", "no"};
      for (size_t i = 0; i < sizeof(kTrue) / sizeof(*kTrue); ++i) {
        if (strcasecmp(value, kTrue[i]) == 0) {
          SET_VALUE_AS(bool, true);
          return true;
        } else if (strcasecmp(value, kFalse[i]) == 0) {
          SET_VALUE_AS(bool, false);
          return true;
        }
      }
      return false; // didn't match a legal input

    } else if (type_ == FV_STRING) {
      SET_VALUE_AS(string, value);
      return true;
    }

    // OK, it's likely to be numeric, and we'll be using a strtoXXX method.
    if (value[0] == '\0') // empty-string is only allowed for string type.
      return false;
    char *end;
    // Leading 0x puts us in base 16.  But leading 0 does not put us in base 8!
    // It caused too many bugs when we had that behavior.
    int base = 10; // by default
    if (value[0] == '0' && (value[1] == 'x' || value[1] == 'X'))
      base = 16;
    errno = 0;

    switch (type_) {
    case FV_INT32: {
      const int64 r = strto64(value, &end, base);
      if (errno || end != value + strlen(value))
        return false;                 // bad parse
      if (static_cast<int32>(r) != r) // worked, but number out of range
        return false;
      SET_VALUE_AS(int32, static_cast<int32>(r));
      return true;
    }
    case FV_UINT32: {
      while (*value == ' ')
        value++;
      if (*value == '-')
        return false; // negative number
      const uint64 r = strtou64(value, &end, base);
      if (errno || end != value + strlen(value))
        return false;                  // bad parse
      if (static_cast<uint32>(r) != r) // worked, but number out of range
        return false;
      SET_VALUE_AS(uint32, static_cast<uint32>(r));
      return true;
    }
    case FV_INT64: {
      const int64 r = strto64(value, &end, base);
      if (errno || end != value + strlen(value))
        return false; // bad parse
      SET_VALUE_AS(int64, r);
      return true;
    }
    case FV_UINT64: {
      while (*value == ' ')
        value++;
      if (*value == '-')
        return false; // negative number
      const uint64 r = strtou64(value, &end, base);
      if (errno || end != value + strlen(value))
        return false; // bad parse
      SET_VALUE_AS(uint64, r);
      return true;
    }
    case FV_DOUBLE: {
      const double r = strtod(value, &end);
      if (errno || end != value + strlen(value))
        return false; // bad parse
      SET_VALUE_AS(double, r);
      return true;
    }
    default: {
      assert(false); // unknown type
      return false;
    }
    }
  }

  string ToString() const {
    char intbuf[64]; // enough to hold even the biggest number
    switch (type_) {
    case FV_BOOL:
      return VALUE_AS(bool) ? "true" : "false";
    case FV_INT32:
      snprintf(intbuf, sizeof(intbuf), "%" PRId32, VALUE_AS(int32));
      return intbuf;
    case FV_UINT32:
      snprintf(intbuf, sizeof(intbuf), "%" PRIu32, VALUE_AS(uint32));
      return intbuf;
    case FV_INT64:
      snprintf(intbuf, sizeof(intbuf), "%" PRId64, VALUE_AS(int64));
      return intbuf;
    case FV_UINT64:
      snprintf(intbuf, sizeof(intbuf), "%" PRIu64, VALUE_AS(uint64));
      return intbuf;
    case FV_DOUBLE:
      snprintf(intbuf, sizeof(intbuf), "%.17g", VALUE_AS(double));
      return intbuf;
    case FV_STRING:
      return VALUE_AS(string);
    default:
      assert(false);
      return ""; // unknown type
    }
  }

  ValueType Type() const { return static_cast<ValueType>(type_); }

private:
  void *const value_buffer_; // points to the buffer holding our data
  const int8 type_;          // how to interpret value_
  const bool owns_value_;    // whether to free value on destruct
};

int main(int argc, char **argv) {
  int val1 = 30;
  FlagValue value1(&val1, false);
  std::cout << value1.ToString() << std::endl;

  string val2 = "test!!!";
  FlagValue value2(&val2, false);
  std::cout << value2.ToString() << std::endl;

  return 0;
}
  • 构造了不同类型的模板构造函数(看编译器对模板的实现,是不是多个构造函数重载,不误导读者),且构造函数参数根据类型传入不同类型FlagValueTraits结构体,根据结构体kValueType值映射类型,和前一种实现显示传入类型并无本质区别
  • Google这种实现好处是什么?
  • 为了更好理解代码,只预编译源代码如下
//g++ main.cc -E -g -o main
//已经去掉了标准库的预处理替换(2万多行)
......

using std::cout;
using std::string;

typedef signed char int8;
typedef unsigned char uint8;
typedef int32_t int32;
typedef uint32_t uint32;
typedef int64_t int64;
typedef uint64_t uint64;
typedef std::string clstring;

enum ValueType {
  FV_BOOL = 0,
  FV_INT32 = 1,
  FV_UINT32 = 2,
  FV_INT64 = 3,
  FV_UINT64 = 4,
  FV_DOUBLE = 5,
  FV_STRING = 6,
  FV_MAX_INDEX = 6,
};

template <typename FlagType> struct FlagValueTraits;

template <> struct FlagValueTraits<bool> {
  static const ValueType kValueType = ValueType::FV_BOOL;
};
template <> struct FlagValueTraits<int32> {
  static const ValueType kValueType = ValueType::FV_INT32;
};
template <> struct FlagValueTraits<uint32> {
  static const ValueType kValueType = ValueType::FV_UINT32;
};
template <> struct FlagValueTraits<int64> {
  static const ValueType kValueType = ValueType::FV_INT64;
};
template <> struct FlagValueTraits<uint64> {
  static const ValueType kValueType = ValueType::FV_UINT64;
};
template <> struct FlagValueTraits<double> {
  static const ValueType kValueType = ValueType::FV_DOUBLE;
};
template <> struct FlagValueTraits<std::string> {
  static const ValueType kValueType = ValueType::FV_STRING;
};

class FlagValue {
public:
  template <typename FlagType>
  FlagValue(FlagType *valbuf, bool transfer_ownership_of_value)
      : value_buffer_(valbuf), type_(FlagValueTraits<FlagType>::kValueType),
        owns_value_(transfer_ownership_of_value) {}

  ~FlagValue() {
    if (!owns_value_) {
      return;
    }
    switch (type_) {
    case FV_BOOL:
      delete reinterpret_cast<bool *>(value_buffer_);
      break;
    case FV_INT32:
      delete reinterpret_cast<int32 *>(value_buffer_);
      break;
    case FV_UINT32:
      delete reinterpret_cast<uint32 *>(value_buffer_);
      break;
    case FV_INT64:
      delete reinterpret_cast<int64 *>(value_buffer_);
      break;
    case FV_UINT64:
      delete reinterpret_cast<uint64 *>(value_buffer_);
      break;
    case FV_DOUBLE:
      delete reinterpret_cast<double *>(value_buffer_);
      break;
    case FV_STRING:
      delete reinterpret_cast<string *>(value_buffer_);
      break;
    }
  }

  bool ParseFrom(const char *value) {
    if (type_ == FV_BOOL) {
      const char *kTrue[] = {"1", "t", "true", "y", "yes"};
      const char *kFalse[] = {"0", "f", "false", "n", "no"};
      for (size_t i = 0; i < sizeof(kTrue) / sizeof(*kTrue); ++i) {
        if (strcasecmp(value, kTrue[i]) == 0) {
          *reinterpret_cast<bool *>(value_buffer_) = (true);
          return true;
        } else if (strcasecmp(value, kFalse[i]) == 0) {
          *reinterpret_cast<bool *>(value_buffer_) = (false);
          return true;
        }
      }
      return false;

    } else if (type_ == FV_STRING) {
      *reinterpret_cast<string *>(value_buffer_) = (value);
      return true;
    }

    if (value[0] == '\0')
      return false;
    char *end;

    int base = 10;
    if (value[0] == '0' && (value[1] == 'x' || value[1] == 'X'))
      base = 16;

# 125 "/home/niu/cplusstudy/aa.cc" 3 4
    (*__errno_location())
# 125 "/home/niu/cplusstudy/aa.cc"
        = 0;

    switch (type_) {
    case FV_INT32: {
      const int64 r = strtoll(value, &end, base);
      if (
# 130 "/home/niu/cplusstudy/aa.cc" 3 4
          (*__errno_location())
# 130 "/home/niu/cplusstudy/aa.cc"
          || end != value + strlen(value))
        return false;
      if (static_cast<int32>(r) != r)
        return false;
      *reinterpret_cast<int32 *>(value_buffer_) = (static_cast<int32>(r));
      return true;
    }
    case FV_UINT32: {
      while (*value == ' ')
        value++;
      if (*value == '-')
        return false;
      const uint64 r = strtoull(value, &end, base);
      if (
# 143 "/home/niu/cplusstudy/aa.cc" 3 4
          (*__errno_location())
# 143 "/home/niu/cplusstudy/aa.cc"
          || end != value + strlen(value))
        return false;
      if (static_cast<uint32>(r) != r)
        return false;
      *reinterpret_cast<uint32 *>(value_buffer_) = (static_cast<uint32>(r));
      return true;
    }
    case FV_INT64: {
      const int64 r = strtoll(value, &end, base);
      if (
# 152 "/home/niu/cplusstudy/aa.cc" 3 4
          (*__errno_location())
# 152 "/home/niu/cplusstudy/aa.cc"
          || end != value + strlen(value))
        return false;
      *reinterpret_cast<int64 *>(value_buffer_) = (r);
      return true;
    }
    case FV_UINT64: {
      while (*value == ' ')
        value++;
      if (*value == '-')
        return false;
      const uint64 r = strtoull(value, &end, base);
      if (
# 163 "/home/niu/cplusstudy/aa.cc" 3 4
          (*__errno_location())
# 163 "/home/niu/cplusstudy/aa.cc"
          || end != value + strlen(value))
        return false;
      *reinterpret_cast<uint64 *>(value_buffer_) = (r);
      return true;
    }
    case FV_DOUBLE: {
      const double r = strtod(value, &end);
      if (
# 170 "/home/niu/cplusstudy/aa.cc" 3 4
          (*__errno_location())
# 170 "/home/niu/cplusstudy/aa.cc"
          || end != value + strlen(value))
        return false;
      *reinterpret_cast<double *>(value_buffer_) = (r);
      return true;
    }
    default: {

# 176 "/home/niu/cplusstudy/aa.cc" 3 4
      ((
# 176 "/home/niu/cplusstudy/aa.cc"
           false
# 176 "/home/niu/cplusstudy/aa.cc" 3 4
           )
           ? static_cast<void>(0)
           : __assert_fail(
# 176 "/home/niu/cplusstudy/aa.cc"
                 "false"
# 176 "/home/niu/cplusstudy/aa.cc" 3 4
                 ,
                 "/home/niu/cplusstudy/aa.cc", 176, __PRETTY_FUNCTION__))
# 176 "/home/niu/cplusstudy/aa.cc"
          ;
      return false;
    }
    }
  }

  string ToString() const {
    char intbuf[64];
    switch (type_) {
    case FV_BOOL:
      return *reinterpret_cast<bool *>(value_buffer_) ? "true" : "false";
    case FV_INT32:
      snprintf(intbuf, sizeof(intbuf),
               "%"
# 188 "/home/niu/cplusstudy/aa.cc" 3 4
               "d"
# 188 "/home/niu/cplusstudy/aa.cc"
               ,
               *reinterpret_cast<int32 *>(value_buffer_));
      return intbuf;
    case FV_UINT32:
      snprintf(intbuf, sizeof(intbuf),
               "%"
# 191 "/home/niu/cplusstudy/aa.cc" 3 4
               "u"
# 191 "/home/niu/cplusstudy/aa.cc"
               ,
               *reinterpret_cast<uint32 *>(value_buffer_));
      return intbuf;
    case FV_INT64:
      snprintf(intbuf, sizeof(intbuf),
               "%"
# 194 "/home/niu/cplusstudy/aa.cc" 3 4
               "l"
               "d"
# 194 "/home/niu/cplusstudy/aa.cc"
               ,
               *reinterpret_cast<int64 *>(value_buffer_));
      return intbuf;
    case FV_UINT64:
      snprintf(intbuf, sizeof(intbuf),
               "%"
# 197 "/home/niu/cplusstudy/aa.cc" 3 4
               "l"
               "u"
# 197 "/home/niu/cplusstudy/aa.cc"
               ,
               *reinterpret_cast<uint64 *>(value_buffer_));
      return intbuf;
    case FV_DOUBLE:
      snprintf(intbuf, sizeof(intbuf), "%.17g",
               *reinterpret_cast<double *>(value_buffer_));
      return intbuf;
    case FV_STRING:
      return *reinterpret_cast<string *>(value_buffer_);
    default:

# 205 "/home/niu/cplusstudy/aa.cc" 3 4
      ((
# 205 "/home/niu/cplusstudy/aa.cc"
           false
# 205 "/home/niu/cplusstudy/aa.cc" 3 4
           )
           ? static_cast<void>(0)
           : __assert_fail(
# 205 "/home/niu/cplusstudy/aa.cc"
                 "false"
# 205 "/home/niu/cplusstudy/aa.cc" 3 4
                 ,
                 "/home/niu/cplusstudy/aa.cc", 205, __PRETTY_FUNCTION__))
# 205 "/home/niu/cplusstudy/aa.cc"
          ;
      return "";
    }
  }

  ValueType Type() const { return static_cast<ValueType>(type_); }

private:
  void *const value_buffer_;
  const int8 type_;
  const bool owns_value_;
};

int main(int argc, char **argv) {
  int val1 = 30;
  FlagValue value1(&val1, false);
  std::cout << value1.ToString() << std::endl;

  string val2 = "test!!!";
  FlagValue value2(&val2, false);
  std::cout << value2.ToString() << std::endl;

  return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值