如何可移植地将 std::wstring 写入文件?

Posted

技术标签:

【中文标题】如何可移植地将 std::wstring 写入文件?【英文标题】:How to portably write std::wstring to file? 【发布时间】:2011-05-02 12:29:50 【问题描述】:

我有一个 wstring 声明如下:

// random wstring
std::wstring str = L"abcàdëefŸg€hhhhhhhµa";

文字将是 UTF-8 编码的,因为我的源文件是。

[编辑:根据 Mark Ransom 的说法,情况不一定如此,编译器将决定使用什么编码 - 让我们假设我从编码的文件中读取这个字符串,例如UTF-8]

我非常希望将其放入文件读取中(当文本编辑器设置为正确的编码时)

abcàdëefŸg€hhhhhhhµa

但是ofstream 不是很配合(拒绝接受wstring 参数),而且wofstream 据说需要知道语言环境和编码设置。我只想输出这组字节。一般人是怎么做到的?

编辑:它必须是跨平台的,并且不应依赖编码为 UTF-8。我恰好在wstring 中存储了一组字节,并想输出它们。它很可能是 UTF-16 或纯 ASCII。

【问题讨论】:

Win32 API 为此提供了WideCharToMultiByte 我需要一个跨平台的解决方案,对不起。 为什么不使用 C++ 的标准语言环境功能? stdcxx.apache.org/doc/stdlibref/codecvt-byname.html @basilevs:查看对您答案的评论 更多L""字符串编码信息:***.com/questions/1810343/… 【参考方案1】:

对于std::wstring,您需要std::wofstream

std::wofstream f(L"C:\\some file.txt");
f << str;
f.close();

【讨论】:

如果字符串实际上包含非 8 位字符,这在 Windows 中不起作用【参考方案2】:

std::wstring 用于 UTF-16 或 UTF-32,not UTF-8。对于 UTF-8,您可能只想使用std::string,并通过std::cout 写出。只是 FWIW,C++0x 将有 Unicode 文字,这应该有助于澄清这种情况。

【讨论】:

@oystein:Jerry 告诉你的是(1)wstring 没有给你 UTF-8 编码,(2)string 有,如果你的源代码是 UTF- 8 编码。干杯&hth., @oystein:wstring 根本不是 UTF-8。您可以将 UTF-8 存储在 std::string 中,但使用诸如 find 之类的字符串方法时必须非常小心。 @oystein:wchar_t 不能(合理地)表示 UTF-8 — 它的整个 存在理由 是表示宽字符而不是多字节编码。 不,wstring 只是一个基本字符串。仅此而已。 @oystein:是的,但 UTF-8 的全部意义在于将代码点编码为 8 位“块”。 wchar_t 专门用于处理大于 8 位的“块”。因此,虽然您可以将 UTF-8 存储到 wchar_t 中,但这样做完全没有意义。 char 保证为(至少)8 位,这(反过来)保证它将毫无问题地保存 UTF-8 数据。【参考方案3】:

为什么不将文件写成二进制文件。只需将 ofstream 与 std::ios::binary 设置一起使用。那时编辑应该能够解释它。不要忘记开头的 Unicode 标志 0xFEFF。 使用库编写可能会更好,请尝试以下方法之一:

http://www.codeproject.com/KB/files/EZUTF.aspx

http://www.gnu.org/software/libiconv/

http://utfcpp.sourceforge.net/

【讨论】:

问题是我不知道这是 UTF-8,所以我必须不用 BOM。但是,我会看看我是否可以使用二进制文件。不过,这对我正在做的事情来说有点麻烦——如果可能的话,我宁愿避免这样做。 我决定放弃对 unicode 的支持,对我来说不值得。然而,我觉得这个答案是最接近有效解决方案的答案,所以你得到了接受的状态(至少现在是这样)。【参考方案4】:

有一个(特定于 Windows 的)解决方案应该适合您 here。基本上,将wstring 转换为UTF-8 代码页,然后使用ofstream

#include < windows.h >

std::string to_utf8(const wchar_t* buffer, int len)

        int nChars = ::WideCharToMultiByte(
                CP_UTF8,
                0,
                buffer,
                len,
                NULL,
                0,
                NULL,
                NULL);
        if (nChars == 0) return "";

        string newbuffer;
        newbuffer.resize(nChars) ;
        ::WideCharToMultiByte(
                CP_UTF8,
                0,
                buffer,
                len,
                const_cast< char* >(newbuffer.c_str()),
                nChars,
                NULL,
                NULL); 

        return newbuffer;


