Re: Обробка текстів, символьні рядки
Виходить, буква а - це багатосимвольна константа
Загалом - ні. Якби у нас стояла задача дати загальне визначення, то ми б могли сказати, що буква може займати >= 1 байт. А те, скільки байт займатиме одна буква залежатиме від кодування. Власне у сучасному світі в якому ми живемо (майже) так воно і є. Пан koala вже дещо згадав про UTF-8, зараз усі сучасні операційні системи в своїх "нутрощах" користуються саме UTF кодуваннями. Наприклад, Linux користується UTF-8, a Windows UTF-16. Тож раджу детально познайомитися з даними кодуваннями, дещо я вже писав тут на форумі тиць.
Отже, у двох словах в UTF-кодуванні кириличні букви займають 2 байти, а не звичний один. Відповідно, коли ви намагаєтеся "порівняти" дві букви отак:
word=='а'
, то отримуєте помилку, що а multi-character character constant. А одинарні лапки в С/С++ вказують на те, що символ однобайтний, тобто має тип char, проте для букви а, як ми вже вияснили тре як мінімум два байти (char'и). Тобто символ а можна записати як:
char a[] = "a"; // << подвійні лапки
Чому так? Як ви вже, напевне, знаєте тип char займає 1 байт, а в 1 байт влізе не дуже багато даних, цифри, латинський алфавіт (включаючи капс), та ще деякі символи. Кирилиці місця не вистачить. В utf-кодуванні це вирішили банально, просто сказали, що, а давайте тоді все що не влізло в один байт, ми засунемо у два байти, а якщо і у два не влізе, тоді в 4 засунемо і т.д. Саме так буква "а" почала кодуватися, як "\xd0\xb0". Для більших деталей читайте моє посилання вище.
Тож, щоб вам вирішити ваше завдання, вам прийдеться розібратися в тому, як працює UTF-кодування. Ось, що вдалось мені накидати на С:
Я не став використовувати так звані широкі символи, вони ж wchar_t, тому що цей тип є доволі слизьким і його розмір сильно залежить від платформи, тож я утримався від його використання.
Скажу ще пару слів про Windows, бо там все трохи цікавіше. По-перше, використання UTF-8 в віндовс — ідея спірна, тому що всередині все одно буде UTF-16 і перекодування.
Виходить, що спосіб з мінімальними присіданнями полягає у двох ключових моментах:
1) Використовувати setmode з актуального msvc runtime (тобто використовувати, вважай, тільки компілятор майкрософт)
2) Використовувати нативне кодування — UTF-16.
При дотриманні умов і введення і виведення працюють однаково добре з мінімальними присіданнями:
_setmode(_fileno(stdout), _O_U16TEXT);
_setmode(_fileno(stdin) , _O_U16TEXT);
На жаль однакових програм відразу під всі системи таким чином не бачити. Потрібно буде робити якісь макропідстановки, щоб звести все до спільного знаменника.
Щодо локальних однобайтних кодувань вінди і чому і їх НЕ варто використовувати.
За замовчуванням віндова консоль налаштована на однобайтове кодування DOS, яке відповідає поточній мові системи. Для української Windows - це кодування 866. Так існує два системних локальних кодування: одна для додатків в псевдо-DOS режимі, інша для всього іншого (1251). DOS-кодування для консолі залишена з міркувань сумісності. Тому runtime для консольного виведення пробує перекодувати те, що йому передають в 866. Це перекодування працює на основі налаштувань поточної локалі. За замовчуванням програма стартує в стандартній локалі мови С, яка підтримує тільки символи ASCII. Тому ви бачите порожній вивід (UTF-16 кодується використовуючи налаштування локалі в "cирої" ASCII, що призводить до втрати інформації, далі швидше за все процес не йде, тому що перекодування завершилося з помилкою).
Якщо ми додамо рядок setlocale(LC_ALL, ""), то ми змусимо змінити локаль відповідно до поточних установок системи. Для української Windows в Visual Studio воно вибере локаль: "Ukrainian_Ukraine.1251". Це можна побачити ось таким кодом:
char const * loc = setlocale(LC_ALL, "");
std::cout << loc << '\n';
І це "несподівано" полагодить (настільки, наскільки це можливо для cp866, букви і все ще не буде) вивід, але за цією легкою дією буде стояти дуже багато. По-перше, кодування виведення самої консолі все ще DOS 866. Завдяки деякій "магічній" внутрішній "силі", runtime вміє кодувати те, що передається на вивід з одного однобайтового кодування, в інше однобайтове. Тому під капотом цієї простої дії ми отримуємо ось що:
UTF-16 з програми користувача кодується в 1251 (використовуючи налаштування локалі),
Потім "магічна" сила всередині runtime кодує 1251 до 866,
Потім відбувається "коректний" вивід.
А ось з введенням все ще складніше, тому що "магічна сила" тут чомусь уже не працює. Вводимо ми в консолі все ще 866. Але поточна локаль встановлена в 1251, тому перетворення в UTF-16 виконується так, як ніби-то ми ввели рядок в 1251. В результаті ми отримуємо некоректний UTF-16 рядок.
Можна змінити кодування консолі на 1251 за допомогою SetConsoleCP і SetConsoleOutputCP. Це зазвичай радять дуже часто в інтернеті. Тоді вийде:
Це створить ілюзію працездатності, якої, в принципі, достатньо більшості новачків на форумі. Але якщо ми таки пишемо інтернаціональний продукт, то цей спосіб тільки додає проблем. По-перше, тому що ми примусово виставили локаль прямо в коді, а по-друге, тому що саме по собі виставлення поточної локалі позбавляє можливості вводити текст декількома мовами одночасно. Саме тому явне використання в коді setlocale як з пустими скобками, так і з якимось конкретним ім'ям - не підходить для справжніх інтернаціональних додатків: наприклад, в такій конфігурації в французькій Windows працюватимуть тільки перекодування "французьке дос-кодування <-> юнікод UTF-16 "і ніякі інші варіанти.
P.S. - багато нюансів було спеціально спрощено, для розуміння початківцям, а також, щоб не роздувати й так великий пост.