перше, що потрібно вміти робити з локалізованими рядками - це записувати символьні константи широких локалізованих символів і відрізняти їх від звичайних рядків char[]. Для цього рядок записується з попереднім їй кваліфікаторів L:
1 2 3 4 5 6 7 8 | #include <stdio.h> #include <wchar.h> #include <locale> int main( void ) { wchar_t w[] = L"русскоязычная строка"; setlocale( LC_ALL, "" ); printf( "%ls [%lu байт, %lu букв]\n", w, sizeof( w ), wcslen( w ) ); } |
результатом буде:
1 2 | $ ./2 русскоязычная строка [84 байт, 20 букв] |
звернемо увагу, що довжина рядка (число символов) в цьому випадку чітко менше, ніж число байт виділених під рядок (у вашій операційній системі їхнє ставлення може відрізнятися від того, що я показую в Linux, але це ніяк не впливає на техніку програмування).
У такій рядку поруч з рівним успіхом можуть стояти символи найрізноманітнішої природи: різних мов, спеціальні математичні символи, загальноприйняті в їхК.Є. позначення з грецького алфавіту (α, е, Я, Fr., р, L, F, Про ...), музичні ноти і ін. Як ви, очевидно, розумієте, точно також в складі рядків широких символів, з рівним успіхом, можуть зустрічатися і символи латинського алфавіту (основна таблиця ASCII), при цьому кожен такий символ теж буде займати 2 или 4 байт (в залежності від угод прийнятих в операційній системі), на відміну від звичного 1 байта.
Проробимо ряд операцій з російськомовними рядками, але записуючи їх (Бувай) в традиційній формі масивів char:
1 2 3 4 5 6 7 8 9 10 | #include <string.h> #include <iostream> using namespace std; int main( void ) { char s1[] = "это ", s2[] = "фрагменты ", s3[] = "русскоязычной ", s4[] = "строки ", s5[ 120 ]; strcpy( s5, strcat( s1, strcat( s2, strcat( s3, s4 ) ) ) ); cout << s5 << endl; } |
виконуємо:
1 2 | $ ./3a это фрагменты русскоязычной строки [66] |
Здавалося б, що (майже) все працює в точності як за підручником і навіщо нам якісь широкі локалізовані рядки? Але це оманлива ілюзія! Справа тут в тому, що деякі традиційні малі функції (strcat(), strcpy(), strdup(), strstr() та ін.) повертатимуть коректний результат. Це тому що вони виконують операції над байтами, байт за байтом, не вникаючи у внутрішню структуру копіюються символів.
Але інші операції (і помилковий результат strlen() на це вже наочно вказує) будуть працювати належним чином: strncpy(), strchr(), strsep(), strtok() та ін. И вони будуть створювати вам дуже несподівані результати, вельми складні для тлумачення. подивисявони як спрацює побайтовой реверс рядки, і як різниться його робота на англомовній та російськомовній рядку:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | #include <string.h> #include <iostream> using namespace std; char* revers( char* s ) { for( int i = 0, j = strlen( s ) - 1; i < j; i++, j-- ) { char c = s[ i ]; s[ i ] = s[ j ]; s[ j ] = c; } return s; } int main( void ) { char se[] = "this is english string", sr[] = "это русскоязычная строка"; cout << revers( se ) << endl << revers( sr ) << endl; } |
Чи спрацює це так, і це безперечно НЕ, що ви розраховували отримати:
1 2 3 | $ ./3b gnirts hsilgne si siht �коЀтс� �ѰнЇыѷЏѾкЁсур� �Ђэ� |
Nа це ми закінчимо розгляд можливості подання російськомовних рядків традиційними масивами char[] і обробка їх традиційними малими функціями, і завершимо це розгляд висновком: работать з російськомовними рядками як масивом char можна только:
а). або коли ми використовуємо рядкові константи в незмінному вигляді, тільки як рядки для їх введення-виведення в незмінному вигляді;
б). або для обробки їх функціями (бібліотечними або власними), які не враховують внутрішню структуру символів, не вникаючи в вмістце строк, а оперують з ними просто як з безглуздою послідовністю байт.
У всіх решті випадків коректная робота з кирилицею можлива тільки як з масивами широких локалізованих символів wchar_t (з завершающім рядок широким нульовим символом L » 0′). Для роботи з таким поданням локалізованих рядків бібліотека C надає широкий набір малих функцій, повністю аналогічний традиційним рядковим функцій, але замість префікса str в їхніх іменах використовується префікс WCS: wcslen() замість strlen(), wcsncpy() замість strncpy() і т.д.
Подивимося як це працює на прикладі:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | #include <stdio.h> #include <wchar.h> #include <locale> wchar_t* revers( wchar_t *w ) { wchar_t *sec, wb[ 80 ]; if( ( sec = wcschr( w, L' ' ) ) != NULL ) { wcsncpy( wb, w, sec - w )[ sec - w ] = L'\0'; while( L' ' == *sec ) sec++; revers( sec ); wcscat( wcscat( wmemmove( w, sec, wcslen( sec ) + 1 ), L" " ), wb ); } return w; } int main( void ) { setlocale( LC_ALL, "" ); // только после этого работают преобразования! wchar_t ws[] = L"тестовая русскоязычная строка из нескольких слов "; while( L' ' == ws[ wcslen( ws ) - 1 ] ) ws[ wcslen( ws ) - 1 ] = L'\0'; printf( "устранение завершающих пробелов: '%ls'\n", ws ); printf( "1-е реверсирование слов: '%ls'\n", revers( ws ) ); printf( "2-е реверсирование слов: '%ls'\n", revers( ws ) ); } |
1 2 3 4 | $ ./4 устранение завершающих пробелов: 'тестовая русскоязычная строка из нескольких слов' 1-е реверсирование слов: 'слов нескольких из строка русскоязычная тестовая' 2-е реверсирование слов: 'тестовая русскоязычная строка из нескольких слов' |
Такий ілюстрації цілком достатньо для того, щоб побачити прямі аналогії використання функцій роботи з символами wchar_t. той, хто має деякий досвід роботи з рядками char без зусиль перенесе його широкі рядки. Установка мовної локалі (виклик setlocale()) пристрою виведення (терміналу) - обов'язкова, тому що за замовчуванням програма C / C ++ встановлює локаль “C” (тадо склалося історично), яка допускає висновок тільки 128 символів молодшої половини 8-бітної таблиці ASCII.
У показаному написанні функція встановлює локаль, використовувану в операційній системі за замовчуванням - я припускаю, що ми експериментуємо в російськомовній встановленій системі. Новий стандарт мови (C99) вводить і новий формат для функцій форматування рядків (Printf(), Sprintf()) - %Ls, це форматування рядків wchar_t[].
ТТочно так само, як і з масивами char, які перейшли в C ++ з C, бібліотека C ++ вводить повну аналог контейнерного класу string, але що містить в своєму складі широкі локалізовані символи, і носить назву цей клас wstring:
1 2 3 4 5 6 7 8 | #include <locale> #include <iostream> using namespace std; int main( void ) { locale::global( locale( "" ) ); wstring ws = L"строка"; wcout << ws << endl; } |
Тут висновок рядка локалізованих символів (WS) повинен виводиться в потік виводу wcout (аналогічний за змістом cout, але відмінний від cout).
У показаному написанні: місце дії::глобальної( місце дії( “” ) ) - це установка локалі за замовчуванням в ООП манері C ++, аналогічно тому, як це було показано раніше в манері C.
Вопитування введення-виведення рядків широких символів (на термінал або в файл) окремий непростий предмет, тому його розгляд ми відкладемо до окремої замітки на цей рахунок.