std::string to_utf8(const std::wstring& str)

        return to_utf8(str.c_str(), (int)str.size());


int main()

        std::ofstream testFile;

        testFile.open("demo.xml", std::ios::out | std::ios::binary); 

        std::wstring text =
                L"< ?xml version=\"1.0\" encoding=\"UTF-8\"? >\n"
                L"< root description=\"this is a naïve example\" >\n< /root >";

        std::string outtext = to_utf8(text);

        testFile << outtext;

        testFile.close();

        return 0;

【讨论】:

这一切都很好,但我不知道我的字符串的编码,所以这不会真正有帮助..而且我需要跨平台 @luke - 我在响应的第一个版本的第一行中确实链接到了那个。 aaaaahhh,我的历史记录中已经有了这个链接,所以它看起来像纯文本。非常抱歉。 @luke - np ; @oystein - 无论如何我都会把它留在这里以供将来参考 - 抱歉,它在你的场景中没有用。 对我有用的独特反应......谢谢【参考方案5】:

C++ 可以在输出或文件写入时执行从宽字符到本地化字符的转换。 Use codecvt facet 用于此目的。

您可以使用标准的std::codecvt_byname,或非标准的codecvt_facet implementation。

#include <locale>
using namespace std;
typedef codecvt_facet<wchar_t, char, mbstate_t> Cvt;
locale utf8locale(locale(), new codecvt_byname<wchar_t, char, mbstate_t> ("en_US.UTF-8"));
wcout.imbue(utf8locale);
wcout << L"Hello, wide to multybyte world!" << endl;

请注意,在某些平台上,codecvt_byname 只能为系统中安装的语言环境发出转换。因此,我建议在 *** 中搜索“utf8 codecvt” " 并从列出的许多自定义 codecvt 实现的引用中进行选择。

编辑: 由于 OP 声明字符串已经编码,他应该做的就是从他的代码的每个标记中删除前缀 L 和“w”。

【讨论】:

其实codecvt可以用来执行任何需要的转换,但是STL提供的最常用的一种是输入/输出操作。 是的,但我不想转换任何东西,或者我错过了什么?字符串已经编码 那你为什么要制作编译器将其转换为带有 L 前缀的 UNICODE?只需用窄流输出即可。 Encoded - 表示存储在外部编码中。在您的情况下,您 write 使用外部编码。然后编译器将您的代码转换为 UNICODE、内部编码并将其存储在目标模块中。因此,如果你想输出一些东西,你应该执行反向转换或停止让编译器做不必要的事情。 顺便说一句,这里是基于 winapi 和 iconv 的 codecvt 的半工作实现。它们说明了代码点大小的问题:fakedetector.cvs.sourceforge.net/viewvc/fakedetector/fakebase/…fakedetector.cvs.sourceforge.net/viewvc/fakedetector/fakebase/…【参考方案6】:

注意,宽流只输出 char * 变量,所以也许你应该尝试使用c_str() 成员函数来转换std::wstring,然后将其输出到文件中。那么它应该可以工作吗?

【讨论】:

似乎对我不起作用,不适用于 wofstream,也不适用于 ofstream 啊哎呀。很抱歉没有提供帮助。【参考方案7】:

如果你想编写可移植的代码,你应该使用 UTF-8 编码的源文件。对不起。

std::wstring str = L"abcàdëefŸg€hhhhhhhµa";

(我不确定这是否真的会损害标准,但我认为确实如此。但即使如此,为了安全起见你也不应该这样做。)

是的,纯粹使用std::ostream 是行不通的。有很多方法可以将 wstring 转换为 UTF-8。我最喜欢的是使用International Components for Unicode。这是一个很大的库,但它很棒。你会得到很多额外的东西和你将来可能需要的东西。

【讨论】:

对不起,我觉得人们不明白这个问题的重点,也许我不够清楚。问题不在于 UTF-8。这只是我选择的一个例子。我可能会从文件中读取 (w) 字符串,它可以有任何编码。问题是将其写回文件。 我明白了。那么您可能只需要确保以二进制模式打开文件。 @oystein,哇,我现在遇到了你的问题。如果您不知道编码,则无法转换代码点。如果你不能这样做,那么在 wchar_t 中就没有任何意义。票数最高的答案肯定是正确的。 可能,请参阅 inf.ig.sh 的答案。我可能就这样结束了。 @basilevs:我使用 wchar_t 是有原因的。在我写回该字符串之前,我想对该字符串进行大量繁重的操作,并且必须依赖我的字符串的每个元素都是一个完整的字符。一旦您走出英语世界,std::string 就不会出现这种情况。有了宽弦,我就可以忍受了。【参考方案8】:

