【问题标题】:Unicode problems in C++ but not CC++ 中的 Unicode 问题,但 C 中没有
【发布时间】:2014-02-17 16:37:09
【问题描述】:

我正在尝试在 Windows 上用 C++ 将 unicode 字符串写入屏幕。我将控制台字体更改为Lucida Console,并将输出设置为CP_UTF8 aka 65001。

我运行以下代码:

#include <stdio.h>  //notice this header file..
#include <windows.h>
#include <iostream>

int main()
{
    SetConsoleOutputCP(CP_UTF8);
    const char text[] = "Россия";
    printf("%s\n", text);
}

打印出来就好了!

但是,如果我这样做:

#include <cstdio>  //the C++ version of the header..
#include <windows.h>
#include <iostream>

int main()
{
    SetConsoleOutputCP(CP_UTF8);
    const char text[] = "Россия";
    printf("%s\n", text);
}

打印:������������

我不知道为什么..

另一件事是当我这样做时:

#include <windows.h>
#include <iostream>

int main()
{
    std::uint32_t oldcodepage = GetConsoleOutputCP();
    SetConsoleOutputCP(CP_UTF8);

    std::string text = u8"Россия";
    std::cout<<text<<"\n";

    SetConsoleOutputCP(oldcodepage);
}

我得到与上面相同的输出(非工作输出)。

std::string 上使用printf,但效果很好:

#include <stdio.h>
#include <windows.h>
#include <iostream>

int main()
{
    std::uint32_t oldcodepage = GetConsoleOutputCP();
    SetConsoleOutputCP(CP_UTF8);

    std::string text = u8"Россия";
    printf("%s\n", text.c_str());

    SetConsoleOutputCP(oldcodepage);
}

但前提是我使用stdio.h 而不是cstdio

任何想法我可以如何使用std::cout?我如何也可以使用cstdio? 为什么会这样? cstdio 不只是 stdio.h 的 c++ 版本吗?

编辑:我刚刚尝试过:

#include <iostream>
#include <io.h>
#include <fcntl.h>

int main()
{
    _setmode(_fileno(stdout), _O_U8TEXT);
    std::wcout << L"Россия" << std::endl;
}

是的,它有效,但前提是我使用 std::wcoutwide strings。我真的很想避免wide-strings,到目前为止我看到的唯一解决方案是C-printf:l

所以问题仍然存在..

【问题讨论】:

  • 如果在包含cstdio 的情况下执行std::printf 会怎样?
  • 它打印相同的坏字符。有或没有std:: 没有区别我使用的是Mingw 4.8.1。最新版本。
  • 我在VS2010中尝试过一次这个实验。结果:不要使用 UTF8。 IIRC 的主要问题是流的缓冲区,即cout 一次将一个字符传递给控制台,然后无法正确呈现多单元代码点。
  • @MarkRansom 是的,你可以用你自己的替换 cout 的缓冲区。我也试过了,它“有效”,但 CRT 本身不完全支持 (*) 并且控制台也不支持 IIRC。 (*) MSDN 说 setlocale 不支持每个字符超过两个字节的语言环境。
  • dyp 所说的 - 代码页 65001 已损坏到通常无法使用的程度。多字节编码仅在 MS CRT 中正确支持 ANSI 代码页,如 Windows 在某些东亚语言环境中默认使用的 932 和 936。在内部处理 UTF-8 格式的字符串可能是一件明智的事情,但在 Windows 上,它仍然是一个二等公民,不能与任何标准的面向字节的 C stdlib 接口一起工作。遗憾的是,通常最好使用一层将 Win32 范围的 API 转换为 UTF-8。

标签: c++ c unicode utf-8


【解决方案1】:

虽然您已将控制台设置为期望 UTF-8 输出,但我怀疑您的编译器将字符串文字视为其他字符集中。我不知道为什么 C 编译器的行为不同。

好消息是 C++11 包括对 UTF-8 的一些支持,并且微软已经实现了标准的相关部分。代码有点复杂,但您需要查看 std::wstring_convert(与 UTF-8 相互转换)和 &lt;cuchar&gt; 标头。

您可以使用这些函数将其转换为 UTF-8,并且假设您的控制台需要 UTF-8,那么一切应该可以正常工作。

就个人而言,当我需要调试这样的东西时,我经常将输出定向到文本文件。文本编辑器似乎比 Windows 控制台更好地处理 Unicode。就我而言,我经常正确输出代码点,但控制台设置不正确,所以我最终还是会打印垃圾。


我可以告诉你,这在 Linux(使用 Clang)和 Windows(使用 GCC 4.7.3 和 Clang 3.5)中都适用;你需要在命令行中添加“std=c++11”才能编译GCC 或 Clang):

