Salida universal de datos formateados a una cadena
Al generar una cadena para mostrarla al usuario, guardarla en un archivo o enviarla por Internet, puede ser necesario incluir en ella los valores de varias variables de distintos tipos. Este problema se puede resolver mediante la conversión explícita de todas las variables al tipo (string) y la adición de las cadenas resultantes, pero en este caso, la instrucción de código MQL será larga y difícil de entender. Probablemente sería más conveniente utilizar la función StringConcatenate, pero este método no resuelve completamente el problema.
El hecho es que una cadena suele contener no sólo variables, sino también algunas inserciones de texto que actúan como enlaces de conexión y proporcionan la estructura correcta del mensaje global. Resulta que trozos de texto de formato se mezclan con variables. Este tipo de código es difícil de mantener, lo que va en contra de uno de los principios más conocidos de la programación: la separación entre contenido y presentación.
Existe una solución especial para este problema: la función StringFormat.
El mismo esquema se aplica a otra función de la API de MQL5: PrintFormat.
string StringFormat(const string format, ...)
La función convierte argumentos de tipo integrado arbitrarios en una cadena según el formato especificado. El primer parámetro es la plantilla de la cadena que se desea preparar, en la que se indican de forma especial los lugares para insertar variables y se determina el formato de su salida. Estos comandos de control pueden intercalarse con texto sin formato, que se copia en la cadena de salida sin cambios. Los siguientes parámetros de función, separados por comas, enumeran todas las variables en el orden y los tipos que se han reservado para ellas en la plantilla.
Interacción de los argumentos de cadena de formato y StringFormat
Cada punto de inserción de una variable en una cadena se marca con un especificador de formato, el carácter '%', tras el cual se pueden especificar varios ajustes.
La cadena de formato se analiza de izquierda a derecha. Cuando se encuentra el primer especificador (si existe), el valor del primer parámetro después de la cadena de formato se convierte y se añade a la cadena resultante de acuerdo con la configuración especificada. El segundo especificador hace que el segundo parámetro se convierta y se imprima, y así sucesivamente, hasta el final de la cadena de formato. Todos los demás caracteres del patrón entre los especificadores se copian sin cambios en la cadena resultante.
La plantilla puede no contener ningún especificador, es decir, puede ser una simple cadena. En este caso, debe pasar un argumento ficticio a la función además de la cadena (el argumento no se colocará en la cadena).
Si desea mostrar el signo de porcentaje en la plantilla, deberá escribirlo dos veces seguidas: %%. Si el signo % no se duplica, los caracteres siguientes a % se analizan siempre como un especificador.
Un atributo obligatorio de un especificador es un símbolo que indica el tipo esperado y la interpretación del siguiente argumento de la función. Llamemos condicionalmente a este símbolo T. Entonces, en el caso más simple, un especificador de formato se parece a %T.
De forma generalizada, el especificador puede constar de varios campos más (los campos opcionales se indican entre corchetes):
%[Z][W][.P][M]T
Cada campo cumple su función y adopta uno de los valores permitidos. A continuación, vamos a examinar gradualmente todos los campos.
Tipo T
Para los números enteros se pueden utilizar los siguientes caracteres como T, con una explicación de cómo se muestran los números correspondientes en la cadena:
- c - carácter Unicode
- C - carácter ANSI
- d, i - decimal con signo
- o - octal sin signo
- u - decimal sin signo
- x - hexadecimal sin signo (minúsculas)
- X - hexadecimal sin signo (mayúsculas)
Recordemos que, según el método de almacenamiento interno de datos, los tipos enteros también incluyen los tipos MQL5 integrados datetime, color, bool y las enumeraciones.
Para los números reales, los siguientes símbolos son aplicables como T:
- e - formato científico con exponente ('e' minúscula)
- E - formato científico con exponente ('E' mayúscula)
- f - formato normal
- g - análogo de f o e (se elige la forma más compacta)
- G - análogo de f o E (se elige la forma más compacta)
- a - formato científico con exponente, hexadecimal (minúsculas)
- A - formato científico con exponente, hexadecimal (mayúsculas)
Por último, sólo hay una versión del carácter T disponible para las cadenas: s.
Tamaño de los enteros M
Para los tipos enteros, se puede especificar además de forma explícita el tamaño de la variable en bytes anteponiendo a T uno de los siguientes caracteres o combinaciones de ellos (los hemos generalizado bajo la letra M):
- h - 2 bytes (short, ushort)
- l (L minúscula) - 4 bytes (int, uint)
- I32 (i mayúscula) - 4 bytes (int, uint)
- ll (dos L minúsculas) - 8 bytes (long)
- I64 (i mayúscula) - 8 bytes (long, ulong)
Anchura W
El campo W es un número decimal no negativo que especifica el número mínimo de espacios de caracteres disponibles para el valor formateado. Si el valor de la variable cabe en menos caracteres, se añade el número correspondiente de espacios a la izquierda o a la derecha. Se selecciona el lado izquierdo o derecho en función de la alineación (véase la bandera '-' en el campo Z). Si la bandera '0' está presente, se añade el número correspondiente de ceros delante del valor de salida. Si el número de caracteres de salida es mayor que la anchura especificada, se ignora el ajuste de anchura y el valor de salida no se trunca.
Si se especifica un asterisco '*' como anchura, entonces la anchura del valor de salida debe especificarse en la lista de parámetros pasados. Debe ser un valor del tipo int en la posición que precede a la variable que se está formateando.
Precisión P
El campo P también contiene un número decimal no negativo y siempre va precedido de un punto '.'. Para T entero, este campo especifica el número mínimo de dígitos significativos. Si el valor cabe en menos dígitos, se le anteponen ceros.
Para los números reales, P especifica el número de posiciones decimales (por defecto es 6), excepto para los especificadores g y G, para los que P es el número total de dígitos significativos (mantisa y decimal).
Para una cadena, P especifica el número de caracteres que se van a mostrar. Si la longitud de la cadena supera el valor de precisión, la cadena se mostrará truncada.
Si se especifica el asterisco '*' como la precisión, se trata de la misma manera que para la anchura pero controla la precisión.
La anchura y/o precisión fijas, junto con la alineación a la derecha, permiten mostrar los valores en una columna ordenada.
Banderas Z
Por último, el campo Z describe las banderas:
- - (menos) - alineación a la izquierda dentro de la anchura especificada (en ausencia de la bandera, se realiza la alineación a la derecha);
- + (más) - visualización incondicional de un signo+' o '-' antes del valor (sin esta bandera sólo se muestra '-' para los valores negativos);
- 0 - se añaden ceros antes del valor de salida si es menor que la anchura especificada;
- (espacio) - se antepone un espacio al valor visualizado si es con signo y positivo;
- # - controla la visualización de prefijos de números octales y hexadecimales en los formatos o, x o X (por ejemplo, para el formato x se añade el prefijo «0x» delante del número visualizado; para el formato X, el prefijo «0X»), punto decimal en números reales (formatos e, E, a o A) con parte fraccionaria cero, y algunos otros matices.
Puede obtener más información sobre las posibilidades de la salida formateada a una cadena en la documentación.
El número total de parámetros de función no puede ser superior a 64.
Si el número de argumentos pasados a la función es mayor que el número de especificadores, se omiten los argumentos adicionales.
Si el número de especificadores en la cadena de formato es mayor que los argumentos, el sistema intentará mostrar ceros en lugar de los datos que faltan, pero se incrustará una advertencia de texto («parámetro de cadena que falta») para los especificadores de cadena.
Si el tipo del valor no coincide con el tipo del especificador correspondiente, el sistema intentará leer los datos de la variable de acuerdo con el formato y mostrará el valor resultante (puede tener un aspecto extraño debido a una mala interpretación de la representación de bits interna de los datos reales). En el caso de cadenas, puede que se incluya una advertencia («no cadena pasada») en el resultado.
Probemos la función con el script StringFormat.mq5.
En primer lugar, vamos a probar diferentes opciones para T y el especificador de tipo de datos.
PRT(StringFormat("[Infinity Sign] Unicode (ok): %c; ANSI (overflow): %C",
|
Aquí se representan tanto los especificadores correctos como los incorrectos (los incorrectos aparecen en segundo lugar en cada instrucción y están marcados con la palabra «desbordamiento», ya que el valor pasado no cabe en el tipo de formato).
Esto es lo que ocurre en el registro (las interrupciones de líneas largas aquí y abajo se han hecho para su publicación):
StringFormat(Plain string,0)='Plain string'
|
Todas las instrucciones siguientes son correctas:
PRT(StringFormat("ulong (ok): %I64u", ULONG_MAX));
|
El resultado de su trabajo se muestra a continuación:
StringFormat(ulong (ok): %I64u,ULONG_MAX)=
|
Veamos ahora los distintos modificadores.
Con la alineación a la derecha (por defecto) y un ancho de campo fijo (número de caracteres), podemos utilizar diferentes opciones para rellenar la cadena resultante a la izquierda: con un espacio o con ceros. Además, para cualquier alineación, puede activar o desactivar la indicación explícita del signo del valor (de modo que no sólo se muestre menos para negativo, sino también más para positivo).
PRT(StringFormat("space padding: %10i", SHORT_MAX));
|
Obtenemos lo siguiente en el registro:
StringFormat(space padding: %10i,SHORT_MAX)='space padding: 32767'
|
Para alinear a la izquierda, debe utilizar la bandera '-' (menos), la adición de la cadena a la anchura especificada se produce a la derecha:
PRT(StringFormat("no sign (default): %-10i", SHORT_MAX));
|
Resultado:
StringFormat(no sign (default): %-10i,SHORT_MAX)='no sign (default): 32767 '
|
Si es necesario, podemos mostrar u ocultar el signo del valor (por defecto, sólo se muestra el signo menos para los valores negativos), añadir un espacio para los valores positivos, y así garantizar el mismo formato cuando necesite mostrar variables en una columna:
PRT(StringFormat("default: %i", SHORT_MAX)); // standard
|
Este es el aspecto que tiene en el registro:
StringFormat(default: %i,SHORT_MAX)='default: 32767'
|
Ahora comparemos cómo afectan la anchura y la precisión a los números reales.
PRT(StringFormat("double PI: %15.10f", M_PI));
|
Resultado:
StringFormat(double PI: %15.10f,M_PI)='double PI: 3.1415926536'
|
Si no se especifica la anchura explícita, los valores se emiten sin rellenar con espacios.
PRT(StringFormat("double PI: %.10f", M_PI));
|
Resultado:
StringFormat(double PI: %.10f,M_PI)='double PI: 3.1415926536'
|
El establecimiento de la anchura y la precisión de los valores mediante el signo '*' y basándose en argumentos de función adicionales se realiza del siguiente modo:
PRT(StringFormat("double PI: %*.*f", 12, 5, M_PI));
|
Tenga en cuenta que se pasan 1 o 2 valores de tipo entero antes del valor de salida, según el número de asteriscos '*' del especificador: puede controlar la precisión y la anchura por separado o ambas juntas.
StringFormat(double PI: %*.*f,12,5,M_PI)='double PI: 3.14159'
|
Por último, veamos algunos errores de formato comunes.
PRT(StringFormat("string: %s %d %f %s", "ABCDEFGHIJ"));
|
La primera instrucción tiene más especificadores que argumentos. En otros casos, los tipos de especificadores y valores pasados no coinciden. Como resultado, obtenemos la siguiente salida:
StringFormat(string: %s %d %f %s,ABCDEFGHIJ)=
|
Disponer de una única cadena de formato en cada llamada a la función StringFormat permite utilizarla, en especial, para traducir la interfaz externa de programas y mensajes a distintos idiomas: basta con descargar y sustituir en StringFormat varias cadenas de formato (preparadas de antemano) en función de las preferencias del usuario o de la configuración del terminal.