最可靠方法是用 std::filesystem::path 的 stem() 和 extension():stem() 返回去扩展名的文件名部分,extension() 返回含点的扩展名;需用 filename().stem() 获取纯文件名,避免顺序错误。

用 std::filesystem::path 提取文件名和扩展名最可靠
直接调用 stem() 和 extension() 就行,不用自己切字符串。C++17 起 std::filesystem 是标准方案,跨平台、处理点号多、带隐藏文件逻辑,比手写 find_last_of('.') + substr 少踩一堆坑。
常见错误是把 filename() 当成“文件名”,其实它返回的是带扩展名的完整基名(如 "report.pdf");而 stem() 才是去掉扩展名的部分("report"),extension() 返回扩展名(".pdf")。
-
stem()会自动剥离最后一段扩展名,对"archive.tar.gz"返回"archive.tar"(不是"archive") -
extension()返回含点的字符串,比如".txt",不是"txt" - 如果路径末尾是目录分隔符(如
"/home/user/"),stem()和extension()都返回空path - 不区分大小写?不区分——
extension()原样返回小写或大写,不会自动转成".PNG"或".png"
extension() 返回空时的三种典型情况
不是 bug,是设计行为。遇到空 extension() 先别急着改代码,先看路径本身:
- 路径以目录分隔符结尾:如
std::filesystem::path{"/tmp/"}→extension()为空 - 路径没有点号:如
"README"或"Makefile"→extension()为空(stem()就是它本身) - 点号在开头且无后续点:如
".gitignore"→extension()是".gitignore",stem()是空(因为点前没内容);但".config"同理,stem()也是空
所以判断“有没有扩展名”,不能只看 extension().empty(),得结合 has_extension() 方法更稳妥。
立即学习“C++免费学习笔记(深入)”;
想拿纯文件名(不含路径)又要去掉扩展名?别链式调错顺序
容易写成 p.stem().filename() 或 p.filename().stem() —— 这俩都错。正确顺序是:p.filename().stem()。
原因:filename() 先剥离路径,得到 "data.json";再对这个结果调 stem(),才得到 "data"。反过来,p.stem().filename() 先剥扩展名,但 stem() 可能还含路径(比如 "/a/b/file.txt" 的 stem() 是 "/a/b/file"),再调 filename() 就只剩 "file",看似对,实则依赖巧合,遇到 "./log.txt" 就翻车。
示例:
std::filesystem::path p = "/var/log/app.log"; auto name_only = p.filename().stem(); // "app" —— 正确 auto ext_only = p.filename().extension(); // ".log"
Windows 下要注意 replace_extension() 不会动盘符和斜杠
这个函数只替换最后一段扩展名,不影响路径结构。比如 "C:\temp\readme.TXT" 调用 .replace_extension(".md") 得到 "C:\temp\readme.md",不是 "C:readme.md" —— 它保留了所有分隔符和盘符。
但注意:如果你传入的 path 对象本身不含扩展名(has_extension() == false),replace_extension(".new") 会直接拼上去,变成 "old.new";如果已有扩展名,则替换。这点和 extension() 的语义一致,但新手常误以为它“总是追加”。
真正容易被忽略的点:路径里有多个点、有 Unicode、或者扩展名含点(如 ".tar.gz"),stem()/extension() 的行为由标准定义,不依赖系统 API,所以 Linux/macOS/Windows 表现一致——这点比用 _splitpath 或 PathFindExtension 安全得多。



















