20

这是我尝试这样做的方式:

#include <stdio.h>
#include <windows.h>
using namespace std;

int main() {
  SetConsoleOutputCP(CP_UTF8);
   //german chars won't appear
  char const* text = "aäbcdefghijklmnoöpqrsßtuüvwxyz";
  int len = MultiByteToWideChar(CP_UTF8, 0, text, -1, 0, 0);
  wchar_t *unicode_text = new wchar_t[len];
  MultiByteToWideChar(CP_UTF8, 0, text, -1, unicode_text, len);
  wprintf(L"%s", unicode_text);
}

效果是只显示我们的ascii字符。没有显示错误。源文件以 utf8 编码。

那么,我在这里做错了什么?

对WouterH:

int main() {
  SetConsoleOutputCP(CP_UTF8);
  const wchar_t *unicode_text = L"aäbcdefghijklmnoöpqrsßtuüvwxyz";
  wprintf(L"%s", unicode_text);
}
  • 这也行不通。效果是一样的。我的字体当然是 Lucida Console。

第三次采取:

#include <stdio.h>
#define _WIN32_WINNT 0x05010300
#include <windows.h>
#define _O_U16TEXT  0x20000
#include <fcntl.h>

using namespace std;

int main() {
    _setmode(_fileno(stdout), _O_U16TEXT);
    const wchar_t *u_text = L"aäbcdefghijklmnoöpqrsßtuüvwxyz";
    wprintf(L"%s", u_text);
}

好的,有些东西开始起作用了,但输出是:ańbcdefghijklmno÷pqrs▀tuŘvwxyz.

4

7 回答 7

17

默认情况下,Windows 上的宽打印功能不处理 ascii 范围之外的字符。

有几种方法可以将 Unicode 数据传输到 Windows 控制台。

  • 直接使用控制台 API,WriteConsoleW。您必须确保您实际上是在写入控制台,并在输出到其他东西时使用其他方式。

  • 将标准输出文件描述符的模式设置为“Unicode”模式之一,_O_U16TEXT 或 _O_U8TEXT。这会导致宽字符输出函数正确地将 Unicode 数据输出到 Windows 控制台。如果它们用于不代表控制台的文件描述符,那么它们会导致字节输出流分别为 UTF-16 和 UTF-8。请注意,设置这些模式后,相应流上的非宽字符函数将无法使用并导致崩溃。您只能使用宽字符函数。

  • 如果使用正确的函数,可以通过将控制台输出代码页设置为 CP_UTF8 将 UTF-8 文本直接打印到控制台。大多数更高级别的函数(例如)basic_ostream<char>::operator<<(char*)不能以这种方式工作,但您可以使用较低级别的函数或实现自己的 ostream 来解决标准函数所存在的问题。

第三种方法的问题是:

putc('\302'); putc('\260'); // doesn't work with CP_UTF8

puts("\302\260"); // correctly writes UTF-8 data to Windows console with CP_UTF8 

与大多数操作系统不同,Windows 上的控制台不仅仅是另一个接受字节流的文件。它是由程序创建和拥有的特殊设备,可通过其自己独特的 WIN32 API 访问。问题是,当控制台被写入时,API 可以准确地看到在使用其 API 时传递的数据的范围,并且从窄字符到宽字符的转换发生时没有考虑到数据可能不完整。当使用对控制台 API 的多次调用传递一个多字节字符时,每个单独传递的部分都被视为非法编码,并被视为非法编码。

解决这个问题应该很容易,但微软的 CRT 团队认为这不是他们的问题,而在控制台上工作的任何团队都可能不在乎。

您可以通过实现自己的 streambuf 子类来解决它,该子类可以正确处理到 wchar_t 的转换。即考虑到多字节字符的字节可能单独出现的事实,保持写入之间的转换状态(例如,std::mbstate_t)。

于 2012-06-04T16:04:01.080 回答
13

另一个技巧,而不是SetConsoleOutputCP,将使用_setmode on stdout

// Includes needed for _setmode()
#include <io.h>
#include <fcntl.h>

int main() {
    _setmode(_fileno(stdout), _O_U16TEXT);  
    wchar_t * unicode_text = L"aäbcdefghijklmnoöpqrsßtuüvwxyz";
    wprintf(L"%s", unicode_text);
    return 0;
}

不要忘记删除对SetConsoleOutputCP(CP_UTF8);

于 2012-06-04T14:13:20.303 回答
7
//Save As UTF8 without signature
#include<stdio.h>
#include<windows.h>
int main() {
  SetConsoleOutputCP(65001);
  const char unicode_text[]="aäbcdefghijklmnoöpqrsßtuüvwxyz";
  printf("%s\n", unicode_text);
}

结果:
aäbcdefghijklmnoöpqrsßtuüvwxyz

于 2013-09-28T21:15:50.060 回答
4

我有类似的问题,但没有一个现有的答案对我有用。我观察到的另一件事是,如果我将 UTF-8 字符粘贴在字符串文字中,它们会正确打印,但如果我尝试使用 UTF-8 文字 ( u8"text"),这些字符会被编译器杀死(通过打印证明一次输出一个字节的数值;原始文字具有正确的 UTF-8 字节,正如在 Linux 机器上验证的那样,但 UTF-8 文字是垃圾)。

经过一番摸索,我找到了解决方案:/utf-8. 有了它,一切正常;我的来源是 UTF-8,我可以使用明确的 UTF-8 文字,并且输出无需其他更改即可工作。

于 2018-09-10T15:35:22.290 回答
2

