C ++ Visual Studio字符编码问题

时间:2009-12-07 03:32:28

标签: c++ visual-studio character-encoding

无法绕过这一个是一个真正的耻辱源......

我在法语Windows(XP)中使用法语版的Visual Studio(2008)。发送到输出窗口的字符串中的法语重音被破坏。同上从输出窗口输入。典型的字符编码问题,我输入ANSI,得到UTF-8作为回报,或者那样的东西。在向输出窗口显示“硬编码”字符串时,什么设置可以确保字符保留在ANSI中?

编辑:

示例:

#include <iostream>

int main()
{
std:: cout << "àéêù" << std:: endl;

return 0;
}

将在输出中显示:

  

奥羽

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

我真的很想表现出来:

  

àéêù

8 个答案:

答案 0 :(得分:13)

在进一步讨论之前,我应该提一下你所做的不是c / c ++兼容。 2.2中的specification状态在源代码中有哪些字符集有效。它并不多,所有使用的字符都在ascii中。所以...下面的所有内容都是关于特定的实现(就像在美国语言环境机器上的VC2008一样)。

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

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

  1. 源文件的编码方式(即编译器将如何看待您的C ++文件)
  2. 您的编译器使用字符串文字做什么,以及它理解的源代码编码
  3. 您的<<如何解释您传入的已编码字符串
  4. 控制台期望的编码
  5. 控制台如何将该输出转换为字体字形。
  6. 现在......

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

    3更容易。除了控制代码,<<只是将数据传递给char *。

    4由SetConsoleOutputCP控制。它应默认为您的默认系统代码页。你也可以用GetConsoleOutputCP找出你拥有的那个(输入的控制方式不同,通过SetConsoleCP

    5是一个有趣的。我猛烈地想弄清楚为什么我不能使用CP1252(西欧,windows)来正确显示é。事实证明,我的系统字体没有该字符的字形,并且有助于使用我的标准代码页的字形(资本Theta,如果我没有调用SetConsoleOutputCP,我会得到的字形)。要修复它,我不得不将我在控制台上使用的字体更改为Lucida Console(一种真正的字体)。

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

    • 源代码的编码无关紧要,只要编译器可以解决它(特别是将其更改为UTF8并未更改生成的代码。我的“é”字符串仍然使用CP1252编码为{{1} })
    • VC正在为我似乎无法控制的字符串文字选择一个代码页。
    • 控制控制台显示的内容比我期待的更痛苦

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

    • 不要在字符串文字中使用非ascii。使用资源,控制编码。
    • 确保您知道控制台所期望的编码,并且您的字体具有代表您发送的字符的字形。
    • 如果你想弄清楚你的情况下使用了什么编码,我建议将字符的实际值打印为整数。 233 0确实为我显示了233,这恰好是CP1252中的编码。
    顺便说一句,如果你得到的是“ÓÚÛ”而不是你粘贴的东西,那么看起来你的4个字节被解释为CP850

答案 1 :(得分:5)

试试这个:

#include <iostream>
#include <locale>

int main()
{
 std::locale::global(std::locale(""));
 std::cout << "àéêù" << std::endl;

 return 0;
}

答案 2 :(得分:2)

我试过这段代码:

#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都具有预期值(即“àéêù”),输出文件也是如此。然而,控制台中出现的是óúÛ¨。

因此问题出在Visual Studio控制台,而不是C ++。使用Bahbar的优秀答案,我补充道:

    SetConsoleOutputCP(1252);

作为第一行,然后控制台输出就应该出现了。

答案 3 :(得分:2)

因为我被要求,我会做一些死灵法术。其他答案来自2009年,但这篇文章仍然是我在2018年进行的搜索。今天的情况非常不同。此外,即使在2009年,接受的答案也是不完整的。

源字符集

每个编译器(包括Microsoft的Visual Studio 2008及更高版本,gcc,clang和icc)都会读取以BOM开头没有问题的UTF-8源文件,而clang除了UTF-8之外不会读取任何内容,因此UTF-8使用BOM是C和C ++源文件的最小公分母。

语言标准没有说明编译器需要支持哪些源字符集。一些真实的源文件甚至保存在与ASCII不兼容的字符集中。 2008年的Microsoft Visual C ++支持带有字节顺序标记的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'é'

对于语言律师而言,标准中对于如何编码这些要求的要求非常少,但Visual C和C ++设法打破它们。它必须包含大约100个不能包含负值的字符,并且数字'0''9'的编码必须是连续的。既不是大写字母也不是小写字母,因为它们不在一些旧的大型机上。 (也就是说,'0'+9必须与'9'相同,但目前仍有一个编译器在实际使用中,其默认行为是'a'+9不是'j'但是'«',这是合法的。)宽字符执行集必须包含基本执行集,并且有足够的位来保存任何支持的语言环境的所有字符。每个主流编译器都支持至少一个Unicode语言环境,并且理解用\Uxxxxxxxx指定的有效Unicode字符,但是编译器不能声称符合标准。

Visual C和C ++违反语言标准的方式是制作wchar_t UTF-16,当标准说wchar_t必须是固定的时,它只能代表某些字符作为代理对。宽度编码。这是因为微软在20世纪90年代将wchar_t定义为16位宽,之后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(这都是愚蠢的),这将把字符串编码为以null结尾的UTF-8字节表示形式。理论上的例子但是,为了向后兼容,它仍然是IBM Z系列大型机编译器的默认设置。也就是说,它相当于用{ 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!";

答案 4 :(得分:1)

使用_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;单击常规 - &gt; 字符集 = 使用Unicode字符集)。

MinGW用户:

  1. 定义UNICODE_UNICODE
  2. -finput-charset=iso-8859-1添加到编译器选项以解决此错误:&#34; 转换为执行字符集:无效参数&#34; < / LI>
  3. -municode添加到链接器选项以绕过&#34; 未定义引用`WinMain @ 16 &#34; (read more)。

答案 5 :(得分:0)

//Save As Windows 1252
#include<iostream>
#include<windows.h>

int main()
{
    SetConsoleOutputCP(1252);
    std:: cout << "àéêù" << std:: endl;
}

Visual Studio不支持UTF 8 for C ++,但部分支持C:

//Save As UTF8 without signature
#include<stdio.h>
#include<windows.h>

int main()
{
    SetConsoleOutputCP(65001);
    printf("àéêù\n");
}

答案 6 :(得分:0)

确保你不要忘记将控制台的字体改为Lucida Consolas ,正如Bahbar所提到的那样:在我的案例中这是至关重要的(法国在VC 2012中获胜7 64位)。

然后如其他人所提到的,对于C ++使用SetConsoleOutputCP(1252),但它可能会失败,具体取决于可用页面,因此您可能希望使用GetConsoleOutputCP()来检查它是否有效或至少检查SetConsoleOutputCP(1252)是否返回零。更改全局语言环境也有效(出于某种原因,不需要执行cout.imbue(locale());但它可能会破坏一些库; @ / p>

在C 中,SetConsoleOutputCP(65001);或者基于语言环境的方法对我有用一旦我将源代码保存为没有签名的UTF8 (向下滚动,sans-signature选项在页面列表中如下)。

使用SetConsoleCP(65001)

输入;显然是由于Windows中的页面65001执行不当导致我失败了。在C和C ++中,语言环境方法也都失败了。似乎需要一个更复杂的解决方案,而不是依赖于本地字符,而是依赖于wchar_t。

答案 7 :(得分:0)

我在中文输入上也遇到了同样的问题。我的源代码是utf8,并在编译器选项中添加了/ utf-8。它在c ++宽字符串和宽字符下工作正常,但在窄字符串/字符下工作不正常,它在Visual Studio 2019调试器和我的SQL数据库中显示乱码字符/代码。由于转换为SQLAPI ++的SAString,因此必须使用窄字符。最终,我发现检查以下选项(控制面板->区域->管理->更改系统区域设置)可以解决此问题。我知道这不是理想的解决方案,但确实可以帮助我。

enter image description here