std :: wstring VS std :: рядок


740

Я не в змозі зрозуміти відмінності між std::stringі std::wstring. Я знаю, що wstringпідтримує широкі символи, такі як символи Unicode. У мене є такі питання:

  1. Коли я повинен використовувати std::wstringбільше std::string?
  2. Може std::stringвмістити весь набір символів ASCII, включаючи спеціальні символи?
  3. Чи std::wstringпідтримується всіма популярними компіляторами C ++?
  4. Що таке « широкий характер »?

10
Набір символів ASCII не має великої кількості "спеціальних" символів, найекзотичніше, мабуть, `(зворотне котирування). std :: string може вміщувати близько 0,025% усіх символів Unicode (зазвичай, 8 бітових знаків)
MSalters

3
Хорошу інформацію про широких персонажів та тип їх використання можна знайти тут: programmers.stackexchange.com/questions/102205/…
Ярів

14
Ну, а оскільки ми в 2012 році, написано utf8everywhere.org . Він в значній мірі відповідає на всі питання щодо прав та несправедливості з C ++ / Windows.
Павло Радзівіловський

42
@MSalters: std :: string може містити 100% усіх символів Unicode, навіть якщо CHAR_BIT дорівнює 8. Це залежить від кодування std :: string, яке може бути UTF-8 на системному рівні (як майже скрізь, крім Windows ) або на рівні заявки. Рідне вузьке кодування не підтримує Unicode? Немає проблем, просто не використовуйте, замість цього використовуйте UTF-8.
Яків Галка

8
Чудове читання на цю тему: utf8everywhere.org
Тимофій Шилдс

Відповіді:


990

string? wstring?

std::stringє basic_stringшаблоном на a charі std::wstringна a wchar_t.

char vs. wchar_t

charповинен містити символ, як правило, 8-бітний символ.
wchar_tповинен мати широкий характер, і тоді, справи стають складними: в
Linux це a wchar_t4 байти, а в Windows - 2 байти.

Що тоді з Unicode ?

Проблема полягає в тому, що ні один, charні wchar_tбезпосередньо не пов'язаний з unicode.

У Linux?

Візьмемо ОС Linux: Моя система Ubuntu вже відома unicode. Коли я працюю з рядком char, він кодується в UTF-8 (тобто рядок символів Unicode). Наступний код:

#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;
}

виводить наступний текст:

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

Ви побачите, що текст "olé" charсправді побудований чотирма знаками : 110, 108, 195 та 169 (не рахуючи прорахунку нуля). (Я дам вам вивчити wchar_tкод як вправу)

Отже, працюючи з charLinux, зазвичай, ви повинні використовувати Unicode, навіть не підозрюючи про це. І як це std::stringпрацює char, так std::stringуже готовий унікод.

Зауважте, що std::stringподібно до API рядка C, вважати, що рядок "olé" має 4 символи, а не три. Тому вам слід бути обережними під час обрізання / гри з символами unicode, оскільки деяка комбінація символів заборонена в UTF-8.

У Windows?

У Windows це дещо інакше. Win32 повинен був підтримувати безліч додатків, що працюють з charрізними шаблонами / кодовами, створеними у всьому світі, до появи Unicode.

Тож їхнє рішення було цікавим: якщо програма працює char, тоді рядки символів кодуються / друкуються / відображаються на ярликах графічного інтерфейсу, використовуючи локальну схему / кодову сторінку на машині. Наприклад, "olé" був би "olé" у французькій локалізованій Windows, але був би дещо іншим на локалізованій кирилицею Windows ("olй", якщо ви використовуєте Windows-1251 ). Таким чином, "історичні додатки" зазвичай все ще працюватимуть так само, як і раніше.

Для додатків на основі Unicode Windows використовує wchar_tширину в 2 байти і кодується в UTF-16 , який кодується Unicode на 2-байтових символах (або, принаймні, в основному сумісному UCS-2, що майже є те саме, IIRC).

Додатки , що використовують charназиваються «мультибайтних» (тому що кожен символ складається з одного або більше charами), в той час як додатки , що використовують wchar_tназиваються «WideChar» (тому що кожен символ складається з одного або двох wchar_tДив. MultiByteToWideChar і WideCharToMultiByte Win32 перетворення API для отримання додаткової інформації.

Таким чином, якщо ви працюєте в Windows, ви дуже хочете використовувати wchar_t(якщо ви не використовуєте рамки, які приховують це, як GTK + або QT ...). Справа в тому, що за лаштунками Windows працює з wchar_tрядками, тому навіть історичні додатки charконвертують свої рядки wchar_tпри використанні типу API SetWindowText()(функція API низького рівня для встановлення мітки на графічному інтерфейсі Win32).

Проблеми з пам’яттю?

UTF-32 становить 4 байти на символи, тому багато чого не можна додати, якщо тільки те, що текст UTF-8 та UTF-16 завжди використовуватиме менше або такий же об'єм пам'яті, ніж текст UTF-32 (і зазвичай менше ).

Якщо виникла проблема з пам'яттю, тоді ви повинні знати, ніж для більшості західних мов, текст UTF-8 використовуватиме менше пам'яті, ніж той самий UTF-16.

Однак для інших мов (китайської, японської тощо) використовувана пам'ять буде однаковою, або трохи більшою для UTF-8, ніж для UTF-16.

Загалом, UTF-16 в основному використовуватиме 2 та випадково по 4 байти на символи (якщо ви не маєте справу з якимось езотеричним мовою гліфів (Klingon? Elvish?), Тоді як UTF-8 витратить від 1 до 4 байт.

Див. Http://en.wikipedia.org/wiki/UTF-8#Compared_to_UTF-16 для отримання додаткової інформації.

Висновок

  1. Коли я повинен використовувати std :: wstring над std :: string?

    У Linux? Майже ніколи (§).
    У Windows? Майже завжди (§).
    На кросплатформенний код? Залежить від вашого інструментарію ...

    (§): якщо ви не використовуєте інструментарій / рамку, якщо сказати інше

  2. Чи може std::stringмістити весь набір символів ASCII, включаючи спеціальні символи?

    Примітка: A std::stringпідходить для зберігання "двійкового" буфера, де а std::wstringнемає!

    У Linux? Так.
    У Windows? Доступні лише спеціальні символи для поточної локальної програми користувача Windows.

    Редагувати (Після коментаря Йоганна Герелла ):
    a std::stringбуде достатньо для обробки всіх charрядків, що базуються (кожен charмає число від 0 до 255). Але:

    1. ASCII повинен переходити від 0 до 127. Вищі chars НЕ ASCII.
    2. a charвід 0 до 127 буде проведено правильно
    3. a charвід 128 до 255 матиме означення залежно від кодування (unicode, non-unicode тощо), але він зможе вмістити всі гліфи Unicode, доки вони закодовані в UTF-8.
  3. Чи std::wstringпідтримується майже всіма популярними компіляторами C ++?

    Переважно, за винятком компіляторів на основі GCC, які переносяться на Windows.
    Він працює на моєму g ++ 4.3.2 (під Linux), і я використовував Unicode API на Win32 з Visual C ++ 6.

  4. Що таке саме широкий характер?

    На C / C ++ це написаний тип символів, wchar_tякий більший за простий charтип символів. Він повинен використовуватися для розміщення всередині символів, показники яких (наприклад, гліфи Unicode) перевищують 255 (або 127, залежно від ...).


4
@gnud: Можливо, wchar_t повинно було вистачити для обробки всіх символів UCS-2 (більшість символів UTF-16) до появи UTF-16 ... Або, можливо, у Microsoft були інші пріоритети, ніж POSIX, наприклад, легкий доступ до Unicode без зміни кодованого використання char на Win32.
paercebal

4
@Sorin Sbarnea: UTF-8 міг би займати 1-6 байт, але, мабуть, стандарт обмежує його на 1-4. Див. En.wikipedia.org/wiki/UTF8#Description для отримання додаткової інформації.
paercebal

8
Хоча ці приклади дають різні результати в Linux та Windows, програма C ++ містить поведінку, визначену щодо того, чи olèкодовано це як UTF-8 чи ні. Більш того, по цій причині ви не можете спочатку потік wchar_t *в std::coutтому , що типи несумісні призводить до погано сформованої програмі , і це не має нічого спільного з використанням кодувань. Варто зазначити, що ви використовуєте std::stringчи std::wstringзалежить від власних переваг кодування, а не платформи, особливо якщо ви хочете, щоб ваш код був переносним.
Джон Лейдегрен

14
Windows насправді використовує UTF-16 і вже досить довгий час у старих версіях Windows використовується UCS-2, але це вже не так. Єдине моє питання тут - висновок, який std::wstringслід використовувати в Windows, оскільки він краще підходить для API Unicode Windows, який, на мою думку, є помилковим. Якщо ви турбуєтесь лише про те, щоб закликати API Unicode Windows, а не марширувати рядки, тоді обов'язково, але я не купую це як загальний випадок.
Джон Лейдегрен

15
@ Джон Лейдегрен:: If your only concern was calling into the Unicode Windows API and not marshalling strings then sureТоді, ми згодні. Я кодую C ++, а не JavaScript. Уникнення марного маршангу або будь-якої іншої потенційно дорогої обробки під час виконання, коли це можна зробити під час компіляції, лежить в основі цієї мови. Кодування проти WinAPI та використання std::string- це лише невиправдане витрачання ресурсів виконання. Ви вважаєте це помилковим, і це нормально, як це ваша точка зору. Моє власне те, що я не буду писати код з песимізацією в Windows тільки тому, що це краще виглядає з боку Linux.
paercebal

71

Я рекомендую уникати std::wstringв Windows чи в інших місцях, за винятком випадків, коли цього вимагає інтерфейс або десь поблизу викликів API Windows та відповідних перетворень кодування як синтаксичного цукру.

Моя думка узагальнена в http://utf8everywhere.org, співавтором якого я є.

Якщо ваша програма не орієнтована на API-виклик, наприклад, головним чином програму UI, пропонується зберігати рядки Unicode в std :: string та кодуються в UTF-8, виконуючи перетворення поблизу викликів API. Переваги, викладені у статті, переважають за очевидний роздратування конверсії, особливо у складних програмах. Це вдвічі більше для розвитку багатоплатформ і бібліотеки.

А тепер, відповідаючи на ваші запитання:

  1. Кілька слабких причин. Він існує з історичних причин, де, як вважали, широкі версії є правильним способом підтримки Unicode. Зараз він використовується для інтерфейсу API, які віддають перевагу рядкам UTF-16. Я використовую їх лише в безпосередній близькості від таких дзвінків API.
  2. Це не має нічого спільного з std :: string. Він може містити будь-яке кодування, яке ви вкладете в нього. Питання лише в тому, як ви ставитесь до його вмісту. Моя рекомендація - UTF-8, тому вона зможе правильно утримувати всі символи Unicode. Це звична практика в Linux, але я думаю, що це повинні робити і програми Windows.
  3. Ні.
  4. Широкий характер - заплутане ім’я. У перші дні Unicode існувало вірування, що персонаж може бути закодований у два байти, звідси і назва. Сьогодні воно розшифровується як "будь-яка частина символу, яка має два байти". UTF-16 розглядається як послідовність таких байтових пар (також Широкі символи). Символ UTF-16 займає одну або дві пари.

37

Отже, кожен читач, який зараз тут, повинен мати чітке розуміння фактів, ситуації. Якщо ні, то ви повинні прочитати надзвичайно вичерпну відповідь paercebal [btw: спасибі!].

Мій прагматичний висновок приголомшливо простий: все, що C ++ (і STL) "кодування символів" істотно зламане і марне. Звинувачуйте це в Microsoft чи ні, це все одно не допоможе.

Моє рішення, після поглибленого розслідування, сильних розладів та наслідків цього досвіду:

  1. прийміть, що ви повинні самостійно нести відповідальність за кодування та перетворення матеріалів (і ви побачите, що значна частина цього є досить тривіальною)

  2. використовувати std :: string для будь-яких рядків, закодованих UTF-8 (лише a typedef std::string UTF8String)

  3. визнай, що такий об'єкт UTF8String - це просто німий, але дешевий контейнер. Ніколи не отримуйте доступу та / або не маніпулюйте символами безпосередньо (не шукайте, замінюйте тощо). Ви могли б, але ви просто просто дуже, не хочете витрачати свій час на написання алгоритмів маніпуляції текстом для багатобайтових рядків! Навіть якщо інші люди вже робили такі дурні речі, не робіть цього! Нехай так буде! (Ну, є сценарії, де це має сенс ... просто використовуйте для цього бібліотеку ICU).

  4. використовувати std :: wstring для кодованих рядків UCS-2 ( typedef std::wstring UCS2String) - це компроміс і поступка безладу, який запровадив API WIN32). UCS-2 достатньо для більшості з нас (докладніше про це пізніше ...).

  5. використовувати екземпляри UCS2String, коли потрібен доступ до символу (читати, маніпулювати тощо). Будь-яка обробка на основі символів повинна здійснюватися в багатоканальному представленні NON. Це просто, швидко, легко.

  6. додати дві функції утиліти для перетворення назад і назад між UTF-8 та UCS-2:

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

Конверсії прості, Google має допомогти тут ...

Це воно. Використовуйте UTF8String там, де пам'ять дорогоцінна і для всіх UTF-8 вводу / виводу. Використовуйте UCS2String там, де рядок повинен бути проаналізований та / або маніпульований. Ви можете будь-коли конвертувати між цими двома представленнями.

Альтернативи та вдосконалення

  • перетворення з & в однобайтові кодування символів (наприклад, ISO-8859-1) можна здійснити за допомогою простих таблиць перекладу, наприклад, const wchar_t tt_iso88951[256] = {0,1,2,...};і відповідного коду для перетворення в & з UCS2.

  • якщо UCS-2 недостатньо, переключіться на UCS-4 ( typedef std::basic_string<uint32_t> UCS2String)

ICU або інші бібліотеки Unicode?

Для вдосконалених матеріалів.


Данг, недобре знати, що підтримки рідного Unicode немає.
Mihai Danila

@Frunsi, мені цікаво дізнатись, чи ти пробував Glib :: ustring, і якщо так, то що ти думаєш?
Caroline Beltran

@CarolineBeltran: Я знаю Glib, але я його ніколи не використовував, і, мабуть, ніколи навіть не буду його використовувати, тому що він досить обмежений досить неспецифічною цільовою платформою (unixoid системи ...). Його порт Windows заснований на зовнішньому шарі win2unix, і там IMHO взагалі не є рівнем сумісності з OSX. Весь цей матеріал чітко спрямований у неправильний напрямок, принаймні для мого коду (на цьому рівні арки ...) ;-) Отже,
Glib

9
Пошук, заміна тощо - це чудово працює на рядках UTF-8 (частина послідовності байтів, що представляє символ, ніколи не може бути неправильно інтерпретована як інший символ). Насправді UTF-16 і UTF-32 зовсім не полегшують це: всі три кодування на практиці є багатобайтовими кодуваннями, тому що сприйнятий користувачем символ (кластер графеми) може бути довгим будь-якою кількістю кодових точок унікоду! Прагматичне рішення - використовувати UTF-8 для всього і конвертувати в UTF-16 лише під час роботи з Windows API.
Даніель

5
@Frunsi: пошук і заміна працює так само добре, як і з UTF-8, як і з UTF-32. Саме тому, що при правильній обробці тексту Unicode потрібно мати справу з "символами" з декількома кодовими точками, то використання кодування змінної довжини, наприклад UTF-8, не ускладнює обробку рядків. Тому просто використовуйте UTF-8 скрізь. Нормальні функції рядка C добре працюватимуть на UTF-8 (і відповідають порядковим порівнянням рядка Unicode), і якщо вам потрібне щось більш зрозуміле для мови, вам доведеться все одно зателефонувати в бібліотеку Unicode, UTF-16/32 не можу тебе врятувати від цього.
Даніель

25
  1. Коли ви хочете, щоб у вашому рядку зберігалися широкі символи. wideзалежить від реалізації. Візуальний C ++ за замовчуванням до 16 біт, якщо я пам'ятаю правильно, тоді як GCC за замовчуванням залежно від цілі. Тут довгі 32 біти. Зверніть увагу, що wchar_t (широкий тип символів) не має нічого спільного з unicode. Це просто гарантовано, що він може зберігати всіх членів найбільшого набору символів, які реалізація підтримує своїми локалями, і принаймні до тих пір, як char. Ви також можете добре зберігати рядки Unicode, std::stringвикористовуючи utf-8кодування. Але це не зрозуміє значення точок коду unicode. Томуstr.size()не дасть вам кількості логічних символів у вашому рядку, а лише кількість символів char або wchar_t, що зберігаються у цій рядку / wstring. З цієї причини люди із обгорткою gtk / glib C ++ розробили Glib::ustringклас, який може працювати з utf-8.

    Якщо ваш wchar_t довжиною 32 біти, ви можете використовувати utf-32як кодування unicode, а ви можете зберігати і обробляти рядки unicode, використовуючи фіксовану (utf-32 - фіксовану довжину) кодування. Це означає , що ваш wstring в s.size()функції буде потім повернути потрібну кількість wchar_t елементів і логічних символів.

  2. Так, char завжди має принаймні 8 біт, а це означає, що він може зберігати всі значення ASCII.
  3. Так, всі основні компілятори підтримують це.

Мені цікаво №2. Я думав, що 7 бітів також будуть технічно дійсними? Або потрібно мати можливість зберігати що-небудь раніше 7-бітових символів ASCII?
джельф

1
так, джальф. c89 визначає мінімальні діапазони для основних типів у своїй документації limit.h (для неподписаних знаків, це 0..255 хв), і чисту двійкову систему для цілих типів. випливає, що char, неподписаний char та підписаний char мають мінімальну довжину бітів 8. c ++ успадковує ці правила.
Йоханнес Шауб - ліб

15
"Це означає, що функція s.size () вашого wstring поверне потрібну кількість елементів wchar_t та логічних символів." Це не зовсім точно, навіть для Unicode. Точніше було б сказати кодову точку, ніж "логічний символ", навіть в UTF-32 даний символ може складатися з декількох точок коду.
Логан Капальдо

Ви, по суті, говорите, що C ++ не має вбудованої підтримки для набору символів Unicode?
Mihai Danila

1
"Але це не зрозуміє значення точок коду unicode." На вікнах це не робить std::wstring.
Дедупликатор

5

Я часто використовую std :: string, щоб утримувати символи utf-8 без проблем. Я щиро рекомендую робити це під час взаємодії з API, які також використовують utf-8 як рідний тип рядка.

Наприклад, я використовую utf-8 при взаємодії свого коду з інтерпретатором Tcl.

Основний застереження - це довжина рядка std ::, це більше не число символів у рядку.


1
Хуан: Ви маєте на увазі, що std :: string може містити всі символи unicode, але довжина буде повідомляти неправильно? Чи є причина, що вона повідомляє про неправильну довжину?

3
При використанні кодування utf-8 один символ Unicode може складатися з декількох байтів. Ось чому кодування utf-8 є меншим при використанні в основному символів зі стандартного набору ascii. Вам потрібно скористатися спеціальними функціями (або прокатати власну), щоб виміряти кількість символів Unicode.

2
(Для конкретних Windows) Більшість функцій очікують, що рядок, що використовує байти, є ASCII, а 2 байти - Unicode, старіші версії MBCS. Що означає, що якщо ви зберігаєте 8-бітовий однокодовий код, вам доведеться перетворити в 16-бітний однокодовий код, щоб викликати стандартну функцію Windows (якщо ви не використовуєте лише ASCII-частину).
Грег Дом’ян

2
Не тільки, що std :: string повідомляє про довжину неправильно, але й виведе неправильний рядок. Якщо якийсь символ Unicode представлений в UTF-8 у вигляді декількох байтів, який std :: string вважає власними символами, то, звичайно, підпрограми маніпулювання std :: string, ймовірно, виведуть кілька дивних символів, які є результатом неправильного тлумачення одного правильний характер.
Михай Даніла

2
Я пропоную змінити відповідь, щоб вказати, що рядки слід вважати лише контейнерами байтів, і, якщо байти - це кодування Unicode (UTF-8, UTF-16, ...), то вам слід використовувати конкретні бібліотеки, які розуміють що. Стандартні API на основі рядків (довжина, підряд та ін.) Будуть погано виходити з багатобайтових символів. Якщо це оновлення буде зроблено, я вилучу свій нижчий запис.
Михай Даніла

4
  1. Коли ви хочете зберегти "широкі" (Unicode) символи.
  2. Так: з них 255 (без 0).
  3. Так.
  4. Ось вступна стаття: http://www.joelonsoftware.com/articles/Unicode.html

11
std :: string може містити 0 просто добре (будьте обережні, якщо ви зателефонуєте методу c_str ())
пан Фооз

3
І, строго кажучи, char не гарантує 8 біт. :) Ваше посилання в №4 є обов'язковим для читання, але я не думаю, що воно відповідає на питання. Широкий характер суворо не має нічого спільного з unicode. Це просто ширший характер. (Наскільки ширше залежить від ОС, але, як правило, 16 або 32 біт)
jalf

2
  1. коли ви хочете використовувати рядки Unicode, а не просто ascii, що корисно для інтернаціоналізації
  2. так, але це не добре грає з 0
  3. не знаю ні про що, що не
  4. Широкий символ - це специфічний для компілятора спосіб обробки фіксованого представлення довжини символу unicode, для MSVC це 2-байтний символ, для gcc я розумію, що це 4 байти. та +1 для http://www.joelonsoftware.com/articles/Unicode.html

1
2. Строка :: std :: може добре містити символ NULL. Він також може містити utf-8 і широкі символи.

@Juan: Це знову заплутало мене. Якщо std :: string може зберігати символи unicode, що особливого з std :: wstring?

1
@Appu: std :: string може містити символи UTF-8 unicode. Існує ряд стандартів Unicode, орієнтованих на різну ширину символів. UTf8 шириною 8 біт. Також є UTF-16 і UTF-32 на 16 і 32 біт завширшки відповідно
Грег Д

З std :: wstring. Кожен символ unicode може бути одним wchar_t при використанні кодувань фіксованої довжини. Наприклад, якщо ви вирішили використовувати Joel на програмному підході як посилання на Грега. Тоді довжина введення - це рівно кількість символів Unicode в рядку. Але це займає більше місця

Я не сказав, що він не може утримувати 0 '\ 0', і те, що я мав на увазі, не грає добре, це те, що деякі методи можуть не дати очікуваного результату, що містить усі дані введення. Тож суворі голоси проти.
Грег Дом’ян

2

Програми, які не задовольняються лише 256 різними символами, мають можливість використовувати широкі символи (більше 8 біт) або кодування змінної довжини (багатобайтове кодування в терміні C ++), наприклад UTF-8. Широким символам зазвичай потрібно більше місця, ніж кодування змінної довжини, але їх швидше обробляти. Багатомовні програми, які обробляють велику кількість тексту, зазвичай використовують широкі символи при обробці тексту, але перетворюють його на UTF-8 при зберіганні на диску.

Єдина різниця між a stringі a wstring- це тип даних символів, які вони зберігають. Рядок зберігає chars, розмір якого гарантується принаймні 8 біт, тому ви можете використовувати рядки для обробки, наприклад, ASCII, ISO-8859-15 або UTF-8 тексту. Стандарт не говорить нічого про набір символів або кодування.

Практично кожен компілятор використовує набір символів, перші 128 символів яких відповідають ASCII. Це також стосується компіляторів, які використовують кодування UTF-8. Важливо, що слід пам'ятати при використанні рядків у UTF-8 або іншому кодуванні змінної довжини, - це те, що індекси та довжини вимірюються в байтах, а не символами.

Тип даних wstring є wchar_t розмір, розмір якого не визначений у стандарті, за винятком того, що він повинен бути принаймні таким же великим, як діаграма, як правило, 16 біт або 32 біт. wstring може використовуватися для обробки тексту при виконанні певного широкого кодування кодування. Оскільки кодування не визначено у стандарті, перетворення між рядками та wstrings не є простим. Не можна вважати, що в wstrings також є кодування фіксованої довжини.

Якщо вам не потрібна багатомовна підтримка, можливо, ви будете добре використовувати лише звичайні рядки. З іншого боку, якщо ви пишете графічну заявку, часто трапляється так, що API підтримує лише широкі символи. Тоді, ймовірно, ви хочете використовувати однакові широкі символи при обробці тексту. Майте на увазі, що UTF-16 - це кодування змінної довжини, що означає, що ви не можете припустити length()повернення кількості символів. Якщо API використовує кодування фіксованої довжини, наприклад UCS-2, обробка стає простою. Перетворення між широкими символами та UTF-8 складно здійснити портативно, але знову ж таки, API вашого інтерфейсу користувача, ймовірно, підтримує перетворення.


Отже, перефразовуючи перший абзац: Програмі, яка потребує більше 256 символів, потрібно використовувати багатобайтове кодування або можливо_мультибайтове кодування.
Дедуплікатор

Як правило, 16 і 32 бітні кодування, такі як UCS-2 і UCS-4, не називаються багатобайтовими кодуваннями. Стандарт C ++ розрізняє багатобайтові кодування та широкі символи. Широке представлення символів використовує фіксовану кількість (загалом більше 8) біт на символ. Кодування, які використовують один байт для кодування найпоширеніших символів, і декілька байтів для кодування решти набору символів, називаються багатобайтовими кодуваннями.
Seppo Enarvi

Вибачте, неохайний коментар. Повинно було сказати кодування змінної довжини. UTF-16 - кодування змінної довжини, як і UTF-8. Прикидатися це не є поганою ідеєю.
Дедуплікатор

Це хороший момент. Немає причини, чому wstrings не можна було б використовувати для зберігання UTF-16 (замість UCS-2), але тоді втрачається зручність кодування фіксованої довжини.
Seppo Enarvi

2

Гарне запитання! Я думаю , що кодовані дані (іноді CHARSET також бере участь) є ПАМ'ЯТЬ експресії МЕХАНІЗМ для того , щоб зберегти дані в файл даних або передачі по мережі, тому я відповім на це питання , як:

1. Коли я повинен використовувати std :: wstring над std :: string?

Якщо платформа програмування або функція API є однобайтовою, і ми хочемо обробити або проаналізувати деякі дані Unicode, наприклад, прочитати з файлу Windows'.REG або мережевий 2-байтний потік, ми повинні легко оголосити змінну std :: wstring легко обробляти їх. наприклад: wstring ws = L "中国 a" (6 октетів пам'яті: 0x4E2D 0x56FD 0x0061), ми можемо використовувати ws [0] для отримання символів '中' і ws [1], щоб отримати символи '国' і ws [2] для отримати характер 'а' тощо.

2. Чи може std :: string утримувати весь набір символів ASCII, включаючи спеціальні символи?

Так. Але зауважте: американський ASCII означає, що кожен октет 0x00 ~ 0xFF означає один символ, включаючи текст для друку, наприклад "123abc & * _ &", і ви сказали спеціальний, в основному друкуйте його як "." не плутайте редакторів чи терміналів. І деякі інші країни розширюють власну шаблону "ASCII", наприклад китайці використовують два октети, щоб позначитись на одному символі.

3.Is std :: wstring підтримується всіма популярними компіляторами C ++?

Може, або здебільшого. Я використав: VC ++ 6 та GCC 3.3, ТАК

4. Що таке «широкий характер»?

широкий символ в основному вказує на використання 2 октетів або 4 октетів для утримання символів усіх країн. 2 октет UCS2 є репрезентативним зразком, і далі, наприклад, англійською мовою 'a', її пам'ять становить 2 октети 0x0061 (проти ASCII '' пам'ять a - 1 октет 0x61)


0

Тут є кілька дуже хороших відповідей, але я думаю, що я можу додати кілька речей щодо Windows / Visual Studio. Це засновано на моєму досвіді роботи з VS2015. В Linux в основному відповідь полягає в тому, щоб використовувати UTF-8, закодовані std::stringскрізь. У Windows / VS він стає складнішим. Ось чому. Windows очікує, що рядки, збережені за допомогою chars, будуть кодовані за допомогою локальної кодової сторінки. Це майже завжди набір символів ASCII, а за ним - 128 інших спеціальних символів, залежно від вашого місцезнаходження. Дозвольте лише зазначити, що це не лише при використанні API Windows, є три інші основні місця, де ці рядки взаємодіють зі стандартним C ++. Це рядкові літерали, вихідні дані з std::coutвикористанням <<та передача імені файлу std::fstream.

Я буду тут наперед, що я програміст, а не мовний фахівець. Я розумію, що USC2 та UTF-16 не однакові, але для моїх цілей вони досить близькі, щоб бути взаємозамінними, і я їх тут використовую як таких. Я насправді не впевнений, яку Windows використовує, але мені взагалі не потрібно знати. У цій відповіді я заявив про UCS2, тому заздалегідь вибачте, якщо я засмутив когось своїм незнанням цього питання, і я радий змінити його, якщо у мене щось не так.

Строкові літерали

Якщо ви вводите рядкові літерали, що містять лише символи, які можуть бути представлені вашою кодовою сторінкою, тоді VS зберігає їх у вашому файлі з 1 байтом на кодування символів на основі вашої кодової сторінки. Зауважте, що якщо ви зміните свою кодову сторінку або надаєте джерело іншому розробнику, використовуючи іншу кодову сторінку, то я думаю (але не перевіряв), що символ у кінцевому підсумку буде іншим. Якщо ви запускаєте свій код на комп’ютері, використовуючи іншу кодову сторінку, то я не впевнений, чи зміниться також символ.

Якщо ви введете будь-які рядкові літерали, які не можуть бути представлені вашою кодовою сторінкою, тоді VS попросить вас зберегти файл як Unicode. Потім файл буде кодуватися як UTF-8. Це означає, що всі символи, що не належать до ASCII (включаючи ті, що знаходяться на вашій кодовій сторінці) будуть представлені 2 або більше байтами. Це означає, що якщо ви дасте джерело комусь іншому, джерело буде виглядати так само. Однак, перш ніж передавати джерело компілятору, VS перетворює закодований текст UTF-8 у закодований текст кодової сторінки, а будь-які символи, відсутніх на кодовій сторінці, замінюються ?.

Єдиний спосіб гарантувати правильне представлення літерального рядка Unicode у VS - це передувати рядковому літралу, Lзробивши його широким літеральним рядком. У цьому випадку VS перетворить закодований текст UTF-8 з файлу в UCS2. Потім вам потрібно передати цей рядковий буквал в std::wstringконструктор або вам потрібно перетворити його в utf-8 і поставити його в a std::string. Або якщо ви хочете, ви можете використовувати функції API Windows, щоб кодувати її за допомогою кодової сторінки, щоб поставити її вstd::string , але тоді ви, можливо, також не використали широкий рядковий літерал.

std :: cout

При виведенні на консоль за допомогою <<ви можете використовувати тільки std::string, а не, std::wstringа текст повинен бути закодований за допомогою вашої локальної кодової сторінки. Якщо у вас є, std::wstringви повинні перетворити його за допомогою однієї з функцій API Windows, а будь-які символи, які не знаходяться на кодовій сторінці, замінюються на? (можливо, ви можете змінити символ, я не можу згадати).

std :: файлові файли fstream

ОС Windows використовує UCS2 / UTF-16 для своїх імен файлів, тому незалежно від кодової сторінки, ви можете мати файли з будь-яким символом Unicode. Але це означає, що для доступу або створення файлів з символами, які не знаходяться на кодовій сторінці, ви повинні використовувати std::wstring. Іншого шляху немає. Це розширення для Microsoft, яке, std::fstreamможливо, не компілюється в інших системах. Якщо ви використовуєте std :: string, ви можете використовувати лише назви файлів, які містять лише символи на вашій кодовій сторінці.

Ваші варіанти

Якщо ви просто працюєте на Linux, то, ймовірно, цього не досягли. Просто використовуйте UTF-8 std::stringскрізь.

Якщо ви просто працюєте в Windows, просто використовуйте UCS2 std::wstringскрізь. Деякі пуристи можуть сказати, що використовувати UTF8, а потім конвертувати, коли це потрібно, але навіщо турбуватися із клопотами.

Якщо ви є крос-платформою, то це буде безладдя відверто. Якщо ви намагаєтесь використовувати UTF-8 скрізь у Windows, то вам потрібно бути дуже обережними зі своїми рядковими літералами та виводити на консоль. Ви можете легко зіпсувати там свої струни. Якщо ви використовуєте std::wstringвсюди в Linux, ви, можливо, не маєте доступу до широкої версії std::fstream, тому вам доведеться зробити конверсію, але немає ризику пошкодження. Тож особисто я думаю, що це кращий варіант. Багато хто не погодиться, але я не самотній - це, наприклад, шлях wxWidgets.

Іншим варіантом може бути введенняdedef, unicodestringяк std::stringу Linux та std::wstringWindows, і мати макрос під назвою UNI (), який має префікси L в Windows, а нічого в Linux, а потім код

#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;
}

Було б добре на будь-якій платформі, я думаю.

Відповіді

Отже, щоб відповісти на ваші запитання

1) Якщо ви програмуєте для Windows, то весь час, якщо крос-платформа, то, можливо, весь час, якщо ви не хочете мати справу з можливими корупційними проблемами в Windows або написати якийсь код з платформою, специфічною #ifdefsдля вирішення відмінностей, якщо просто використовувати Тоді Linux ніколи.

2) Так. Крім Linux, ви можете використовувати його і для всіх Unicode. У Windows ви можете використовувати його для всіх unicode, лише якщо ви вирішите кодувати вручну за допомогою UTF-8. Але Windows API і стандартні класи C ++ очікують, що std::stringвони будуть закодовані за допомогою локальної кодової сторінки. Сюди входять усі ASCII та ще 128 символів, які змінюються залежно від кодової сторінки, на яку налаштовано ваш комп'ютер.

3) Я так вважаю, але якщо ні, то це просто простий typedef 'std :: basic_string', який використовує wchar_tзамістьchar

4) Широкий символ - це тип символів, який більший за стандартний charтип на 1 байт . У Windows це 2 байти, в Linux - 4 байти.


1
Що стосується "Однак, перш ніж передавати джерело компілятору, VS перетворює закодований текст UTF-8 в закодований текст кодової сторінки, а будь-які символи, відсутніх на сторінці коду, замінюються на?". -> Я не думаю, що це правда, коли компілятор використовує кодування UTF-8 (використання /utf-8).
Рой Дантон

Я не усвідомлював це як варіант. З цього посилання docs.microsoft.com/en-us/cpp/build/reference/…, здається, немає властивостей для вибору у властивостях проекту, ви повинні додати його як додатковий параметр командного рядка. Гарне місце!
Філ Розенберг

-2

1) Як згадував Грег, wstring корисний для інтернаціоналізації, саме тоді ви будете випускати свій продукт іншими мовами, ніж англійською

4) Перевірте це на наявність широкого характеру http://en.wikipedia.org/wiki/Wide_character


-6

Коли НЕ слід використовувати широкоформатні символи?

Коли ви пишете код до 1990 року.

Очевидно, я перевертаюсь, але насправді це зараз 21 століття. 127 символів давно перестали бути достатніми. Так, ви можете використовувати UTF8, але чому турбувати головні болі?


16
@dave: Я не знаю, який головний біль створює UTF-8, який більший, ніж у Widechars (UTF-16). в UTF-16 у вас також є багатозначні символи.
Павло Радзівіловський

Проблема полягає в тому, що якщо ви знаходитесь десь, окрім країни, що розмовляє англійською мовою, ви ПОПЕРЕДЖЕНО використовувати wchar_t. Не кажучи вже про те, що деякі алфавіти мають значно більше символів, ніж ви можете вмістити в байт. Ми були там, на DOS. Шизофренія з кодовою сторінкою, ні, дякую, не більше ..
Swift - П’ятниця Пиріг

1
@Swift Проблема wchar_tполягає в тому, що його розмір і значення залежать від ОС. Він просто замінює старі проблеми новими. Тоді як a char- це charнезалежно від ОС (принаймні, на подібних платформах). Таким чином, ми можемо просто використовувати UTF-8, упакувати все в послідовності chars і скаржитися на те, як C ++ залишає нас повністю самостійно без будь-яких стандартних методів вимірювання, індексації, пошуку тощо в таких послідовностях.
підкреслюй_d

1
@Swift Ви, здається, маєте це повністю назад. wchar_tце тип даних фіксованої ширини, тому масив 10 wchar_tзавжди займатиме sizeof(wchar_t) * 10байти платформи. А UTF-16 - це кодування змінної ширини, в якому символи можуть складатися з 1 або 2 16-бітових кодових точок (і s / 16/8 / g для UTF-8).
підкреслити_

1
@SteveHollasch wchar_t подання рядка у Windows кодує символи, що перевищують FFFF, як аспектну сурогатну пару, інші братимуть лише один елемент wchar_t. Отже, це представлення не буде сумісним із представленням, створеним компілятором gnu (де всі символи менше FFFF матимуть нульове слово перед ними). Що зберігається у wchar_t, визначається програмістом і компілятором, а не якоюсь угодою
Swift - П'ятниця Пиріт
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.