15- Cadenas de caracteres en Arduino.

Cadenas y matrices de caracteres Arduino.

Ya nos hemos empapado con los símbolos en la lección sobre tipos de datos. Permítame recordarle: un símbolo es una variable (o constante) del tipo char y almacena el código de la letra en la tabla de símbolos. Creado para la conveniencia del programador, para que pueda trabajar no con códigos, sino con caracteres legibles. Como en la vida, los símbolos se combinan en palabras, aquí se llaman cadenas (string). Tenemos dos conjuntos de herramientas para trabajar con ellos: cadenas regulares (matrices de caracteres) y cadenas de caracteres.

  • Una matriz de caracteres es simplemente una matriz de datos de tipo char, ya hemos hablado de matrices recientemente y debería comprender de qué estamos hablando. Características clave: el tamaño máximo de una matriz de cadenas debe conocerse de antemano y se puede hacer referencia a cada elemento de dicha cadena mediante corchetes. Cualquier texto incluido explícitamente en «doble comillas», es percibido por el programa como una serie de caracteres.
    • Información para quienes lean la lección sobre punteros: al ser una matriz ordinaria, una cadena es un puntero a su primer elemento (es decir, el programa solo sabe exactamente dónde comienza la cadena). Las funciones integradas para trabajar con cadenas están guiadas por el carácter nulo, que siempre está al final de la cadena. Así es como se determina la longitud de la cadena: desde el principio hasta el carácter nulo.
  • La principal diferencia entre una cadena de caracteres y una matriz de caracteres es que una cadena es una matriz dinámica, que no necesita especificarse en tamaño; puede cambiar durante la operación del programa. Además, una cadena no es solo un tipo de datos, sino un objeto de una clase muy poderosa de la biblioteca del mismo nombre. String, que se conecta automáticamente al código y agrega una gran cantidad de herramientas útiles para trabajar con texto: dividir, recortar, buscar y reemplazar, etc. Una cadena se puede crear a partir de cualquier tipo de datos y volver a convertirlos en casi todos.

Cadenas de strings.

Echemos un vistazo a un gran ejemplo que dejará claro cómo declarar una cadena y cómo trabajar con ella, además de tener en cuenta algunas sutilezas:

  String string0 = "Hello cadena" ;             //  palabras entre comillas
  String cadena1 = String ( "lol" ) + Cadena ( "kek" ) ; // suma de dos líneas
  String string2 = String ( 'a' ) ;                // cadena de caracteres entre comillas simples
  String string3 = String ( "Esto es una cadena" ) ;   // convertir cadena a cadena
  String string4 = String ( string3 + "más" ) ;  // agrega a string3 texto entre comillas
  String string5 = String ( 13 ) ;                 // convertir de número a String
  String string6 = String ( 20, DEC ) ;            // convertir de un número especificando una base (decimal)
  String string7 = String ( 45, HEX ) ;            // convertir de un número que indica la base (hexadecimal)
  String string8 = String ( 255, BIN ) ;           // convertir de un número que especifica la base (binario)
  String string9 = String ( 5.698 , 3 ) ;           // de flotante indicando el número de decimales (aquí 3)
  // las cadenas se pueden agregar entre sí
  string string10 = string0 + string1;         // string10 es igual a Hello String lol kek
 // puede formar un nombre a partir de piezas, por ejemplo, para trabajar con archivos. 
#define NAME "speed"
#define TYPE "-log"
#define EXT ".txt"
  // al agregar, es suficiente especificar String 1 vez para la primera línea
 String filename = String(NAME) + TYPE + EXT; // el nombre del archivo será igual a speed-log.txt
  // el acceso a un elemento de una cadena funciona con el mismo mecanismo que una matriz
  string0 [ 0 ] = 'a' ; // comillas simples, porque ¡Asigna un SÍMBOLO ÚNICO!
  // ahora en lugar de Hello cadena tenemos aello cadena

