Что такое Unicode и как с ним работать в C++?

Ответ

Unicode — это универсальный стандарт кодирования символов, который ставит в соответствие каждому символу из большинства письменных систем мира уникальный числовой код (кодовую точку, code point). В отличие от ASCII (128 символов), Unicode охватывает более 140 000 символов, включая исторические шрифты, эмодзи и специальные знаки.

Основные кодировки (форматы хранения байтов):

  • UTF-8: Переменная длина (1-4 байта). Обратно совместима с ASCII (первые 128 символов кодируются одним байтом). Стандарт для веба и многих систем. Является кодировкой по умолчанию для строковых литералов во многих компиляторах.
  • UTF-16: Использует 2 или 4 байта (суррогатные пары для символов за пределами Basic Multilingual Plane). Нативный для Windows API и Java.
  • UTF-32: Фиксированная длина (4 байта на кодовую точку). Прост для обработки, но неэффективен по памяти.

Работа с Unicode в современном C++ (C++11 и выше):

#include <iostream>
#include <string>
#include <locale>
#include <codecvt> // Для конвертации (устарело в C++17, но пока используется)

int main() {
    // Строковые литералы с префиксами:
    std::string utf8_str = u8"Привет, 世界! 😊"; // UTF-8 (char)
    std::u16string utf16_str = u"Привет, 世界! 😊"; // UTF-16 (char16_t)
    std::u32string utf32_str = U"Привет, 世界! 😊"; // UTF-32 (char32_t)
    std::wstring wide_str = L"Привет, 世界! 😊"; // Широкая строка (wchar_t, зависит от платформы)

    std::cout << "UTF-8 output: " << utf8_str << std::endl;

    // Конвертация UTF-8 в UTF-16 (используя устаревший, но пока распространенный codecvt)
    std::wstring_convert<std::codecvt_utf8_utf16<char16_t>, char16_t> converter;
    std::u16string converted = converter.from_bytes(utf8_str);

    // **Важно:** Для корректного вывода в консоль Windows может потребоваться
    // изменить кодовую страницу консоли или использовать API WriteConsoleW.
    // На Linux/macOS с UTF-8 локалью вывод обычно работает корректно.

    // Итерация по кодовым точкам UTF-8 (упрощенно):
    for (char c : utf8_str) {
        // Но так мы итерируем по байтам, а не по символам!
        // Для посимвольной обработки нужны библиотеки (ICU, Boost.Nowide) или C++20 <text_encoding>.
    }
    return 0;
}

Проблемы и рекомендации для C++:

  1. std::string и std::wstring не знают о кодировке. Они просто хранят последовательности char/wchar_t. Ответственность за корректную интерпретацию лежит на программисте.
  2. Консольный ввод/вывод может некорректно отображать Unicode в зависимости от настроек терминала/консоли.
  3. Для сложных операций (нормализация, разбиение на графемы, сортировка) используйте специализированные библиотеки, такие как International Components for Unicode (ICU).
  4. C++20 вводит новые типы (std::u8string) и улучшенную поддержку текста, но полная библиотека пока не реализована во всех компиляторах.

Правило для современных кроссплатформенных проектов: Храните и обрабатывайте текст внутри программы в UTF-8 (используя std::string), конвертируя в другие кодировки только на границах ввода/вывода (файлы, сеть, UI).

Ответ 18+ 🔞

Бля, ну вот смотри, с этим Unicode история такая, что ядрёна вошь, честно. Вроде бы простая идея — каждой букве, смайлику или иероглифу дать свой уникальный номер, чтобы компы не путались. Но как это всё реализовано, ёпта... Это ж овердохуища правил, исключений и подводных камней.

Представь себе ASCII — это как маленькая деревня, там 128 домиков-символов, всем хватает. А Unicode — это хуй с горы, мегаполис на 140+ тысяч жителей, где у каждого паспорт — это кодовая точка (code point). И живут там все: от древнегреческих букв до этих ваших ебушки-воробушки эмодзи.

