Obsługa polskich znaków w C++

0

Podlinkowałby ktoś stronę z porządnym opisem obsługi polskich znaków w C i C++, chodzi mi o dokładne i szczegółowe wytłumaczenie niuansów, nie tylko co ale i w jaki sposób to działa.

#include<iostream>
using namespace std;

int main()
{
	string test="ą";
	cout<<test<<endl;
	
	
	wstring wtest=L"ą";
	wcout<<wtest<<endl;
}

Konkratna sprawa która mnie zastanawia: dlaczego w tym programie pierwszy cout wypisuje ładnie ą, za to wcout wypisuje pustą linię?

4
  1. Jakie kodowanie ma źródło?
  2. Jakie kodowanie używa kompilator do czytania źródła (czy jest zgodne z 1)
  3. Jakiego kodowania używa kompilator do zapisu literałów w kodzie wynikowym? Czy to kodowanie pokrywa polskie znaki?
  4. Jakie locale (kodowanie znaków) ma system na jakim uruchomiony jest program? To ma wpływ na to jak interpretowane jest wyjście z programu.

Na systemach POSIX na każde to pytanie odpowiedzią jest UTF-8 i nie ma problemów.
Na Windows jest bagno i trzeba uważać.

Czy wiesz jak działa kodowanie UTF-8 a jak działa Window1250/CP-1250?

Biorąc pod uwagę, że działa ci dla wstring/wcou, punkty 1 2 są ok, a problem jest dla punktów 3 4.

0

Mam linuxa mint 20 w xfce, g++, xterm i xeda. W jaki sposób sprawdzać kodowanie? Bo w terminalu mam na pewno UTF-8. Nie uczyłem się o kodowaniach, wiem bardzo pobieżnie.

1
szybki_procesor napisał(a):

Konkratna sprawa która mnie zastanawia: dlaczego w tym programie pierwszy cout wypisuje ładnie ą, za to wcout wypisuje pustą linię?

Bo linuxy są upośledzone pod względem wstringów..

MarekR22 napisał(a):

Na Windows jest bagno i trzeba uważać.

Ale za to wstring jest używalny i jest zdefiniowany jako UTF-16, a nie nie wiedzieć na co komu UTF-32.

3
Azarien napisał(a):
szybki_procesor napisał(a):

Konkratna sprawa która mnie zastanawia: dlaczego w tym programie pierwszy cout wypisuje ładnie ą, za to wcout wypisuje pustą linię?

Bo linuxy są upośledzone pod względem wstringów..

MarekR22 napisał(a):

Na Windows jest bagno i trzeba uważać.

Ale za to wstring jest używalny i jest zdefiniowany jako UTF-16, a nie nie wiedzieć na co komu UTF-32.

wstring na windowsie: 2 bajty na znak, wstring na innych systemach 4 bajty na znak.
MS jak zwykle po swojemu 🤮

3

OP, dużo by pisać. Jest to puszka pandory i jedna z tych rzeczy, której do tej pory komitetowi standaryzacyjnemu nie udało się ogarnąć do końca.

Zachęcam do sprawdzenia wyniku tego programu:

#include <iostream>
#include <string>
#include <locale>
#include <codecvt>
using namespace std;

template <typename O, typename S>
void write_hex(O &out, const S &s) {
	for (auto x : s) {
    	out << std::hex << static_cast<uint16_t>(x) << " ";
    }
    out << endl;
}


int main() {
    wstring_convert<codecvt_utf8<char16_t>, char16_t> cvu16;
    wstring_convert<codecvt_utf8<wchar_t>> cvu32;
	
    cout << "string:" << endl; 
    string str = u8"abecą";
    cout << str << endl;
    write_hex(cout, str);
    cout << endl;
	
    cout << "u16string:" << endl; 
    u16string str_u16 = u"abecą";
    cout << cvu16.to_bytes(str_u16) << endl;
    write_hex(cout, str_u16);
    cout << endl;
  
    cout << "wstring:" << endl; 
    wstring str2 = L"abecą";
    wcout << str2 << endl;
    cout << cvu32.to_bytes(str2) << endl;
    write_hex(cout, str2);
    cout << endl;
}

Dla leniwych: https://ideone.com/VIRApH

A więc to nie kwestia kodowania wewnątrz programu, a systemu operacyjnego. Linux od dawna wspiera utf-8. Za to nie wspiera utf16/utf32 jako locale, bo są niekompatybilne z ASCII.
Więc wewnątrz swojego programu możesz używać wstring, zakodowanego jako utf16, ale już nie wypiszesz na konsolę bez konwersji. EDIT: Trzeba, jak zostało wspomniane poniżej w innych postach, ustawić locale, na zgodne z systemem.

Swoją drogą, codecvt_utf8 jest deprecated :), ale nie ma dla niego alternatywy w bibliotece standardowej. Nad wsparciem dla unicode pracuje podgrupa w komitecie standaryzacyjnym. Może w końcu kiedyś udam im się wypracować to co inne języki, nawet te młodsze, już dawno mają ;)
Więc może zainteresuj się boost.locale.