根据我使用不同字符编码的经验,我建议您仅在加载时处理 UTF-8 并节省时间。如果您尝试将内部表示存储在 UTF-8 中,您将陷入痛苦的世界,因为单个字符可能是 1 字节到 4 之间的任何字符。所以像 strlen 这样的简单操作需要查看每个字节来决定 len 而不是分配的缓冲区(尽管您可以通过查看 char 序列中的第一个字节来优化,例如 00..7f 是单字节字符,c2..df 表示 2 字节字符等)。

人们经常提到“Unicode 字符串”时,他们表示 UTF-16,而在 Windows 上 wchar_t 是固定的 2 个字节。在 Windows 中,我认为 wchar_t 很简单:

typedef SHORT wchar_t;

很少需要完整的 UTF-32 4 字节表示并且非常浪费,这里是 Unicode 标准 (5.0) 必须说的:

“平均而言,超过 99% 的 UTF-16 是使用单个代码单元表示的……UTF-16 提供了紧凑的大小与处理 BMP 之外的偶发字符的能力的正确组合”

简而言之,使用 whcar_t 作为您的内部表示,并在加载和保存时进行转换(除非您知道需要它,否则不要担心完整的 Unicode)。

关于执行实际转换,请查看 ICU 项目:

http://site.icu-project.org/

【讨论】:

这里有一些明智的话。老实说,我试图完全避免编码,因为我真的不知道在这种情况下我会遇到什么。这使得进行任何转换变得困难。将其存储为 vector (或类似的)意味着我必须创建自己的字符串类,并且 unicode 支持 真的 不值得那么多编码时间。看起来我现在要放弃 unicode 支持了,但我们拭目以待。 (1) 知道字符串中有多少字节(用于内存分配、磁盘空间等)通常比知道有多少字节更有用字符 在字符串中。为此,strlen 确实适用于 UTF-8。 (2) “大多数操作系统将 wchar_t 视为固定的 2 个字节”或 UTF-16 是不正确的。这是 Windows 的事情,是为了向后兼容基于 UCS-2 的旧版本 NT。在 Linux 上,wchar_t 通常是 UTF-32。因此,对于跨平台代码,您需要使用 UTF-8 或 typedef 您自己的 UTF-16 / UTF-32 类型。幸运的是,新的 C++ 标准将有 char16_tchar32_t @dan04 老实说,我大部分时间都在 Win 世界度过,所以我无法与其他操作系统争论。 Unicode 标准 (5.0) 声明“平均超过 99% 的 UTF-16 使用单个代码单元表示...... UTF-16 提供了紧凑大小与处理 BMP 之外的偶尔字符的能力的正确组合” .这是我的主要观点。关于知道字符大小而不是字节大小有多有用......尝试在不知道字符长度的情况下编写任何字符处理代码! UTF-8 非常适合可移植性(没有字节顺序问题),但不适用于工作。 我写了 很多 不关心字符长度的字符串处理代码。例如,考虑一个将 DOS 样式换行符转换为 Unix 样式换行符的例程。 3个字节“\xE2\x82\xAC”是否代表单个字符无关紧要;你只是要输出它们不变。您只关心 '\r' 和 '\n' ,它们在 UTF-8 和 ASCII 中是一样的。【参考方案9】:

我前段时间遇到了同样的问题,把我在博客上找到的解决方案写了下来。你可能想看看它是否有帮助,尤其是函数wstring_to_utf8

http://pileborg.org/b2e/blog5.php/2010/06/13/unicode-utf-8-and-wchar_t

【讨论】:

谢谢你,但这不是我想要的,因为我不知道我的字符串将采用什么编码。对于这个例子,我只选择了 UTF-8。另外我不认为 w_char 保证能够包含 4 字节字符(UCS-4)?它在 Linux 上,但我认为 Windows 用户在这里会遇到一些问题。 链接已断开。 这不是你拼写“had”的方式。

以上是关于如何可移植地将 std::wstring 写入文件?的主要内容,如果未能解决你的问题,请参考以下文章

将 std::wstring 从 Visual Studio 移植到 mingw gcc

如何初始化和打印 std::wstring?

如何将 UTF-8 std::string 转换为 UTF-16 std::wstring?

如何使用正则表达式删除 std::wstring 中特定短语的所有实例?

如何将 std::wstring 转换为 const TCHAR*?

将 std::wstring 转换为 WCHAR*