C++ Unicode 字符打印

11

我需要使用 iostream 在 Linux 终端上打印一些 Unicode 字符。但是奇怪的事情发生了。当我写下:

cout << "\u2780";

我得到了:,这几乎是我想要的。但是,如果我写:
cout << '\u2780';

我得到:14851712
问题是,我不知道在编译时要打印的确切字符。因此,我想做这样的事情:
int x;
// Some calculations...
cout << (char)('\u2780' + x);

输出的结果是:。使用wcoutwchar_t也不起作用。我该如何正确打印?

从我在互联网上找到的资料来看,似乎很重要的一点是我要直接从Debian 7(Wheezy)存储库中使用GCC 4.7.2编译器(可执行文件g++)。


你是否在使用wchar_t和运算符L?如果可能,请发布完整的代码或sscce.org - pinkpanther
如果您不想处理Unicode编码,可以使用表格将字符串映射到可能的“x”值,而不是添加它。 - dyp
可能是如何在C++中打印Unicode字符?的重复问题。 - Adrian McCarthy
4个回答

8

Unicode字符\u2780超出了char数据类型的范围。您应该已经收到编译器警告来告知您:(至少我的g++ 4.7.3会给出此警告)

test.cpp:6:13: warning: multi-character character constant [-Wmultichar]

如果您想将 U+2780 这样的字符作为单个单位处理,您需要使用 widechar 数据类型 wchar_t,或者如果您足够幸运能够使用 C++11,则可以使用 char32_tchar16_t。请注意,一个 16 位单位不足以表示 Unicode 字符的全部范围。

如果这对您没有用,那很可能是因为默认的 "C" 区域设置不支持非 ASCII 输出。要解决这个问题,您可以在程序开头调用 setlocale;这样,您就可以输出用户区域设置支持的所有字符范围:(可能不支持您使用的所有字符)

#include <clocale>
#include <iostream>

using namespace std;

int main() {
    setlocale(LC_ALL, "");
    wcout << L'\u2780';
    return 0;
}

2
除了编码前缀 L 之外,还有 u8 表示 UTF8 编码,u 表示 char16_t,以及 U 表示 char32_t - Appleshell
谢谢@DyP,我已经添加了有关新字符数据类型的注释。 - Joni
尽管在Linux上使用g++时,wchar_t实际上是一个32位的Unicode码点。如果你更关心在Linux上让它工作而不是可移植性,那么这是一个很好的知识点。 - aschepler
1
@Sventimir 如果我没记错的话,C++11中流没有包含Unicode支持;不支持使用char16_tchar32_t进行wcout << 。你需要自定义将它们转换为期望的编码wchar_t或使用未格式化输出。 - dyp
谢谢大家。不幸的是,对我来说没有任何作用。char16_tchar32_tcoutwcout上都打印字符的十进制表示。设置CL_ALL语言环境也不起作用。看来我必须像DyP建议的那样考虑将int值映射到字符串。 - Sventimir
显示剩余7条评论

4

当你写代码时

cout << "\u2780";

编译器将\u2780转换为执行字符集中该字符的适当编码。这可能是UTF-8,因此字符串最终有四个字节(三个用于字符,一个用于空终止符)。
如果您想在运行时生成字符,则需要找到一种方法,在运行时执行与编译器在编译时执行的相同的UTF-8转换。
C++11提供了一个方便的模板和codecvt facets,可以实现这一点,然而libstdc++,即随GCC一起提供的标准库实现,尚未实现它们(截至GCC 4.8.0(2013-03-22))。以下展示了如何使用这些特性,但您需要使用不同的标准库实现或等待libstdc++实现它们。
#include <codecvt>

int main() {
  char32_t base = U'\u2780';

  std::wstring_convert<std::codecvt_utf8<char32_t>, char32_t> convert;
  std::cout << convert.to_bytes(base + 5) << '\n';
}

您也可以使用任何其他可用的UTF-8生成方法。例如,iconvICU和手动使用C++11之前的codecvt_byname facets都可以工作。(我没有展示这些的示例,因为那段代码比wstring_convert允许的简单代码更复杂。)


如果字符数量较少,一种可行的替代方案是使用字面量创建字符串数组。

char const *special_character[] = { "\u2780", "\u2781", "\u2782",
  "\u2783", "\u2784", "\u2785", "\u2786", "\u2787", "\u2788", "\u2789" };

std::cout << special_character[i] << '\n';

2
该程序打印一个整数,这是因为C++11 §2.14.3/1规定:

多字符字面量或包含单个不能用执行字符集表示的c-char的普通字符字面量是有条件支持的,类型为int,并具有实现定义的值。

执行字符集是指char可以表示的字符集,即ASCII。
你得到了14851712,或者十六进制的E29E80,这是U+2780(带圈无衬线数字1)的UTF-8表示。将UTF-8(一种多字节编码)放入int中是疯狂和愚蠢的,但这就是“有条件支持、实现定义”的特性所带来的结果。
要获取一个UTF-32值,请使用U'\u2780'。第一个U指定了char32_t类型和UTF-32编码(即最多31位但没有代理对)。第二个\u指定了包含代码点的通用字符名称。要获取一个据称与wcout兼容的值,请使用L'\u2780',但这并不一定使用Unicode运行时值,也不能获得超过两个字节的存储空间。
至于可靠地操作和打印Unicode代码点,正如其他答案所指出的那样,C++标准还没有完全到达那里。Joni's answer是最好的方法,但它仍然假设编译器和用户的环境使用相同的区域设置,这通常是不正确的。

您还可以在源代码中使用u8"\u2780"指定UTF-8字符串,并使用类似于std::locale::global(std::locale("en_US.UTF-8"));的方法强制运行时环境为UTF-8。但这仍然存在一些问题。Joni建议使用来自<clocale>的C接口std::setlocale而不是来自<locale>的C++接口std::locale::global,这是对C++接口在OS X上的GCC中出现故障的一种解决方法,也许在其他平台上也会出现这种情况。这些问题与平台有关,以至于您的Linux发行版可能已经将补丁放入了他们自己的GCC软件包中。


你或者我可能漏掉了什么,因为编译器现在提示“U未在范围内声明”。 - Sventimir
@Sventimir 看起来在GCC 4.7.2中不支持,但它是C++11标准的一部分。只需使用L'xxx'即可,在Linux中应该基本上具有相同的功能。 - Potatoswatter
使用gcc --std=c++11调用添加C++11支持也不起作用。现在虽然编译了,但是打印的是字符的十进制值(10112),而不是字符本身。 - Sventimir

0
在Linux中,我已成功以最简单的方式直接打印任何Unicode,例如the
std::cout << "ΐ, Α, Β, Γ, Δ, Θ, Λ, Ξ, ... ±, ... etc."

那怎么回答问题了?它甚至没有包括[U+2780]。 - Peter Mortensen
一个类似的未经解释的回答:https://dev59.com/HWct5IYBdhLWcg3wjd-H#41546489。 - Peter Mortensen

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