如何使用Unicode文件名打开std :: fstream(ofstream或ifstream)?

75

您可能无法想象,使用C++标准库在Windows应用程序中打开文件这样基本的操作竟然有些棘手...但事实就是如此。在此处所指的Unicode是UTF-8格式,但我可以进行转换为UTF-16或其他格式,重点是获取一个能够处理Unicode文件名的ofstream实例。在我自己动手之前,是否有更好的解决方案?特别是一种跨平台的方法?


我这是一个duplicate问题。请看看那里的回答是否有帮助。 - Yorgos Pagles
为什么你不使用像std::wofstream这样的数据类型呢?注意w - sergiol
7个回答

63

C++标准库不支持Unicode。 charwchar_t不一定是Unicode编码。

在Windows上,wchar_t是UTF-16,但标准库中没有直接支持UTF-8文件名的方法(在Windows上,char数据类型不是Unicode)

使用MSVC(因此使用Microsoft STL),提供了一个以const wchar_t*文件名作为参数的文件流构造函数,允许您创建流如下:

wchar_t const name[] = L"filename.txt";
std::fstream file(name);
然而,C++11标准没有规定这种重载(它只保证基于char的版本存在),因此这种重载也不存在于其他STL实现(例如MinGW(-w64)的GCC libstdc++),截至g++ 4.8.x版本。

需要注意的是,就像在Windows上的char不是UTF8一样,在其他操作系统上wchar_t可能不是UTF16。因此,总体来说,这不太具有可移植性。按照标准定义,使用wchar_t文件名打开流并未被定义,而在char中指定文件名可能较为困难,因为不同操作系统使用的字符编码也不同。


4
更完整和最新的答案,包括如何使用g++以及其他Windows API路径等内容,在一个更新的线程中可找到。 - Cheers and hth. - Alf
@MichalM:不是的。wchar_t当然只是一个16位宽字符类型,可以用来存储任何你喜欢的东西。它并不关心编码。但是接受wchar_t参数的Win32 API希望它们包含UTF-16数据。自Windows 2000以来,Windows API就没有使用UCS-2了。 - jalf
@MichalM:wchar_t 中实际存储的是什么(而不是接近什么),是一个 UTF-16 代码单元。它不是 UCS-2,虽然它接近 UCS-2,但更接近 UTF-16 代码单元(因为这才是它实际上的内容)。UTF-16 指定一个代码点由一个或两个代码单元表示,后者称为代理对。 - jalf
2
自C++17以来,此答案已过时。 - Nikolai
真的吗?这当然存在于MinGW中,因为MinGW是MSVC的复制粘贴。 - Алексей Неудачин
@jalf:你似乎对Unicode和C++非常了解。你能否提供一个为期2或3天的课程,让我学习有关Unicode的所有知识(最好包括Microsoft MBCS、Codepages和ANSI)?或者你可以在Pluralsight上创建一个课程(它没有Unicode课程)。 - Thomas Weller

32

自从C++17以来,有一种跨平台的方法可以使用std::filesystem ::path 重载打开Unicode文件名的std :: fstream。示例:

std::ofstream out(std::filesystem::path(u8"こんにちは"));
out << "hello";

1
当我在Windows上尝试时,创建的文件名为“ã“ã‚“ã«ã¡ã¯”(源文件保存为UTF-8)。您是否需要执行其他步骤才能使此示例创建正确的文件名? - thomthom
@thomthom 当使用C++17时,应该是std::filesystem::u8path(u8"whatever") - fkorsa

3

1
这个最终/理论上是否可移植? - Andrew Beatty
3
并非所有的操作系统和文件系统都支持Unicode文件名,因此它并不具有可移植性。根据我所了解的,wchar_t* open()和fstream构造函数是Microsoft的扩展,因为NTFS支持Unicode文件名。 - John Downey
3
更确切地说,这是因为NTFS使用UTF16来编码Unicode文件名。Linux也支持Unicode文件名,但使用UTF8,因此常规的char*版本在那里可以工作。 - jalf
3
如果使用MinGW编译器,是否没有任何选项可供选择? - Tebe

