如何使用cin和cout输入输出一些Unicode文本?

15

我希望您能提供一个代码片段,用于输入Unicode文本、将另一个Unicode文本连接到第一个Unicode文本,最后输出结果。

附注:这个代码将有助于我解决与Unicode相关的另一个更大的问题。但在这之前,首要任务是完成我提出的要求。

补充说明:顺便说一下,当我运行可执行文件时,无法在命令行中输入任何Unicode符号。我该怎么做?


3
Unicode不够精确。你使用的是UTF-[8/16/32]吗?你想在内部和序列化到文件时使用相同的表示方法吗?如果要转换表示,你想手动转换还是通过locale和codecvt facet进行转换? - Martin York
随你便!没有文件和其他的,只有cin和cout! - Narek
在阅读了各种关于这个主题的帖子之后,我的结论是在C++中无法实现。从C++和C标准中去掉cincout和其他所有内容,使用纯Windows函数ReadConsoleWWriteConsoleW。C和C++标准在这方面只是有缺陷的。 - Philipp
1
@philip - C++标准根本没有涉及Unicode。就像它没有涉及与网络层通信一样。C++0x在某种程度上解决了Unicode问题,但我还不熟悉它...到那时,您将拥有标准的C++功能来处理Unicode。虽然C++不知道“控制台”是什么,但我敢打赌它会被处理好的。 - Edward Strange
5个回答

12

我之前也遇到过类似的问题,我的解决方法是使用 imbuesync_with_stdio。你可以尝试以下代码:

#include <iostream>
#include <locale>
#include <string>

using namespace std;

int main() {
    ios_base::sync_with_stdio(false);
    wcin.imbue(locale("en_US.UTF-8"));
    wcout.imbue(locale("en_US.UTF-8"));

    wstring s;
    wstring t(L" la Polynésie française");

    wcin >> s;
    wcout << s << t << endl;
    return 0;
}

2
我已经调试过了,看起来这一行是问题所在: wcin.imbue(locale("en_US.UTF-8")); - Narek
1
@Narek 是的,我测试了这段代码。在我的Ubuntu上运行没有问题。你用的是什么系统? - Bolo
5
wcinwcout在Windows上不起作用,就像等效的C函数一样。只有本地API可用。 - Philipp
谢谢。你的技巧解决了我的问题(如果输入包含重音字母,则跳过cin)。 - Aminos

10

根据您所说的Unicode类型不同,我猜测您只是在使用std::wstring。在这种情况下,您可以使用std::wcinstd::wcout

如果需要进行编码转换,则可以使用操作系统函数,例如Win32中的WideCharToMultiByteMultiByteToWideChar,或者使用类似libiconv这样的库。


1
只有当您的操作系统理解UTF-16时,才可以使用UTF-16代替UTF-8。 - Edward Strange
+1:wcout用于wstring和wchar_t(主要是Windows的UTF-16),cout用于string和char(Linux默认为UTF-8)。 - rubenvb
@Philipp:wcinwcout在哪方面对您不起作用?它们无法显示控制台字体不支持的Unicode字符,但这是控制台而不是iostreams的错误。 - Ben Voigt
1
@Ben Voight:它们根本不显示Unicode字符,即使字体支持它。请参见我的答案以获取示例。原因是它们没有包装ReadConsoleW/WriteConsoleW - Philipp

8

这里有一个例子展示了四种不同的方法,其中只有第三种(C conio)和第四种(本地Windows API)有效(但仅在stdin/stdout未被重定向时有效)。请注意,您仍然需要一种包含您想要显示的字符的字体(Lucida Console至少支持希腊语和西里尔语)。请注意,这里的所有内容都是完全不可移植的,因为没有一种可移植的方式可以在终端上输入/输出Unicode字符串。

#ifndef UNICODE
#define UNICODE
#endif

#ifndef _UNICODE
#define _UNICODE
#endif

