如何在C++中打印Unicode字符

84

我正在尝试打印一个俄语字符 "ф" (U+0444 CYRILLIC SMALL LETTER EF),其代码为十进制 1092。使用 C++,我应该如何打印出这个字符?我本以为以下类似的方式可以工作,但是......

int main (){
   wchar_t f = '1060';
   cout << f << endl;
}

2
请注意,这个问题是双重的(至少对于有效的C++程序而言):在代码中表达字符,并正确地传递给std::cout。即使这两个步骤都正确完成,正确显示字符在连接到std::cout的任何内容中仍然是一个完全不同的问题。 - Luc Danton
这个回答解决了你的问题吗?C++11中字符串字面量的Unicode编码 - M.J. Rayburn
11个回答

79

为了表示字符,您可以使用通用字符名称(UCN)。字符'ф'具有Unicode值U+0444,因此在C++中,您可以将其写为'\u0444'或'\U00000444'。此外,如果源代码编码支持此字符,则可以在源代码中直接按字面意义编写它。

// both of these assume that the character can be represented with
// a single char in the execution encoding
char b = '\u0444';
char a = 'ф'; // this line additionally assumes that the source character encoding supports this character

输出此类字符取决于你将要输出到哪里。如果你要输出到 Unix 终端仿真器,终端仿真器正在使用支持该字符的编码,并且该编码与编译器的执行编码相匹配,则可以执行以下操作:

#include <iostream>

int main() {
    std::cout << "Hello, ф or \u0444!\n";
}

这个程序并不要求'ф'可以用单个字符表示。在OS X和大多数现代Linux安装中,这将完美运作,因为源代码、执行以及控制台编码都是UTF-8(支持所有Unicode字符)。

对于Windows来说,情况会更加复杂,因为有不同的可能性和权衡取舍。

如果你不需要可移植的代码(你将使用wchar_t,在其他平台上真的应该避免使用),最好的方法是将输出文件句柄的模式设置为只接受UTF-16数据。

#include <iostream>
#include <io.h>
#include <fcntl.h>

int main() {
    _setmode(_fileno(stdout), _O_U16TEXT);
    std::wcout << L"Hello, \u0444!\n";
}

可移植性代码更加困难。


6
我很确定 '\u0444' 除非编译器将 char 提升为 int,否则无法放入一个 char 中,但如果你想要这种行为,应该使用 int。 - Edward Falk
1
如果执行字符集是ISO-8859-5,那么@EdwardFalk的“ф”将适合于8位char。具体来说,它将是字节0xE4。请注意,我并不建议使用这样的执行字符集是一个好习惯,我只是在描述C++的工作原理。 - bames53
1
啊,你的意思是编译器会将\u0444识别为Unicode字符,并将其转换为当前字符集,结果可以放入一个字节中?我不知道它会这样做。 - Edward Falk
1
是的。这就是为什么使用\u与使用\x不同的原因。 - bames53
无法在我的Lubuntu 16笔记本电脑上使用Terminator终端和g++ 5.4.0工作,但使用std :: string可以。 - Austin_Anderson

18

当使用-std=c++11进行编译时,可以简单地

  const char *s  = u8"\u0444";
  cout << s << endl;

4
让我推荐Boost.Nowide,以便以便携方式打印UTF-8字符串到终端,这样上述代码将几乎不会改变。 - Yakov Galka
2
@ybungalobill,你的评论值得单独回答。你介意写一个吗? - Jorge Leitao
1
仅供参考:\uXXXX\UXXXXXXXX 被称为通用字符名称。形如 u8"..." 的字符串字面值是UTF-8字符串字面值。两者均在标准中指定。 - ynn

12

最终,这完全取决于平台。不幸的是,在标准的C++中,对Unicode的支持非常差。对于GCC编译器,你必须将其转换为窄字符串,因为它们使用UTF-8编码,而Windows需要宽字符串,并且必须输出到wcout

// GCC
std::cout << "ф";
// Windoze
wcout << L"ф";