1
nalik napisał(a):

A więc to nie kwestia kodowania wewnątrz programu, a systemu operacyjnego. Linux od dawna wspiera utf-8. Za to nie wspiera utf16 jako locale, bo są niekompatybilne z ASCII.
Więc wewnątrz swojego programu możesz używać wstring, zakodowanego jako utf16, ale już nie wypiszesz na konsolę bez konwersji.

A pod Windows mogę :P

3

A to ciekawe, poza ustawieniem locale na std::wcout trzeba wyłączyć synchronizację z API C, żeby to zadziałało:
https://wandbox.org/permlink/f6YpuptWoHbz0bYa

#include<iostream>
#include <locale>

int main()
{
    std::ios_base::sync_with_stdio(false);
    std::wcout.imbue(std::locale{""});
    std::cout << "char ąłóżę" << std::endl;
    std::wcout << L"wchar_t ałóężź" << std::endl;
}

Wskazówkę znalazłem tu, ale boost okazał się zbędny.

4

Ja tylko delikatnie chciałbym przypomnieć, że Windowsowy wstring pozwala trzymać napisy w kodowaniu UCS-2 a nie UTF16. UTF16 jest kodowaniem o zmiennej długości (2 do 4 bajtów na znak).
Z punktu widzenia poprawności programu nie ma znaczenia, czy napis trzymamy w std::string w UTF8 czy w std::wstring w UTF16 - tak czy inaczej nie można zakładać, że i-ty znak będzie na pozycji txt[i]

UTF32 jest już kodowaniem o stałej "szerokości", więc ten niedobry Linuksowy 32-bitowy wchar jest przynajmniej w stanie go jednoznacznie reprezentować.

BTW - w ramach ciekawostki - dawno dawno temu, w NDK do Androida wchar_t był zdefiniowany jako char (zmienili w wersji 2.3).

2
Bartłomiej Golenko napisał(a):

Ja tylko delikatnie chciałbym przypomnieć, że Windowsowy wstring pozwala trzymać napisy w kodowaniu UCS-2 a nie UTF16.

Tak było gdzieś za czasów Windows NT. Obecnie natywnym kodowaniem pod Windows jest UTF-16. I tak, to czasami oznacza że jeden znak zajmuje dwa wchary.

0

#MarekR22

Dzęki działa, ja jeszcze wpadłem na coś takiego:

#include<iostream>
#include <locale.h>
using namespace std;

int main()
{
	std::locale::global(std::locale("pl_PL.utf8"));
	
	
	string test="ą";
	cout<<test<<endl;
	
	freopen ("/dev/tty", "w", stdout);
	
	
	wstring wtest=L"ćą";
	wcout<<wtest<<endl;
}

Pytanie tylko czy jest jakiś podręcznik, strona, dokumment czy jakikolwiek inny tekst który solidnie omawia te sprawy?
edit:
Co powiecie na to? http://www.cplusplus.com/reference/iostream/wcout/

A program should not mix output operations on wcout with output operations on cout (or with other narrow-oriented output operations on stdout): Once an output operation has been performed on either, the standard output stream acquires an orientation (either narrow or wide) that can only be safely changed by calling freopen on stdout.

Teraz pytanie: Dlaczego rozwiązanie #MarekR22 działa? xD Co stdio z C ma tutaj do rzeczy?
http://www.cplusplus.com/reference/ios/ios_base/sync_with_stdio/

0
szybki_procesor napisał(a):

Teraz pytanie: Dlaczego rozwiązanie #MarekR22 działa? xD Co stdio z C ma tutaj do rzeczy?

Wygląda mi to na bug, po prostu… miało być tak pięknie, UTF-8 w konsoli, a tu się okazuje że i tak nie działa to na zasadzie „po prostu” tylko jakieś locale, jakieś freopen..

i w tym kontekście...

MarekR22 napisał(a):

Na systemach POSIX na każde to pytanie odpowiedzią jest UTF-8 i nie ma problemów.
Na Windows jest bagno i trzeba uważać.

No jak widać są problemy. Czyli też jest bagno.

0
#include<iostream>
#include <locale>
using namespace std;
int main()
{
	std::locale::global(std::locale("pl_PL.utf8"));
	
	//freopen ("/dev/tty", "w", stdout);
	
	
	
	
	for(int i=0;i<500;i++)
	{
		wcout<<static_cast<wchar_t>(i)<<L" "<<i<<endl;
		
	}
	
}

Może mi ktoś wytłumaczyć do tu się odpierdala dzieje? Dlaczego wypisuje tylko do 156 a potem koniec?

	for(int i=160;i<500;i++)
	{
		
		wcout<<static_cast<wchar_t>(i)<<L" "<<i<<endl;
		
	}

A od 160 wypisuje, co tam siedzi w tych 157, 158, 159?

1 użytkowników online, w tym zalogowanych: 0, gości: 1