9

`std::wstring` против `std::string`: когда использовать и в чем разница?

7

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

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

5 ответ(ов)

1

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

Моя точка зрения изложена на сайте http://utf8everywhere.org, одним из авторов которого я являюсь.

Если ваше приложение не сосредоточено на вызовах API, например, это в основном приложение с пользовательским интерфейсом, то я рекомендую хранить строки Unicode в std::string, закодированных в UTF-8, выполняя преобразование вблизи вызовов API. Преимущества, изложенные в статье, перевешивают явные неудобства от преобразования, особенно в сложных приложениях. Это особенно актуально для разработки многоплатформенных приложений и библиотек.

Теперь, отвечая на ваши вопросы:

  1. Существует несколько слабых причин. std::wstring существует по историческим причинам, когда считалось, что широкие символы являются правильным способом поддержки Unicode. В настоящее время его используют для взаимодействия с API, которые предпочитают строки в UTF-16. Я использую их только в непосредственной близости от таких вызовов API.
  2. Это не имеет ничего общего с std::string. Он может содержать любую кодировку, которую вы в него поместите. Единственный вопрос - как вы обрабатываете его содержимое. Моя рекомендация - использовать UTF-8, чтобы он мог корректно хранить все символы Unicode. Это общепринятая практика на Linux, но я считаю, что программы на Windows также должны это делать.
  3. Нет.
  4. Широкий символ - это запутанное название. В ранние дни Unicode существовало мнение, что символ можно закодировать в два байта, откуда и возникло это название. Сегодня это обозначает "любой участок символа, который занимает два байта". UTF-16 рассматривается как последовательность таких пар байтов (так называемых широких символов). Символ в UTF-16 занимает либо одну, либо две пары.
0

При использовании std::string для хранения символов в кодировке UTF-8 я никогда не сталкивался с проблемами. Я настоятельно рекомендую использовать этот подход для работы с API, где UTF-8 является нативным типом строк.

Например, я использую UTF-8 при взаимодействии со своим кодом и интерпретатором Tcl.

Однако стоит отметить, что длина std::string больше не соответствует количеству символов в строке. Это важно учитывать, так как один символ UTF-8 может занимать от 1 до 4 байт, и, следовательно, длина строки в байтах может отличаться от ожидаемого количества символов.

0

Хороший вопрос!

Я считаю, что КОДИРОВКА ДАННЫХ (иногда также связанная с КОДИРОВКОЙ ЗНАКОВ) является МЕХАНИЗМОМ ВЫРАЖЕНИЯ ПАМЯТИ для сохранения данных в файл или передачи данных по сети. Поэтому я отвечу на этот вопрос следующим образом:

1. Когда следует использовать stdwstring вместо stdstring?

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

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

Да, может. Но стоит учесть: американский ASCII означает, что каждый байт в диапазоне 0x00~0xFF соответствует одному символу, включая печатный текст, такой как "123abc&*_&" и так называемые специальные символы, которые в большинстве случаев печатаются как '.' чтобы избежать путаницы в редакторах или терминалах. В некоторых других странах расширяют свой собственный набор символов "ASCII", например, в Китае используется 2 байта для представления одного символа.

3. Поддерживается ли std::wstring всеми популярными компиляторами C++?

Возможно, или скорее всего. Я использовал: VC++6 и GCC 3.3, и они оба поддерживают.

4. Что такое "широкий символ"?

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

0
  1. Когда вам нужно хранить «широкие» (Unicode) символы.
  2. Да: 255 из них (исключая 0).
  3. Да.
  4. Вот вступительная статья: http://www.joelonsoftware.com/articles/Unicode.html
0

Есть несколько очень хороших ответов на этот вопрос, но я думаю, что могу добавить пару деталей относительно Windows и Visual Studio, основанных на моем опыте работы с VS2015. На Linux в основном ответ заключается в том, чтобы использовать кодировку UTF-8 для std::string повсюду. На Windows и в VS ситуация более сложная. Вот почему. Windows ожидает, что строки, хранящиеся в символах char, будут закодированы с использованием кодовой страницы локали. Обычно это набор символов ASCII, за которым следуют 128 других специальных символов в зависимости от вашего местоположения. И важно отметить, что это касается не только работы с Windows API. Есть три других основных места, где эти строки взаимодействуют со стандартным C++. Это строковые литералы, вывод в std::cout с использованием оператора <<, и передача имени файла в std::fstream.

