C++中如何将Unicode写入文件

9

我在使用C++写入文件时遇到了一个Unicode编码的问题。 我想要将几个笑脸(可以通过按ALT + NUMPAD(2)获得)写入带有自己扩展名的文件中。通过创建一个char并将值设置为'\2'并显示笑脸,我可以在CMD上显示它,但它无法写入文件。

以下是我的程序代码片段:

ofstream myfile;
myfile.open("C:\Users\My Username\test.exampleCodeFile");
myfile << "\2";
myfile.close();

它会写入文件,但它不会显示我想要的内容。我本想给你展示它所显示的内容,但由于StackOverflow不允许我显示字符,所以无法呈现。谢谢提前。

5
你需要区分输入法和字符,同时还要区分Unicode和各种字符编码(Unicode不是一种编码)。请查看《每个软件开发人员绝对必须了解的Unicode和字符集基础知识(无任何借口!)》以获取指南。 - user395760
你用什么程序打开这个文件?很可能,你需要先向文件添加BOM,然后将实际字节写入文件,而不是它们的字符表示。 - ryanbwork
我正在用记事本打开它。整个程序实际上是在文件上制作并显示笑脸。该项目是一个编译器,用于将脚本语言编译成自定义十六进制代码以供解释器读取。 - Garrett Ratliff
你应该只向文件写入UTF-8编码。 - Pavel Radzivilovsky
3个回答

10
您需要使用Unicode来指定您想要显示的字符。在控制台中,由字节02h表示的字符是由代码页437(cp437)转换为Unicode字符U+263B。使用以UTF-8带BOM保存的源文件使得使用Unicode更加容易,因为您可以粘贴或输入所需的字符,而无需使用Unicode转义码。
对于文件流,需要将流配置为UTF-8。有各种方法可以实现这一点,具体取决于编译器,但是使用Visual Studio 2012,保存在UTF-8 w / BOM格式的源代码,再加上一些谷歌搜索即可实现。
#include <locale>
#include <codecvt>
#include <fstream>
#include <iostream>
#include <io.h>
#include <fcntl.h>
using namespace std;

int main()
{
    const std::locale utf8_locale = std::locale(std::locale(), new std::codecvt_utf8<wchar_t>());
    wofstream f(L"sample.txt");
    f.imbue(utf8_locale);
    f << L"\u263b我是美国人。我叫马克。" << endl;

    _setmode(_fileno(stdout),_O_U16TEXT);
    wcout << L"\u263b我是美国人。我叫马克。" << endl;
}

在记事本中查看sample.txt的内容:

☻我是美国人。我叫马克。

十六进制转储(正确的UTF-8格式):
E68891E698AFE7BE8EE59BBDE4BABAE38082E68891E58FABE9A9ACE5858BE380820D0A

以下是控制台输出的剪贴内容。如果没有正确的字体,中文字符的视觉显示将会是 �,但在 SO 或记事本中剪贴后,这些字符将能够正常显示。

☻我是美国人。我叫马克。

终于!!!感谢您提供可用的代码。(file.imbue(std::locale(std::locale(), new std::codecvt_utf8<wchar_t>())); - Andrew
_setmode() 调用对我来说不是必要的。 - Andrew

5

ALT+NUMPAD2不同于ASCII字符2,你的代码实际上是写入文件的ASCII字符2。ALT代码是DOS处理非ASCII字符的方法。CMD.COM显示的ALT+NUMPAD2对应的字形实际上是Unicode码点U+263B“黑色笑脸”。由于这是一个Unicode字符,因此最好使用UTF-8或UTF-16编码文件,例如:

encoding="utf-8"encoding="utf-16"
ofstream myfile;
myfile.open("C:\\Users\My Username\\test.txt");
myfile << "\xEF\xBB\xBF"; // UTF-8 BOM
myfile << "\xE2\x98\xBB"; // U+263B
myfile.close();

.

ofstream myfile;
myfile.open("C:\\Users\\My Username\\test.txt");
myfile << "\xFF\xFE"; // UTF-16 BOM
myfile << "\x3B\x26"; // U+263B
myfile.close();

这两种方法都会在记事本中显示一个笑脸(前提是你使用支持笑脸的字体),因为它首先读取BOM,然后根据此解码Unicode代码点。


3
您正在使用与Unicode完全相反的编码方式。控制台采用8位代码页,西方计算机上的默认代码页是代码页437,它与旧IBM PC字符ROM的字符集匹配,并且是大多数传统DOS程序所期望的代码页。第一组字符代码,即0到8号代码如下图所示:

enter image description here

请注意,代码0x02对应的笑脸就是您在控制台上看到的那个。您可以在此Wikipedia文章中查看其余的字形。8位字符编码的一个大问题是有太多这样的编码方式。记事本使用的是不同的代码页来读取您的文件。在西欧和美洲的计算机上,默认代码页为Windows-1252。该页面没有任何控制字符的字形,这就是为什么您在记事本中没有看到笑脸的原因。
处理代码页是一个很大的头痛。这就是为什么Unicode被发明的原因。
将控制台切换到Unicode代码页是可能的。但是它仍然必须是一个8位编码,这是支持输出重定向的控制台程序的另一个传统遗留问题。因此,正确的选择是utf-8。您可以在启动程序之前通过键入chcp 65001来从控制台本身进行切换。或者您可以在代码中执行此操作,调用SetConsoleOutputCP(CP_UTF8);
还有一个不幸的细节需要注意,您还需要更改用于控制台的字体。默认字体是TERMINAL,它是一种旨在显示IBM PC字形但对Unicode一无所知的传统字体。使用系统菜单进行切换(按Alt + Space,属性),可供选择的不多,但Consolas或Lucinda Console都是合适的选择。
现在,您可以显示Unicode了,这是Remy介绍的另一个故事。

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