#include <cstdio>

int main()
{
    const char text[] = u8"Россия";
    std::printf("%s\n", text);
}

使用 Visual C++(2012,但我相信它也适用于 2010),我不得不使用:

#include <codecvt>
#include <cstdio>
#include <locale>
#include <string>

int main()
{
    std::wstring_convert<std::codecvt_utf8<wchar_t>> converter;
    auto text = converter.to_bytes(L"Россия");
    std::printf("%s\n", text.c_str());
}

【讨论】:

    【解决方案2】:

    C 实现在这里工作比 C++ 不工作更令人惊讶。 char 只能包含一个字节(数值 0-255),因此控制台应该只显示 ASCII 字符。

    C 必须在这里为你做了一些魔法——事实上,它猜测这些字节超出了 ASCII 范围(即 0-127),你正在提供 form 一个 Unicode(可能是 UTF- 8) 多字节字符。 C++ 仅显示 const char[] 数组的每个字节,并且由于单独处理的 UTF 字节在您的字体中没有不同的字形,因此它将这些...请注意,您分配 6 个字母并得到 12 个问号。

    如果需要,您可以阅读有关 UTF-8ASCII 编码的信息,但重点是 std::wstringstd::wcout 确实是处理大于字节字符的最佳解决方案。

    (如果您根本不使用拉丁字符,那么当您使用基于char 的解决方案(例如const char[]std::string 而不是std::wstring)时甚至不会节省内存。所有这些西里尔字母代码无论如何都必须占用一些空间)。

    【讨论】:

    • 如果我使用std::wstringstd::wcout,它什么也没有打印出来。。什么也没有。事实上,这是我尝试的第一件事。我也对 C 代码有效但 C++ 代码无效感到惊讶。我尝试了 gcc/g++ 的所有内容,包括 setlocale(LC_ALL, "Russian")system("chcp 65001 &gt; 0");。一切。唯一有效的解决方案是 C 和 _setmode 一个,它们在 OP 上。没有其他工作/工作。甚至 C++ 的 printf.
    • 你确定吗?我很肯定 UTF8 不需要 std::wstringwide-charsL 前缀。毕竟,printf 不需要这样做。
    • 嗯什么。 UTF-8 使用字节;对于存储 UTF-8,你不想要,我重复一遍:你不想要 wchars。 wchars 用于 Windows 上的 UTF-16 和 *nixes 上的 UTF-32;它们不是,我重复一遍:它们不适用于 UTF-8。另外:只要你不直接和 WinAPI 交互,你就不敢用宽字符和宽字符串。永远不会。
    • @szym_rutkowski,另一个原因:你应该(大部分)信任委员会的人,因为他们(通常)知道自己在做什么。取模 u8 应该使用一些新类型,而不是旧类型的事实 - u8 给你一个 chars 的字符串,而不是宽字符。让我们重申一下:永远不要对 UTF-8 使用宽字符。永远不能。如果您需要进一步解释,请考虑阅读有关 UTF-8 和使用它的信息。
    • 我关于“C++ 显示某些东西”的声明只是为了让事情变得更简单,我知道你所说的。另外:我考虑过使用wchars,让每个wchar 存储一个UTF8 字符,而不管其实际字节长度如何。以您所描述的方式使用wchar 显然是愚蠢的。也许我会用 gdb 检查它的实际行为。
    【解决方案3】:

    如果您的文件编码为 UTF-8,您会发现字符串长度为 12。在其上运行 strlen from &lt;string.h&gt; (&lt;cstring&gt;) 以了解我的意思。设置输出代码页将完全按照您看到的方式打印字节。

    编译器看到的等价于以下内容:

    const char text[] = "\xd0\xa0\xd0\xbe\xd1\x81\xd1\x81\xd0\xb8\xd1\x8f";
    

    将它包裹在一个宽字符串中(尤其是wchar_t),事情就不那么好了。

    为什么 C++ 处理它的方式不同?我没有丝毫线索,除了 C++ 版本底层代码使用的机制可能有些无知(例如std::cout 很高兴地盲目地输出你想要的任何东西)。不管是什么原因,显然坚持使用 C 是最安全的……考虑到微软自己的 C 编译器甚至无法编译 C99 代码这一事实,这对我来说实际上是出乎意料的。

    无论如何,如果可能的话,我建议不要输出到 Windows 控制台,无论是否是 Unicode。文件更可靠,更不用说麻烦了。

    【讨论】:

      猜你喜欢
      • 2013-12-18
      • 2013-02-12
      • 1970-01-01
      • 2011-07-26
      • 2016-11-26
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2018-01-30
      相关资源
      最近更新 更多