А вот где начинается пиздец — так это в транспорте, то есть в кодировках. Как эти номера в байты упаковать?

  • UTF-8: Хитрый, как жопа. Использует от 1 до 4 байтов. Для латиницы — один байт, полная совместимость со старой ASCII. А вот для кириллицы или иероглифов — уже два-три. Для эмодзи — все четыре, бля. Это стандарт де-факто для интернета, и доверия к нему — ебать ноль, потому что все его используют.
  • UTF-16: Прямолинейный, как манда с ушами. Почти всегда по 2 байта на символ, но для редких символов (тех же эмодзи) может выехать сюрприз — суррогатная пара, то есть 4 байта. Это родная кодировка для Windows и Java. Подозрение ебать чувствую к этой неконсистентности.
  • UTF-32: Тупой и честный. Берёт и каждому символу отдаёт по 4 байта, пофиг на важность. Прост для перебора, но памяти жрёт, как не в себя. Редко где используется.

Теперь про C++. Тут волнение ебать начинается. Компилятор вроде как умный, но строка (std::string) — она же нихуя не знает про кодировку! Это просто мешок с байтами. Ответственность — полностью на тебе, чувак.

Смотри, как это выглядит в коде (C++11 и выше):

#include <iostream>
#include <string>
#include <locale>
#include <codecvt> // Эта штука, бля, устарела, но все ещё везде используется!

int main() {
    // Вот так объявляешь строки в разных кодировках
    std::string utf8_str = u8"Привет, 世界! 😊"; // UTF-8, обычные char
    std::u16string utf16_str = u"Привет, 世界! 😊"; // UTF-16, char16_t
    std::u32string utf32_str = U"Привет, 世界! 😊"; // UTF-32, char32_t
    std::wstring wide_str = L"Привет, 世界! 😊"; // Широкая строка. А вот её размер — это хуй в пальто, зависит от системы.

    std::cout << "Пытаемся вывести UTF-8: " << utf8_str << std::endl;
    // И вот тут может случиться "ни хуя себе". Если консоль не в UTF-8, увидишь кракозябры.

    // Конвертация из UTF-8 в UTF-16 (старым, устаревшим методом)
    std::wstring_convert<std::codecvt_utf8_utf16<char16_t>, char16_t> converter;
    std::u16string converted = converter.from_bytes(utf8_str);

    // ВАЖНО: Вывод в консоль Windows — это отдельный ад.
    // Нужно или кодовую страницу менять, или WriteConsoleW вызывать.
    // На нормальных системах (Linux/macOS) с UTF-8 локалью обычно работает.

    // Хочешь пройтись по символам UTF-8? Забудь про простой for по char!
    for (char c : utf8_str) { // Тут ты итерируешься по БАЙТАМ, а не по символам!
        // Для кириллического 'П' (которая 2 байта) ты сделаешь две итерации!
        // Это пиздопроебибна ошибка новичков.
    }
    // Для нормального посимвольного разбора нужны библиотеки вроде ICU.
    return 0;
}

Итоговые рекомендации, чтобы не сойти с ума:

  1. Главное правило, золотое: Внутри своей программы всё храни в UTF-8std::string). Это твой единый язык. Всё остальное — это для общения с внешним миром.
  2. Конвертируй только на границах. Получил из файла или сети в UTF-16? Сразу переведи в UTF-8 для работы. Отдаёшь в Windows API? Сконвертируй обратно в UTF-16 в последний момент.
  3. Консоль — твой враг. Не рассчитывай, что std::cout всё красиво покажет. Это отдельная боль.
  4. Для серьёзных дел (сортировка, поиск, разбиение на слоги) бери ICU. Стандартная библиотека C++ с этим справляется хуже, чем мартышлюшка с интегралами.
  5. C++20 обещает новые типы (std::u8string) и лучшее управление текстом, но пока это всё сыро и не везде работает. Так что пока живём по старым, проверенным граблям.

Короче, Unicode — это мощно и необходимо, но будь готов, что иногда придётся материться и лезть в документацию глубже, чем обычно.