1
如果我没记错的话,Unicode 转义字符是 \uXXXX,其中 XXXX 代表 十六进制 数字。不幸的是,这会将 U+FFFF 之后的所有字符都排除在外。 - Mike DeSimone
1
@Mike:如果你想要表示FFFF以前的字符,可以在Windows上使用两个\u实例来生成一个UTF-16代理对。 - Billy ONeal
9
在C++中,您不使用代理代码点(实际上完全禁止使用代理代码点)。您应该使用格式\UXXXXXXXX。请注意,翻译没有添加任何解释或背景信息。 - bames53
2
GCC不一定绑定使用UTF-8,并且可用于Windows。在Windows之外,std :: wcout也是一种选择。 - Luc Danton
2
@Jam '\u0400' 是一个窄字符字面量。您似乎假设\u0400存在于执行字符集中。根据N3242 [lex.ccon]/5的规定:“通用字符名将被翻译为所命名字符在适当执行字符集中的编码。如果没有这样的编码,则通用字符名将被翻译为实现定义的编码。” - curiousguy
显示剩余4条评论

10

这段代码在Linux中运行良好(C++11GeanyGCC 7.4 (g++. 2018-12-06)):

#include <iostream>

using namespace std;

int utf8_to_unicode(string utf8_code);
string unicode_to_utf8(int unicode);


int main()
{
    cout << unicode_to_utf8(36) << '\t';
    cout << unicode_to_utf8(162) << '\t';
    cout << unicode_to_utf8(8364) << '\t';
    cout << unicode_to_utf8(128578) << endl;

    cout << unicode_to_utf8(0x24) << '\t';
    cout << unicode_to_utf8(0xa2) << '\t';
    cout << unicode_to_utf8(0x20ac) << '\t';
    cout << unicode_to_utf8(0x1f642) << endl;

    cout << utf8_to_unicode("$") << '\t';
    cout << utf8_to_unicode("¢") << '\t';
    cout << utf8_to_unicode("€") << '\t';
    cout << utf8_to_unicode("") << endl;

    cout << utf8_to_unicode("\x24") << '\t';
    cout << utf8_to_unicode("\xc2\xa2") << '\t';
    cout << utf8_to_unicode("\xe2\x82\xac") << '\t';
    cout << utf8_to_unicode("\xf0\x9f\x99\x82") << endl;

    return 0;
}


int utf8_to_unicode(string utf8_code)
{
    unsigned utf8_size = utf8_code.length();
    int unicode = 0;

    for (unsigned p=0; p<utf8_size; ++p)
    {
        int bit_count = (p? 6: 8 - utf8_size - (utf8_size == 1? 0: 1)),
            shift = (p < utf8_size - 1? (6*(utf8_size - p - 1)): 0);

        for (int k=0; k<bit_count; ++k)
            unicode += ((utf8_code[p] & (1 << k)) << shift);
    }

    return unicode;
}


string unicode_to_utf8(int unicode)
{
    string s;

    if (unicode>=0 and unicode <= 0x7f)  // 7F(16) = 127(10)
    {
        s = static_cast<char>(unicode);

        return s;
    }
    else if (unicode <= 0x7ff)  // 7FF(16) = 2047(10)
    {
        unsigned char c1 = 192, c2 = 128;

        for (int k=0; k<11; ++k)
        {
            if (k < 6)
                c2 |= (unicode % 64) & (1 << k);
            else
                c1 |= (unicode >> 6) & (1 << (k - 6));
        }

        s = c1;
        s += c2;

        return s;
    }
    else if (unicode <= 0xffff)  // FFFF(16) = 65535(10)
    {
        unsigned char c1 = 224, c2 = 128, c3 = 128;

        for (int k=0; k<16; ++k)
        {
            if (k < 6)
                c3 |= (unicode % 64) & (1 << k);
            else if
                (k < 12) c2 |= (unicode >> 6) & (1 << (k - 6));
            else
                c1 |= (unicode >> 12) & (1 << (k - 12));
        }

        s = c1;
        s += c2;
        s += c3;

        return s;
    }
    else if (unicode <= 0x1fffff)  // 1FFFFF(16) = 2097151(10)
    {
        unsigned char c1 = 240, c2 = 128, c3 = 128, c4 = 128;

        for (int k=0; k<21; ++k)
        {
            if (k < 6)
                c4 |= (unicode % 64) & (1 << k);
            else if (k < 12)
                c3 |= (unicode >> 6) & (1 << (k - 6));
            else if (k < 18)
                c2 |= (unicode >> 12) & (1 << (k - 12));
            else
                c1 |= (unicode >> 18) & (1 << (k - 18));
        }

        s = c1;
        s += c2;
        s += c3;
        s += c4;

        return s;
    }
    else if (unicode <= 0x3ffffff)  // 3FFFFFF(16) = 67108863(10)
    {
        ;  // Actually, there are no 5-bytes unicodes
    }
    else if (unicode <= 0x7fffffff)  // 7FFFFFFF(16) = 2147483647(10)
    {
        ;  // Actually, there are no 6-bytes unicodes
    }
    else
        ;  // Incorrect unicode (< 0 or > 2147483647)

    return "";
}

