Работа с символами и кодовыми страницами
Поскольку строки состоят из символов, иногда нужно или просто удобнее манипулировать отдельными символами или группами символов в строке на уровне их целочисленных кодов. Например, требуется читать или заменять символы по одиночке, преобразовывать в массивы целочисленных кодов для передачи по коммуникационным протоколам или в программные интерфейсы сторонних динамических библиотек DLL. Во всех таких случаях передача строк в виде текста может быть сопряжена с различными сложностями:
- обеспечение правильной кодировки (которых существует великое множество, и на выбор конкретной влияет локализация операционной системы, настройки программы, конфигурация серверов, с которыми осуществляется связь, и многое другое);
- конвертация символов национального языка из локальной текстовой кодировки в Unicode и обратно;
- выделение и освобождение памяти унифицированным образом.
Использование массивов с целочисленными кодами (что дает, фактически, двоичное, а не текстовое представление строки) упрощает решение этих проблем.
MQL5 API предоставляет набор функций, которые позволяют оперировать отдельными символами или их группами с учетом особенностей кодировки.
Напомним, что строки в MQL5 содержат символы в двухбайтовой кодировке Unicode. Это обеспечивает универсальную поддержку всего разнообразия национальных алфавитов в единой (но очень большой) таблице символов. Два байта позволяют закодировать 65535 элементов.
По умолчанию для символов используется тип ushort. Однако при необходимости строку можно конвертировать в последовательность однобайтовых символов uchar в конкретной языковой кодировке. Данная конвертация может сопровождаться потерей части информации (в частности, буквы, отсутствующие в локализованной таблице символов, могут "потерять" умляуты или вовсе "превратиться" в некий символ-заменитель: в зависимости от контекста он может отображаться по-разному, но обычно как '?' или "квадратик").
Во избежание проблем с текстами, в которых могут встречаться произвольные символы, рекомендуется всегда использовать Unicode. Исключение можно сделать, если некие внешние сервисы или программы, с которыми требуется интегрировать вашу MQL-программу, не поддерживают Unicode, или если текст заведомо предназначен для хранения ограниченного набора символов (например, только числа и латинские буквы).
При конвертации в/из однобайтовые символы MQL5 API по умолчанию применяет ANSI-кодировку, зависящую от текущих настроек Windows. Однако разработчик может задать другую кодовую таблицу (см. далее функции CharArrayToString, StringToCharArray).
Примеры использования описываемых далее функций приведены в файле StringSymbols.mq5.
bool StringSetCharacter(string &variable, int position, ushort character)
Функция изменяет в переданной строке variable символ с номером position на значение character. Номер должен быть в диапазоне от 0 до длины строки (StringLen) минус 1.
Если записываемый символ равен 0, он задает новое окончание строки (выступает в роли терминального нуля), то есть длина строки становится равной position. Размер буфера, выделенный под строку, при этом не меняется.
Если параметр position равен длине строки и записываемый символ не равен 0, то символ добавляется к строке и её длина увеличивается на 1. Это эквивалентно выражению: variable += ShortToString(character).
Функция возвращает true в случае успешного выполнения или false в случае ошибки.
void OnStart()
|
ushort StringGetCharacter(string value, int position)
Функция возвращает код символа, расположенного в указанной позиции строки. Номер позиции должен лежать в пределах от 0 до длины строки (StringLen) минус 1. В случае ошибки функция вернет 0.
Функция эквивалентна записи с использованием оператора '[]': value[position].
string numbers = "0123456789";
|
string CharToString(uchar code)
Функция преобразует ANSI-код символа в односимвольную строку. В зависимости от установленной кодовой страницы Windows, верхняя половина кодов (старше 127) может генерировать отличные буквы (отличается начертание символа, код остается одним и тем же). Например, символ с кодом 0xB8 (184 в десятичной системе) обозначает седиль (нижний крючок) в западноевропейских языках, а в русском здесь располагается буква 'ё'. Или вот еще пример:
PRT(CharToString(0xA9)); // "©"
|
string ShortToString(ushort code)
Функция преобразует Unicode-код символа в односимвольную строку. В качестве параметра code можно использовать литерал или целое число. Например, греческая заглавная буква "сигма" (знак суммы в математических формулах) может быть указана как 0x3A3 или 'Σ'.
PRT(ShortToString(0x3A3)); // "Σ"
|
int StringToShortArray(const string text, ushort &array[], int start = 0, int count = -1)
Функция преобразует строку в последовательность ushort-кодов символов, которая копируется в указанное место массива: начиная с элемента под номером start (по умолчанию — 0, то есть начало массива) и в количестве count.
Обратите внимание: параметр start относится к позиции в массиве, а не в строке. Если требуется сконвертировать часть строки, её необходимо предварительно извлечь с помощью функции StringSubstr.
Когда параметр count равен -1 (или WHOLE_ARRAY), копируются все символы до конца строки (включая терминальный ноль) или по размеру массива, если он фиксированного размера.
В случае динамического массива, он будет при необходимости автоматически увеличен в размере. Если размер динамического массива больше длины строки, то размер массива не уменьшается.
Чтобы скопировать символы без терминального нуля, следует явно указывать вызов StringLen в качестве аргумента count. В противном случае длина массива будет на 1 больше длины строки (и в последнем элементе — 0).
Функция возвращает количество скопированных символов.
...
|
Обратите внимание, что если позиция для копирования выходит за пределы размера массива, то промежуточные элементы будут распределены, но не инициализированы. В результате в них могут оказаться случайные данные (подсвечены желтым выше).
string ShortArrayToString(const ushort &array[], int start = 0, int count = -1)
Функция преобразует часть массива с кодами символов в строку. Диапазон элементов массива задается параметрами start и count: соответственно начальной позицией и количеством. Параметр start должен быть в пределах от 0 до числа элементов в массиве минус 1. Когда count равно -1 (или WHOLE_ARRAY) копируются все элементы до конца массива или до первого нулевого.
Продолжая текущий пример из StringSymbols.mq5, попробуем преобразовать в строку массив array2, который имеет размер 30.
...
|
Поскольку в массив array2 была дважды скопирована строка "ABCDEАБВГД", причем один раз — в самое начало, а второй раз — по смещению 20, промежуточные символы будут случайными и способны сформировать более длинную строку, чем получилось у нас.
int StringToCharArray(const string text, uchar &array[], int start = 0, int count = -1, uint codepage = CP_ACP)
Функция преобразует строку text в последовательность однобайтовых символов, которая копируется в указанное место массива: начиная с элемента под номером start (по умолчанию — 0, то есть начало массива) и в количестве count. В процессе копирования производится конвертация символов из Unicode в выбранную кодовую страницу codepage — по умолчанию, CP_ACP, что означает язык операционной системы Windows (подробнее об этом — чуть ниже).
Когда параметр count равен -1 (или WHOLE_ARRAY), копируются все символы до конца строки (включая терминальный ноль) или по размеру массива, если он фиксированного размера.
В случае динамического массива, он будет при необходимости автоматически увеличен в размере. Если размер динамического массива больше длины строки, то размер массива не уменьшается.
Чтобы скопировать символы без терминального нуля, следует явно указывать вызов StringLen в качестве аргумента count.
Функция возвращает количество скопированных символов.
Перечень допустимых кодовых страниц для параметра codepage смотрите в документации. Вот некоторые из широко распространенных кодовых страниц стандарта ANSI:
Язык |
Код |
---|---|
Центральноевропейский латинский |
1250 |
Кириллица |
1251 |
Западноевропейский латинский |
1252 |
Греческий |
1253 |
Турецкий |
1254 |
Иврит |
1255 |
Арабский |
1256 |
Прибалтийский |
1257 |
Таким образом, на компьютерах с западноевропейскими языками CP_ACP равна 1252, а, например, на русских — 1251.
В процессе конвертации некоторые символы могут быть преобразованы с потерей информации, поскольку таблица Unicode намного больше ANSI (в каждой таблице ANSI-кодов — 256 символов).
В связи с этим особую важность среди всех констант CP_*** имеет CP_UTF8. Она позволяет правильно сохранить национальные символы за счет кодирования с переменной длиной: результирующий массив по-прежнему хранит байты, но каждый национальный символ может занимать несколько байтов, записанных в особом формате. Из-за этого длина массива может быть существенно больше, чем длина строки. Кодировка UTF-8 широко используется в Интернете и различном программном обеспечении. Кстати говоря, UTF расшифровывается как Unicode Transformation Format, и существуют другие модификации, в частности UTF-16 и UTF-32.
Мы рассмотрим пример для StringToCharArray после того, как познакомимся с "обратной" функцией CharArrayToString: их работу необходимо демонстрировать в связке.
string CharArrayToString(const uchar &array[], int start = 0, int count = -1, uint codepage = CP_ACP)
Функция преобразует массив с байтами или его часть в строку. Массив должен содержать символы в определенной кодировке. Диапазон элементов массива задается параметрами start и count: соответственно начальной позицией и количеством. Параметр start должен быть в пределах от 0 до числа элементов в массиве. Когда count равно -1 (или WHOLE_ARRAY) копируются все элементы до конца массива или до первого нулевого.
Посмотрим, как функции StringToCharArray и CharArrayToString работают с разными национальными символами при разных настройках кодовых страниц. Для этого подготовлен тестовый скрипт StringCodepages.mq5.
В качестве подопытных будут использованы две строки — на русском и немецком языках:
void OnStart()
|
Мы будем их копировать в массивы bytes1 и bytes2, а затем восстанавливать в строки.
Для начала преобразуем немецкий текст, применив европейскую кодовую страницу 1252.
...
|
На европейских копиях Windows это эквивалентно более простому вызову функции с параметрами по умолчанию, потому что там CP_ACP = 1252:
StringToCharArray(german, bytes1); |
Затем восстановим текст из массива с помощью следующего вызова и убедимся, что все совпадет с первоисточником:
...
|
Теперь попробуем преобразовать русский текст в той же европейской кодировке (или можно вызвать StringToCharArray(russian, bytes2) в среде Windows, где в качестве кодовой страницы по умолчанию CP_ACP стоит 1252):
...
|
Здесь уже видно, что во время конвертации возникла проблема, потому что 1252 не имеет кириллицы. Восстановление строки из массива наглядно показывает суть:
...
|
Повторим опыт в условной русской среде, то есть преобразуем туда и обратно обе строки с использованием кириллической кодовой страницы 1251.
...
|
Таким образом, налицо хрупкость однобайтовых кодировок.
Наконец, задействуем кодировку CP_UTF8 для обеих тестовых строк. Эта часть примера будет стабильно работать вне зависимости от настроек Windows.
...
|
Обратите внимание, что обе строки в кодировке UTF-8 потребовали более длинных массивов, чем в ANSI. Причем массив с русским текстом стал фактически в 2 раза длиннее, потому что все буквы теперь занимают по 2 байта. Желающие могут найти в открытых источниках подробности о том, как именно устроена кодировка UTF-8. В контексте данной книги для нас важно, что MQL5 API предоставляет готовые функции для работы с ней.