Como habrá notado, las cadenas se pueden declarar de muchas maneras y, literalmente, puede agregar cadenas como números con el operador +… Ya dije que las cadenas son objetos de clase String, y esta clase tiene una gran cantidad de métodos para trabajar con cadenas, a continuación los veremos todos con algunos ejemplos. Pero primero, recuerde esto: las cadenas son una herramienta muy pesada, muy lenta y que ocupan mucha memoria: la mera presencia de cadenas (de una o más) en el firmware toma + 5% de la memoria Flash. Para proyectos pequeños, esto no da miedo, la memoria siempre estará a granel. Además, el uso incorrecto de cadenas puede provocar la fragmentación de la RAM y la congelación del programa, lea más a continuación.

Herramientas para cadenas de caracteres, String.

Además del conjunto de métodos, la librería String tiene varios operadores sobrecargados, gracias a los cuales podemos:

  • Tratar los elementos String como matrices: myString [ 2 ] = ‘a’ ;
  • Compare las cadenas de String entre sí: if ( myString1 == myString2 )
  • Comparar cadenas de String con matrices de caracteres: if ( myString1 == «kek» )
  • Inicialice cadenas de String con cualquier tipo de datos numérico, carácter, matriz de caracteres y matriz de caracteres dentro de la macro F (): String myString = 10.0 ;
  • Agregue cualquier tipo de datos numérico, carácter o matriz de caracteres a una cadena: myString + = 12345;

En todos los casos anteriores, podemos asumir que los datos «se convertirán ellos mismos en una Cadena» e interactuarán con la Cadena a la izquierda del operador. Esto no es del todo cierto, pero es suficiente para comprenderlo.

Entonces, hay métodos para trabajar con cadenas. Como todos los métodos, se aplican a sus objetos (a cadenas) mediante un punto. En los ejemplos siguientes, la cadena se denomina myString.

Ver algunos métodos en la documentación de Arduino sobre la clase String:

Longitud de la línea

Un pequeño comentario sobre la longitud de la cadena: a diferencia de la matriz charsolo puede averiguar la longitud de una cadena String utilizando el método longitud () (porque una cadena es una matriz dinámica y sizeof () ejecutado en tiempo de compilación):

String textString = "Hello";
sizeof(textString);   // devolverá 6 
textString.length();  // devolverá 5

Medidas de precaución

Una cadena es esencialmente una matriz dinámica; su tamaño puede cambiar durante el programa. Esto es muy conveniente, pero también muy peligroso, porque la memoria puede fragmentarse, agotarse, etc. Veamos algunos principios básicos del trabajo seguro con cadenas:

  • Si necesita pasar un String-string a una función, hágalo por referencia. Esto evitará que el programa duplique un dato, porque debido a una línea lo suficientemente grande, la RAM puede agotarse y el programa se congelará. 
  • Ejemplo: void someFunc (String &str); – la función toma una referencia a una cadena. Esto no afectará el uso de la función, pero cuando se llame, ¡no se creará una copia de la cadena!
  • No llame a la conversión a String innecesariamente, ¡la biblioteca lo hará por usted! Por ejemplo, no necesitas escribir myString + = String ( valor );, Mas simple myString + = valor;… Al crear una cadena larga agregando nuevos datos «pieza por pieza», esto le evitará la fragmentación de la memoria.
  • Envuelva trozos de código de trabajo duro entre { llaves }: las cadenas de cadenas creadas localmente se eliminarán de la memoria inmediatamente después del cierre }, que puede evitar la fragmentación y el desbordamiento de la memoria.

Matrices de caracteres.

Las matrices de caracteres, también conocidas como “char array”, son otra forma de trabajar con datos de texto. Esta opción tiene muchas menos posibilidades de trabajar con texto, pero ocupa menos espacio en la memoria (la biblioteca no se usa String) y funciona mucho más rápido. Se aplican las mismas reglas a las matrices de caracteres que a las matrices regulares. Considere un ejemplo en el que declaramos matrices de caracteres de diferentes maneras:

// declara una matriz y establece el texto con símbolos
// el compilador calculará el tamaño
char helloArray [] = { 'H' , 'e' , 'l' , 'l' , 'o' } ;
// pero las matrices de cadenas se pueden declarar así:
char helloArray2 [] = "¡Hola!" ;
// puede declarar una matriz más grande que el texto inicial.
// Habrá espacio libre para otro texto en el futuro
char helloArray3 [ 100 ] = "¡Hola!" ;
// así es como puedes declarar una cadena larga
char longArray [] = "El zorro marrón veloz"
                   "salta sobre el perro perezoso" ;