#define STRICT
#define NOMINMAX
#define WIN32_LEAN_AND_MEAN

#include <iostream>
#include <string>
#include <cstdlib>
#include <cstdio>

#include <conio.h>
#include <windows.h>

void testIostream();
void testStdio();
void testConio();
void testWindows();

int wmain() {
    testIostream();
    testStdio();
    testConio();
    testWindows();
    std::system("pause");
}

void testIostream() {
    std::wstring first, second;
    std::getline(std::wcin, first);
    if (!std::wcin.good()) return;
    std::getline(std::wcin, second);
    if (!std::wcin.good()) return;
    std::wcout << first << second << std::endl;
}

void testStdio() {
    wchar_t buffer[0x1000];
    if (!_getws_s(buffer)) return;
    const std::wstring first = buffer;
    if (!_getws_s(buffer)) return;
    const std::wstring second = buffer;
    const std::wstring result = first + second;
    _putws(result.c_str());
}

void testConio() {
    wchar_t buffer[0x1000];
    std::size_t numRead = 0;
    if (_cgetws_s(buffer, &numRead)) return;
    const std::wstring first(buffer, numRead);
    if (_cgetws_s(buffer, &numRead)) return;
    const std::wstring second(buffer, numRead);
    const std::wstring result = first + second + L'\n';
    _cputws(result.c_str());
}

void testWindows() {
    const HANDLE stdIn = GetStdHandle(STD_INPUT_HANDLE);
    WCHAR buffer[0x1000];
    DWORD numRead = 0;
    if (!ReadConsoleW(stdIn, buffer, sizeof buffer, &numRead, NULL)) return;
    const std::wstring first(buffer, numRead - 2);
    if (!ReadConsoleW(stdIn, buffer, sizeof buffer, &numRead, NULL)) return;
    const std::wstring second(buffer, numRead);
    const std::wstring result = first + second;
    const HANDLE stdOut = GetStdHandle(STD_OUTPUT_HANDLE);
    DWORD numWritten = 0;
    WriteConsoleW(stdOut, result.c_str(), result.size(), &numWritten, NULL);
}
  • 编辑1: 我添加了一个基于conio的方法。
  • 编辑2: 我尝试过一些类似于Michael Kaplan博客中介绍的_O_U16TEXT的方法,但似乎只能让wgetsReadFile读取的(8位)数据解释为UTF-16编码。这个周末我会进一步调查此问题。

谢谢。请告诉我如何在命令行中以Unicode编写?我无法做到!它会忽略并以拉丁文写入。 - Narek
你可能想要写“main”而不是“wmain”,对吗? - Narek
如果您想读取命令行参数,请将wmain声明为int wmain(int argc, wchar_t** argv)w不是打字错误!)。 - Philipp
1
不,无论如何,我都无法在命令行中写入任何亚美尼亚语或俄语字母! - Narek
你尝试了什么?顺带一提,我认为你最好询问一个新问题,评论并不是讨论论坛的好替代品。 - Philipp

0

如果您有实际文本(即逻辑字符的字符串),则插入到宽流中。宽流将自动编码您的字符以匹配区域设置编码所期望的位。 (如果您有编码位,流将解码这些位,然后重新编码以匹配区域设置。)

如果您知道自己有UTF编码的位(即旨在解码为逻辑字符字符串的位数组)并且您知道输出流的目标期望完全相同的位格式,则可以跳过解码和重新编码步骤,并将位按原样写入write()。仅当您知道双方使用相同的编码格式时才有效,这可能适用于不打算与其他语言环境中的进程通信的小型实用程序。


2
Windows 没有本地编码,因此宽流无法工作。 - Philipp

-1

这取决于操作系统。如果您的操作系统理解,您可以简单地发送UTF-8序列。


他使用的是Windows操作系统,它使用UTF-16编码,但需要特殊的API函数(ReadConsole/WriteConsole)才能处理Unicode文本。 - Philipp

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