如何在 C++ 中处理字符串中的非 ASCII 字符?

How to work with non-ascii characters in strings in C++?

在编写程序时,我在处理特殊字符和常规字符的组合时遇到问题。当我将这两种类型分别打印到控制台时,它们工作正常,但是当我在同一行中打印特殊字符和普通字符时,它会导致错误字符而不是预期的输出。 我的代码:

#include <fstream>
#include <iostream>
#include <string>

using namespace std;

void initCharacterMap(){
    const string normal = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz1234567890!@#$%^&*()-_[]{};':\",.<>/?";
    const string inverse = "∀Ↄ◖ƎℲ⅁HIſ⋊⅂WᴎOԀΌᴚS⊥∩ᴧMX⅄Zɐqɔpǝɟƃɥıɾʞʃɯuodbɹsʇnʌʍxʎz12Ɛᔭ59Ɫ860¡@#$%^⅋*)(-‾][}{؛,:„'˙></¿";

    cout << normal << endl;

    for(int i=0;i<normal.length();i++){
        cout << normal[i];
    }
    cout << endl;

    cout << inverse << endl;

    for(int i=0;i<inverse.length();i++){
        cout << inverse[i];
    }
    cout << endl;

    for(int i=0;i<inverse.length();i++){
        cout << normal[i] << inverse[i] << endl;
    }
}

int main() {
    initCharacterMap();
    return 0;
}

控制台输出: https://paste.ubuntu.com/p/H9bqh67WPZ/

在控制台中查看时,\XX 字符显示为未知字符符号,当我打开该日志时,我被警告说某些字符无法查看并且编辑可能会损坏文件。

如果有人对我如何解决这个问题有任何建议,将不胜感激。

编辑: 按照 Marek R 的回答中的建议进行操作后,情况有了很大改善,但这仍然不能给我想要的结果。 新代码:

#include <fstream>
#include <iostream>
#include <string>

using namespace std;

void initCharacterMap(){
    const wchar_t normal[] = L"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz1234567890!@#$%^&*()-_[]{};':\",.<>/?";
    const wchar_t inverse[] = L"∀Ↄ◖ƎℲ⅁HIſ⋊⅂WᴎOԀΌᴚS⊥∩ᴧMX⅄Zɐqɔpǝɟƃɥıɾʞʃɯuodbɹsʇnʌʍxʎz12Ɛᔭ59Ɫ860¡@#$%^⅋*)(-‾][}{؛,:„'˙></¿";

    wcout << normal << endl;

    for(int i=0;i<sizeof(normal)/sizeof(normal[0]);i++){
        wcout << normal[i];
    }
    wcout << endl;

    wcout << inverse << endl;

    for(int i=0;i<sizeof(inverse)/sizeof(inverse[0]);i++){
        wcout << inverse[i];
    }
    wcout << endl;

    for(int i=0;i<sizeof(inverse)/sizeof(inverse[0]);i++){
        wcout << normal[i] << inverse[i] << endl;
    }
}

int main() {
    initCharacterMap();
    return 0;
}

新控制台输出: https://paste.ubuntu.com/p/hcM7JB99zj/

因此,我不再遇到同时使用字符串内容输出的问题,但现在的问题是所有非 ascii 字符都被输出中的问号替换。有什么办法可以让这些字符正常输出吗?

很可能您的代码使用的是 UTF-8 编码。这意味着单个字符可以占用 1 到 4 个字节。 请注意 inverse.size() 的值比您预期的要大。

std::string 对编码一无所知,所以它把每个字节当作一个字符。输出控制台正在解释按相应编码完成的 byres 序列并显示正确的字符。

当您逐字节打印每个字符串时,它可以工作,因为顺序是正确的。 当您从一个字符串打印一个字节而从其他内容打印一个字节时,事情会变得混乱。

修复它的最简单方法是使用 std::wstring wchar_tL"some literal"。它应该适用于您的情况,但正如下面在某些平台上的彗星中指出的那样,某些字符可能不适合单个宽字符。 如果您想了解更多信息,请阅读不同的字符编码。

另一种解决问题的方法是使用映射,它将字节序列(字符串)转换为其他序列(字符串)。 C++11:

auto dictionary = std::unordered_map<std::string, std::string> {
    { "A", "∀" },
    { "B", "" },
    { "C", "Ↄ" },
    { "D", "◖" },
    … … …
}


编辑 我已经测试了您的新代码,您应该添加为输出流配置语言环境的代码。

在我的mac(使用波兰语言环境)上,当使用 clang 构建时,应用程序忽略 inverted 值(wcout 进入无效状态),但是当设置语言环境时一切正常正如您所期待的那样。

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

using namespace std;

void initCharacterMap(){
    wcout.imbue(locale(""));

    const auto normal = L"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz1234567890!@#$%^&*()-_[]{};':\",.<>/?"s;
    const auto inverse = L"∀Ↄ◖ƎℲ⅁HIſ⋊⅂WᴎOԀΌᴚS⊥∩ᴧMX⅄Zɐqɔpǝɟƃɥıɾʞʃɯuodbɹsʇnʌʍxʎz12Ɛᔭ59Ɫ860¡@#$%^⅋*)(-‾][}{؛,:„'˙></¿"s;

    wcout << normal << endl;

    for(auto ch : normal){
        wcout << ch;
    }
    wcout << endl;

    wcout << inverse << endl;

    for(auto ch : inverse){
        wcout << ch;
    }
    wcout << endl;

    for(size_t i=0; i < inverse.length(); ++i){
        wcout << normal[i] << inverse[i] << endl;
    }
}

int main() {
    initCharacterMap();
    return 0;
}

https://wandbox.org/permlink/nTYi5RbZgZXclE5r

我怀疑你的编译器中的标准库也不知道如何使用默认语言环境执行转换,所以它打印问号而不是实际的章程。所以添加这两行(includeimbue)它应该可以工作。如果没有,请提供有关您的平台和编译器的信息。