Puede trabajar con elementos de cadena como con matrices:

helloArray2 [ 0 ] = 'L' ;        // reemplazar el elemento
// ahora helloArray2 == "¡Lello!"

A diferencia de las cadenas, las matrices de caracteres no pueden:

helloArray3 + = textArray; // agregar
textArray = "nuevo texto" ;   // asigna una STRING después de la inicialización
if ( helloArray == helloArray2 ) ; // comparar 

Hay funciones especiales para esto, de las que hablaremos a continuación.

Un punto importante: cualquier «texto entre comillas» en el código del programa es una matriz de caracteres, o más bien const char *: un puntero, ya que es una matriz, y una constante, porque el texto se ingresó antes de la compilación y no puede cambiar mientras el programa se está ejecutando. Una matriz de caracteres se puede convertir en una cadena de caracteres (como al principio de la lección), pero por sí sola no tiene nada que ver con la cadena.

Cuando una matriz de caracteres se inicializa con «texto entre comillas», se crea una matriz con un tamaño de 1 más que el número de caracteres del texto: el compilador agrega un carácter nulo al final de la cadena NUL, gracias a lo cual varias herramientas de cadena verán la longitud de la cadena: desde el primer carácter hasta NUL.

Longitud de la cadena de «matriz de caracteres»

Para determinar la longitud del texto, puede utilizar el operador strlen () que devuelve el número de caracteres de la matriz. Comparemos su trabajo con el operador sizeof ():

char textArray [ 100 ] = "Mundo" ;
sizeof ( textArray ) ; // devolverá 100
strlen ( textArray ) ; // devolverá 5

Aquí operador sizeof () devolvió el número de bytes ocupados por la matriz. Declaré específicamente la matriz con un tamaño mayor que el texto que contiene. Y aquí esta el operador strlen () contó y devolvió el número de caracteres que van desde el principio de la matriz hasta el carácter cero (NUL) al final del texto (excluyéndolo). Y aquí está el resultado al inicializar sin especificar el tamaño de la matriz:

char text [] = "Hola" ;
strlen ( text ) ;  // devolverá 5 (caracteres "legibles")
sizeof ( text ) ;  // devolverá 6 (bytes)

Matriz de cadenas

Una característica muy poderosa de las matrices de caracteres es la capacidad de crear una matriz con varias cadenas y referirse a ellas por número. Se parece a esto:

// declara una matriz de cadenas
const char * names []   = { 
  "Periodo" ,   // 0
  "Trabajo" ,     // 1
  "Stop" ,     // 2
} ;
// mostrar el tercer elemento
Serial.println(names[2]); // imprimirá Stop

Este método de trabajar con cadenas es bueno porque las cadenas se almacenan bajo números, y esto es extremadamente conveniente cuando se trabaja con pantallas y, en particular, cuando se crea un menú de texto: casi todas las bibliotecas de pantallas pueden mostrar una matriz de caracteres con un comando.

La macro F()

Las cadenas (matrices de caracteres) son una carga muy pesada, porque el texto de una cadena se almacena en la RAM del microcontrolador y no hay tanta. Existe una herramienta lista para usar que le permite almacenar convenientemente datos de texto en la memoria Flash del microcontrolador. Este método es bueno para enviar datos de texto fijo, por ejemplo, a un monitor de puerto serie o pantalla:

Serial.println(F("Hello, World!"));

La cadena «¡Hola, mundo!» se escribirá en la memoria Flash y no ocupará 14 bytes (13 + cero) en la RAM.

Cadenas en la memoria

Las «cadenas» en una matriz de cadenas también se almacenan en la RAM, y la anterior macro F () no aplica para ellos. Es decir, este código dará lugar a un error:

const char * names []   = { 
  F ( "Periodo" ) ,   // 0
  F ( "Trabajo" ) ,     // 1
  F ( "Stop" ) ,     // 2
} ;

