std::regex适用于复杂规则校验,如“字母开头、含数字下划线、长3–20”,需用^$锚定并注意平台路径转义;简单规则宜用std::all_of等手动逻辑;文件名校验应先提取filename再匹配,扩展名须用path::extension()避免误判。

用 std::regex 做基础校验最直接
如果你的规则含通配、分组、边界或复杂字符限制(比如“必须以字母开头,中间可含数字和下划线,长度 3–20”),std::regex 是 C++11 起最稳妥的选择。它不依赖外部库,语义清晰,且能一次匹配整个字符串逻辑。
注意:Windows 下路径分隔符是反斜杠 ,写正则时得双写成 \;Linux/macOS 用 / 则无需转义。别直接拿文件路径全量去匹配——先用 std::filesystem::path::filename() 提取纯文件名部分再校验。
示例规则“字母开头,只含字母数字下划线,长度 1–32”:
std::regex name_pattern(R"(^[a-zA-Z][a-zA-Z0-9_]{0,31}$)");
std::string basename = std::filesystem::path("my_file_v2.txt").filename().string();
bool valid = std::regex_match(basename, name_pattern);
- 用原始字符串字面量
R"(...)"避免手动转义反斜杠 -
^和$必须加上,否则"abc123xxx"会意外匹配"[a-z]+[0-9]+"这类不带边界的模式 - MSVC 对
std::regex的 ECMAScript 引擎支持较弱,若遇到匹配异常,优先换用boost::regex或std::regex_constants::basic
简单规则别硬套正则:用 std::all_of + 手动逻辑更稳
当规则只是“不能含 /、、:、* 等非法字符”,或者“必须以 .txt 结尾”,正则反而重且易错。手写遍历或组合标准算法更可控、更快、无运行时异常风险。
立即学习“C++免费学习笔记(深入)”;
比如判断是否为合法 Windows 文件名(不含保留名如 CON、AUX):
bool is_valid_win_filename(const std::string& s) {
if (s.empty() || s.size() > 255) return false;
static const std::unordered_set<std::string> reserved = {"CON", "PRN", "AUX", "NUL",
"COM1", "COM2", "COM3", "COM4", "COM5", "COM6", "COM7", "COM8", "COM9",
"LPT1", "LPT2", "LPT3", "LPT4", "LPT5", "LPT6", "LPT7", "LPT8", "LPT9"};
auto dot_pos = s.find_last_of('.');
std::string name_part = dot_pos == std::string::npos ? s : s.substr(0, dot_pos);
if (reserved.count(to_upper(name_part))) return false;
return std::all_of(s.begin(), s.end(), [](char c) {
return c != '<' && c != '>' && c != ':' && c != '"' && c != '/' &&
c != '\' && c != '|' && c != '?' && c != '*';
});
}
- Windows 保留名大小写不敏感,记得统一转大写再查表
-
std::all_of比循环for更简洁,但别在里头调用可能抛异常的函数(如std::stoi) - Linux 下虽然允许更多字符,但若目标是跨平台兼容,建议也按 Windows 严格规则过滤
检查扩展名要用 std::filesystem::path::extension(),别用 find_last_of('.')'
直接找最后一个点来截扩展名,会在 "archive.tar.gz" 或 ".gitignore" 上出错:tar.gz 被当成扩展名就丢了压缩包语义,而点开头的隐藏文件会被误判为“无扩展名”。
std::filesystem::path 的 extension() 方法已内置这些逻辑:
std::filesystem::path p = "data.config.bak";
std::string ext = p.extension().string(); // ".bak",不是 ".config.bak"
p = ".env.local";
ext = p.extension().string(); // ".local" — 注意:它把第一个点之后都算 extension
// 若你想要“最后一个点之后的内容”,得用:
auto last_dot = p.filename().string().find_last_of('.');
std::string true_ext = (last_dot == std::string::npos) ? "" : p.filename().string().substr(last_dot);
-
extension()返回的是“从最后一个点开始到结尾”,包括点本身;stem()是去掉 extension 后的剩余部分 - 对隐藏文件(如
.gitignore),extension()返回".gitignore",不是空字符串 —— 这是符合 POSIX 的行为,但未必是你业务想要的 - 若需兼容旧编译器(如 GCC std::filesystem 不可用,改用
boost::filesystem或手写find_last_of+ 特殊 case 处理
真实项目中容易漏掉的三件事
文件名校验不是孤立操作,嵌入实际流程时,以下三点常被跳过,导致线上出问题:
- 没做 Unicode 正规化:用户传入的
"café"可能是U+00E9(预组字符)或U+0065 U+0301(e + 重音符),Windows API 对二者处理不一致;建议入库/保存前用 ICU 或std::wstring_convert(C++17 已弃用,慎用)做 NFC 转换 - 没考虑长路径:Windows 默认限制 260 字符,启用
longPathsEnabled=true后才支持 UNC 路径;校验时若只检查 basename,可能放过一个总长超限的全路径 - 没区分“创建文件”和“打开已有文件”:校验规则应不同。例如日志系统允许写
app-2024-04-01.log,但不应允许用户上传同名文件覆盖 —— 这属于权限/业务逻辑层,别混进文件名校验函数里


















