【问题标题】:How to get the file separator symbol in standard C/C++ : / or \?如何在标准 C/C++ 中获取文件分隔符:/ 或 \?
【发布时间】:2012-10-09 21:44:13
【问题描述】:

我想写一个函数:

inline char separator()
{
    /* SOMETHING */
}

在标准 C/C++/C++11 中返回系统的文件分隔符? (我的意思是斜杠或反斜杠,具体取决于系统)。有没有办法做到这一点?

【问题讨论】:

  • 我知道它说的是“标准 C++”,但如果你不介意使用 boost...stackoverflow.com/questions/8384477/…
  • Boost 是用标准 C++ 编写的,所以应该可以接受 - 尤其是 boost.org/doc/libs/1_51_0/libs/filesystem/doc/…
  • 这有多重要? Windows API 都接受斜杠和反斜杠作为分隔符。 cmd.exe 很挑剔;它使用斜杠表示选项,因此在路径中需要反斜杠。为了向用户展示,使用反斜杠可能会更好,但是对于使用斜杠的 URL,人们可能不会像以前那样沉迷于此。
  • @Mark "Boost 是用标准 C++ 编写的,所以应该可以接受" - 好吧,Qt 也是如此,所以只需使用 QDir::separator().toAscii()
  • 来自MSDN:所有正斜杠 (/) 都转换为标准的 Windows 分隔符,即反斜杠 (\)。如果它们存在,则前两个斜线后面的一系列斜线将折叠成一个斜线。

标签: c++ c file c++11 filesystems


【解决方案1】:

除了检查 ifdefs 之外我不知道该怎么做

inline char separator()
{
#ifdef _WIN32
    return '\\';
#else
    return '/';
#endif
}

或(由 PaperBirdMaster 建议)

const char kPathSeparator =
#ifdef _WIN32
                            '\\';
#else
                            '/';
#endif

【讨论】:

  • 所有windows版本定义__WIN32 ??
  • 请注意 - Windows 也支持正斜杠 (/) 作为路径分隔符。
  • 另一个注意事项:为什么是内联函数而不是const char
  • 我只是按照 OP 建议的风格回复。你说得对,const char 也一样好。我会把它添加到答案中
  • @einpoklum 好问题。我的评论并不完全正确。 Win32 API 函数接受斜杠作为路径分隔符,并在调用 NT API 函数时将其转换为反斜杠(处理以 \\?\ 开头的路径时除外)。详情请见msdn.microsoft.com/en-us/library/aa365247.aspx
【解决方案2】:

可能是这样的

#if defined(WIN32) || defined(_WIN32) 
#define PATH_SEPARATOR "\\" 
#else 
#define PATH_SEPARATOR "/" 
#endif 

【讨论】:

  • 注意:就像接受的答案一样,您可能希望将符号定义为 char 文字(使用单引号)
  • 只要您只关心 UNIX 与 Microsoft(DOS、Windows 等)。为了通用,代码会变得更加丑陋(请参阅我的完整答案。)
  • @ardnew,这取决于。如果您想在 C 中的字符串指针上使用它,那么它是字符还是字符串没有太大区别,您只需为 snprintf() 选择正确的格式说明符。另一方面,如果您想在文字字符串上使用字符串版本,则更方便。使用字符串版本,您只需编写"base_path" PATH_SEPARATOR "final_path" 之类的内容即可连接文字。如果您将分隔符定义为字符,您将无法做到这一点。
【解决方案3】:

这个问题确实暗示了一个更棘手的问题。

如果您只关心 UNIX 与 Winodws,并且只关心目录和文件,那么您已经看到的(大部分)会起作用,但是将路径名拼接到其组件中的更一般的问题是更丑的问题。根据平台,路径可能包含以下一项或多项:

  • 卷标识符
  • 目录列表
  • 文件名
  • 文件中的子流
  • 版本号

虽然为此有 3rd 方库(如各种 CPAN Perl 模块、Boost 等),并且每个操作系统都为此提供了系统功能,但 C 中没有为此内置任何功能,C++ 标准仅获得了此功能(通过合并 Boost 模块)在 2017 年。

此类函数可能需要处理的一些示例如下:

  • UNIX 和类 UNIX 系统使用由“/”字符分隔的字符串列表,以“/”开头,表示绝对路径(相对于相对路径)。在某些情况下(如 NFS),也可能存在主机名前缀(带有“:”分隔符)
  • DOS 和 DOS 派生的操作系统(Windows、OS/2 和其他)使用“\”作为目录分隔符(API 也接受“/”),但路径也可以使用卷信息作为前缀。它可以是驱动器号 ("C:") 或 UNC 共享名称 ("\\MYSERVER\SHARE\")。还有其他前缀表示不同类型的服务器,后缀表示文件中的非默认流。
  • Mac(经典 Mac OS、Carbon 和一些 Cocoa API)使用“:”作为目录分隔符,第一个术语是卷名,而不是目录名。 Mac 文件还可能包含子流(“forks”),这些子流使用专用 API 通过相同的名称访问。这对于在经典 Mac 软件中广泛使用的资源分支来说尤其重要。
  • Mac OS X,在使用 UNIX API 时,通常会做类 UNIX 系统所做的事情,但它们也可以通过后缀“.”来表示命名的子流(“forks”)。后跟 fork-name 到文件名。
  • 最新版本的 Cocoa(Mac OS X、iOS 等)建议使用基于 URL 的 API 来表示文件,因为这个问题越来越复杂。想想基于云的文档和其他复杂的网络文件系统。
  • VMS 相当复杂 (https://web.archive.org/web/20160324205714/http://www.djesys.com/vms/freevms/mentor/vms_path.html),但它具有表示卷、目录路径、文件和文件修订版本的组件。

还有很多其他的。

值得注意的是,C++17 文件系统库并未涵盖所有这些可能性。 std::filesystem::path 由一个可选的 root-name(卷标识符)、一个可选的 root-directory(用于标识绝对路径)和一系列文件名组成,由目录分隔符。这涵盖了在 UNIX 平台上可能有效的所有内容以及其他平台的大多数用例,但并不全面。例如,它不支持子流(依靠操作系统以某种方式将它们映射到文件名上——这是由 Mac OS X 完成的,但不是经典的 MacOS)。它也不包括对文件版本号的支持。

另请参阅 Wikipedia's entry on Path 和 C++17 std::filesystem::path

http://en.cppreference.com/w/cpp/filesystem

我建议你看看你想用目录分隔符做什么(提取基本名称,将路径分解为目录列表等)并编写一个函数来执行此操作。如果您使用的是 C++17(并且您确定您的代码不会被 17 之前的 C++ 编译器编译),那么您可以(可能)使用标准 C++ 库代码来编写此函数的可移植实现。如果没有,该功能将需要为您将支持的每个平台使用特定于平台的#ifdefs,如果不满足任何条件,则使用#error,以强制您为意外平台添加条件。

如果可以接受,或者使用包含所有这些功能的第 3 方库(如 Boost)。

【讨论】:

【解决方案4】:

接受的答案在 Cygwin 下不起作用。在 Windows 上运行的 Cygwin 编译程序可以使用 Windows 风格的 '\' 分隔符,但它没有定义 _WIN32 等。在 Cygwin 下工作的修改后的解决方案:

inline char separator()
{
#if defined _WIN32 || defined __CYGWIN__
    return '\\';
#else
    return '/';
#endif
}

const char kPathSeparator =
#if defined _WIN32 || defined __CYGWIN__
    '\\';
#else
    '/';
#endif

【讨论】:

  • 然而,Cygwin 保证能够识别“/”,因此这不应该是一个真正的问题。所以答案工作得很好——它将使用“/”字符并且完全没有任何问题。
【解决方案5】:

我很惊讶没有人提供以下服务。这一点建立在其他人在这里提供的基础之上。

虽然在本例中我试图动态获取正在运行的可执行文件的名称以供使用,但根据需要进行跳转并重新应用它并不难。

Windows 使用正斜杠来表示参数。所以你可以先在第一个参数argv[0] 中检查它,它包含正在运行的程序的名称。

请注意,以下结果会去除最后一个斜杠之前的路径名,留下sepd 作为程序的文件名。

#include <string.h>
#include <stdio.h>

int main(int argc, char *argv[]){
//int a = 1
//int this = (a == 1) ? 20 : 30;  //ternary operator
//is a==1 ? If yes then 'this' = 20, or else 'this' = 30
    char *sepd = (strrchr(argv[0], '\/') != NULL) ? 
        strrchr(argv[0], '\/') : 
        strrchr(argv[0], '\\');
    printf("%s\n\n", sepd);
    printf("usage: .%s <host> \n\n", sepd);
    while (getchar() != '\n');
}

但实际上,这很肮脏,而且由于 Windows 最近采取了包含 Bash 的举措(此时尚未实施),这可能会产生意外或未预料到的结果。

它也不像其他人提供的那样理智和不受错误影响,尤其是#ifdef _WIN32

【讨论】:

  • Windows 上的某些程序也使用-,而/ 可以在非Windows 平台上使用。您也不能保证argv[0] 包含任何内容,当它包含任何内容时,除了启动程序的进程名称之外,它可能包含也可能不包含任何内容——这可能与您的程序无关。
  • @Clearer:你说得对,不能保证argv[0] 有任何有用的东西。此外,参数分隔符完全取决于应用程序。虽然 Microsoft 的命令使用 / 并且许多应用程序(尤其是从 Unix 移植的应用程序)使用 -,但它们实际上可以是应用程序开发人员想要使用的任何东西。
【解决方案6】:

如果您的编译器提供 c++17 功能,那么您可以使用 std::filesystem::path::preferred_separator,它会根据您的平台生成首选的分隔符字符。例如,在 Windows 上,这通常是 \,而在 Linux 上,你会得到 /

更多信息请参见this

【讨论】:

  • 轻微唠叨,没什么大不了的。关于 preferred_separator 应该是什么的文档只是一个示例;即在 Windows 上,你会得到一个 \\ ,而在 POSIX 上,你会得到一个 /。其他平台可能会提供其他结果。
  • 这不再是实验性的:en.cppreference.com/w/cpp/filesystem/path
【解决方案7】:

std::filesystem::path::preferred_separator 现在应该被用作一个干净的C++17 库函数了。

【讨论】:

    猜你喜欢
    • 2017-06-09
    • 2020-06-18
    • 2012-12-25
    • 1970-01-01
    相关资源
    最近更新 更多