可以将控制台设置为显示 UTF-8 字符:@vladasimovic 答案SetConsoleOutputCP(CP_UTF8)可用于此目的。或者,您可以通过 DOS 命令或主程序中chcp 65001的系统调用来准备控制台。system("chcp 65001 > nul")不要忘记将源代码也保存为 UTF-8。

要检查 UTF-8 支持,请运行

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

BOOL CALLBACK showCPs(LPTSTR cp) {
  puts(cp);
  return true;
}

int main() {
  EnumSystemCodePages(showCPs,CP_SUPPORTED);
}

65001应该出现在列表中。

Windows 控制台默认使用OEM 代码页,并且大多数默认光栅字体仅支持国家字符。Windows XP 和更新版本还支持 TrueType 字体,它应该显示缺失的字符(@Devenec 在他的回答中建议 Lucida Console)。

为什么 printf 失败

正如@bames53 在他的回答中指出的那样,Windows 控制台不是流设备,您需要写入多字节字符的所有字节。有时会printf搞砸工作,将字节一个一个地放入输出缓冲区。尝试使用sprintf然后puts结果,或强制 fflush 仅累积的输出缓冲区。

如果一切都失败了

注意UTF-8 格式:一个字符显示为 1-5 个字节。使用此函数转移到字符串中的下一个字符:

const char* ucshift(const char* str, int len=1) {
  for(int i=0; i<len; ++i) {
    if(*str==0) return str;
    if(*str<0) {
      unsigned char c = *str;
      while((c<<=1)&128) ++str;
    }
    ++str;
  }
  return str;
}

...以及将字节转换为 unicode 数字的此函数:

int ucchar(const char* str) {
  if(!(*str&128)) return *str;
  unsigned char c = *str, bytes = 0;
  while((c<<=1)&128) ++bytes;
  int result = 0;
  for(int i=bytes; i>0; --i) result|= (*(str+i)&127)<<(6*(bytes-i));
  int mask = 1;
  for(int i=bytes; i<6; ++i) mask<<= 1, mask|= 1;
  result|= (*str&mask)<<(6*bytes);
  return result;
}

然后你可以尝试使用一些狂野/古老/非标准的winAPI函数,比如MultiByteToWideChar(不要忘记setlocale()之前调用!)

或者您可以使用自己的从 Unicode 表映射到您的活动工作代码页。例子:

int main() {
  system("chcp 65001 > nul");
  char str[] = "příšerně"; // file saved in UTF-8
  for(const char* p=str; *p!=0; p=ucshift(p)) {
    int c = ucchar(p);
    if(c<128) printf("%c\n",c);
    else printf("%d\n",c);
  }
}

这应该打印

p
345
237
353
e
r
n
283

如果您的代码页不支持该捷克语解释,您可以映射 345=>r、237=>i、353=>s、283=>e。至少有 5(!)种不同的字符集仅适用于捷克语。在不同的 Windows 语言环境中显示可读字符是一件很可怕的事情。

于 2017-01-13T23:44:19.377 回答
1

我通过以下方式解决了这个问题:

Lucida Console 似乎不支持变音符号,因此例如将控制台字体更改为 Consolas 就可以了。

#include <stdio.h>
#include <Windows.h>

int main()
{
    SetConsoleOutputCP(CP_UTF8);

    // I'm using Visual Studio, so encoding the source file in UTF-8 won't work
    const char* message = "a" "\xC3\xA4" "bcdefghijklmno" "\xC3\xB6" "pqrs" "\xC3\x9F" "tu" "\xC3\xBC" "vwxyz";

    // Note the capital S in the first argument, when used with wprintf it
    // specifies a single-byte or multi-byte character string (at least on
    // Visual C, not sure about the C library MinGW is using)
    wprintf(L"%S", message);
}

编辑:修复了愚蠢的拼写错误和字符串文字的解码,对此感到抱歉。

于 2015-06-10T07:52:25.927 回答
1

UTF-8 不适用于 Windows 控制台。时期。我尝试了所有组合都没有成功。由于不同的 ANSI/OEM 字符分配会出现问题,因此一些答案说没有问题,但这些答案可能来自使用 7 位纯 ASCII 或具有相同 ANSI/OEM 代码页(中文、日文)的程序员。

要么您坚持使用 UTF-16 和宽字符函数(但您仍然受限于 OEM 代码页的 256 个字符- 中文/日语除外),或者您在源文件中使用 OEM 代码页 ASCII 字符串。

是的,这根本就是一团糟。

对于多语言程序,我使用字符串资源,并编写了一个LoadStringOem()函数,可以将 UTF-16 资源自动转换为 OEM 字符串,WideCharToMultiByte()而无需使用中间缓冲区。当 Windows 从资源中自动选择正确的语言时,它有望加载可转换为目标 OEM 代码页的语言的字符串。

因此,您不应将 8 位印刷字符用于英语-美国语言资源(如省略号 ... 和引号“”),因为当未检测到语言匹配(即回退)时,Windows 会选择英语-美国。例如,您有德语、捷克语、俄语和英语-美国的资源,而用户有中文,如果您使文本看起来漂亮,他/她将看到英语加上垃圾而不是您精心制作的排版。

现在,在 Windows 7 和 10 上,SetConsoleOutputCP(65001/*aka CP_UTF8*/)可以按预期工作。您应该将源文件保存在没有 BOM 的 UTF-8 中,否则,您的字符串文字将被编译器重新编码为 ANSI。此外,控制台字体必须包含所需的字符,并且不能是“终端”。不幸的是,即使您同时安装了两个语言包,也没有涵盖变音符号和汉字的字体,因此您无法真正一次显示所有字符形状。

于 2017-10-05T09:18:20.693 回答