0

看一下Boost.Nowide

#include <boost/nowide/fstream.hpp>
#include <boost/nowide/cout.hpp>
using boost::nowide::ifstream;
using boost::nowide::cout;

// #include <fstream>
// #include <iostream>
// using std::ifstream;
// using std::cout;

#include <string>

int main() {
    ifstream f("UTF-8 (e.g. ß).txt");
    std::string line;
    std::getline(f, line);
    cout << "UTF-8 content: " << line;
}

nowide的效果非常好...可惜它不在标准的boost发行版中;但让它工作起来相当简单...终于能够避开wchar了,太棒了 :) - user176145

0
如果你正在使用混合了 std::ifstream 的 Qt:
return std::wstring(reinterpret_cast<const wchar_t*>(qString.utf16()));

请注意,std::basic_ifstream 构造函数通常不接受 const w_char*,但是在 MS 的 STL 实现中 却可以。对于其他实现,您可能需要调用 qString.utf8(),并使用 const char* 构造函数。

没有ofstream constructor 接受 std::wstring 参数。这似乎是对不同问题的回答。 - IInspectable
这仍然不准确。额外的 basic_ifstream 构造函数是 Microsoft 特定的 C++ 库实现扩展。其他 Windows 编译器可能会或可能不会提供这些构造函数。无论如何,正如 此答案 所解释的那样,您不需要担心字符编码或使用哪个构造函数。只需传递 filesystem::path,它就可以在任何操作系统上运行。 - IInspectable
@IInspectable 再次更新。并非所有人都能使用 C++17。我的回答旨在帮助使用 Qt 的人们。 - Andreas Haferburg
如果您无法使用C++17或Microsoft的C++标准库实现,则没有任何安全的替代方案。对于任何给定平台,传递UTF-8编码的字符串要么不安全,要么已记录为不安全,要么已记录为安全。如果您属于后两个类别中的任意一个,那么您可以自认为很幸运,但实际上您可能会发现自己属于第一个类别。在这种情况下,建议使用utf8()几乎是恶意的,因为它经常不会失败。 - IInspectable
@IInspectable 我不知道你想从我这里得到什么。你认为有什么需要做的吗?为什么要使用“恶意”的词汇?那不是朝着共同目标建设性工作的方式。 - Andreas Haferburg

0
使用 std::wofstreamstd::wifstreamstd::wfstream。它们支持 Unicode 文件名。文件名必须是 wstringwchar_t 数组,或者带有 _T() 宏或在文本前加上 L 前缀。

2
你能提供std::wfstreamUnicode的证据吗?据我所知,它们只使用wchar_t,这是一个宽字符,通常为16位。但内容可能是Unicode,也可能不是。 - Adrian Maire
我的意思是它们接受 Unicode 字符串,这回答了问题,不是吗? - Brackets
实际上它回答了问题的一半:假设您在wfstream中获得了UTF16文件路径(或在fstream中获得了UTF8文件路径)。Windows不接受Unicode,如果有一些特殊字符(例如中文),它将返回“错误的URL”。 - Adrian Maire
1
Windows怎么可能不支持Unicode呢?你是在说Windows的早期版本吗?如果还有人在使用那些版本,他们需要解决更大的问题。 - Brackets
1
你可能是对的。我刚刚遇到了两种情况,需要使用ofstream写入Unicode字符,但wofstream无法帮助我。我尝试了简单的file << L"фыв" << endl;,但它不仅没有写入,还停止了通过file流进行任何进一步的写入。因此,我改用了winAPI的WriteFile - Brackets
wofstream 使用 wchar_t 作为其信息单位。它不提供基于 wchar_t 的任何字符串类型的构造函数。如果您的编译器提供了这个功能,那么它是一个非标准扩展。如果该编译器是 Microsoft 编译器,您可以同样使用 ofstream。非标准扩展可用于任何 basic_ofstream 类模板实例化。 - IInspectable

0

使用

wfstream

替代

fstream

并且

wofstream

替代

ofstream

等等...

您可以在iosfwd头文件中找到这些信息。

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