如何在C++中设置正确的编码方式?

11

如何在C ++中最好设置编码?

我习惯使用Unicode(以及,,,和L"…")。我也保存UTF-8格式的源代码。

目前我使用MinGW(Windows 7),在Windows控制台(cmd.exe)上运行程序,但有时我也可以使用GNU \ Linux上的gcc,并在Linux控制台上使用UTF-8编码运行程序。

我希望能够在Windows和Linux上编译我的源代码,并且希望所有Unicode符号都被正确输入和输出。

当我遇到下一个编码问题时,我查找了各种解决方案:setlocale(LC_ALL, "")setlocale(LC_ALL, "xx_XX.UTF-8")std::setlocale(LC_ALL, "")std::setlocale(LC_ALL, "xx_XX.UTF-8")来自,

SetConsoleCP()SetConsoleOutputCP() 来自等等。

最后,我对这种巫术感到烦恼,我想问你:如何正确地设置编码?


你到底想要改变什么?你想要改变线程区域设置吗?系统区域设置?用户界面语言?还是活动代码页?对于线程、控制台或系统,都有很多选项,远远超出了单个“setlocale”函数所暗示的范围。在我们告诉你要切换哪个开关之前,你必须解释你想要看到的效果 - Cody Gray
@CodyGray,我需要确保任何Unicode符号/字符串都能正确输入和输出。这个描述足够清晰了吗?我认为这意味着我需要更改启动程序的控制台的编码方式。 - shau-kote
一般来说,我认为程序不应该修改语言环境 - 它应该在提供的语言环境内工作。否则,这有点违背了“国际化”的目的。 - Nathan Ernst
2个回答

9
我需要确保任何Unicode符号/字符串都能正确输入和输出。
这当然是可能的,不过让Windows命令提示符控制台正确支持Unicode需要一些特殊的技巧。不幸的是,我认为标准库函数的任何实现都不会做到这一点。
您会在Stack Overflow上找到很多关于此问题的提问,但是这个问题是一个好的例子。基本上,控制台默认使用所谓的“OEM”代码页(有些错误)。您需要将其更改为UTF-8代码页,其值由CP_UTF8定义。为此,您需要调用SetConsoleCP函数(设置输入代码页)和SetConsoleOutputCP函数(设置输出代码页)。代码应该类似于以下内容:
if (!SetConsoleCP(CP_UTF8))
{
    // An error occurred; handle it. Call GetLastError() for more information.
    // ...
}
if (!SetConsoleOutputCP(CP_UTF8))
{
    // An error occurred; handle it. Call GetLastError() for more information.
    // ...
}

为了提高稳定性,您可能还需要确保先支持UTF-8代码页,而后再尝试设置和使用它。您可以通过调用IsValidCodePage函数来实现。例如:

if (IsValidCodePage(CP_UTF8))
{
    // We're all good, so set the console code page...
}

你还需要将默认字体(“光栅字体”)更改为包含所需Unicode字符字形的字体,例如Lucida Console或Consolas(参考)。使用SetCurrentConsoleFontEx函数轻松实现。
不幸的是,该功能在Vista之前的Windows版本中不存在。如果您绝对需要支持这些旧操作系统,则唯一我知道的方法是调用未记录的SetConsoleFont函数。通常,我会强烈建议不要使用未记录的函数,但我认为在这里问题较小,因为您只会在旧操作系统中使用它。您知道这些不会改变。在新版本中可用时,请调用支持的函数。以下是示例未经测试的代码:
bool IsWinVistaOrLater()
{
    OSVERSIONINFOEX osvi;
    osvi.dwOSVersionInfoSize = sizeof(osvi);
    GetVersionEx(reinterpret_cast<LPOSVERSIONINFO>(&osvi));

    if (osvi.dwPlatformId == VER_PLATFORM_WIN32_NT)
    {
        return osvi.dwMajorVersion >= 6;
    }
    return false;
}

void SetConsoleToUnicodeFont()
{
    HANDLE hConsole = GetStdHandle(STD_OUTPUT_HANDLE);
    if (IsWinVistaOrLater())
    {
        // Call the documented function.
        typedef BOOL (WINAPI * pfSetCurrentConsoleFontEx)(HANDLE, BOOL, PCONSOLE_FONT_INFOEX);
        HMODULE hMod = GetModuleHandle(TEXT("kernel32.dll"));
        pfSetCurrentConsoleFontEx pfSCCFX = (pfSetCurrentConsoleFontEx)GetProcAddress(hMod, "SetCurrentConsoleFontEx");

        CONSOLE_FONT_INFOEX cfix;
        cfix.cbSize       = sizeof(cfix);
        cfix.nFont        = 12;
        cfix.dwFontSize.X = 8;
        cfix.dwFontSize.Y = 14;
        cfix.FontFamily   = FF_DONTCARE;
        cfix.FontWeight   = 400;  // normal weight
        lstrcpy(cfix.FaceName, TEXT("Lucida Console"));

        pfSCCFX(hConsole,
                FALSE, /* set font for current window size */
                &cfix);
    }
    else
    {
        // There is no supported function on these older versions,
        // so we have to call the undocumented one.
        typedef BOOL (WINAPI * pfSetConsoleFont)(HANDLE, DWORD);
        HMODULE hMod = GetModuleHandle(TEXT("kernel32.dll"));
        pfSetConsoleFont pfSCF = (pfSetConsoleFont)GetProcAddress(hMod, "SetConsoleFont");
        pfSCF(hConsole, 12);
    }
}

请注意,我已经将添加所需的错误检查留给读者作为练习。这里的重点是技术和可读性;用错误处理来混淆问题只会让事情更加混乱。
我不知道如何在Linux上完成任何这样的操作。我猜想这需要的工作量要少得多,因为人们告诉我该操作系统在内部使用UTF-8。无论哪种方式,你都必须自己解决;让Windows运行良好已经足够了!

2
我刚刚需要将Unicode文本输出到控制台,只有这个函数WriteConsoleW(GetStdHandle(STD_OUTPUT_HANDLE), ...);能够帮助我。对于输入,我认为ReadConsoleW(GetStdHandle(STD_INPUT_HANDLE), ...);会起作用。 PS:WriteOutput在输出字符串大小上有限制。如果它更长,您可能需要分块迭代输出。

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