Универсальный форматированный вывод данных в строку
При формировании строки для показа пользователю, сохранения в файл или передачи в Интернет может потребоваться включить в нее значения нескольких переменных разных типов. Эту задачу можно решить явным приведением всех переменных к типу (string) и сложением получившихся строк, но в этом случае инструкция MQL-кода окажется длинной и трудной для понимания. Вероятно, более удобным было бы применение функции StringConcatenate, но этот способ не решает проблему целиком.
Дело в том, что строка обычно содержит не только переменные, но и некие текстовые вставки, выступающие связующими звеньями и обеспечивающие правильную структуру общего сообщения. Получается, что куски форматирующего текста перемешаны с переменными. Такой код сложно поддерживать — он противоречит одному из известных принципов программирования: отделяй представление от данных.
Для этой задачи существует специальное решение: функция StringFormat.
По такому же принципу действует другая функция MQL5 API: PrintFormat.
string StringFormat(const string format, ...)
Функция преобразует произвольные аргументы встроенных типов в строку в соответствии с заданным форматом. Первым параметром передается шаблон подготавливаемой строки, в котором особым способом указаны места вставки переменных и определен формат их вывода. Эти управляющие команды могут перемежаться с обычным текстом, который копируется в выходную строку без изменений. В последующих параметрах функции, разделенных запятыми, перечисляются все переменные в том порядке и тех типов, как для них зарезервированы места в шаблоне.
Взаимодействие форматной строки и аргументов StringFormat
Каждое место вставки переменной в строку отмечается форматным спецификатором: символом '%', после которого может указываться несколько настроек.
Строка формата анализируется слева направо. Когда в ней встречается первый спецификатор (если он есть), то значение первого параметра после строки формата преобразовывается и добавляется в результирующую строку согласно заданным настройкам. Второй спецификатор вызывает преобразование и вывод второго параметра, и так далее, до конца строки формата. Все прочие символы в шаблоне между спецификаторами копируются в результирующую строку без изменений.
Шаблон может не содержать ни одного спецификатора, то есть быть простой строкой. В таком случае в функцию нужно передать дополнительный фиктивный аргумент помимо самой строки (аргумент не будет помещен в строку).
Если в шаблоне требуется вывести знак процента, то следует написать его два раза подряд %%. Если знак % не задвоен, то несколько последующих символов после него всегда анализируется как спецификатор.
Обязательным атрибутом спецификатора является некий символ, обозначающий ожидаемый тип и интерпретацию очередного аргумента функции. Назовем этот символ условно T. Тогда в простейшем случае один спецификатор формата выглядит как %T.
В обобщенном виде спецификатор может состоять еще из нескольких полей (в квадратных скобках указаны опциональные поля):
%[Z][W][.P][M]T
Каждое поле выполняет свою функцию и принимает одно из разрешенных значений. Далее мы постепенно затронем все поля.
Тип T
Для целых чисел в качестве T могут применяться следующие символы, с пояснением как соответствующие им числа отображаются в строке:
- c — Unicode символ
- С — ANSI символ
- d, i — знаковое десятичное
- o — беззнаковое восьмеричное
- u — беззнаковое десятичное
- x — беззнаковое шестнадцатеричное (строчные буквы)
- X — беззнаковое шестнадцатеричное (заглавные буквы)
Напомним, что по способу внутреннего хранения данных к целым типам также относятся встроенные типы MQL5 datetime, color, bool и перечисления.
Для вещественных чисел в качестве T применимы следующие символы:
- e — научный формат с показателем (маленькая 'e')
- E — научный формат с показателем (большая 'E')
- f — обычный формат
- g — аналог f или e (выбирается наиболее компактный вид)
- G — аналог f или E (выбирается наиболее компактный вид)
- a — научный формат с показателем, шестнадцатеричное (строчные буквы)
- A — научный формат с показателем, шестнадцатеричное (заглавные буквы)
Наконец, для строк доступен всего один вариант символа T: s.
Размер целых чисел M
Для целых типов дополнительно можно явным образом указать размер переменной в байтах, если перед T поставить один из следующих знаков или их комбинаций (мы обобщили их под буквой M):
- h — 2 байта (short, ushort)
- l (маленькая L) — 4 байта (int, uint)
- I32 (большая i) — 4 байта (int, uint)
- ll (две мелких L) — 8 байт (long)
- I64 (большая i) — 8 байт (long, ulong)
Ширина W
Поле W — это неотрицательное десятичное число, которое задает минимальное количество знакомест, выделяемых под отформатированное значение. Если значение переменной укладывается в меньшее количество символов, то слева или справа добавляется соответствующее количество пробелов. Левая или правая сторона выбирается в зависимости от выравнивания (см. далее флаг '–' в поле Z). При наличии флага '0' перед выводимым значением добавляется соответствующее количество нулей. Если число выводимых символов больше заданной ширины, то настройка ширины игнорируется и выводимое значение не усекается.
Если в качестве ширины указана звездочка '*', то в списке передаваемых параметров, на предыдущей позиции перед форматируемой переменной должно находиться значение типа int, которое будет использовано в качестве ширины выводимого значения.
Точность P
Поле P тоже содержит неотрицательное десятичное число и всегда предваряется точкой '.'. Для целочисленных T данное поле задает минимальное количество значащих цифр. Если значение укладывается в меньшее количество цифр, оно предваряется нулями.
Для вещественных чисел P задает количество знаков в дробной части (по умолчанию — 6), за исключением спецификаторов g и G, для которых P — это общее количество значащих цифр (в мантиссе и дробной части).
Для строки P определяет количество отображаемых символов. Если длина строки превышает значение точности, то строка будет показана в усеченном виде.
Если в качестве точности указана звездочка '*', она обрабатывается по такому же принципу, как и для ширины, но управляет точностью.
Фиксированная ширина и/или точность, вместе с выравниванием по правому краю, позволяют выводить значения аккуратным столбиком.
Флаги Z
Наконец поле Z описывает флаги:
- - (минус) — выравнивание по левому краю в пределах заданной ширины (в отсутствие флага делается выравнивание по правому краю);
- + (плюс) — безусловный вывод знака '+' или '-' перед значением (без этого флага '-' отображается только для отрицательных значений);
- 0 — перед выводимым значением добавляются нули, если оно меньше заданной ширины;
- (пробел) — перед выводимым значением ставится пробел, если оно является знаковым и положительным;
- # — управляет отображением префиксов восьмеричной и шестнадцатеричной записи чисел в форматах o, x или X (например, для формата x перед выводимым числом добавляется префикс "0x", для формата X — префикс "0X"), десятичной точки в вещественных числах (форматы e, E, a или A) с нулевой дробной частью, и некоторыми другими нюансами.
Более подробно изучить возможности форматированного вывода в строку можно в документации.
Общее количество параметров функции не может превышать 64.
Если количество переданных в функцию аргументов больше, чем количество спецификаторов, то лишние аргументы опускаются.
Если количество спецификаторов в форматной строке больше аргументов, то система попытается вывести вместо отсутствующих данных нули, однако для строковых спецификаторв будет встроено текстовое предупреждение ("missing string parameter").
Если тип значения не совпадает с типом соответствующего спецификатора, система попытается прочитать данные из переменной в соответствии с форматом и отобразит получившуюся величину (она может выглядеть странно за счет неправильной интерпретации внутреннего битового представления реальных данных). В случае строк в результат может быть встроено предупреждение ("non-string passed").
Протестируем функцию с помощью скрипта StringFormat.mq5.
Сперва попробуем разные варианты спецификатора типов T и данных.
PRT(StringFormat("[Infinity Sign] Unicode (ok): %c; ANSI (overflow): %C",
|
Здесь представлены как правильные, так и неправильные спецификаторы (неправильные идут вторыми в каждой инструкции и помечены словом "overflow", так как передаваемое значение не умещается в типе формата).
Вот что получится в журнале (переносы длинных строк здесь и далее сделаны для публикации):
StringFormat(Plain string,0)='Plain string'
|
Все следующие инструкции — правильные:
PRT(StringFormat("ulong (ok): %I64u", ULONG_MAX));
|
Результат их работы представлен ниже:
StringFormat(ulong (ok): %I64u,ULONG_MAX)=
|
Теперь рассмотрим различные модификаторы.
При выравнивании вправо (по умолчанию) и фиксированной ширине поля (количество знакомест) мы можем использовать разные варианты дополнения результирующей строки слева: пробелом или нулями. Кроме того, при любом выравнивании можно включать или отключать явное указание знака значения (чтобы выводился не только минус для отрицательных, но и плюс для положительных).
PRT(StringFormat("space padding: %10i", SHORT_MAX));
|
Получим в журнале следующее:
StringFormat(space padding: %10i,SHORT_MAX)='space padding: 32767'
|
Для выравнивания влево необходимо применить флаг '-' (минус), дополнение строки до заданной ширины при этом происходит справа:
PRT(StringFormat("no sign (default): %-10i", SHORT_MAX));
|
Результат:
StringFormat(no sign (default): %-10i,SHORT_MAX)='no sign (default): 32767 '
|
При необходимости мы можем показывать или скрывать знак значения (по умолчанию выводится только минус у отрицательных значений), добавлять пробел для положительных значений и тем самым обеспечивать одинаковое форматирование, когда нужно вывести переменные столбиком:
PRT(StringFormat("default: %i", SHORT_MAX)); // стандарт
|
Вот как это выглядит в журнале:
StringFormat(default: %i,SHORT_MAX)='default: 32767'
|
Теперь сравним, как ширина и точность влияют на вещественные числа.
PRT(StringFormat("double PI: %15.10f", M_PI));
|
Результат:
StringFormat(double PI: %15.10f,M_PI)='double PI: 3.1415926536'
|
В отсутствие явно заданной ширины, значения выводятся без дополнения пробелами.
PRT(StringFormat("double PI: %.10f", M_PI));
|
Результат:
StringFormat(double PI: %.10f,M_PI)='double PI: 3.1415926536'
|
Установление ширины и точности значений с помощью знака '*' и на основании дополнительных аргументов функции выполняется следующим образом:
PRT(StringFormat("double PI: %*.*f", 12, 5, M_PI));
|
Обратите внимание, что перед выводимым значением передается 1 или 2 значения целого типа — по числу звездочек '*' в спецификаторе: вы можете управлять отдельно точностью, отдельно шириной, или тем и другим одновременно.
StringFormat(double PI: %*.*f,12,5,M_PI)='double PI: 3.14159'
|
Наконец, рассмотрим несколько типичных ошибок форматирования.
PRT(StringFormat("string: %s %d %f %s", "ABCDEFGHIJ"));
|
В первой инструкции спецификаторов больше, чем аргументов. В остальных случаях не совпадают типы спецификаторов и передаваемых значений. В результате получим следующий вывод:
StringFormat(string: %s %d %f %s,ABCDEFGHIJ)=
|
Наличие единой форматной строки в каждом вызове функции StringFormat позволяет использовать её, в частности, для перевода внешнего интерфейса программ и сообщений на разные языки: достаточно загружать и подставлять в StringFormat различные форматные строки (подготовленные заранее) в зависимости от предпочтений пользователя или настроек терминала.