本文档依据当前工程中的以下实现编写:
src/util/StringUtil.h文档以 .cpp 的实际行为为准。StringUtil.h 中包含两个工具类:
StringUtil:字符串判断、格式化、分割、编码转换、字节转换、JSON 拼接和数值转换。TransPY:中文字符串转拼音。#include "util/StringUtil.h"
#include <cstdint>
#include <iostream>
#include <map>
#include <regex>
#include <string>
#include <vector>
using stt::KV_Str_Str;
using stt::util::StringUtil;
using stt::util::TransPY;
两个类的方法都是静态方法,不需要创建对象:
std::string upper = StringUtil::toUpperCase("alarm");
std::string pinyin = TransPY::getPinyin("中国");
也可以使用完整名称:
bool ok = stt::util::StringUtil::endWith("report.csv", ".csv");
std::string::size()、splitByLength、charsToString 等接口按字节处理,不按 Unicode 字符处理。toLowerCase 和 toUpperCase 只适合 ASCII 英文字母,不会正确执行 Unicode 大小写转换。TransPY::getPinyin 先把 UTF-8 转为 GBK,再查询内置拼音表。trancoding 依赖 iconv,编码名称必须是当前平台 iconv 支持的名称,例如 UTF-8、GBK、UTF-16LE。match、rreplace 在正则表达式非法时可能抛出 std::regex_error,接口内部不捕获。formatDate 会访问全局 HMI Runtime;必须在 Runtime 已初始化后调用。formatDate 使用 localtime/gmtime 返回的静态缓冲区,不适合多个线程同时调用。trancoding 失败时向 std::cerr 输出系统错误,然后返回空字符串;空字符串也可能表示输入本来为空。0,无法区分“转换失败”和合法数字 0。关键业务应使用带输出参数的 bool 重载。以下行为不是通用 C++ 字符串库的常规语义,调用前必须注意:
| 接口 | 当前实现行为 | 建议 |
|---|---|---|
formatFloat(..., true, ...) |
千分位逗号插入位置错误,可能生成 1234,.50 |
当前版本不要启用千分位 |
formatDate |
deli1、deli2 的普通分隔符值没有真正使用,实际固定为 - 和 : |
不要依赖自定义 / 等分隔符 |
splitByLength |
循环条件和补齐长度写死为 8,且会修改原字符串 |
只按 8 字节使用,传 len=8 |
replace |
连续或相邻的多个匹配项可能漏替换;from 为空会死循环 |
调用前保证 from 非空;多次替换优先用 rreplace |
safeStoi、safeStoll |
没有检查是否消费完整字符串,"12abc" 也会成功并得到 12 |
严格输入先用 match,或自行检查 |
vectorToJsonc、mapToJson |
不转义引号、反斜杠、换行等 JSON 特殊字符 | 外部输入应使用 json-c 等正式 JSON API |
StringToChars |
不检查输出指针和容量,超长输入还有循环计数风险 | 预分配足够空间,限制十六进制文本不超过 510 个字符 |
| 分类 | 方法 |
|---|---|
| 正则匹配 | match |
| 前后缀判断 | startWith、startWithNoCase、endWith、endWithNoCase |
| 数字和日期格式化 | formatFloat、formatDate、formatScience、snprintf |
| 分割和拼接 | split、join、splitByLength |
| 大小写和替换 | toLowerCase、toUpperCase、replace、rreplace |
| SQL | sqlEsc |
| 编码转换 | trancoding |
| 十六进制和字节 | hexToUInt、charsToString、StringToChars、charArrayToString、stringToCharArray |
| JSON 文本拼接 | vectorToJsonc、mapToJson、vertInt |
| 安全数值转换 | safeStoi、safeStof、safeStoll、safeStod |
| 去除首尾字符 | trim |
| 中文转拼音 | TransPY::getPinyinByCode、TransPY::getPinyin |
matchstatic bool match(const string& reg, const string& str);
判断整个 str 是否符合正则表达式 reg。实现使用:
std::regex(reg, std::regex_constants::extended)
因此它采用 POSIX Extended 语法,并通过 regex_match 匹配整个字符串,而不是搜索其中一段。
参数:
reg:正则表达式。str:待检查字符串。返回值:整个字符串匹配时返回 true,否则返回 false。
示例:
bool digits = StringUtil::match("^[0-9]+$", "20261020"); // true
bool code = StringUtil::match("^[A-Z]{2}[0-9]{4}$", "AB0123"); // true
bool mobile = StringUtil::match("^1[3-9][0-9]{9}$", "13800138000"); // true
bool bad = StringUtil::match("^[0-9]+$", "12A"); // false
常用兼容写法:
| 需求 | 表达式 |
|---|---|
| 非空十进制数字 | ^[0-9]+$ |
| 可选负号的整数 | ^-?[0-9]+$ |
| 最多两位小数 | ^-?[0-9]+(\.[0-9]{1,2})?$ |
| 英文、数字、下划线 | ^[A-Za-z0-9_]+$ |
日期 yyyy-MM-dd |
^[0-9]{4}-[0-9]{2}-[0-9]{2}$ |
注意事项:
\d、\w、(?=...) 等写法。\d、\w 或前瞻,和当前 extended 语法不一致;优先改写为 [0-9]、[A-Za-z0-9_] 等形式。{m,n} 中的 m、n 必须替换成真实数字,例如 {2,5}。std::regex_error。正则来自外部配置时应捕获异常。try {
bool ok = StringUtil::match(userPattern, input);
// 使用 ok
} catch (const std::regex_error& e) {
std::cerr << "正则表达式错误: " << e.what() << std::endl;
}
startWithstatic bool startWith(const string& s, const string& sub);
区分大小写地判断 s 是否以 sub 开始。
StringUtil::startWith("SAMPLE_4.db", "SAMPLE_"); // true
StringUtil::startWith("SAMPLE_4.db", "sample_"); // false
StringUtil::startWith("abc", ""); // true
StringUtil::startWith("ab", "abc"); // false
空前缀按 C++ 字符串规则视为匹配。
startWithNoCasestatic bool startWithNoCase(const string& s, const string& sub);
先把两个字符串转成小写,再判断前缀。适合 ASCII 英文:
bool ok = StringUtil::startWithNoCase("Report.CSV", "report"); // true
该接口不是 Unicode 大小写比较;UTF-8 中文不受影响,其他非 ASCII 字符不要依赖其结果。
endWithstatic bool endWith(const string& s, const string& sub);
区分大小写地判断后缀:
StringUtil::endWith("report.csv", ".csv"); // true
StringUtil::endWith("report.CSV", ".csv"); // false
StringUtil::endWith("abc", ""); // true
endWithNoCasestatic bool endWithNoCase(const string& s, const string& sub);
忽略 ASCII 大小写判断后缀:
bool isCsv = StringUtil::endWithNoCase("REPORT.CSV", ".csv"); // true
formatFloatstatic string formatFloat(
double fvalue,
int fraclen,
bool thousandth,
int intnum = 0,
bool intbl = false,
const string& prefix = "",
const string& suffix = "");
参数:
fvalue:待格式化数值。fraclen:小数位数。大于等于 0 时使用定点格式并四舍五入;小于 0 时不固定小数位。thousandth:是否添加千分位。intnum:启用补零时传给 std::setw 的总字段宽度。intbl:是否使用字符 0 补足字段宽度。prefix:结果前缀。suffix:结果后缀。普通格式化示例:
std::string a = StringUtil::formatFloat(12.345, 2, false); // "12.35"
std::string b = StringUtil::formatFloat(42, 0, false, 5, true); // "00042"
std::string c = StringUtil::formatFloat(9.5, 1, false, 0, false, "$", " V");
// "$9.5 V"
注意事项:
formatFloat(1234.5, 2, true) 会产生类似 1234,.50 的错误结果,当前版本应传 false。intnum 是整个输出字段的最小宽度,不是严格的“整数位数”。小数点、小数位和负号都会占宽度。fraclen < 0 且输入正好是整数时,输出不带小数;非整数使用流的默认精度,可能出现精度截断或科学计数法。NaN 和无穷值没有专门拦截,会按标准流规则输出 nan、inf 等文本。formatSciencestatic string formatScience(double fvalue, int fraclen);
把数值转换为小写 e 的科学计数法:
std::string value = StringUtil::formatScience(12345.0, 2);
// "1.23e+04"
注意事项:
fraclen 应使用非负、合理的小数位数,例如 0..15。sprintf 动态拼接格式串,没有对极端的负数或超大 fraclen 做边界保护。snprintfstatic string snprintf(const char* pszFmt, ...);
按 printf 格式生成长度自动扩展的 std::string:
std::string date = StringUtil::snprintf("%04d-%02d-%02d", 2026, 10, 20);
// "2026-10-20"
std::string message = StringUtil::snprintf(
"设备=%s, 值=%.2f",
"M1",
12.345);
// "设备=M1, 值=12.35"
注意事项:
\n”,但当前实现不会自动添加换行。%s 必须传 const char*,传 std::string 时要调用 .c_str()。%d 不能直接接收 long long。pszFmt 不能为 nullptr。% 时写 %%。formatDatestatic string formatDate(
unsigned long long lvalue,
const string& deli1 = "-",
const string& deli2 = ":",
bool showYear = true,
bool showMonth = true,
bool showDay = true,
bool showHour = true,
bool showMinute = true,
bool showSecond = true,
bool showWeek = false,
bool isLocal = true);
lvalue 是从 Unix Epoch(1970-01-01 00:00:00 UTC)开始的秒数,不是毫秒数。
参数:
deli1:日期格式控制。当前实现只有特殊值 "中文" 会产生中文年月日;其他值均使用 -。deli2:头文件设计为时间分隔符,但当前实现没有读取该参数,时间固定使用 :。showYear 到 showSecond:分别控制每个时间分量是否显示。showWeek:是否追加本地化的星期文本。isLocal:true 使用本地时区,false 使用 UTC。完整时间示例:
unsigned long long seconds = 1792425600ULL;
std::string local = StringUtil::formatDate(
seconds,
"-", ":",
true, true, true,
true, true, true,
false,
true);
只显示日期:
std::string date = StringUtil::formatDate(
seconds,
"-", ":",
true, true, true,
false, false, false,
false,
true);
// 形如 "2026-10-20",具体日期受秒值和时区影响
显示中文年月日和星期:
std::string text = StringUtil::formatDate(
seconds,
"中文", "中文",
true, true, true,
true, true, true,
true,
true);
只有当 Runtime 当前语言为 zh_CN 且 deli1 == "中文" 时,结果才使用 年、月、日、时、分、秒。否则仍使用 - 和 :。
注意事项:
1000:formatDate(ms / 1000, ...)。deli1/deli2 内容。传入 / 仍会得到 -,传入 . 仍会得到 :。getRuntime()->getLanguage(),即使不显示星期也要求 Runtime 有效。showWeek=true 时还会调用语言管理器取得本地化文本。TZ 配置。hexToUIntstatic uint32_t hexToUInt(const string& str);
使用 %x 把十六进制文本转换为 uint32_t:
uint32_t a = StringUtil::hexToUInt("FF"); // 255
uint32_t b = StringUtil::hexToUInt("0x10"); // 16
uint32_t c = StringUtil::hexToUInt("7b"); // 123
注意事项:
0,与合法的 "0" 无法区分。%x 可以在遇到非法尾随字符时停止,因此 "FFxyz" 可能仍得到 255。match("^(0[xX])?[0-9A-Fa-f]+$", text)。splitstatic void split(
const string& str,
const string& split,
vector<string>& res);
按字符串分隔符拆分,调用时会先清空 res。
std::vector<std::string> fields;
StringUtil::split("A,B,,D", ",", fields);
// fields = {"A", "B", "", "D"}
StringUtil::split("key--value--tail", "--", fields);
// fields = {"key", "value", "tail"}
边界行为:
StringUtil::split("", ",", fields); // fields 为空
StringUtil::split("abc", "", fields); // fields = {"abc"}
StringUtil::split(",a,", ",", fields); // fields = {"", "a", ""}
分隔符可以包含多个字节。函数只做字节序列匹配,不理解转义、引号或 CSV 规则,因此不能用于可靠解析 CSV。
joinstatic string join(const vector<string>& src, const string& sub);
用 sub 连接字符串列表:
std::vector<std::string> names = {"A", "B", "C"};
std::string result = StringUtil::join(names, ","); // "A,B,C"
std::vector<std::string> empty;
std::string result2 = StringUtil::join(empty, ","); // ""
空元素会保留:
std::vector<std::string> values = {"A", "", "C"};
std::string result = StringUtil::join(values, "|"); // "A||C"
splitByLengthstatic vector<string> splitByLength(int len, char c, string& src);
当前实现按固定 8 字节块工作,并且会从 src 中持续删除已经取出的内容。虽然有 len 参数,循环条件和最后补齐长度仍写死为 8。
推荐的唯一用法:
std::string source = "ABCDEFGHIJK";
std::vector<std::string> blocks =
StringUtil::splitByLength(8, '0', source);
// blocks[0] == "ABCDEFGH"
// blocks[1] == "IJK00000"
// source == ""
注意事项:
len=8;其他长度可能分段错误或补齐错误。len<=0 且源字符串至少 8 字节时可能死循环。src 是非常量引用,调用后内容会被清空或改变。toLowerCasestatic string toLowerCase(const string& src);
std::string value = StringUtil::toLowerCase("Alarm_A1");
// "alarm_a1"
toUpperCasestatic string toUpperCase(const string& src);
std::string value = StringUtil::toUpperCase("Alarm_A1");
// "ALARM_A1"
两个方法都返回新字符串,不修改输入。它们逐字节调用 C 字符分类函数,只应对 ASCII 文本使用。UTF-8 中文会保持原始字节,但非 ASCII 字节传入 tolower/toupper 在不同平台上存在可移植性风险。
replacestatic string replace(
string str,
const string& from,
const string& to);
执行普通文本替换,不把 from 当正则表达式:
std::string path = StringUtil::replace("data/file.db", ".db", ".csv");
// "data/file.csv"
当前实现有两个重要限制:
from.empty() 时会无限循环,调用前必须拦截。"aa" 中的每个 "a",当前实现不会可靠地全部替换。安全使用方式:只在确认目标只出现一次时使用。
if (!from.empty() && source.find(from) != std::string::npos) {
std::string result = StringUtil::replace(source, from, to);
}
需要可靠替换全部匹配项时,可使用 rreplace 并对查找文本进行正则转义,或者在业务代码中使用标准的循环替换实现。
rreplacestatic string rreplace(
string str,
const string& pattern,
const string& to);
使用 std::regex_replace 替换所有正则匹配项:
std::string a = StringUtil::rreplace(
"Hello world, WORLD!",
"[Ww][Oo][Rr][Ll][Dd]",
"C++");
// "Hello C++, C++!"
std::string b = StringUtil::rreplace("A B C", " +", " ");
// "A B C"
捕获组可在替换串中用 $1、$2 引用:
std::string date = StringUtil::rreplace(
"2026-10-20",
"([0-9]{4})-([0-9]{2})-([0-9]{2})",
"$1/$2/$3");
// "2026/10/20"
注意事项:
rreplace 使用 std::regex 默认 ECMAScript 语法,而 match 使用 POSIX Extended 语法,两者语法并不完全相同。std::regex_error。.、*、[ 等会改变匹配含义。sqlEscstatic string sqlEsc(const string& src);
把每个单引号 ' 替换为两个单引号 '',用于 SQLite 单引号字符串字面量:
std::string name = "O'Reilly";
std::string sql =
"SELECT * FROM S_USER WHERE UNAME='" +
StringUtil::sqlEsc(name) +
"'";
// SELECT * FROM S_USER WHERE UNAME='O''Reilly'
注意事项:
trancodingstatic string trancoding(
const string& from,
const string& to,
const string& str);
通过 iconv 把 str 从 from 编码转换到 to 编码。
UTF-8 转 GBK:
std::string utf8 = "中文";
std::string gbk = StringUtil::trancoding("UTF-8", "GBK", utf8);
if (gbk.empty() && !utf8.empty()) {
std::cerr << "编码转换失败" << std::endl;
}
GBK 转 UTF-8:
std::string utf8Again = StringUtil::trancoding("GBK", "UTF-8", gbk);
注意事项:
std::cerr 输出错误。std::string 自己保存准确长度。\0,后续不要只用 C 字符串函数处理。charsToStringstatic string charsToString(unsigned char* data, int len);
把二进制字节转换为不带空格、不带 0x 前缀的大写十六进制文本:
unsigned char data[] = {0x5A, 0x00, 0xFF};
std::string hex = StringUtil::charsToString(data, 3);
// "5A00FF"
data == nullptr 或 len <= 0 时返回空字符串。输出长度始终是有效输入字节数的两倍。
StringToCharsstatic int StringToChars(
const string& str,
unsigned char* data);
把十六进制文本转换为二进制字节,支持大小写字母:
std::string hex = "5A00ff";
std::vector<unsigned char> data(hex.size() / 2);
int count = StringUtil::StringToChars(hex, data.data());
if (count > 0) {
// data = {0x5A, 0x00, 0xFF}
}
返回值:
| 返回值 | 含义 |
|---|---|
大于 0 |
成功写出的字节数 |
0 |
遇到非法十六进制字符,或输入为空 |
-1 |
输入字符数为奇数 |
注意事项:
data 至少有 str.size()/2 字节空间。data 不能为 nullptr,否则会崩溃。uint8_t,输出超过 255 字节时有回绕风险。应限制十六进制字符串最多 510 个字符。0x 前缀。"AA BB"、"0xAA" 都不是有效输入。推荐封装:
bool parseHex(const std::string& text, std::vector<unsigned char>& output) {
output.clear();
if (text.empty() || text.size() % 2 != 0 || text.size() > 510) {
return false;
}
output.resize(text.size() / 2);
int count = StringUtil::StringToChars(text, output.data());
if (count != static_cast<int>(output.size())) {
output.clear();
return false;
}
return true;
}
charArrayToStringstatic string charArrayToString(
vector<uint8_t>& arry,
const string& coding = "");
把字节向量原样复制到 std::string:
std::vector<uint8_t> bytes = {'A', 0x00, 'B'};
std::string value = StringUtil::charArrayToString(bytes);
// value.size() == 3
// value[0] == 'A', value[1] == '\0', value[2] == 'B'
注意事项:
0 会截断,但当前实现用显式长度构造字符串,嵌入的 0 不会截断。coding 参数当前没有使用,不会执行字符编码转换。const 引用,但函数不会修改向量。stringToCharArraystatic vector<uint8_t> stringToCharArray(
const string& src,
const string& coding = "");
把字符串中的全部字节原样复制到 vector<uint8_t>:
std::string source("A\0B", 3);
std::vector<uint8_t> bytes = StringUtil::stringToCharArray(source);
// bytes.size() == 3
// bytes = {'A', 0x00, 'B'}
coding 参数当前没有使用。该接口不附加末尾 \0;需要 C 字符串结尾时由调用方自行添加。
vectorToJsoncstatic string vectorToJsonc(vector<KV_Str_Str>& arry);
把键值对向量拼成 JSON 对象文本。普通值加双引号;外观为 {...} 或 [...] 的值直接作为嵌套 JSON 写入。
std::vector<KV_Str_Str> values = {
{"cmd", "start"},
{"device", "M1"},
{"options", "{\"speed\":10}"},
{"items", "[1,2,3]"}
};
std::string json = StringUtil::vectorToJsonc(values);
// {"cmd":"start","device":"M1","options":{"speed":10},"items":[1,2,3]}
空向量返回 {}。
注意事项:
"123" 会输出为 JSON 字符串 "123",不是数字。"、\、换行或控制字符时,结果可能是非法 JSON。const 引用,但当前实现不会修改向量。mapToJsonstatic string mapToJson(map<string, string>& jsonMap);
和 vectorToJsonc 类似,但输入是 std::map,并额外识别 vertInt 产生的 _I_ 标记。
std::map<std::string, std::string> values;
values["name"] = "Pump-1";
values["count"] = StringUtil::vertInt("3");
values["enabled"] = "true"; // 普通值,仍会加引号
std::string json = StringUtil::mapToJson(values);
// 按 map 键顺序输出,count 不带引号
因为 std::map 按键排序,输出顺序不是插入顺序。
vertIntstatic string vertInt(const string& str);
给文本添加 _I_ 标记,供 mapToJson 把它作为不带引号的值写出:
std::string marked = StringUtil::vertInt("42"); // "_I_42"
std::map<std::string, std::string> values;
values["count"] = marked;
std::string json = StringUtil::mapToJson(values);
// {"count":42}
注意事项:
vertInt 不验证内容是否真的是整数。vertInt("abc") 会生成非法 JSON 值 abc。mapToJson 删除标记时使用字符集合查找;标记后的值如果以额外的 _ 或 I 开始,可能被多删字符。mapToJson 同样不执行 JSON 转义,不应直接处理不可信文本。每种数值类型都有两个重载:
0,写法简短,但无法判断失败。bool 版本:返回是否成功,并通过引用输出结果,适合需要输入校验的业务。safeStoistatic int safeStoi(const string& str);
static bool safeStoi(const string& str, int& result) noexcept;
简化用法:
int count = StringUtil::safeStoi("123"); // 123
int bad = StringUtil::safeStoi("abc"); // 0
推荐用法:
int count = 0;
if (!StringUtil::safeStoi(input, count)) {
std::cerr << "不是有效整数" << std::endl;
}
当前实现基于 strtoll,但没有检查 *endptr == '\0':
int value = 0;
bool ok = StringUtil::safeStoi("12abc", value);
// 当前实现:ok == true, value == 12
其他注意事项:
long long 是否溢出,不检查结果是否超出 int 范围;缩窄到 int 时结果依赖平台。bool 重载不会主动清零 result,应先初始化。34 判断范围错误,而不是符号常量 ERANGE,存在跨平台风险。严格整数建议先匹配:
int value = 0;
bool ok = StringUtil::match("^-?[0-9]+$", input) &&
StringUtil::safeStoi(input, value);
safeStollstatic long long safeStoll(const string& str);
static bool safeStoll(const string& str, long long& result) noexcept;
long long timestamp = 0;
if (StringUtil::safeStoll("1792425600000", timestamp)) {
// 使用 timestamp
}
它和 safeStoi 一样没有检查尾随字符,因此 "123ms" 会成功并得到 123。前导空格和正负号可被接受。溢出或完全没有数字时返回 false。
safeStofstatic float safeStof(const string& str);
static bool safeStof(const string& str, float& result) noexcept;
float pressure = 0.0f;
bool ok = StringUtil::safeStof("12.75", pressure);
// ok == true, pressure == 12.75f
浮点版本会检查是否消费完整字符串,并拒绝无穷和 NaN:
float value = 0.0f;
StringUtil::safeStof("12.5abc", value); // false
StringUtil::safeStof("nan", value); // false
StringUtil::safeStof("inf", value); // false
注意事项:
false。safeStodstatic double safeStod(const string& str);
static bool safeStod(const string& str, double& result) noexcept;
double value = 0.0;
if (!StringUtil::safeStod("-0.00125", value)) {
std::cerr << "数值格式错误" << std::endl;
}
规则与 safeStof 相同:要求完整消费输入,拒绝 NaN、无穷、上溢和下溢。
| 输入 | safeStoi/safeStoll |
safeStof/safeStod |
|---|---|---|
"123" |
成功 | 成功 |
"-12" |
成功 | 成功 |
" 12" |
成功 | 成功 |
"12 " |
当前实现成功并取 12 |
失败 |
"12abc" |
当前实现成功并取 12 |
失败 |
"abc" |
失败 | 失败 |
"nan" |
失败 | 失败 |
| 空字符串 | 失败 | 失败 |
trim(string)static string trim(string s);
只删除首尾 ASCII 空格字符 ' ':
std::string value = StringUtil::trim(" alarm text ");
// "alarm text"
std::string tabs = StringUtil::trim("\talarm\t");
// 制表符不会被删除
中间的空格不会删除,输入字符串本身也不会被修改,因为参数按值传递。
trim(string, char)static string trim(string s, char c);
删除首尾连续出现的指定单字节字符:
std::string value = StringUtil::trim("---ABC---", '-');
// "ABC"
std::string path = StringUtil::trim("///data///", '/');
// "data"
该方法一次只能指定一个字节,不能直接传字符串或多字节中文字符。
TransPY::getPinyinstatic string getPinyin(
const std::string& str,
int mode = 1);
输入应为 UTF-8 中文字符串。返回结果不包含汉字之间的空格或分隔符。
mode 含义:
| mode | 中文拼音输出 | 示例形态 |
|---|---|---|
1 |
全部大写 | ZHONGGUO |
2 |
全部小写 | zhongguo |
3 |
每个汉字拼音首字母大写 | ZhongGuo |
4 |
每个汉字只保留一个小写首字母 | zg |
示例:
std::string upper = TransPY::getPinyin("中国", 1); // "ZHONGGUO"
std::string lower = TransPY::getPinyin("中国", 2); // "zhongguo"
std::string title = TransPY::getPinyin("中国", 3); // "ZhongGuo"
std::string initials = TransPY::getPinyin("中国", 4); // "zg"
ASCII 字符会原样保留,不受 mode 大小写规则影响:
std::string value = TransPY::getPinyin("中国-A1", 2);
// 形如 "zhongguo-A1"
注意事项:
UTF-8 -> GBK。编码转换失败时通常返回空字符串。mode 没有默认处理分支,中文部分不会追加,ASCII 部分仍可能保留。TransPY::getPinyinByCodestatic const char* getPinyinByCode(uint32_t code);
这是底层查表接口。code 不是 Unicode 码点;当前 getPinyin 使用 GBK 两字节计算内部索引:
uint32_t code = (highByte - 0xA0) * 100 + lowByte - 0xA0;
const char* pinyin = TransPY::getPinyinByCode(code);
普通业务应直接调用 getPinyin。返回的是只读 const char*,调用方不要释放或修改它。
std::string input;
vm->getChar("数量", input);
input = StringUtil::trim(input);
int count = 0;
if (!StringUtil::match("^[0-9]+$", input) ||
!StringUtil::safeStoi(input, count) ||
count < 1 || count > 1000) {
vm->setChar("提示", "数量必须是 1 到 1000 的整数");
return;
}
vm->setChar("提示", StringUtil::snprintf("数量=%d", count));
std::string userName;
vm->getChar("用户名", userName);
std::string sql =
"SELECT UID, UNAME FROM S_USER WHERE UNAME='" +
StringUtil::sqlEsc(userName) +
"'";
这里 sqlEsc 只处理字符串值。如果数据库接口支持参数绑定,应改用参数绑定。
std::string frameText;
vm->getChar("发送内容", frameText);
frameText = StringUtil::trim(frameText);
frameText = StringUtil::toUpperCase(frameText);
if (!StringUtil::match("^[0-9A-F]+$", frameText) ||
frameText.size() % 2 != 0 ||
frameText.size() > 510) {
vm->setChar("提示", "十六进制格式错误");
return;
}
std::vector<unsigned char> frame(frameText.size() / 2);
int count = StringUtil::StringToChars(frameText, frame.data());
if (count != static_cast<int>(frame.size())) {
vm->setChar("提示", "十六进制转换失败");
return;
}
std::string echo = StringUtil::charsToString(frame.data(), count);
std::string recipeName;
vm->getChar("配方名称", recipeName);
std::string asciiName = TransPY::getPinyin(recipeName, 4);
// 只保留文件名中容易处理的字符
asciiName = StringUtil::rreplace(asciiName, "[^A-Za-z0-9_-]", "_");
std::string fileName = asciiName + ".csv";
仍应在生成后检查空文件名、重名和目标路径权限。
startWith、endWith,文件扩展名判断使用 endWithNoCase。safeStoi/safeStoll,应先用兼容的正则限制格式和范围。bool 重载,不要依赖失败时返回的 0。formatFloat 的千分位功能。splitByLength 当前只按 len=8 使用,并记住它会修改源字符串。