更多:


8

如果您使用Windows (请注意,我们正在使用printf(),而不是cout):

// Save as UTF-8 without a signature
#include <stdio.h>
#include<windows.h>

int main (){
    SetConsoleOutputCP(65001);
    printf("ф\n");
}

虽然不是Unicode,但它可以工作——使用Windows-1251而不是UTF-8:

// Save as Windows 1251
#include <iostream>
#include<windows.h>

using namespace std;

int main (){
    SetConsoleOutputCP(1251);
    cout << "ф" << endl;
}

在这种情况下,SetConsoleOutputCP() 的名称更好。 - Cong Ma
仅供参考:Windows中默认的西里尔文控制台编码为OEM 866。 - Qwertiy
我不得不使用- SetConsoleOutputCP(CP_UTF8); 和 printf(u8"Привет мир\n"); - Eugene Kartoyev

3

'1060' 是四个字符,在标准下无法编译。如果你的宽字符与Unicode一一对应(检查你的区域设置),你应该将字符视为数字。

int main (){
    wchar_t f = 1060;
    wcout << f << endl;
}

我认为这就是iostreams的一个重点之一:通过重载的operator <<来检测类型并执行正确的操作。但是似乎并不总是如此? - Mike DeSimone
4
'1060' 是 C++ 标准下类型为 int 的多字符字面值,完全合法。但它的值是由实现定义的。大多数实现会将字符的值连接起来形成一个单一的整数值。这些有时被用于所谓的“FourCC”。 - bames53
3
或许你会惊讶于完全合法的代码中有多少警告。C++标准声明:“包含不止一个c-char的普通字符字面值是多字符字面值。多字符字面值类型为int,其实现定义的值。” [lex.ccon] 2.14.3/1 - bames53
2
@MikeDeSimone说:“我使用过的每个非Mac编译器都至少发出了一个警告”,因为它1)在非Mac系统上几乎从不被有意使用2)不是可移植的结构。 - curiousguy
@curiousguy 最后我检查了一下,有两件关于位域的事情是实现定义的:1)连续的位域是从高位到低位还是从低位到高位打包的,2)如果总位数小于存储类型,则哪一端(MSB或LSB)会得到填充位。另请参见http://www.linuxforu.com/2012/01/joy-of-programming-understanding-bit-fields-c/和http://yarchive.net/comp/linux/bitfields.html,所以如果标准费心解决这两个问题,请引用它。 - Mike DeSimone
显示剩余7条评论

1

我需要在用户界面中显示字符串,并将其保存到 XML 配置文件中。上述指定的格式适用于 C++ 中的字符串,我想补充一下,我们可以通过将 "\u" 替换为 "&#x" 并在末尾添加一个 ";" 来获得与 XML 兼容的特殊字符的字符串。

举个例子:

C++: "\u0444" → XML: "&#x0444;"


1

特别感谢这里的答案,解决了我类似的问题。

对于我来说,我只需要setlocale(LC_ALL, "en_US.UTF-8");

然后,我甚至可以使用原始的wchar_t字符。


0
在Linux中,我只需要执行以下操作:
std::cout << "ф";

我刚刚从此处复制粘贴了字符,对于我尝试的随机样本来说,它没有失败。


0
在Linux上,Unicode字符(UTF-16 / UTF-32)可以转换为UTF-8并打印到std :: cout。我使用了这些functions

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接