繁体   English   中英

C++ Visual Studio 字符编码问题

[英]C++ Visual Studio character encoding issues

无法将我的头环绕在这个周围是一种真正的耻辱......

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

编辑:

例子:

#include <iostream>

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

return 0;
}

将在输出中显示:

(这里编码为 HTML 以供您观看)

我真的很想它显示:

àéêù

在我继续之前,我应该提到你正在做的事情不符合 c/c++ 标准。 规范在 2.2 中说明了源代码中哪些字符集是有效的。 它在那里并不多,并且所有使用的字符都是ascii。 所以......下面的一切都是关于一个特定的实现(碰巧的是,美国语言环境机器上的 VC2008)。

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

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

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

现在...

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

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

4 由SetConsoleOutputCP控制。 它应该默认为您的默认系统代码页。 您还可以通过GetConsoleOutputCP找出您拥有的是哪一个(通过SetConsoleCP不同方式控制输入)

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

我从中学到了一些有趣的事情:

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

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

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

顺便说一句,如果您得到的是“ÓÚÛ¨”而不是您粘贴的内容,那么看起来您的 4 个字节在某处被解释为CP850

试试这个:

#include <iostream>
#include <locale>

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

 return 0;
}

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

源字符集

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

语言标准没有说明编译器需要支持哪些源字符集。 一些现实世界的源文件甚至以与 ASCII 不兼容的字符集保存。 2008 年的 Microsoft Visual C++ 支持带有字节顺序标记的 UTF-8 源文件,以及两种形式的 UTF-16。 如果没有字节顺序标记,它会假设文件是​​用当前的 8 位代码页编码的,它始终是 ASCII 的超集。

执行字符集

2012 年,编译器向CL.EXE添加了/utf-8开关。 今天,它还支持/source-charset/execution-charset开关,以及/validate-charset来检测您的文件是否实际上不是 UTF-8。 MSDN 上的这个页面有一个链接,指向关于每个版本的 Visual C++ 的 Unicode 支持的文档。

当前版本的 C++ 标准说编译器必须有一个执行字符集,它确定像'a'这样的字符常量的数值,以及一个执行宽字符集,它确定像L'é'这样的宽字符常量的值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必须是固定宽度编码时,它只能将某些字符表示为代理对。 这是因为微软在 1990 年代将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(这是一个愚蠢的理论示例,但仍然,为了向后兼容,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 中支持它们。

编辑:据 Matthew 报道, u8"字符串在某些版本的 MSVC 中存在问题,包括 19.14。事实证明,即使您指定/utf-8/source-charset:utf-8 /execution-charset:utf-8 ,文字非 ASCII 字符也是如此/source-charset:utf-8 /execution-charset:utf-8 . 上面的示例代码在 19.22.27905 中正常工作。

还有另一种方法可以在 Visual C 或 C++ 2008 中执行此操作,但是:八进制和十六进制转义码。 您可以在该版本的编译器中对 UTF-8 文字进行编码:

const unsigned char hola_utf8[] = "\xC2\xA1Hello, world!";

我试过这个代码:

#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);

作为第一行,然后控制台输出显示为它应有的样子。

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

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

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

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

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

使用_setmode()工作¹并且可以说比更改代码页或设置语言环境更好,因为它实际上会使您的程序输出为 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 设置项目(右键单击 *Project* -> 单击 *General* -> *Character Set* = *Use Unicode Character Set*)。

MinGW用户:

  1. 定义UNICODE_UNICODE
  2. -finput-charset=iso-8859-1添加到编译器选项以解决此错误:“转换为执行字符集:无效参数
  3. -municode添加到链接器选项以绕过“对`WinMain@16 的未定义引用”(阅读更多)。

**编辑:** 设置 unicode *input* 的等效调用是:`_setmode( _fileno(stdin), _O_U16TEXT);`

编辑 2:一条重要的信息,特别是考虑到该问题使用std::cout 这不受支持。 MSDN Docs指出(强调我的):

Unicode 模式用于宽打印功能(例如 wprintf),不支持窄打印功能 在 Unicode 模式流上使用窄打印功能会触发断言。

所以,当控制台输出模式为_O_U16TEXT时不要使用std::cout 同样,当控制台输入是_O_U16TEXT时不要使用std::cin 您必须使用这些工具的广泛版本( std::wcoutstd::wcin )。
并且请注意, wcout在同一输出中混合coutwcout (但我发现如果在窄操作和宽操作之间切换之前先调用flush()然后_setmode() ,它会起作用)。

确保您不要忘记将控制台的字体更改为 Bahbar 提到的Lucida Consolas :这对我来说至关重要(French win 7 64 bit with VC 2012)。

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

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

使用 SetConsoleCP(65001)输入 显然,由于在 Windows 中页面 65001 的实现不当,我失败了。 语言环境方法在 C 和 C++ 中也失败了。 一个更复杂的解决方案,不依赖于原生字符,而是依赖 wchar_t 似乎是必需的。

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

在此处输入图片说明

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM