std :: wstring VS std :: string

740

Nie jestem w stanie zrozumieć różnic między std::stringi std::wstring. Wiem, że wstringobsługuje szerokie znaki, takie jak znaki Unicode. Mam następujące pytania:

  1. Kiedy należy używać std::wstringw ciągu std::string?
  2. Czy std::stringpomieści cały zestaw znaków ASCII, w tym znaki specjalne?
  3. Czy jest std::wstringobsługiwany przez wszystkie popularne kompilatory C ++?
  4. Czym jest dokładnie „ szeroki charakter ”?
Rapptz
źródło
10
Zestaw znaków ASCII nie ma wielu „znaków specjalnych”, najbardziej egzotycznym jest prawdopodobnie „(cytat). std :: string może pomieścić około 0,025% wszystkich znaków Unicode (zwykle 8-bitowy znak)
MSalters
3
Dobre informacje o szerokich znakach i typach można znaleźć tutaj: programmers.stackexchange.com/questions/102205/…
Yariv
14
Cóż, a ponieważ jesteśmy w 2012 roku, utf8everywhere.org został napisany. Prawie odpowiada na wszystkie pytania o prawa i zła w C ++ / Windows.
Pavel Radzivilovsky
42
@MSalters: std :: string może pomieścić 100% wszystkich znaków Unicode, nawet jeśli CHAR_BIT to 8. Zależy to od kodowania std :: string, które może być UTF-8 na poziomie systemu (jak prawie wszędzie oprócz Windowsa ) lub na poziomie aplikacji. Rodzime wąskie kodowanie nie obsługuje Unicode? Nie ma problemu, po prostu go nie używaj, zamiast tego użyj UTF-8.
Jakow Galka
8
Świetna lektura na ten temat: utf8everywhere.org
Timothy Shields,

Odpowiedzi:

990

string? wstring?

std::stringjest basic_stringszablonem na chari std::wstringna wchar_t.

char vs. wchar_t

charma zawierać postać, zwykle 8-bitową.
wchar_tma mieć szeroki znak, a potem sprawy stają się trudne: w
Linuksie a wchar_tma 4 bajty, podczas gdy w Windowsie ma 2 bajty.

Co zatem z Unicode ?

Problem polega na tym, że ani charani nie wchar_tjest bezpośrednio związany z Unicode.

W systemie Linux?

Weźmy Linux OS: Mój system Ubuntu jest już świadomy Unicode. Kiedy pracuję z ciągiem znaków, jest on natywnie kodowany w UTF-8 (tj. Ciąg znaków Unicode). Poniższy kod:

#include <cstring>
#include <iostream>

int main(int argc, char* argv[])
{
   const char text[] = "olé" ;


   std::cout << "sizeof(char)    : " << sizeof(char) << std::endl ;
   std::cout << "text            : " << text << std::endl ;
   std::cout << "sizeof(text)    : " << sizeof(text) << std::endl ;
   std::cout << "strlen(text)    : " << strlen(text) << std::endl ;

   std::cout << "text(ordinals)  :" ;

   for(size_t i = 0, iMax = strlen(text); i < iMax; ++i)
   {
      std::cout << " " << static_cast<unsigned int>(
                              static_cast<unsigned char>(text[i])
                          );
   }

   std::cout << std::endl << std::endl ;

   // - - - 

   const wchar_t wtext[] = L"olé" ;

   std::cout << "sizeof(wchar_t) : " << sizeof(wchar_t) << std::endl ;
   //std::cout << "wtext           : " << wtext << std::endl ; <- error
   std::cout << "wtext           : UNABLE TO CONVERT NATIVELY." << std::endl ;
   std::wcout << L"wtext           : " << wtext << std::endl;

   std::cout << "sizeof(wtext)   : " << sizeof(wtext) << std::endl ;
   std::cout << "wcslen(wtext)   : " << wcslen(wtext) << std::endl ;

   std::cout << "wtext(ordinals) :" ;

   for(size_t i = 0, iMax = wcslen(wtext); i < iMax; ++i)
   {
      std::cout << " " << static_cast<unsigned int>(
                              static_cast<unsigned short>(wtext[i])
                              );
   }

   std::cout << std::endl << std::endl ;

   return 0;
}

wyświetla następujący tekst:

sizeof(char)    : 1
text            : olé
sizeof(text)    : 5
strlen(text)    : 4
text(ordinals)  : 111 108 195 169

sizeof(wchar_t) : 4
wtext           : UNABLE TO CONVERT NATIVELY.
wtext           : ol�
sizeof(wtext)   : 16
wcslen(wtext)   : 3
wtext(ordinals) : 111 108 233

Zobaczysz, że tekst „olé” charjest w rzeczywistości zbudowany z czterech znaków: 110, 108, 195 i 169 (nie licząc końcowego zera). (Pozwolę ci przestudiować wchar_tkod jako ćwiczenie)

Tak więc, pracując z charLinuksem, zwykle powinieneś używać Unicode, nawet o tym nie wiedząc. I jak std::stringdziała char, std::stringjest już gotowy na Unicode.

Zauważ, że std::stringpodobnie jak interfejs API łańcucha C, łańcuch „olé” będzie traktował 4 znaki, a nie trzy. Dlatego należy zachować ostrożność podczas obcinania / gry znakami Unicode, ponieważ niektóre kombinacje znaków są zabronione w UTF-8.

W systemie Windows?

W systemie Windows jest to nieco inne. Przed pojawieniem się Unicode Win32 musiał obsługiwać wiele aplikacji współpracujących z charróżnymi zestawami znaków i stronami kodowymi produkowanymi na całym świecie.

Ich rozwiązanie było więc interesujące: jeśli aplikacja współpracuje char, łańcuchy znaków są kodowane / drukowane / wyświetlane na etykietach GUI przy użyciu lokalnego zestawu znaków / strony kodowej na komputerze. Na przykład „olé” byłoby „olé” w systemie Windows zlokalizowanym we Francji, ale byłoby czymś innym w systemie Windows zlokalizowanym w cyrylicy („olé”, jeśli używasz Windows-1251 ). Dlatego „aplikacje historyczne” zwykle będą nadal działać w ten sam stary sposób.

W przypadku aplikacji opartych na Unicode, Windows używa wchar_t2-bajtowej szerokości i jest zakodowany w UTF-16 , który jest Unicode zakodowany na 2-bajtowych znakach (lub przynajmniej w większości kompatybilnym UCS-2, który jest prawie to samo IIRC).

Używające aplikacje charsą nazywane „wielobajtowymi” (ponieważ każdy glif składa się z jednego lub więcej chars), podczas gdy aplikacje używające wchar_tsą nazywane „widechar” (ponieważ każdy glif składa się z jednego lub dwóch wchar_t. Zobacz API konwersji MultiByteToWideChar i WideCharToMultiByte Win32, aby uzyskać więcej informacji.

Tak więc, jeśli pracujesz w systemie Windows, bardzo chcesz go używać wchar_t(chyba że używasz frameworku, który to ukrywa, np. GTK + lub QT ...). Faktem jest, że za kulisami system Windows działa z wchar_tciągami, więc nawet aplikacje historyczne będą miały charprzekonwertowane ciągi wchar_tpodczas korzystania z interfejsu API podobnego SetWindowText()(funkcja API niskiego poziomu do ustawiania etykiety w interfejsie GUI Win32).

Problemy z pamięcią?

UTF-32 ma 4 bajty na znak, więc nie ma wiele do dodania, choćby tylko tekst UTF-8 i tekst UTF-16 zawsze zużywały mniej lub tyle samo pamięci niż tekst UTF-32 (i zwykle mniej ).

Jeśli występuje problem z pamięcią, powinieneś wiedzieć, że w przypadku większości języków zachodnich tekst UTF-8 zużyje mniej pamięci niż ten sam język UTF-16.

Jednak w przypadku innych języków (chiński, japoński itp.) Używana pamięć będzie taka sama lub nieco większa dla UTF-8 niż dla UTF-16.

Podsumowując, UTF-16 zużywa głównie 2, a czasami 4 bajty na znaki (chyba że masz do czynienia z jakimś ezoterycznym glifem języka (Klingon? Elvish?), Podczas gdy UTF-8 wyda od 1 do 4 bajtów.

Zobacz http://en.wikipedia.org/wiki/UTF-8#Compared_to_UTF-16, aby uzyskać więcej informacji.

Wniosek

  1. Kiedy powinienem używać std :: wstring zamiast std :: string?

    W systemie Linux? Prawie nigdy (§).
    W systemie Windows? Prawie zawsze (§).
    Na kodzie międzyplatformowym? Zależy od zestawu narzędzi ...

    (§): chyba że używasz zestawu narzędzi / frameworku, który mówi inaczej

  2. Czy std::stringpomieści cały zestaw znaków ASCII, w tym znaki specjalne?

    Uwaga: A std::stringnadaje się do przechowywania bufora „binarnego”, gdzie std::wstringnie ma!

    W systemie Linux? Tak.
    W systemie Windows? Dostępne są tylko znaki specjalne dla bieżących ustawień regionalnych użytkownika systemu Windows.

    Edit (po komentarzu od Johann Gerell ): będzie wystarczająco, aby obsłużyć wszystkie sznurki opartych (każdy jest liczbą od 0 do 255). Ale:
    std::stringcharchar

    1. ASCII ma zmieniać się od 0 do 127. Wyższe charNIE są ASCII.
    2. charod 0 do 127 odbędzie się prawidłowo
    3. a charod 128 do 255 będzie miało znaczenie w zależności od twojego kodowania (Unicode, non-Unicode itp.), ale będzie w stanie pomieścić wszystkie glify Unicode, o ile są one zakodowane w UTF-8.
  3. Jest std::wstringobsługiwany przez prawie wszystkie popularne kompilatory C ++?

    Głównie, z wyjątkiem kompilatorów opartych na GCC, które są portowane w systemie Windows.
    Działa na moim g ++ 4.3.2 (pod Linuksem), a ja użyłem Unicode API w Win32 od Visual C ++ 6.

  4. Jaka jest dokładnie szeroka postać?

    W C / C ++ jest to napisany typ znaków, wchar_tktóry jest większy niż prosty chartyp znaków. Powinien być używany do wstawiania znaków, których indeksy (takie jak glify Unicode) są większe niż 255 (lub 127, zależnie od ...).

paercebal
źródło
4
@gnud: Być może wchar_t miał być wystarczający do obsługi wszystkich znaków UCS-2 (większość znaków UTF-16) przed pojawieniem się UTF-16 ... A może Microsoft miał inne priorytety niż POSIX, takie jak zapewnienie łatwego dostępu do Unicode bez modyfikowania kodowanego użycia char w Win32.
paercebal
4
@Sorin Sbarnea: UTF-8 może zająć 1-6 bajtów, ale najwyraźniej standard ogranicza go do 1-4. Zobacz en.wikipedia.org/wiki/UTF8#Description aby uzyskać więcej informacji.
paercebal,
8
Podczas gdy te przykłady dają różne wyniki w systemach Linux i Windows, program C ++ zawiera zdefiniowane w implementacji zachowanie, czy olèkodowane jest jako UTF-8, czy nie. Co więcej, powód nie może natywnie strumieniowo wchar_t *do std::coutdlatego, że typy są niezgodne skutkuje źle utworzonego programu i to nie ma nic wspólnego z wykorzystaniem kodowania. Warto podkreślić, że to, czy używasz std::stringlub std::wstringzależy od własnych preferencji kodowania zamiast platformy, zwłaszcza, jeśli chcesz, aby Twój kod być przenośne.
John Leidegren,
14
Windows faktycznie używa UTF-16 i już od dłuższego czasu starsze wersje Windows używały UCS-2, ale już tak nie jest. Moim jedynym problemem jest wniosek, który std::wstringpowinien być stosowany w systemie Windows, ponieważ lepiej pasuje do interfejsu Windows Unicode API, który moim zdaniem jest błędny. Jeśli Twoim jedynym zmartwieniem było wywoływanie interfejsu API systemu Windows w standardzie Unicode, a nie zbieranie ciągów, to na pewno, ale nie kupuję tego jako ogólnej sprawy.
John Leidegren,
15
@ John Leidegren:: If your only concern was calling into the Unicode Windows API and not marshalling strings then sureZgadzamy się. Piszę w C ++, a nie JavaScript. Unikanie zbędnego gromadzenia danych lub innego potencjalnie kosztownego przetwarzania w czasie wykonywania, gdy można tego dokonać w czasie kompilacji, jest sercem tego języka. Kodowanie w oparciu o WinAPI i używanie std::stringto tylko nieuzasadnione marnowanie zasobów środowiska wykonawczego. Uważasz to za błędne i jest w porządku, ponieważ jest to twój punkt widzenia. Po swojemu nie piszę kodu z pesymizacją w systemie Windows tylko dlatego, że wygląda lepiej od strony Linuksa.
paercebal,
71

Zalecam unikanie std::wstringw systemie Windows lub gdzie indziej, z wyjątkiem sytuacji, gdy wymaga tego interfejs lub gdziekolwiek w pobliżu wywołań interfejsu API systemu Windows i odpowiednich konwersji kodowania jako cukru syntaktycznego.

Mój pogląd został streszczony na stronie http://utf8everywhere.org, której jestem współautorem.

O ile twoja aplikacja nie jest API-call-centric, np. Głównie aplikacja UI, sugeruje się przechowywanie ciągów Unicode w std :: string i kodowanych w UTF-8, wykonując konwersję w pobliżu wywołań API. Korzyści przedstawione w artykule przewyższają pozorną irytację konwersji, szczególnie w złożonych aplikacjach. Dzieje się tak podwójnie w przypadku rozwoju wielu platform i bibliotek.

A teraz odpowiadając na twoje pytania:

  1. Kilka słabych powodów. Istnieje z przyczyn historycznych, gdzie uważano, że widechary są właściwym sposobem wspierania Unicode. Jest teraz używany do interfejsu API, które preferują ciągi UTF-16. Używam ich tylko w bezpośrednim sąsiedztwie takich wywołań API.
  2. Nie ma to nic wspólnego ze std :: string. Może przechowywać dowolne kodowanie, które w nim umieścisz. Pytanie tylko, jak można traktować jej zawartość. Moje zalecenie to UTF-8, więc będzie mógł poprawnie przechowywać wszystkie znaki Unicode. Jest to powszechna praktyka w systemie Linux, ale myślę, że programy Windows również powinny to robić.
  3. Nie.
  4. Szeroka postać to myląca nazwa. Na początku Unicode istniało przekonanie, że postać może być zakodowana w dwóch bajtach, stąd nazwa. Dziś oznacza „każdą część znaku o długości dwóch bajtów”. UTF-16 jest postrzegany jako ciąg takich par bajtów (zwanych także Szerokimi znakami). Postać w UTF-16 bierze jedną lub dwie pary.
Pavel Radzivilovsky
źródło
37

Tak więc każdy czytelnik tutaj powinien mieć jasne zrozumienie faktów i sytuacji. Jeśli nie, musisz przeczytać niezwykle wyczerpującą odpowiedź paercebala [btw: dzięki!].

Mój pragmatyczny wniosek jest szokująco prosty: wszystkie te „kodujące” znaki C ++ (i STL) są w znacznym stopniu zepsute i bezużyteczne. Obwiniaj to Microsoft, czy nie, to i tak nie pomoże.

Moje rozwiązanie, po dogłębnym badaniu, dużej frustracji i związanych z tym doświadczeniach, jest następujące:

  1. zaakceptuj, że musisz samodzielnie ponosić odpowiedzialność za kodowanie i konwersję (i zobaczysz, że większość z nich jest dość trywialna)

  2. użyj std :: string dla dowolnych łańcuchów kodowanych w UTF-8 (tylko a typedef std::string UTF8String)

  3. zaakceptować, że taki obiekt UTF8String to tylko głupi, ale tani kontener. Nigdy nie otwieraj i / lub nie manipuluj bezpośrednio w nim znakami (bez wyszukiwania, zamiany itp.). Możesz, ale naprawdę, naprawdę, nie chcesz tracić czasu na pisanie algorytmów manipulacji tekstem dla ciągów wielobajtowych! Nawet jeśli inni ludzie robili już takie głupie rzeczy, nie rób tego! Niech będzie! (Cóż, istnieją scenariusze, w których ma to sens ... wystarczy użyć biblioteki ICU).

  4. użyj std :: wstring dla łańcuchów zakodowanych w UCS-2 ( typedef std::wstring UCS2String) - jest to kompromis i ustępstwo w stosunku do bałaganu wprowadzonego przez interfejs API WIN32). UCS-2 jest wystarczający dla większości z nas (więcej o tym później ...).

  5. używaj instancji UCS2String, ilekroć wymagany jest dostęp znak po znaku (czytaj, manipuluj itd.). Wszelkie przetwarzanie oparte na znakach powinno odbywać się w reprezentacji innej niż wielobajtowa. To jest proste, szybkie, łatwe.

  6. dodaj dwie funkcje narzędziowe do konwersji w obie strony między UTF-8 i UCS-2:

    UCS2String ConvertToUCS2( const UTF8String &str );
    UTF8String ConvertToUTF8( const UCS2String &str );

Konwersje są proste, Google powinien tutaj pomóc ...

Otóż ​​to. Używaj UTF8String wszędzie tam, gdzie cenna jest pamięć i dla wszystkich I / O UTF-8. Użyj UCS2String wszędzie tam, gdzie ciąg musi zostać przeanalizowany i / lub zmanipulowany. Możesz konwertować między tymi dwoma reprezentacjami w dowolnym momencie.

Alternatywy i ulepszenia

  • konwersje z & na jednobajtowe kodowanie znaków (np. ISO-8859-1) można zrealizować za pomocą zwykłych tabel translacji, np. const wchar_t tt_iso88951[256] = {0,1,2,...};i odpowiedniego kodu do konwersji do i z UCS2.

  • jeśli UCS-2 nie jest wystarczający, przełącz się na UCS-4 ( typedef std::basic_string<uint32_t> UCS2String)

ICU lub inne biblioteki Unicode?

Dla zaawansowanych rzeczy.

Frunsi
źródło
Dang, nie jest dobrze wiedzieć, że nie ma natywnej obsługi Unicode.
Mihai Danila,
@Frunsi, ciekawi mnie, czy próbowałeś Glib :: ustring, a jeśli tak, jakie są twoje przemyślenia?
Caroline Beltran,
@CarolineBeltran: Znam Glib, ale nigdy go nie użyłem i prawdopodobnie nigdy nawet go nie użyję, ponieważ jest on ograniczony do raczej nieokreślonej platformy docelowej (systemy unixoidowe ...). Port systemu Windows jest oparty na zewnętrznej warstwie Win2unix, a tam IMHO nie ma warstwy kompatybilności z OSX. Wszystkie te rzeczy wyraźnie zmierzają w złym kierunku, przynajmniej dla mojego kodu (na tym poziomie łuku ...) ;-) Tak więc Glib nie wchodzi w grę
Frunsi
9
Wyszukiwanie, zamienianie itd. Działa dobrze na ciągach UTF-8 (część sekwencji bajtów reprezentująca znak nigdy nie może zostać źle zinterpretowana jako inny znak). W rzeczywistości UTF-16 i UTF-32 wcale tego nie ułatwiają: wszystkie trzy kodowania są w praktyce kodowaniami wielobajtowymi, ponieważ postać postrzegana przez użytkownika (klaster grafemiczny) może mieć dowolną liczbę pojedynczych punktów kodowych! Pragmatycznym rozwiązaniem jest użycie UTF-8 do wszystkiego i konwersja do UTF-16 tylko w przypadku interfejsu API systemu Windows.
Daniel
5
@Frunsi: Wyszukiwanie i zamiana działa tak samo dobrze z UTF-8, jak z UTF-32. To właśnie dlatego, że właściwe przetwarzanie tekstu obsługujące Unicode i tak musi radzić sobie z wielopunktowymi „znakami”, dlatego użycie kodowania o zmiennej długości, takiego jak UTF-8, nie komplikuje przetwarzania łańcucha. Więc po prostu używaj UTF-8 wszędzie. Normalne funkcje łańcucha C będą działały poprawnie na UTF-8 (i odpowiadają porządkowym porównaniom na łańcuchu Unicode), a jeśli potrzebujesz czegoś bardziej świadomego języka, musisz zadzwonić do biblioteki Unicode, UTF-16/32 nie mogę cię przed tym uratować.
Daniel
25
  1. Gdy chcesz mieć szerokie znaki w swoim ciągu. widezależy od wdrożenia. Domyślnie Visual C ++ to 16 bitów, jeśli dobrze pamiętam, podczas gdy GCC domyślnie w zależności od celu. Ma tutaj 32 bity. Uwaga: wchar_t (szeroki typ znaków) nie ma nic wspólnego z Unicode. Jest tylko zagwarantowane, że może przechowywać wszystkich członków największego zestawu znaków obsługiwanego przez implementację przez jej ustawienia regionalne, a przynajmniej tak długo, jak char. Możesz również zapisać łańcuchy Unicode, aby std::stringużywać utf-8kodowania. Ale nie zrozumie znaczenia punktów kodu Unicode. Więcstr.size()nie da ci ilości logicznych znaków w twoim ciągu, ale jedynie ilość elementów char lub wchar_t przechowywanych w tym ciągu / łańcuchu. Z tego powodu ludzie korzystający z pakietu C ++ gtk / glib opracowali Glib::ustringklasę, która może obsługiwać utf-8.

    Jeśli twój wchar_t ma 32 bity, możesz użyć go utf-32jako kodowania Unicode, a także możesz przechowywać i obsługiwać ciągi Unicode za pomocą stałego kodowania (utf-32 ma stałą długość). Oznacza to, że funkcja twojego ciągu s.size()zwróci następnie odpowiednią liczbę elementów wchar_t i znaków logicznych.

  2. Tak, char ma zawsze co najmniej 8 bitów, co oznacza, że ​​może przechowywać wszystkie wartości ASCII.
  3. Tak, wszystkie główne kompilatory go obsługują.
Johannes Schaub - litb
źródło
Ciekawi mnie # 2. Myślałem, że 7 bitów też będzie technicznie poprawnych? A może wymagana jest możliwość przechowywania czegokolwiek poza 7-bitowymi znakami ASCII?
lipiec
1
tak, jalf. c89 określa minimalne zakresy dla podstawowych typów w dokumentacji limitów. h (dla znaku bez znaku, to 0..255 min) oraz czysty system binarny dla typów całkowitych. podąża za char, niepodpisany char i podpisany char mają minimalną długość bitów 8. c ++ dziedziczy te reguły.
Johannes Schaub - litb
15
„Oznacza to, że funkcja s.size () Twojego wstringa zwróci wtedy odpowiednią liczbę elementów wchar_t i znaków logicznych.” Nie jest to do końca dokładne, nawet w przypadku Unicode. Bardziej dokładne byłoby powiedzenie punktu kodowego niż „znaku logicznego”, nawet w UTF-32 dany znak może składać się z wielu punktów kodowych.
Logan Capaldo
Czy w istocie mówicie, że C ++ nie ma natywnej obsługi zestawu znaków Unicode?
Mihai Danila,
1
„Ale nie zrozumie znaczenia punktów kodu Unicode”. W systemie Windows też nie std::wstring.
Deduplicator
5

Często używam std :: string do przechowywania znaków utf-8 bez żadnych problemów. Polecam to zrobić w przypadku interfejsów API, które używają utf-8 jako rodzimego typu łańcucha.

Na przykład używam utf-8 podczas łączenia mojego kodu z interpreterem Tcl.

Głównym zastrzeżeniem jest długość std :: string, nie jest to już liczba znaków w ciągu.


źródło
1
Juan: Czy masz na myśli, że std :: string może pomieścić wszystkie znaki Unicode, ale długość będzie podawać niepoprawnie? Czy istnieje powód, dla którego zgłasza nieprawidłową długość?
3
Podczas korzystania z kodowania utf-8 pojedynczy znak Unicode może składać się z wielu bajtów. Dlatego kodowanie utf-8 jest mniejsze, gdy używa się głównie znaków ze standardowego zestawu ascii. Musisz użyć funkcji specjalnych (lub rzucić własne), aby zmierzyć liczbę znaków Unicode.
2
(Specyficzne dla systemu Windows) Większość funkcji oczekuje, że ciąg wykorzystujący bajty to ASCII, a 2 bajty to Unicode, starsze wersje MBCS. Co oznacza, że ​​jeśli przechowujesz 8-bitowy Unicode, będziesz musiał przekonwertować na 16-bitowy Unicode, aby wywołać standardową funkcję systemu Windows (chyba że używasz tylko części ASCII).
Greg Domjan
2
Nie tylko std :: string nieprawidłowo zgłasza długość, ale także wyświetla niepoprawny ciąg. Jeśli jakiś znak Unicode jest reprezentowany w UTF-8 jako wiele bajtów, które std :: string traktuje jako swoje własne znaki, wówczas twoje procedury manipulacji std :: string prawdopodobnie wygenerują kilka dziwnych znaków, które wynikają z błędnej interpretacji jednego poprawny charakter.
Mihai Danila,
2
Sugeruję zmianę odpowiedzi, aby wskazać, że ciągi powinny być traktowane tylko jako kontenery bajtów, a jeśli bajty są kodowaniem Unicode (UTF-8, UTF-16, ...), powinieneś użyć określonych bibliotek, które rozumieją że. Standardowe interfejsy API oparte na łańcuchach znaków (długość, podłoże itp.) Zawiodą przy znakach wielobajtowych. Jeśli ta aktualizacja zostanie wykonana, usunę moją opinię.
Mihai Danila
4
  1. Gdy chcesz przechowywać znaki „szerokie” (Unicode).
  2. Tak: 255 z nich (bez 0).
  3. Tak.
  4. Oto artykuł wprowadzający: http://www.joelonsoftware.com/articles/Unicode.html
ChrisW
źródło
11
std :: string może przechowywać 0 w porządku (po prostu zachowaj ostrożność, jeśli wywołasz metodę c_str ())
Mr Fooz
3
I ściśle mówiąc, nie jest gwarantowane, że char ma 8 bitów. :) Twój link w punkcie 4 jest obowiązkowy, ale nie sądzę, że odpowiada na pytanie. Szeroka postać nie ma nic wspólnego z Unicode. To po prostu szerszy charakter. (O ile szerszy, zależy od systemu operacyjnego, ale zwykle 16 lub 32-bitowy)
grudnia08
2
  1. gdy chcesz używać ciągów Unicode, a nie tylko ascii, pomocne w internacjonalizacji
  2. tak, ale nie gra dobrze z 0
  3. nie zdając sobie sprawy z tego, że nie
  4. szeroki znak jest specyficznym dla kompilatora sposobem obsługi reprezentacji stałej długości znaku unicode, dla MSVC jest to 2-bajtowy znak, dla gcc rozumiem, że jest to 4 bajty. oraz +1 dla http://www.joelonsoftware.com/articles/Unicode.html
Greg Domjan
źródło
1
2. Std :: string może dobrze przechowywać znak NULL. Może również przechowywać znaki utf-8 i szerokie.
@Juan: To znów wprawiło mnie w zamieszanie. Jeśli std :: string może przechowywać znaki Unicode, co jest specjalnego w std :: wstring?
1
@Appu: std :: string może zawierać znaki Unicode UTF-8. Istnieje wiele standardów Unicode ukierunkowanych na różne szerokości znaków. UTf8 ma szerokość 8 bitów. Są też UTF-16 i UTF-32 odpowiednio o szerokości 16 i 32 bity
Greg D
Ze std :: wstring. Każdy znak Unicode może być jednym wchar_t, gdy używasz kodowania o stałej długości. Na przykład, jeśli zdecydujesz się użyć joel w podejściu programowym jako Greg. Zatem długość ciągu jest dokładnie liczbą znaków Unicode w ciągu. Ale zajmuje więcej miejsca
Nie powiedziałem, że nie może utrzymać 0 '\ 0', a to, co miałem na myśli, nie brzmi dobrze, ponieważ niektóre metody mogą nie dać oczekiwanego wyniku zawierającego wszystkie dane ciągu. Tak szorstkie w głosowaniu w dół.
Greg Domjan
2

Aplikacje, które nie są usatysfakcjonowane tylko 256 różnymi znakami, mają opcje użycia szerokich znaków (więcej niż 8 bitów) lub kodowania o zmiennej długości (kodowanie wielobajtowe w terminologii C ++), takiego jak UTF-8. Szerokie znaki zwykle wymagają więcej miejsca niż kodowanie o zmiennej długości, ale ich przetwarzanie jest szybsze. Aplikacje wielojęzyczne przetwarzające duże ilości tekstu zwykle używają szerokich znaków podczas przetwarzania tekstu, ale konwertują go na UTF-8 podczas przechowywania na dysku.

Jedyną różnicą między a stringi a wstringjest typ danych przechowywanych znaków. Ciąg znaków przechowuje chars, których rozmiar gwarantuje co najmniej 8 bitów, więc możesz używać ciągów do przetwarzania np. Tekstu ASCII, ISO-8859-15 lub UTF-8. Standard nie mówi nic o zestawie znaków ani kodowaniu.

Praktycznie każdy kompilator używa zestawu znaków, którego pierwsze 128 znaków odpowiada ASCII. Dotyczy to również kompilatorów korzystających z kodowania UTF-8. Ważną rzeczą, o której należy pamiętać, używając ciągów znaków w UTF-8 lub innym kodowaniu o zmiennej długości, jest to, że wskaźniki i długości są mierzone w bajtach, a nie znakach.

Typ danych ciągu jest taki wchar_t, którego rozmiar nie jest zdefiniowany w standardzie, z tym wyjątkiem, że musi on być co najmniej tak duży jak znak, zwykle 16 bitów lub 32 bity. Wstring może być wykorzystywany do przetwarzania tekstu w kodowaniu szerokopasmowym zdefiniowanym przez implementację. Ponieważ kodowanie nie jest zdefiniowane w standardzie, konwersja między łańcuchami i łańcuchami nie jest prosta. Nie można również zakładać, że łańcuchy mają kodowanie o stałej długości.

Jeśli nie potrzebujesz obsługi wielu języków, możesz używać tylko zwykłych ciągów znaków. Z drugiej strony, jeśli piszesz aplikację graficzną, często zdarza się, że API obsługuje tylko szerokie znaki. Wtedy prawdopodobnie będziesz chciał użyć tych samych szerokich znaków podczas przetwarzania tekstu. Pamiętaj, że UTF-16 jest kodowaniem o zmiennej długości, co oznacza, że ​​nie możesz założyć, length()że zwrócisz liczbę znaków. Jeśli interfejs API używa kodowania o stałej długości, takiego jak UCS-2, przetwarzanie staje się łatwe. Konwersja szerokich znaków i UTF-8 jest trudna w przenośny sposób, ale z drugiej strony interfejs API interfejsu użytkownika prawdopodobnie obsługuje konwersję.

Seppo Enarvi
źródło
Tak więc parafrazując pierwszy akapit: Aplikacja wymagająca więcej niż 256 znaków musi używać kodowania wielobajtowego lub być może kodowania wielobajtowego.
Deduplicator,
Zasadniczo kodowania 16 i 32 bitowe, takie jak UCS-2 i UCS-4, nie są jednak nazywane kodowaniem wielobajtowym. Standard C ++ rozróżnia kodowanie wielobajtowe i szerokie znaki. Szeroka reprezentacja znaków wykorzystuje stałą liczbę (zwykle więcej niż 8) bitów na znak. Kodowania wykorzystujące pojedynczy bajt do kodowania najczęstszych znaków oraz wiele bajtów do kodowania reszty zestawu znaków, nazywane są kodowaniem wielobajtowym.
Seppo Enarvi
Przepraszamy, niechlujny komentarz. Powinien był powiedzieć kodowanie o zmiennej długości. UTF-16 jest kodowaniem o zmiennej długości, podobnie jak UTF-8. Udawanie, że to nie jest zły pomysł.
Deduplicator,
Trafne spostrzeżenie. Nie ma powodu, dla którego łańcuchy nie mogłyby być używane do przechowywania UTF-16 (zamiast UCS-2), ale utracono wygodę kodowania o stałej długości.
Seppo Enarvi,
2

Dobre pytanie! Myślę, że KODOWANIE DANYCH (czasami także CHARSET ) to MECHANIZM WYRAŻANIA PAMIĘCI w celu zapisania danych do pliku lub przesłania danych przez sieć, dlatego odpowiadam na to pytanie jako:

1. Kiedy powinienem używać std :: wstring zamiast std :: string?

Jeśli platforma programistyczna lub funkcja API jest jednobajtowa i chcemy przetwarzać lub analizować niektóre dane Unicode, np. Odczytane z pliku Windows'.REG lub sieciowego 2-bajtowego strumienia, powinniśmy zadeklarować zmienną std :: wstring, aby łatwo przetwarzaj je. np .: wstring ws = L "中国 a" (pamięć 6 oktetów: 0x4E2D 0x56FD 0x0061), możemy użyć ws [0], aby uzyskać znak „中” i ws [1], aby uzyskać znak „国”, a ws [2] do zdobądź znak „a” itp.

2. Czy std :: string może przechowywać cały zestaw znaków ASCII, w tym znaki specjalne?

Tak. Ale zauważ: amerykański ASCII oznacza, że ​​każdy oktet 0x00 ~ 0xFF oznacza jeden znak, w tym tekst do wydrukowania, taki jak „123abc & * _ &”, i powiedziałeś, że specjalny, najczęściej drukuj go jako „.” unikaj mylących edytorów lub terminali. A niektóre inne kraje rozszerzają swój własny zestaw znaków „ASCII”, np. Chiński, używają 2 oktetów, aby zastąpić jedną postać.

3.Czy std :: wstring jest obsługiwany przez wszystkie popularne kompilatory C ++?

Może lub głównie. Użyłem: VC ++ 6 i GCC 3.3, TAK

4. Czym dokładnie jest „szeroki charakter”?

szeroki znak oznacza najczęściej użycie 2 lub 4 oktetów do przechowywania znaków wszystkich krajów. 2 oktet UCS2 jest reprezentatywną próbką, a ponadto np. Angielski „a”, jego pamięć to 2 oktety 0x0061 (w porównaniu do ASCII „a pamięć to 1 oktet 0x61)

Leiyi.China
źródło
0

Jest tu kilka bardzo dobrych odpowiedzi, ale myślę, że mogę dodać kilka rzeczy dotyczących Windows / Visual Studio. To jest oparte na moich doświadczeniach z VS2015. W Linuksie w zasadzie odpowiedzią jest używanie std::stringwszędzie zakodowanych w UTF-8 . W systemie Windows / VS staje się bardziej złożony. Oto dlaczego. System Windows oczekuje, że ciągi przechowywane przy użyciu chars zostaną zakodowane przy użyciu lokalnej strony kodowej. Jest to prawie zawsze zestaw znaków ASCII, po którym następuje 128 innych znaków specjalnych, w zależności od lokalizacji. Pozwolę sobie tylko stwierdzić, że nie tylko przy korzystaniu z Windows API, istnieją trzy inne główne miejsca, w których te ciągi wchodzą w interakcje ze standardowym C ++. Są to literały łańcuchowe, dane wyjściowe do std::coutużywania <<i przekazywania nazwy pliku std::fstream.

Będę tutaj z góry, że jestem programistą, a nie specjalistą od języków. Rozumiem, że USC2 i UTF-16 nie są takie same, ale dla moich celów są wystarczająco blisko, aby były wymienne i używam ich jako takich tutaj. Nie jestem pewien, którego systemu Windows używa, ale generalnie nie muszę też wiedzieć. W tej odpowiedzi podałem UCS2, więc z góry przepraszam, jeśli zdenerwowałem kogoś swoją niewiedzą w tej sprawie i cieszę się, że mogę go zmienić, jeśli coś jest nie tak.

Literały łańcuchowe

Jeśli wpiszesz literały łańcuchowe zawierające tylko znaki, które mogą być reprezentowane przez twoją stronę kodową, VS zapisze je w twoim pliku z 1 bajtem na kodowanie znaków na podstawie twojej strony kodowej. Zauważ, że jeśli zmienisz stronę kodową lub przekażesz swoje źródło innemu programistowi, używając innej strony kodowej, to myślę (ale nie przetestowałem), że znak skończy się inaczej. Jeśli uruchomisz kod na komputerze przy użyciu innej strony kodowej, nie jestem pewien, czy znak również się zmieni.

Jeśli wpiszesz literały ciągów, które nie mogą być reprezentowane przez twoją stronę kodową, VS poprosi cię o zapisanie pliku jako Unicode. Plik zostanie następnie zakodowany jako UTF-8. Oznacza to, że wszystkie znaki spoza ASCII (w tym te, które znajdują się na stronie kodowej) będą reprezentowane przez 2 lub więcej bajtów. Oznacza to, że jeśli podasz swoje źródło komuś innemu, źródło będzie wyglądać tak samo. Jednak przed przekazaniem źródła do kompilatora VS konwertuje tekst zakodowany w UTF-8 na tekst zakodowany na stronie kodowej, a wszelkie znaki brakujące na stronie kodowej są zastępowane przez? .

Jedynym sposobem, aby zagwarantować prawidłowe odwzorowanie literału ciągów Unicode w VS, jest poprzedzenie literału ciągów literą Lszeroką. W takim przypadku VS skonwertuje tekst zakodowany w UTF-8 z pliku na UCS2. Następnie musisz przekazać dosłowny ciąg znaków do std::wstringkonstruktora lub przekonwertować go na utf-8 i umieścić w pliku std::string. Lub jeśli chcesz, możesz użyć funkcji Windows API do zakodowania go za pomocą strony kodowej, aby umieścić go w std::string, ale równie dobrze możesz nie użyć szerokiego ciągu literałów.

std :: cout

Podczas wysyłania do konsoli za pomocą <<możesz używać tylko std::string, std::wstringa nie, a tekst musi być zakodowany przy użyciu lokalnej strony kodowej. Jeśli tak std::wstring, musisz go przekonwertować za pomocą jednej z funkcji Windows API, a wszelkie znaki spoza twojej strony kodowej zostaną zastąpione przez ?(być może możesz zmienić znak, nie pamiętam).

std :: nazwy plików fstream

System operacyjny Windows używa UCS2 / UTF-16 dla swoich nazw plików, więc bez względu na stronę kodową możesz mieć pliki o dowolnym znaku Unicode. Oznacza to jednak, że aby uzyskać dostęp do plików ze znakami spoza strony kodowej lub tworzyć je, musisz ich użyć std::wstring. Nie ma innego wyjścia. Jest to rozszerzenie specyficzne dla Microsoft, std::fstreamwięc prawdopodobnie nie będzie się kompilowało w innych systemach. Jeśli używasz std :: string, możesz używać tylko nazw plików zawierających tylko znaki na stronie kodowej.

Twoje opcje

Jeśli pracujesz tylko w systemie Linux, prawdopodobnie nie zaszedłeś tak daleko. Po prostu użyj UTF-8 std::stringwszędzie.

Jeśli pracujesz tylko w systemie Windows, użyj UCS2 std::wstringwszędzie. Niektórzy puriści mogą powiedzieć, że używają UTF8, a następnie konwertują w razie potrzeby, ale po co zawracać sobie głowę kłopotami.

Jeśli jesteś wieloplatformowy, to szczerze mówiąc, to bałagan. Jeśli próbujesz używać UTF-8 wszędzie w systemie Windows, musisz być bardzo ostrożny z literałami ciągów i przesyłaniem ich do konsoli. Możesz łatwo zepsuć tam swoje łańcuchy. Jeśli używasz std::wstringwszędzie w systemie Linux, możesz nie mieć dostępu do szerokiej wersji std::fstream, więc musisz wykonać konwersję, ale nie ma ryzyka uszkodzenia. Więc osobiście uważam, że jest to lepsza opcja. Wielu by się nie zgodziło, ale nie jestem sam - jest to ścieżka podana na przykład przez wxWidgets.

Inną opcją może być wpisanieef unicodestringjak std::stringw Linuksie i std::wstringWindowsie i posiadanie makra o nazwie UNI (), które ma prefiks L w Windows i nic w Linuksie, a następnie kod

#include <fstream>
#include <string>
#include <iostream>
#include <Windows.h>

#ifdef _WIN32
typedef std::wstring unicodestring;
#define UNI(text) L ## text
std::string formatForConsole(const unicodestring &str)
{
    std::string result;
    //Call WideCharToMultiByte to do the conversion
    return result;
}
#else
typedef std::string unicodestring;
#define UNI(text) text
std::string formatForConsole(const unicodestring &str)
{
    return str;
}
#endif

int main()
{

    unicodestring fileName(UNI("fileName"));
    std::ofstream fout;
    fout.open(fileName);
    std::cout << formatForConsole(fileName) << std::endl;
    return 0;
}

myślę, że byłoby dobrze na każdej platformie.

Odpowiedzi

Aby odpowiedzieć na twoje pytania

1) Jeśli programujesz dla systemu Windows, to cały czas, jeśli masz wiele platform, to może cały czas, chyba że chcesz poradzić sobie z możliwymi problemami z korupcją w systemie Windows lub napisać kod z konkretną platformą #ifdefs aby obejść różnice, jeśli tylko używasz Linux wtedy nigdy.

2) Tak. Ponadto w systemie Linux możesz używać go również do wszystkich znaków Unicode. W systemie Windows możesz go używać tylko dla wszystkich kodów Unicode, jeśli wybierzesz ręczne kodowanie przy użyciu UTF-8. Ale interfejs API systemu Windows i standardowe klasy C ++ będą oczekiwaćstd::string kodowania przy użyciu lokalnej strony kodowej. Obejmuje to wszystkie znaki ASCII oraz kolejne 128 znaków, które zmieniają się w zależności od strony kodowej, z której komputer ma korzystać.

3) Uważam, że tak, ale jeśli nie, to jest to po prostu zwykła czcionka „std :: basic_string” używająca wchar_tzamiastchar

4) Szeroki znak to typ znaku, który jest większy niż standardowy 1-bajtowy chartyp. W systemie Windows jest to 2 bajty, w systemie Linux - 4 bajty.

Phil Rosenberg
źródło
1
Odnośnie „Jednak przed przekazaniem źródła do kompilatora VS konwertuje tekst zakodowany w UTF-8 na tekst zakodowany na stronie kodowej, a wszelkie znaki brakujące na stronie kodowej są zastępowane?”. -> Nie sądzę, że jest to prawdą, gdy kompilator korzysta z kodowania (użycia /utf-8) UTF-8 .
Roi Danton
Nie wiedziałem o tym jako opcji. Z tego linku docs.microsoft.com/en-us/cpp/build/reference/… wydaje się, że we właściwościach projektu nie ma pola wyboru, musisz dodać go jako dodatkową opcję wiersza poleceń. Dobre miejsce!
Phil Rosenberg,
-6

Kiedy NIE powinieneś używać szerokich znaków?

Kiedy piszesz kod przed rokiem 1990.

Oczywiście jestem przerzucany, ale tak naprawdę to jest teraz 21 wiek. 127 znaków już dawno przestało wystarczać. Tak, możesz użyć UTF8, ale po co męczyć się z bólami głowy?


źródło
16
@dave: Nie wiem, jaki ból głowy powoduje UTF-8, który jest większy niż w przypadku Widechars (UTF-16). w UTF-16 masz także znaki wieloznakowe.
Pavel Radzivilovsky
Problem polega na tym, że jeśli jesteś gdziekolwiek poza krajem anglojęzycznym, OUGHT możesz użyć wchar_t. Nie wspominając o tym, że niektóre alfabety mają o wiele więcej znaków, niż można zmieścić w bajcie. Byliśmy tam na DOS. Schizofrenia strony kodowej, nie, dziękuję, nie więcej ..
Swift - Piątek Pie
1
@Swift Problem wchar_tpolega na tym, że jego rozmiar i znaczenie zależą od systemu operacyjnego. Po prostu zamienia stare problemy na nowe. Natomiast a charjest charniezależnym od systemu operacyjnego (przynajmniej na podobnych platformach). Więc równie dobrze możemy po prostu użyć UTF-8, spakować wszystko w sekwencje chars i lamentować, że C ++ pozostawia nas całkowicie samodzielnie bez żadnych standardowych metod pomiaru, indeksowania, znajdowania itp. W takich sekwencjach.
underscore_d
1
@ Swift Wygląda na to, że masz go całkowicie do tyłu. wchar_tjest typem danych o stałej szerokości, więc tablica 10 wchar_tzawsze będzie zajmować sizeof(wchar_t) * 10bajty platformy. A UTF-16 jest kodowaniem o zmiennej szerokości, w którym znaki mogą składać się z 1 lub 2 16-bitowych punktów kodowych (i s / 16/8 / g dla UTF-8).
underscore_d
1
@SteveHollasch wchar_t reprezentacja ciągu w oknach koduje znaki większe niż FFFF jako aspecialna para zastępcza, inne przyjmowałyby tylko jeden element wchar_t. Tak więc ta reprezentacja nie będzie zgodna z reprezentacją utworzoną przez kompilator gnu (gdzie wszystkie znaki mniejsze niż FFFF będą miały przed sobą zero słów). O tym, co jest przechowywane w wchar_t, decyduje programista i kompilator, a nie jakakolwiek umowa
Swift - Friday Pie