Я сразу скажу, что я программист, а не специалист по языкам. Я понимаю, что UCS2 и UTF-16 не одно и то же, но для моих целей они достаточно близки и я использую их как взаимозаменяемые. На самом деле я не уверен, что именно использует Windows, но обычно мне не нужно это знать. Я указал UCS2 в этом ответе, так что извините, если я кого-то обидел своим незнанием в этом вопросе, и я с радостью изменю это, если у меня есть ошибки.

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

Если вы вводите строковые литералы, которые содержат только символы, которые могут быть представлены вашей кодировкой, то VS сохраняет их в файле с использованием кодировки 1 байт на символ на основе вашей кодовой страницы. Обратите внимание, что если вы измените свою кодовую страницу или передадите свой исходный код другому разработчику с другой кодировкой, символ может оказаться другим. Если вы запустите свой код на компьютере с другой кодовой страницей, то я не уверен, изменится ли символ.

Если вы вводите любую строку, которая не может быть представлена вашей кодировкой, Visual Studio предложит вам сохранить файл как Unicode. В этом случае файл будет закодирован в UTF-8. Это означает, что все не ASCII символы (включая те, которые находятся в вашей кодировке) будут представлены 2 и более байтами. Это означает, что если вы передадите свой исходный код кому-то другому, исходный код будет выглядеть одинаково. Однако перед передачей исходного кода компилятору VS конвертирует текст, закодированный в UTF-8, в текст с кодировкой кодовой страницы, и любые символы, отсутствующие в кодовой странице, заменяются на ?.

Единственный способ гарантировать корректное представление строкового литерала в Unicode в VS — это предшествовать строковому литералу буквой L, делая его широким строковым литералом. В этом случае VS конвертирует текст, закодированный в UTF-8, из файла в UCS2. Затем вам необходимо передать этот строковый литерал в конструктор std::wstring или преобразовать его в UTF-8 и поместить в std::string. Или, если хотите, вы можете использовать функции Windows API для кодирования с использованием вашей кодовой страницы, чтобы поместить его в std::string, но в таком случае можно было бы вообще не использовать широкий строковый литерал.

std::cout

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

Имена файлов std::fstream

Операционная система Windows использует UCS2/UTF-16 для имен файлов, поэтому вне зависимости от вашей кодовой страницы, вы можете использовать файлы с любым символом Unicode. Но это означает, что для доступа к файлам с символами, отсутствующими в вашей кодировке, вы должны использовать std::wstring. Иного способа нет. Это расширение Microsoft для std::fstream, так что, вероятно, оно не скомпилируется на других системах. Если вы используете std::string, то вы можете использовать только имена файлов, которые содержат символы из вашей кодовой страницы.

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

Если вы работаете только на Linux, то, вероятно, вы не дочитали до этого места. Просто используйте UTF-8 std::string повсюду.

Если вы работаете только на Windows, просто используйте UCS2 std::wstring повсюду. Некоторые пуристы скажут, что лучше использовать UTF-8, а затем конвертировать по необходимости, но зачем заморачиваться.

Если вы разрабатываете кроссплатформенное приложение, то, откровенно говоря, это сущий хаос. Если вы попытаетесь использовать UTF-8 повсюду на Windows, вам нужно быть очень осторожными с вашими строковыми литералами и выводом в консоль. Вы можете легко испортить ваши строки. Если вы используете std::wstring повсюду на Linux, вам может не быть доступа к широкой версии std::fstream, поэтому вам придется делать преобразования, но риск порчи данных отсутствует. Лично я считаю, что это лучший вариант. Многие будут не согласны, но я не одинок - такой путь выбрал, например, wxWidgets.

Другой вариант — сделать typedef для unicodestring, используя std::string на Linux и std::wstring на Windows, и создать макрос под названием 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;
    // Вызовите WideCharToMultiByte для выполнения конвертации
    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 или писать код с платформозависимыми #ifdef, чтобы обойти различия. Если вы только на Linux, то никогда.

  2. Да. В дополнение на Linux вы можете использовать это для всего Unicode. На Windows вы можете использовать это для всего Unicode только если решите самостоятельно закодировать с использованием UTF-8. Но API Windows и стандартные классы C++ будут ожидать, что std::string закодирован с использованием кодовой страницы локали. Это включает весь ASCII плюс еще 128 символов, которые меняются в зависимости от кодовой страницы, которую настроен использовать ваш компьютер.

  3. Я считаю, да, но если нет, то это просто, вероятно, typedef для 'std::basic_string', использующий wchar_t вместо char.

  4. Широкий символ — это тип символа, который больше стандартного 1-байтового char. На Windows это 2 байта, на Linux - 4 байта.

Чтобы ответить на вопрос, пожалуйста, войдите или зарегистрируйтесь