1.前言
字符串格式化是非常广泛的需求。对比C++格式化输出的几种方法:
(1)printf 类格式化输出
C 标准库中的 printf
类函数, 实际上是非常广泛使用的。他们主要的问题是不安全 (既不类型安全, 也可能造成缓冲区的溢出), 以及无法拓展 (无法兼容更多类型)。
(2)C++ 的流式 I/O
cout
之类的做到了类型安全, 也做到了拓展性, 但使用起来比较麻烦。而就其实现上来说, 效率也并不见得高。
如果想输出一个浮点数并保留小数点之后 3 位, printf
只需要 %.3f
, 而 cout
需要:
1 2 3
| #include <iomanip> #include <iostream> std::cout << std::setiosflags(std::ios::fixed) << std::setprecision(3) << 0.;
|
这使用起来很麻烦. 更不要说如果你想格式化 5 个参数, 就可能要输入 10 个 <<
操作符了。
(3)fmtlib/std::format
鉴于传统C++格式化方法的局限性,fmtlib
被开发出来,并在C++20标准中被引入为std::format
库,旨在提供一种更现代、更安全、更灵活的格式化方法。引入它们的主要动机包括:
- 提高可读性:
fmtlib/std::format
采用了一种更加简洁、易懂的语法,使得格式化字符串更具可读性。 - 增强类型安全:
fmtlib/std::format
在编译期间就可以检查参数类型的正确性,从而降低运行时错误的风险。 - 扩展功能:
fmtlib/std::format
支持自定义类型的格式化,同时兼容宽字符和多字节字符集。这使得开发人员能够满足更为复杂的格式化需求。 - 性能优化:
fmtlib/std::format
设计时充分考虑了性能问题,相比传统的格式化方法,它在许多场景下能够提供更高的性能。
总之,fmtlib/std::format
旨在解决传统C++格式化方法的问题,并为开发者提供一种更现代、更安全、更灵活的格式化工具。
LString::format基于LarkSDK的字符串类LString,旨在提供一种对标fmtlib/std::format的现代的C++字符串格式化方法。
字符串格式化的最主要过程就是处理占位符,LString中的segmentReplace方法为处理占位符提供了极大的方便:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
|
struct ReplaceSlotStruct { int pos = 0; int len = 0; int ref = 0; ReplaceSlotStruct(int pos, int len, int ref) : pos(pos), len(len), ref(ref) {} };
void segmentReplace(const LVector<ReplaceSlotStruct> &slots, const LVector<LString> &values);
|
segmentReplace方法实现”片段替换”,它将待替换表values中的每个LString片段替换到this对象的“格式化槽”slots中的对应位置(由ReplaceSlotStruct指定)。
所以LString在处理格式化字符串时,只要在找到每个占位符作为slots、获取每个输入参数转换为LString作为values后调用segmentReplace方法即可。
format函数原型如下:
1 2 3 4 5 6 7
|
template <typename... Args> LString format(const Args &...args);
|
2.1 获取输入参数
getArg方法获取输入参数。
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| template <typename T> LVector<LString> LString::getArg(const T &t) { LVector<LString> res; res.append(LString::fromValue(t)); return res; } template <typename T, typename... Args> LVector<LString> LString::getArg(const T &t, const Args &...args) { LVector<LString> res = getArg(args...); res.prepend(LString::fromValue(t)); return res; }
|
getArg通过递归调用的方式,将形参包“…args”展开并保存到LVector中返回。
2.2 输入参数统一成LString
fromValue方法在getArg方法当中被调用,将输入参数统一成LString。
1 2 3 4 5 6 7 8 9
|
template <typename T> static LString fromValue(const T &value);
|
fromValue方法对以下类型实现了特化:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122
| template <typename T> inline LString LString::fromValue(const T &value) { return LString(); }
template <> inline LString LString::fromValue(const bool &value) { if (value) { return LString("True"); } else { return LString("False"); } }
template <> inline LString LString::fromValue(const char &value) { return LString(LChar(value)); }
template <> inline LString LString::fromValue(const unsigned char &value) { return LString(LChar(value)); }
template <> inline LString LString::fromValue(const short &value) { return LString::fromInt(value); }
template <> inline LString LString::fromValue(const unsigned short &value) { return LString::fromInt(value); }
template <> inline LString LString::fromValue(const int &value) { return LString::fromInt(value); }
template <> inline LString LString::fromValue(const unsigned int &value) { return LString::fromInt(value); }
template <> inline LString LString::fromValue(const long &value) { return LString::fromInt(value); }
template <> inline LString LString::fromValue(const unsigned long &value) { return LString::fromInt(value); }
template <> inline LString LString::fromValue(const long long &value) { return LString::fromInt(value); }
template <> inline LString LString::fromValue(const unsigned long long &value) { return LString::fromInt(value); }
template <> inline LString LString::fromValue(const float &value) { return LString::fromReal(value); }
template <> inline LString LString::fromValue(const double &value) { return LString::fromReal(value); }
template <> inline LString LString::fromValue(const long double &value) { return LString::fromReal(value); }
template <> inline LString LString::fromValue(const LString &value) { return LString(value); }
template <> inline LString LString::fromValue(const LChar &value) { return LString(value); }
template <typename T> inline LString LString::fromValue(T *value) { return LString("0x") << LString::fromInt((unsigned long long)value, 16); }
template <> inline LString LString::fromValue(const char *value) { return LString(value); }
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107
| template <typename... Args> LString LString::format(const Args &...args) {
static const auto openBrace = LChar("{").unicode(); static const auto closeBrace = LChar("}").unicode(); constexpr uint32_t slotStarted = 0b1; constexpr uint32_t slotFinish = ~slotStarted; constexpr uint32_t firstSlotPassed = 0b1 << 1; constexpr uint32_t isOrderMode = 0b1 << 2; static LVector<LString> initArgs{LString(), LString("{"), LString("}")}; constexpr size_t nullSlot = 0; constexpr size_t openBraceSlot = 1; constexpr size_t closeBraceSlot = 2; constexpr int initArgNum = 3; LVector<LString> strArgs = getArg(args...); LVector<LString> resultArgs = initArgs; LVector<LString::ReplaceSlotStruct> fmtSlots; FormatContextStruct context; for (size_t i = 0; i < size(); i++) { auto nowChar = LVector<unsigned short>::at(i); if (nowChar == openBrace) { if (i + 1 >= size()) { break; } if (LVector<unsigned short>::at(i + 1) == openBrace) { if (!(context.state & slotStarted)) { fmtSlots.append(LString::ReplaceSlotStruct(i, 2, openBraceSlot)); } i++; } else { if (context.state & slotStarted) { } else { context.state |= slotStarted; context.slotStart = i; } } } else if (nowChar == closeBrace) { if (i + 1 >= size()) { break; } if (LVector<unsigned short>::at(i + 1) == closeBrace) { if (!(context.state & slotStarted)) { fmtSlots.append(ReplaceSlotStruct(i, 2, closeBraceSlot)); } i++; } else { if (context.state & slotStarted) { context.state &= slotFinish; context.slotEnd = i; LString slotContent = substr(context.slotStart + 1, context.slotEnd - context.slotStart - 1); size_t argIndex = analyseSlot(slotContent, strArgs, context, resultArgs); fmtSlots.append(ReplaceSlotStruct(context.slotStart, context.slotEnd - context.slotStart + 1, argIndex)); } else { } } } } if (context.slotEnd <= context.slotStart) { fmtSlots.append(ReplaceSlotStruct(context.slotStart, size() - context.slotStart + 1, nullSlot)); } LString result(*this); result.segmentReplace(fmtSlots, resultArgs); return result; }
|
2.4 analyseSlot函数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107
| size_t LString::analyseSlot(const LString &slotContent, const LVector<LString> &strArgs, FormatContextStruct &context, LVector<LString> &resultArgs) { constexpr uint32_t firstSlotPassed = 0b1 << 1; constexpr uint32_t isOrderMode = 0b1 << 2; constexpr size_t nullSlot = 0; constexpr size_t openBraceSlot = 1; constexpr size_t closeBraceSlot = 2; constexpr int initArgNum = 3; static const auto colon = LChar(":").unicode(); int colonPos = slotContent.LVector<unsigned short>::indexOf(colon, 0); size_t strIndex = 0; size_t resultIndex = 0; LString indexContent, formatContent; if (-1 == colonPos) { indexContent = slotContent; formatContent = LString(); } else { indexContent = slotContent.substr(0, colonPos); formatContent = slotContent.substr(colonPos + 1, slotContent.size() - colonPos - 1); } if (!indexContent.size()) { if (!(context.state & firstSlotPassed)) { context.state |= firstSlotPassed; } if (context.state & isOrderMode) { resultIndex = nullSlot; } strIndex = context.nextArg - initArgNum; resultIndex = context.nextArg++; } else { bool ok = true; int res = indexContent.toInt(&ok); if (ok && res >= 0) { if (!(context.state & firstSlotPassed)) { context.state |= firstSlotPassed; context.state |= isOrderMode; } if (!(context.state & isOrderMode)) { resultIndex = nullSlot; } else { strIndex = res; resultIndex = context.nextArg++; } } else { resultIndex = nullSlot; } } if (formatContent.isNull()) { if (nullSlot != resultIndex) { resultArgs.append(strArgs[strIndex]); } } else { if (nullSlot != resultIndex) { LString fmtDeal = strArgs[strIndex]; resultArgs.append(fmtDeal); } } return resultIndex; }
|