【问题标题】:Read/write unicode c++读/写 unicode c++
【发布时间】:2012-10-08 20:50:32
【问题描述】:

很遗憾,这是我本周第三次发布问题。

我必须使用 unicode 编码(或 UTF8)将文本写入文件。
我就是这样做的:

创建wofstream mystream;,然后我在其中添加wstring,就像这样mystream << L"hello world";

第一个问题:在我的情况下,流使用什么样的编码?

其次,我想加载我的新文件,但是如何读取这些行? ifstreamgetline 不起作用,因为这条线路显然被毁了。

【问题讨论】:

  • 你是用wifstream还是ifstream阅读?
  • ifstream,但那不起作用。那么如何使用wifstream呢?
  • wifstream inStream("FileName.txt")。然后只需使用getline(inStream, std::wstring)
  • 哇!完全一样-_-呵呵。但它现在工作。谢谢。现在处理编码问题。
  • 虽然它有点隐藏,但 Boost 有一个 codecvt facet for UTF-8 你可能会觉得有用。

标签: c++ file-io encoding utf-8 wofstream


【解决方案1】:

我认为“unicode encoding”是指 UTF-16。实际上有几种编码可能被称为 Unicode 编码,但大多数不熟悉 Unicode 的人认为它是指 UTF-16(我认为主要是因为微软在他们的所有文档中都犯了这个错误)。我的回答还假设您正在为 Windows 编写代码,因此您的内部数据是 UTF-16 存储在 wchar_t 字符串中。


使用宽流对象并不意味着文件输入或输出将使用宽字符完成。事实上,宽流将使用流区域设置的 codecvt 方面,以便在流的字符类型 (wchar_t) 和 char 之间进行转换。

在 C++11 中,有一些 codecvt 方面可用于执行 UTF-16 或 UTF-8 输入/输出; codecvt_utf8, codecvt_utf16, codecvt_utf8_utf16.

codecvt_utf8 将在外部 UTF-8 多字节序列和内部 UTF-32/UCS4 或 UCS2 数据之间进行转换。 codecvt_utf16 将在外部 UTF-16 多字节序列和内部 UTF-32/UCS4 或 UCS2 数据之间进行转换。 codecvt_utf8_utf16 将在外部 UTF-8 多字节序列和内部 UTF-16 数据之间进行转换。

没有内置方法可以在外部 UTF-16 多字节序列和内部 UTF-16 数据之间进行转换,这是在内部使用 UTF-16 编码的 wchar_t 字符串和外部使用 UTF-16 编码的文件时所需要的.

但是由于您指出 UTF-8 输出是可以接受的,所以 codecvt_utf8_utf16 方面会很好地工作。

#include <fstream>
#include <codecvt>

int main() {
    std::wofstream mystream("test.txt");
    mystream.imbue(std::locale(std::locale(),
                   new std::codecvt_utf8_utf16<wchar_t, 0x10ffff, std::codecvt_mode(std::consume_header|std::generate_header)>));
    mystream << "Hello, World!\n";
}

另请注意,此示例在 codecvt_utf8_utf16 方面设置选项以生成和读取所谓的“UTF-8 BOM”。这是微软猜测文件编码的惯例,在其他平台上通常不合适。


以下内容与手头的问题无关,但方面的生命周期管理与大多数其他现代 C++ 生命周期管理不同。

面是引用计数的,当具有特定面的最后一个语言环境被破坏时,面会被删除,除非已通过使用1 的 refs 参数构造面而特别禁用它。上面的示例代码将生命周期管理留给了语言环境,因此看起来类似于内存泄漏。但是,代码是正确的。就异常安全而言,唯一可能在成功分配和由语言环境假定的已分配对象的所有权之间运行的代码是表达式 std::locale(),它被声明为 noexcept。

另一种选择是使用不受区域设置管理的构面,并简单地确保它比区域设置和所有副本的寿命更长。使用具有静态存储持续时间的构面很简单,但请记住通过将其引用计数设置为 1 来指示语言环境不应删除构面。

static std::codecvt_utf8_utf16<wchar_t, 0x10ffff, std::codecvt_mode(std::consume_header|std::generate_header)> mycodecvt(1);
mystream.imbue(std::locale(std::locale(), mycodecvt));

如果语言环境仅在特定范围内短时间存在,那么您可以使用普通的局部变量。这与上述相同,但没有static。只需确保在构面超出范围之前销毁语言环境(以及每个副本)即可。

这是智能指针无法让事情变得更好的时候,因为将所有权移交给智能指针无视对象是很棘手的。您必须弄清楚如何手动处理在语言环境接收到构面并因此获得所有权但在智能指针放弃所有权之前发生的异常。

【讨论】:

  • 在参数中使用new 会导致内存泄漏——关闭文件时没有什么可以删除codecvt_utf8_utf16 对象。我知道这对于这个微不足道的例子并不重要,因为它会被程序退出清理,但它很草率并且树立了一个不好的例子。
  • @MarkRansom 语言环境库在这方面很奇怪。构面是引用计数的,当具有特定构面的最后一个语言环境被销毁时,该构面将被删除。如果您想在堆栈上创建一个构面并将其传递给一个语言环境,您必须创建具有正引用计数的构面,这样语言环境就不会尝试删除它。
  • @MarkRansom 在异常安全方面,唯一可以在成功的new 和被另一个对象接受的分配内存的所有权之间抛出的表达式是std::locale(),并且不允许使用此表达式扔。所以我相信我的代码是正确的,但你可能会认为这是一种不好的风格,因为很难确定它是否正确,而且它不符合现代习语。我将添加一些替代示例。
【解决方案2】:

wchar_t,支持wstreamwstring 的类型取决于平台:在Windows 上为2 个字节,在某些(全部?)Linux 上为4 个字节。所以你最终会写出“Unicode”,但究竟哪个 Unicode 会受到许多变量的影响。你可能会写 UTF32/UCS4,你最终可能会使用 UTF16/UCS2。

如果您想使用特定的、受良好控制的编码(例如 UTF8 或 UCS-2LE 与 UCS-2BE 来控制字节序)进行编写,那么您需要像 iconv 这样的东西。你也可以使用std::localeimbue 一个流,见https://stackoverflow.com/a/1275260/105929

【讨论】:

  • 好的,谢谢!但我正在编写 unicode,这是最重要的。它是一个通常使用 unicode 编码的学校项目。
  • 如果你在任何地方都坚持使用wstringwstream,并使用L"string" 常量,你将编写Unicode,并且你的应用程序将能够正确读取它所写的内容。一旦有了任何类型的互操作,准确了解所使用的编码就成为一个问题。
  • 非常感谢。该链接非常有帮助。毕竟,我的老师很可能让我使用 UTF8,但现在我很高兴。谢谢
  • @HansElsen:请记住,UTF32、UTF16、UTF8 和许多其他编码都是所有 Unicode 编码。如果必须是 UTF8,请按照答案建议使用 unicode 库。
  • @MooingDuck 作为一名学生,我必须从字面上理解。确切的定义是:将 Unicode 或 UTF8 与 BOM 一起使用。所以它是Unicode。哈哈
猜你喜欢
  • 1970-01-01
  • 2010-11-14
  • 2011-06-28
  • 2018-03-01
  • 2020-05-28
  • 1970-01-01
  • 2010-11-26
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多