在 Mac 中使用 C++ 读取 UTF-8 数据不起作用

Posted

技术标签:

【中文标题】在 Mac 中使用 C++ 读取 UTF-8 数据不起作用【英文标题】:Reading UTF-8 data with C++ in Mac not working 【发布时间】:2016-04-25 16:20:50 【问题描述】:

虽然我的 C++ 经验相当少,但我正在努力帮助 C++ 程序员让他的库在 Mac 上运行。目前,问题似乎仅与语言环境/编码有关。

为了创建一个最小的工作示例,我测试了以下代码,它将一行 UTF-8 字符读取为一个宽字符串 (wstring),然后遍历该字符串并打印每个字符。

虽然它在 Linux 机器上完美运行,所有字符都打印在不同的行中,但在使用 Mac 机器时,我每行打印每个 字节(而不是每个字符)。

代码是:

#include <sstream>
#include <iostream> 
#include <string>
#include <boost/locale.hpp>

using namespace std;

int main() 
    std::ios_base::sync_with_stdio(false);
    boost::locale::generator gen;
    locale mylocale = gen("pt_PT.UTF-8");
    locale::global(mylocale);

    wstring userInput;
    getline(wcin, userInput);

    wcerr << "Size of string is " << userInput.length() << endl;

    for (int i = 0; i < userInput.length(); ++i) 
        wcerr << userInput.at(i) << endl;
    
    return 0;

我的测试字符串是一个愚蠢的葡萄牙语句子

O coração é um órgão frágil.

我正在尝试使用 Boost_locale,因为有人告诉我这是让 unicode 在 Mac 上正常工作的方法,但我很乐意有一个仅使用 C++ 标准库的解决方案。

编辑:

以下代码适用于 Mac。由于包含 codecvt,它无法在我的 Linux 机器上编译,但我可以通过一些 CPP 指令来管理它。

#include <sstream>
#include <iostream> 
#include <fstream>
#include <codecvt>
#include <locale>
#include <string>

using namespace std;

int main() 
    // setting std::local::global seems not to work (??)

    wcin.imbue(std::locale(locale(""), new std::codecvt_utf8<wchar_t>));
    wcerr.imbue(std::locale(locale(""), new std::codecvt_utf8<wchar_t>));

    wstring userInput;
    getline(wcin, userInput);

    wcerr << "Size of string is " << userInput.length() << endl;

    for (int i = 0; i < userInput.length(); ++i) 
        wcerr << userInput.at(i) << endl;
    
    return 0;

【问题讨论】:

您确定您的终端已正确设置以支持UTF-8 吗? 是的。它与其他编程语言完美配合。无论如何,文件说输入是utf-8,我正在重定向它,所以...... 看来 libc++(Mac 上的标准 C++ 库)不能很好地支持 UTF8。通过在 Linux 机器上使用 -stdlib=libc++ 构建来检查它(您可能必须先安装 libc++;Linux 上的默认 C++ 库是 libstdc++)。 您使用的是哪个版本的编译器? 【参考方案1】:

这种行为是由于在 UTF-8 编码中,一个字符(也称为 代码点)由一个或多个 代码单元表示。 p>

基本上是:

for (int i = 0; i < userInput.length(); ++i)

循环通过代码单元。您可以通过 userInput.length() 是一个大于字符串中字符数的数字来验证该行为。

通过做:

wcerr << userInput.at(i) << endl;

您在每个 代码单元 之后附加一个 endl,从而将属于同一 代码点代码单元 分开,这会产生无效字符。

如果你只是输出:

wcerr << userInput << endl;

你会得到完整的字符串。

如果要分别输出每个字符,则必须考虑属于同一代码点的多个代码单元并分别输出。

更新:

wcin 默认情况下不会转换为代码点。您需要明确说明输入的编码并进行转换。这基本上就是以下代码的作用。与您的示例的唯一主要区别是我使用 C++11 标准库而不是 Boost

#include <codecvt>
#include <iostream>

int main() 

    std::locale::global( std::locale( std::locale(""), new std::codecvt_utf8<wchar_t> ) );

    std::wcin.imbue( std::locale() );
    std::wcout.imbue( std::locale() );
    std::wcerr.imbue( std::locale() );

    std::wstring user_input;
    std::wcin >> user_input;

    for( int i = 0; i < user_input.length(); ++i ) 
        std::wcout << user_input[i] << std::endl;
    

    // Converting characters to uppercase
    const std::ctype<wchar_t>& f = std::use_facet<std::ctype<wchar_t>>( std::locale() );

    for( int i = 0; i < user_input.length(); ++i ) 
        std::wcout << f.toupper(user_input[i]) << std::endl; // f.tolower() for lowercase
    

    return 0;

附:要编译它,您需要传递 C++11 标准标志。

g++ -std=c++11 main.cpp

【讨论】:

为什么这不是 Linux 中的行为?为什么linux每行打印一个字符,而不是一个字节?为什么它们在 Linux 或 Mac 中的长度不同? @Alberto 要回答我需要更多时间,如果有的话,我会更新我的答案并联系你。 @Alberto 哇哦!我刚刚注意到您实际上是自己解决了它。哦,好吧... 谢谢。我的主要问题是理解为什么会有不同的行为。但后来......很好,有些东西正在工作。 :) @Alberto 我更新了我的代码以使用std::locale::global 并使用std::locale 默认构造函数,而不是传递相同的语言环境/转换器。我认为这更好,并且可能会解决多线程行为的问题。

以上是关于在 Mac 中使用 C++ 读取 UTF-8 数据不起作用的主要内容,如果未能解决你的问题,请参考以下文章

如何在 Mac 终端中使用 C++11 支持编译 C++

c++中的ifstream位置

C++ 读取 UTF-8 及 GBK 系列的文本方法及原理

读取 UTF-8 文件,使用 SendInput 将内容传递给其他应用程序

Mac下使用数据库将Excel数据转换存入.plist

访问 mac 的传感器数据