代码之家  ›  专栏  ›  技术社区  ›  The_Fireplace

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

  •  2
  • The_Fireplace  · 技术社区  · 7 年前

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

    #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个字符显示为未知字符符号,打开该日志时,我收到警告,某些字符无法查看,编辑可能会损坏文件。

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

    编辑: 按照马雷克·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字符都被输出中的问号所取代。有没有办法让这些字符正确输出?

    1 回复  |  直到 7 年前
        1
  •  2
  •   Marek R    7 年前

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

    std::string 对编码一无所知,所以它将每个字节视为一个字符。输出控制台按照各自的编码方式解释byres序列,并显示正确的字符。

    当您逐个字节地分别打印每个字符串时,由于顺序正确,所以它可以工作。 当你从一个字符串中打印一个字节,从其他字符串中打印一个字节时,事情会变得一团糟。

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

    解决问题的另一种方法是使用映射,该映射将字节序列(字符串)转换为其他序列(字符串)。 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

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