如何从单独的字符串构建完整路径字符串(安全地)?

114

C++有没有类似于Python的os.path.join函数的等价物呢?基本上,我正在寻找一些可以将文件路径的两个(或更多)部分组合起来,以便您不必担心确保这两个部分完美匹配的东西。如果是在Qt中,那也很酷。

基本上,我花了一个小时调试一些代码,而其中至少一部分是因为root + filename必须是root/ + filename,我想在以后避免这种情况。


可能有些关联:https://dev59.com/a2025IYBdhLWcg3w1JiG(具体来说,与那个问题相关的是boost的`complete`函数) - Lightness Races in Orbit
8个回答

116

仅作为 boost::filesystem 库的一部分。以下是一个示例:

#include <iostream>
#include <boost/filesystem.hpp>

namespace fs = boost::filesystem;

int main ()
{
    fs::path dir ("/tmp");
    fs::path file ("foo.txt");
    fs::path full_path = dir / file;
    std::cout << full_path << std::endl;
    return 0;
}

这是编译和运行的示例(特定于平台):
$ g++ ./test.cpp -o test -lboost_filesystem -lboost_system
$ ./test 
/tmp/foo.txt

1
这也包含在TR2中,很可能会在明年随编译器一起发布。 - ildjarn
1
@Fred:显然,如果功能或问题是特定于版本的,我不会更改URL。在这种情况下,它不是,所以我更改了它。 - ildjarn
1
@ildjarn:你如何预测一个给定的库将来会发生什么变化,以便知道你编辑的所有答案是否对所有未来版本都有意义? - Fred Nurk
1
@Fred:你是在暗示Boost.FileSystem在未来某个时候可能会失去OP所要求的功能吗?真的吗?一般而言,对于你的问题,答案是“我用我的大脑。”到目前为止,这似乎已经运作得很好了。 - ildjarn
2
使用C++17,使用#include <filesystem>namespace fs = std::filesystem;。使用C++14,使用#include <experimental/filesystem>namespace fs = std::experimental::filesystem; - Roi Danton
显示剩余6条评论

66
this answer类似(但不使用boost),此功能作为std::filesystem的一部分可用。以下代码使用Homebrew GCC 9.2.0_1编译,并使用标志--std=c++17
#include <iostream>
#include <filesystem>
namespace fs = std::filesystem;

int main() 
{
    fs::path dir ("/tmp");
    fs::path file ("foo.txt");
    fs::path full_path = dir / file;
    std::cout << full_path << std::endl;
    return 0;
}

9
自 C++17 起,此功能已合并到(非实验性)<filesystem> 头文件中。请参见 https://en.cppreference.com/w/cpp/filesystem。 - Eli_B
1
非常感谢,如果项目支持C++17的话,这个答案是可行的。 - n0ne
1
在我的情况下,只需直接将它们连接起来(而不是在它们之间放置“/”或“\”)。 - 0xB00B
@0xB00B 只需使用 A + Bfs::path 重载运算符 / / + 来进行追加/连接。请参考 https://en.cppreference.com/w/cpp/filesystem/path/concat。 - Kevin Chan
@Kevin Chan,我之前已经在谈论这个问题了,但由于一些实现上的错误,它并没有正常工作。 - 0xB00B

52

这个可以查看QDir:

QString path = QDir(dirPath).filePath(fileName);

4
请注意,Qt采用GPL许可证。这可能会成为许多用户的瓶颈。 - rustyx
7
@RustyX,确切地说,它是LGPL。 - Pa_
1
我喜欢这个答案,不需要对字符串和连接进行繁琐的操作,让框架自己处理。 - jerrylogansquare

10
在Unix/Linux中,即使路径的某些部分已经以“/”结尾,使用“/”连接路径的部分总是安全的,例如“root/path”等同于“root//path”。在这种情况下,您只需要使用“/”来连接路径。尽管如此,如果可用,我同意其他答案中提到的boost::filesystem是一个不错的选择,因为它支持多个平台。

2
QT 对路径分隔符是不加以区分的。如果在 Windows 上打印文件的绝对路径,输出将会是 "C:/Users/Name/MyFile.txt",其中使用了 /(Unix)分隔符。boost::filesystem 很棒,但在我看来,如果项目基于 Qt,就没有必要添加 boost 库的依赖。 - LoSciamano

7
如果你想使用Qt来完成这个任务,你可以使用QFileInfo构造函数:
QFileInfo fi( QDir("/tmp"), "file" );
QString path = fi.absoluteFilePath();

5

使用C++11和Qt,您可以这样做:

QString join(const QString& v) {
    return v;
}

template<typename... Args>
QString join(const QString& first, Args... args) {
    return QDir(first).filePath(join(args...));
}

使用方法:

QString path = join("/tmp", "dir", "file"); // /tmp/dir/file

4
在Qt中,当使用Qt API(QFile、QFileInfo)时,在代码中使用“/”即可。它将在所有平台上正常工作。如果你必须将路径传递给非Qt函数,或者想将其格式化以显示给用户,请使用QDir:toNativeSeparators(),例如:
QDir::toNativeSeparators( path );

它将使用本地等效项(即 Windows 上的 \)替换 /。另一方面,可以通过 QDir::fromNativeSeparators() 实现。


2

这里提供了一个非常简单的C++11友好的替代方案,适用于那些既没有Boost、Qt也没有C++17的人(摘自这里)。

std::string pathJoin(const std::string& p1, const std::string& p2)
{
    char sep = '/';
    std::string tmp = p1;

#ifdef _WIN32
    sep = '\\';
#endif

    // Add separator if it is not included in the first path:
    if (p1[p1.length() - 1] != sep) {
        tmp += sep;
        return tmp + p2;
    } else {
        return p1 + p2;
    }
}

你测试过代码了吗?应该是 p1[p1.length() - 1] != sep 而不是 p1[p1.length()] != sep。我们还可以考虑到 Windows 接受 '\\''/' 两种路径分隔符。 - Rotem
@Rotem:感谢您指出问题,我已经修复了答案中的长度问题。 - Honza Vojtěch

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接