在Windows控制台中正确打印utf8字符

时间:2012-06-04 13:35:41

标签: c++ utf-8 console mingw windows-xp-sp3

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

#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

7 个答案:

答案 0 :(得分:14)

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

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

的来电

答案 1 :(得分:14)

默认情况下,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)。

答案 2 :(得分:5)

//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

答案 3 :(得分:1)

控制台可以设置为显示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 codepages,大多数默认光栅字体仅支持国家字符。 Windows XP和更新版本也支持TrueType字体,它应该显示缺少的字符(@Devenec建议Lucida Console在他的答案中)。

为什么printf失败

由于@ bames53在他的回答中指出,Windows控制台不是流设备,您需要写入所有字节的多字节字符。有时printf会使作业混乱,将字节逐个放入输出缓冲区。尝试使用sprintf然后使用puts结果,或强制只刷新累积的输出缓冲区。

如果一切都失败

注意UTF-8 format:一个字符显示为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 =&gt; r,237 =&gt; i,353 =&gt; s,283 =&gt; e。捷克至少有5个(!)不同的字符集。在不同的Windows语言环境中显示可读字符是一种恐怖。

答案 4 :(得分:1)

我遇到了类似的问题,但是现有的答案都没有对我有用。我观察到的另一件事是,如果我将UTF-8字符粘贴在 plain 字符串文字中,则它们将正确打印,但是如果我尝试使用UTF-8文字(u8"text") ,这些字符会被编译器抢劫(通过一次打印一个字节的数字值来证明; raw 文字具有正确的UTF-8字节,如一台Linux计算机,但UTF-8文字是垃圾)。

经过一番摸索,我找到了解决方法:/utf-8。这样,一切都可以正常工作;我的资源是UTF-8,我可以使用显式的UTF-8文字,并且输出无需其他更改即可工作。

答案 5 :(得分:0)

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

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

编辑:修复了愚蠢的拼写错误和字符串文字的解码,对不起那些。

答案 6 :(得分:0)

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

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

是的,根本就是一团糟。

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

因此,您不应该将8位印刷字符用于英语 - 美国语言资源(作为省略号...和引号“”),因为当没有检测到语言匹配时,Windows会选择英语 - 美国(即后备) 。 例如,你有德语,捷克语,俄语和英语 - 美国的资源,并且用户有中文,他/她会看到英文加垃圾而不是你制作精美的排版,如果你让你的文字好看。

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

相关问题