代码之家  ›  专栏  ›  技术社区  ›  MPelletier

C++ Visual Studio字符编码问题

  •  13
  • MPelletier  · 技术社区  · 15 年前

    不能把我的头绕在这件事上是一个真正的羞耻之源…

    我正在用法语版本的Visual Studio(2008)在法语Windows(XP)中工作。输入字符串并发送到输出窗口的法语重音符号会损坏。同位输入 输出窗口。典型的字符编码问题是,我输入ansi,返回utf-8,或者达到这个效果。在输出窗口显示“硬编码”字符串时,什么设置可以确保字符保持在ANSI格式?

    编辑:

    例子:

    #include <iostream>
    
    int main()
    {
    std:: cout << "àéêù" << std:: endl;
    
    return 0;
    }
    

    将在输出中显示:

    第二章

    (此处编码为HTML以供您查看)

    我真的很想展示:

    艾耶斯

    7 回复  |  直到 7 年前
        1
  •  13
  •   Bahbar    15 年前

    在我进一步说之前,我要提到的是,你所做的不是C/C++兼容的。这个 specification 在2.2中说明哪些字符集在源代码中有效。里面没什么,所有的字符都是ASCII码。所以…下面的所有内容都是关于一个特定的实现(事实上,VC2008是在一台美国本地机器上实现的)。

    首先,你有4个字符 cout 行和输出上的4个标志符号。所以这个问题不是UTF8编码的问题,因为它会将多个源字符组合成较少的字形。

    从源字符串到控制台上的显示,所有这些都起到了作用:

    1. 源代码文件的编码方式(即编译器将如何查看C++文件)
    2. 编译器如何处理字符串文字,以及它理解的源代码
    3. 你如何 << 解释要传递的编码字符串
    4. 控制台需要什么编码
    5. 控制台如何将输出转换为字体glyph。

    现在。。。

    1和2相当容易。看起来编译器会猜测源文件的格式,并将其解码为其内部表示形式。它在当前代码页中生成字符串文本对应的数据块,不管源代码是什么。我在这方面找不到明确的细节/控制。

    3更容易。除控制代码外, << 只需为char*传递数据。

    4由控制 SetConsoleOutputCP . 它应该默认为默认的系统代码页。你也可以弄清楚你和谁在一起 GetConsoleOutputCP (输入的控制方式不同,通过 SetConsoleCP )

    5是一个有趣的。我用CP1252(西欧版,Windows版)敲了敲脑袋,想知道为什么我不能让_)正常出现。结果是,我的系统字体没有该字符的字形,并且有用地使用了我的标准代码页的字形(大写theta,如果我不调用setconsoleoutputcp,我也会得到同样的字形)。为了解决这个问题,我必须将控制台上使用的字体改为Lucida控制台(真正的字体)。

    我学到了一些有趣的东西:

    • 源代码的编码并不重要,只要编译器能够找到它(特别是,将其更改为utf8并不会更改生成的代码)。我的“_”)字符串仍然使用CP1252编码为 233 0 )
    • VC正在为我似乎无法控制的字符串文本选择代码页。
    • 控制控制台显示的内容比我预期的要痛苦得多

    所以…这对你意味着什么?以下是一些建议:

    • 不要在字符串文本中使用非ASCII。使用资源,其中 控制编码。
    • 确保您知道控制台需要什么编码,并且您的字体具有表示您发送的字符的标志符号。
    • 如果您想知道在您的示例中使用的是什么编码,我建议您将字符的实际值打印为整数。 char * a = "é"; std::cout << (unsigned int) (unsigned char) a[0] 显示了233,这恰好是CP1252中的编码。

    顺便说一句,如果你得到的是“___”,而不是你粘贴的内容,那么看起来你的4个字节被解释为 CP850 .

        2
  •  5
  •   ruf    14 年前

    试试这个:

    #include <iostream>
    #include <locale>
    
    int main()
    {
     std::locale::global(std::locale(""));
     std::cout << "àéêù" << std::endl;
    
     return 0;
    }
    
        3
  •  2
  •   Charles Anderson    15 年前

    我试过这个代码:

    #include <iostream>
    #include <fstream>
    #include <sstream>
    
    int main()
    {
        std::wstringstream wss;
        wss << L"àéêù";
        std::wstring s = wss.str();
        const wchar_t* p = s.c_str();
        std::wcout << ws.str() << std::endl;
    
        std::wofstream file("C:\\a.txt");
        file << p << endl;
    
        return 0;
    }
    

    调试器显示,wss、s和p与输出文件一样,都具有预期值(即“___”)。然而,控制台上出现的是¨。

    因此,问题是在VisualStudio控制台中,而不是C++。使用Bahbar出色的答案,我补充道:

        SetConsoleOutputCP(1252);
    

    作为第一行,然后控制台输出按它应该的方式出现。

        4
  •  2
  •   Davislor    7 年前

    因为我被要求去做,我会做一些巫术。其他答案来自2009年,但这篇文章仍然是我在2018年搜索到的。今天的情况完全不同。而且,即使是在2009年,接受的答案也是不完整的。

    源字符集

    每一个编译器(包括微软公司的Visual Studio 2008和以后,GCC,CLAN和ICC)都会读取从BOM开始的UTF-8源文件,而没有问题,CLAN将不读取任何东西,而UTF-8,所以带有BOM的UTF-8是C和C++源文件的最低公分母。

    语言标准没有说明编译器需要支持的源代码字符集。一些真实的源文件甚至保存在与ASCII不兼容的字符集中。微软Visual C++在2008支持具有字节顺序标记的UTF-8源文件,以及UTF-16的两种形式。如果没有字节顺序标记,则假定该文件是在当前的8位代码页中编码的,该页始终是ASCII的超集。

    执行字符集

    2012年,编译器添加了 /utf-8 切换到 CL.EXE . 今天,它还支持 /source-charset /execution-charset 开关,以及 /validate-charset 检测您的文件是否实际上不是UTF-8。 This page on MSDN has a link to the documentation on Unicode support for every version of Visual C++.

    当前版本的C++标准称编译器必须同时具有执行字符集,它决定字符常量的数值。 'a' 以及一个执行范围内的字符集,该字符集确定宽字符常量的值,例如 L'é' .

    对于语言律师来说,在这些标准中很少需要对这些代码进行编码,而VisualC和C++则试图打破它们。它必须包含大约100个不能有负值的字符,以及数字的编码 '0' 通过 '9' 必须是连续的。大写字母和小写字母都不必是,因为它们不在某些旧的大型机上。(也就是说, '0'+9 必须与 “9” ,但在现实世界中仍有一个编译器,其默认行为是 'a'+9 不是 'j' 但是 '«' ,这是合法的。)宽字符执行集必须包括基本执行集,并且具有足够的位来容纳任何支持的区域设置的所有字符。每个主流编译器至少支持一个Unicode区域设置,并理解用指定的有效Unicode字符 \Uxxxxxxxx 但是一个没有t的编译器可以声称符合标准。

    VisualC和C++违反语言标准的方式是通过 wchar_t UTF-16,它只能将一些字符表示为代理项对,当标准声明 瓦查特 必须是固定宽度编码。这是因为微软定义了 瓦查特 早在20世纪90年代,Unicode委员会还没有发现16位对整个世界来说是不够的,微软也不会破坏Windows API。它确实支持标准 char32_t 类型也一样。

    UTF-8字符串文本

    这个问题提出的第三个问题是如何让编译器在内存中将字符串文本编码为utf-8。自从C++ 11以来,你就能够写出这样的东西:

    constexpr unsigned char hola_utf8[] = u8"¡Hola, mundo!";
    

    不管源字符集是UTF-8、UTF-16、Latin-1、CP1252,甚至是IBM EBCDIC 1047(这是一个愚蠢的理论示例,但为了向后兼容,还是IBM的z系列主机编译器上的默认值),这都会将字符串编码为以空结尾的UTF-8字节表示形式。也就是说,它相当于用 { 0xC2, 0xA1, 'H', /* ... , */ '!', 0 } .

    如果在中键入字符太不方便,或者要区分表面上相同的字符(如空格和不间断空格)或预组合和组合字符,则还具有通用字符转义:

    constexpr unsigned char hola_utf8[] = u8"\u00a1Hola, mundo!";
    

    无论源字符集如何,也不管是否将文本重新存储为utf-8、utf-16或ucs-4,都可以使用这些字符集。它们最初是在C99中添加的,但Microsoft在Visual Studio 2015中支持它们。在Visual C或C++ 2008中,还有另一种方法:八进制和十六进制转义代码。您将在该版本的编译器中使用以下代码对UTF-8文本进行编码:

    const unsigned char hola_utf8[] = "\xC2\xA1Hello, world!";
    
        5
  •  1
  •   Community CDub    8 年前

    使用 _setmode() 作品 (source) 可以说,它比更改代码页或设置区域设置要好,因为它实际上会使您的程序使用Unicode。例子:

    #include <iostream>
    #include <io.h>
    #include <fcntl.h>
    
    int wmain()
    {
        _setmode(_fileno(stdout), _O_U16TEXT);
    
        std::wcout << L"àéêù" << std::endl;
    
        return 0;
    }
    


    在Visual Studio中,确保为Unicode设置项目(右键单击 项目 &点击 一般 -gt; 字符集 = 使用Unicode字符集 )

    用户:

    1. 定义两者 UNICODE _UNICODE
    2. 添加 -finput-charset=iso-8859-1 编译器选项 要解决此错误,请执行以下操作:“ 转换为执行字符集:参数无效
    3. 添加 -municode 链接选项 四处走动 未定义对'winmain@16的引用 read more )
        6
  •  0
  •   vladasimovic    11 年前
    //Save As Windows 1252
    #include<iostream>
    #include<windows.h>
    
    int main()
    {
        SetConsoleOutputCP(1252);
        std:: cout << "àéêù" << std:: endl;
    }
    

    VisualStudio不支持C++的UTF 8,但部分支持C:

    //Save As UTF8 without signature
    #include<stdio.h>
    #include<windows.h>
    
    int main()
    {
        SetConsoleOutputCP(65001);
        printf("àéêù\n");
    }
    
        7
  •  0
  •   Mikal    11 年前

    确保你不会忘记 将控制台的字体改为lucida consolas 正如Bahbar所提到的:在我的案例中,这是至关重要的(法国队凭借VC 2012赢得了7 64位)。

    然后,其他人提到C++使用SETCONSOLUTPUPDCP(1252),但是它可能依赖于可用的页面失败,因此您可能希望使用GETCONSOLUTPUT()来检查它是否工作,或者至少检查SETCONSOLUETCPTCP(1252)是否返回零。更改全局区域设置也有效(出于某种原因,不需要执行cout.imbue(locale());但它可能会破坏一些库!

    在C中 ,setconsoleoutputcp(65001);或者基于区域设置的方法对我有效。 有一次我把源代码保存为没有签名的utf8 (向下滚动,sans签名选项在页面列表的下方)。

    输入 使用setconsolecp(65001);显然由于在Windows中执行了错误的page 65001而失败。区域方法在C和C++中都失败了。一个更复杂的解决方案,不是依赖本地字符,而是依赖wchar_t,这似乎是必需的。