Umlaute mit C++



  • Hallo!

    Ich habe ein kleines C++-Programm für Windows 10 geschrieben.
    Das Programm soll eine Textdatei lesen und auf der Konsole ausgeben.
    Außerdem soll der Benutzer Text in der Konsole eingeben, der in einem String gespeichert und ebenfalls auf der Konsole ausgegeben wird.

    Das ist der Quellcode:

    #include <iostream>
    #include <string>
    #include <fstream>
    #include <locale>
    #include <Windows.h>
    using namespace std;
    
    int main()
    {
    	// Datei lesen
    	ifstream datei;
    	string zeile;
    
    	datei.open("test.txt");
    	while (!datei.eof())
    	{
    		getline(datei, zeile);
    		cout << zeile << endl;
    	}
    	cout << endl;
    
    	// Konsoleneingabe lesen
    	string eingabe;
    
    	getline(cin, eingabe);
    	cout << eingabe << endl;
    
    	return 0;
    }
    

    Leider funktioniert die Eingabe von Umlauten und anderen Nicht-ASCII-Zeichen nicht richtig.
    Wenn ich nichts verändere, kann ich die Zeichen problemlos in die Konsole eingeben. Beim Einlesen der Datei werden die Umlaute aber falsch dargestellt.

    Ich kann auch folgenden Einstellungen (am Beginn der main-Funktion) hinzufügen, um die Sprachumgebung des Programms und die Zeichenkodierung der Konsole auf UTF-8 zu setzen:

    locale dt("de_DE.utf-8");
    locale::global(dt);
    SetConsoleOutputCP(65001);
    

    Damit funktioniert das Einlesen der Datei problemlos. Dafür werden die Schriftzeichen beim Einlesen mit der Konsole nicht mehr dargestellt.

    Ich habe es schon mit mehreren Einstellungen getestet (z.B. verschiedene Codepages für die Konsole, verschiedene Sprachumgebungen, mit wstring, verschiedene Zeichencodierungen für die Dateien ...).

    Leider funktioniert das Einlesen entweder nur mit der Konsole oder nur mit der Datei.

    Schreibt mir bitte, falls Ihr wisst, wie man das Programm richtig einstellen muss, um die Umlaute (und vielleicht auch andere Zeichen) richtig darzustellen.



  • Also ich kenne das so:

    SetConsoleOutputCP (1252); // für Umlaut-Ausgabe
    SetConsoleCP (1252); // für Umlaut-Eingabe
    


  • @Zhavok
    Danke für deine Antwort.

    In meinem Programm hat sich aber leider nichts verändert:
    Das Einlesen der Datei funktioniert problemlos (solange ich "locale dt("de_DE.utf8");") verwende. Die Ausgabe der Konsoleneingabe wird abgebrochen, sobald das Programm zu einem Umlaut kommt.



  • Hallo,

    das Stichwort dafür lautet "Encoding" bzw. auf deutsch Zeichenkodierung.
    Du mußt also das Encoding der Textdatei kennen und dann passend einlesen.
    Und bei der Ausgabe auf die Konsole, wie schon geschrieben, dessen Encoding ändern oder aber die Texte in das passende Encoding der Konsole umwandeln.

    Unter Windows wird selten UTF-8 benutzt (aber Standard unter Linux), sondern UTF-16 ist die interne Encoding-Format.

    Einfacher ist jedoch die Texte (auch die Datei) direkt mit der Windows-Codepage 1252 (Western Latin-1) zu erstellen und dann die Konsole entsprechend umzustellen.

    Verwendest du denn einen C++11 (oder neuer) Compiler?
    Dann gibt es dafür explizit neue Datentypen char16_t, char_32_t sowie die Stringdatentypen u16string und u32string, s.a. Unicode und Localization.



  • @Th69 sagte in Umlaute mit C++:

    Einfacher ist jedoch die Texte (auch die Datei) direkt mit der Windows-Codepage 1252 (Western Latin-1) zu erstellen und dann die Konsole entsprechend umzustellen.

    Microsoft benennt Dinge leider oftmals anders als der Rest der Welt. Unter Latin 1 versteht man üblicherweise ISO 8859-1 mit Euro nimmt man eigentlich ISO 8859-15 und das ist nicht gleich der Windows Codepage 1252, sondern der Windows Codepage 28591 bzw. 28605. Wenn man da nicht aufpasst, erlebt man schnell einige hässliche Effekte, wenn man Code von einer auf die andere Plattform überträgt. Persönlich würde ich daher zu einem Unicodeformat raten und es dann ggf. von UTF-8 auf UTF-16 umkodieren.



  • Du musst halt wissen, welche Codierung Du im Programm hast, und welche Du für die Ausgabe brauchst, und dann entsprechend umwandeln.
    Gibst Du etwas über die Konsole ein und später wieder auf die Konsole aus, brauchst Du nichts zu tun.
    Ebenso, wenn Du aus einer Datei einliest, und in die Datei ausgibst.

    Problematisch wird es, wenn Du aus einer Datei, die Du zB. mit dem Editor (notepad) erstellt hast, einliest und auf die Konsole ausgibst.

    Dann musst Du erst die Kodierung ändern.

    Standardmäßig ist die Kodierung der Datei Ansi (denke ich), ich denke, Dein C++ - Editor kodiert auch in Ansi.
    Deshalb kannst Du

    cout << "äöÜ";
    

    auf der Konsole nicht lesen, wenn Du die Ausgabe aber in eine (.txt)-Datei umleitest, die Du dann mit dem Editor öffnest, ist alles gut.

    Zum Umwandeln könntest Du einen Umweg über die WinAPI-Funktionen WideCharToMultiByte und MultiByteToWideChar machen:

    #include <windows.h>
    #include <iostream>
    #include <string>
    #include <memory>
    
    using namespace std;
    
    wstring toWide(string in, UINT from)
    {
       int len = MultiByteToWideChar(from, 0, in.c_str(), -1, 0, 0);
       
       if(!len)
          cout << "Fehler toWide?!\n";
       
       unique_ptr<WCHAR[]> res(new WCHAR[len]);
       
       int result = MultiByteToWideChar(from, 0, in.c_str(), -1, res.get(), len);
          
       if(result != len)
          cout << "Fehler toWide?!\n";
          
       wstring s(res.get());
    
       return s;
    }
    
    string toMultiByte(wstring in, UINT to)
    {
       const int len = WideCharToMultiByte(to, 0, in.c_str(), -1, 0, 0, 0, 0);
       
       if(!len)
          cout << "Fehler toMBCS 1?!\n";
          
       unique_ptr<char[]> res(new char[len]);
       
       int result = WideCharToMultiByte(to, 0, in.c_str(), -1, res.get(), len, 0, 0);
       
       if(result != len)
          cout << "Fehler toMBCS2?!\n";
          
       string s(res.get());
       
       return s;
    }
    
    string AnsiToUTF8(const string &in)
    {
       wstring tmp = toWide(in, CP_ACP);
       string res = toMultiByte(tmp, CP_UTF8);
       return res;
    }
    
    string UTF8ToAnsi(const string &in)
    {
       wstring tmp = toWide(in, CP_UTF8);
       string res = toMultiByte(tmp, CP_ACP);
       return res;
    }
    
    string AnsiToCons(const string &in)
    {
       wstring tmp = toWide(in, CP_ACP);
       string res = toMultiByte(tmp, CP_OEMCP);
       return res;
    }
    
    
    string ConsToAnsi(const string &in)
    {
       wstring tmp = toWide(in, CP_OEMCP);
       string res = toMultiByte(tmp, CP_ACP);
       return res;
    }
    
    
    int main()
    {
       cout << "äöÜ\n";   // Schrott, es sei denn Du leitest die Ausgabe in eine Datei um
    
       string cons = AnsiToCons("äöÜ");
       cout << cons << '\n'; // gut, solange Du es nicht in eine Datei umleitest
    }
    

    Analog musst Du das, was Du aus einer Datei liest, via AnsiToCons umwandeln, bevor Du es auf die Konsole ausgibst.
    Liest Du etwas über die Konsole ein, wandelst Du es via ConsToAnsi um, bevor Du es in eine Datei schreibst.



  • @Erich5384
    Hier sind ein paar Lösungen beschrieben: https://stackoverflow.com/questions/45575863/how-to-print-utf-8-strings-to-stdcout-on-windows

    Das Problem scheint zu sein dass die MSVC C++ Library die Daten Byte für Byte auf die Konsole schreibt, was cmd.exe im UTF-8 Modus aber nicht versteht sobald Zeichen vorkommen die mit mehr als einem Byte kodiert werden.

    Das Problem dass du dann manuell flushen musst kannst du umgehen indem du dir einen eigenen Stream-Buffer implementierst, der erkennt wann ein Zeichen zu Ende ist und dann selbständig flusht. Oder einfacher: du flusht nur automatisch wenn ein Zeilenumbruch erkannt wird.



  • @john-0 sagte in Umlaute mit C++:

    Microsoft benennt Dinge leider oftmals anders als der Rest der Welt. Unter Latin 1 versteht man üblicherweise ISO 8859-1 mit Euro nimmt man eigentlich ISO 8859-15 und das ist nicht gleich der Windows Codepage 1252, sondern der Windows Codepage 28591 bzw. 28605.

    Da hast du Recht.

    Die (komplette) Liste der Windows-Codepages gibt es unter Code Page Identifiers bzw. auf deutsch Code Page Bezeichner.
    Die 152x-Codepages beruhen dabei auf dem (älteren) ANSI-Zeichencode, aus dem dann der ISO-Standard 8859-1 (Latin-1) wurde.

    Ich hätte also korrekterweise "Windows-Codepage 1252 (ANSI Latin-1, Western European (westeuropäisch))" schreiben sollen.

    PS: Interessant finde ich aus dem ISO 8859-1 Artikel den 4. Absatz mit u.a.

    Da beispielsweise in HTML die zusätzlichen Steuerzeichen aus ISO 8859-1 keine Bedeutung haben, werden oft die druckbaren Zeichen aus Windows-1252 verwendet. Aus diesem Grund schreibt der neue HTML5-Standard vor, dass als ISO 8859-1 markierte Texte als Windows-1252 zu interpretieren sind.

    Das wußte ich bisher auch nicht (auch wenn die meistens Websites, Linux-bedingt, mit UTF-8 kodiert sind).



  • @Th69 sagte in Umlaute mit C++:

    Das wußte ich bisher auch nicht (auch wenn die meistens Websites, Linux-bedingt, mit UTF-8 kodiert sind).

    Die tatsächliche Motivation für UTF-8 mag unterscheidliche Gründe haben, aber IMHO ist UTF-8 auch objektiv die sinnvollste Codierung fürs Web. Immerhin bestehen Websites zum größten Teil aus ASCII-Zeichen in Form von HTML-Tags, CSS- und JS-Code. Sobald der Text überwiegend aus diesen Zeichen besteht, ist UTF-8 letztendlich die datensparsamste Codierung (weniger Traffic) - auch wenn der menschenlesbare Nutztext in einigen Sprachen dabei größer wird... natürlich abgesehen von spezifischen "Codepages", aber das ist IMHO ohnehin Legacy-Geraffel, das endlich mal sterben muss 😉



  • Danke für die Antworten.

    Ich habe das Problem gelöst.
    Der Fehler war eine falsche Kodierung der Textdatei (für das Lesen mit "ifstream").

    Mit dieser Einstellung funktioniert es:
    locale::global(locale("de_DE"));
    SetConsoleOutputCP(1252);
    SetConsoleCP(1252);

    Die Textdatei muss mit der ANSI-Kodierung gespeichert werden.

    Das Programm funktioniert jetzt mit Umlauten und einigen weiteren Zeichen.

    Unicode ist damit leider immer noch nicht möglich. Für mein aktuelles Projekt ist es aber ausreichend.



  • @Finnegan sagte in Umlaute mit C++:

    Sobald der Text überwiegend aus diesen Zeichen besteht, ist UTF-8 letztendlich die datensparsamste Codierung (weniger Traffic) - auch wenn der menschenlesbare Nutztext in einigen Sprachen dabei größer wird... natürlich abgesehen von spezifischen "Codepages", aber das ist IMHO ohnehin Legacy-Geraffel, das endlich mal sterben muss

    Die Antwort ergibt nur Sinn wenn du mit Encodings wie UTF-16, UCS-2, UTF-32 etc. vergleichst. Es ging aber doch um ISO 8859-1 vs. 1252. Verstehe also den Sinn nicht.


Anmelden zum Antworten