¿Cómo ser? Podemos almacenar una serie de cadenas en PROGMEM, memoria de programa del microcontrolador, Flash. Esta construcción se puede utilizar como plantilla:

// declaramos nuestras "cadenas"
const char array_1[] PROGMEM = "Period";
const char array_2[] PROGMEM = "Work";
const char array_3[] PROGMEM = "Stop";
// declara la tabla de enlaces
const char* const names[] PROGMEM = {
  array_1, array_2, array_3,
};
void setup() {
  Serial.begin(9600);
  char arrayBuf[10];  //crea buffer
   // copiar a arrayBuf usando el strcpy_P incorporado
  strcpy_P(arrayBuf, (char*)pgm_read_word(&(names[1])));
  Serial.println(arrayBuf); // muestra la salida
}

Sí, es difícil y engorroso, pero con una gran cantidad de datos de texto, ¡esto puede salvar el proyecto! Por ejemplo, al crear un dispositivo con un menú de texto en la pantalla. Para obtener más información sobre cómo almacenar cadenas en PROGMEM, lea el tutorial de PROGMEM.

Herramientas de matrices de caracteres

Hay funciones listas para usar que le permiten convertir varios tipos de datos en cadenas:

  • itoa ( int_data, str, base ) – escribe una variable de tipo En t int_data para encadenar str con base * base .
  • ltoa (long_data, str, base )  – escribe una variable de tipo largo long_data para encadenar str con base * base .
  • ultoa ( unsigned_long_data, str, base )  – escribe una variable de tipo  unsigned_long_data a str con * base .
  • dtostrf(float_data, width, dec, str) – escribe una variable de tipo float_data en cadena str con el número de caracteres de width y decimales dec .

* Nota: la base es la base del sistema de cálculo, como cuando se envía a SerialPort:

  • DIC – decimal
  • BIN – binario
  • OCT – octal
  • HEX – hexadecimal
float x = 12,123 ;
char str [ 10 ] = "" ;
dtostrf ( x, 4, 2, str ) ;
// aquí str == "12.12"
int y = 123;
itoa ( y, str, DEC ) ;
// aquí str == "123"

Por el contrario, puede convertir cadenas en datos numéricos (la función devolverá el resultado):

  • atoi ( str )– convertir str en int
  • atol ( str )– convertir str en long
  • atof ( str )– convertir str en float
float x;
char str[10] = "12.345";
x = atof(str);
// aqui x == "12.345"

¡Atención! Las funciones de conversión que funcionan con el tipo flotante son muy pesadas: ¡su «conexión» ocupa ~ 2 KB de memoria Flash! Evite usarlos en un proyecto grande tanto como sea posible. Para convertir, puede crear su propia función, se pueden encontrar opciones casi listas para todos los tipos de datos en el Arduino Print.cpp estándar ( enlace al archivo en Arduino github).

Las matrices de caracteres no son tan simples como parecen: la biblioteca cstring estándar mejora enormemente sus capacidades. El uso de todos los trucos disponibles para trabajar con matrices de caracteres le permite eliminar por completo las cadenas de caracteres pesadas de su código y hacerlo más ligero, rápido y óptimo. Repasaremos brevemente las herramientas más importantes de manipulación de cadenas en cstring .

Un punto importante: la biblioteca funciona con cadenas como con punteros, y muchas funciones devuelven un puntero como resultado. ¿Cómo entender esto si no ha leído la lección sobre punteros y / o el tema es demasiado complejo? El puntero es el primer carácter de la línea, el trabajo con la línea comenzará con él. El último carácter es nulo NUL carácter, y para el programa, la cadena existe exactamente en este rango. Si alguna función devuelve un puntero a un carácter específico en una cadena, de hecho, devuelve una parte de la cadena desde este carácter hasta el final de la cadena. Por ejemplo, buscábamos el símbolo « , » en la línea «¡Hola Mundo!». El programa nos devolverá un puntero a esta coma, de hecho será una parte de la misma cadena que contiene», mundo!»… El «comienzo» de la línea simplemente cambiará.

Herramientas de cstring

Puede ver una colección de métodos de cstring en este tutorial.


Deja un comentario