Como ha sucedido con otros lenguajes, C++ también ha evolucionado. Ha madurado mucho desde aquellos códigos que programábamos hace años y se nota. Por un lado, podemos pensar que al sumar abstracción en ciertos aspectos nos separa de la máquina y hace nuestro código más lento. Suma comprobaciones, hace más callbacks y en definitiva, una sencilla tarea que completaba en pocos cientos de operaciones, ahora son pocos miles. Aunque en su favor, podemos decir que aquello que programábamos en 15 o 20 líneas de código se ha reducido a una o dos, reduciendo así los puntos de ruptura, posibles bugs y calentamientos de cabeza futuros.
Eso sí, si queremos siempre podemos volver a los orígenes. Aunque será mucho más seguro utilizar estas nuevas formas de trabajo, si estamos seguros de lo que hacemos y queremos que el rendimiento de nuestro programa sea mucho mayor, podemos hacerlo. Es más, muchos programadores, todavía a 2017, suelen preferir programar en C cuando quieren ver un rendimiento superior en sus desarrollos.
He hecho esta pequeña recopilación de chuletas para realizar ciertas operaciones en C++ moderno, que podréis compilar si contáis con soporte para C++11 o superior. He decidido incluir los programas completos, no sólo la parte en que quiero hacer hincapié , para que podáis copiar, pegar y compilar, y así lo veis y lo sentís como lo hago yo.
Nota: Tenemos Glib, Boost y cientos de bibliotecas de C++ que pueden hacer cosas muy chulas (y lo que están preparando para C++17, que hasta finales de año no estará listo y que podremos disfrutar en su totalidad en 2018), pero en este post he querido centrarme en lo que podemos hacer con las herramientas que nos da el lenguaje sin utilizar bibliotecas de terceros
Pasar una cadena completa a mayúsculas/minúsculas
Recordemos los tiempos en los que en C pasábamos a mayúsculas y minúsculas una cadena. La recorríamos por completo y evaluábamos para cada letra cuál sería su mayúscula. No empezaremos con una novedad del lenguaje (podremos compilar con compiladores antiguos, pero sólo estamos abriendo boca). Cuando sólo tenemos un alfabeto inglés podemos hacer lo siguiente:
Utilizaremos transform() para que recorra la cadena y llame a toupper con cada uno de los caracteres, lo típico, toupper de cctype. Y de la misma manera que llamamos a toupper, lo podemos hacer con tolower para las minúsculas.
Mayúsculas y minúsculas en un mundo plurilingüe
Pero, a estas alturas, un ordenador tiene que poder hablar inglés, español, francés y hasta ruso, y todos tenemos derecho a nuestras letras mayúsculas y minúsculas. Ahora tenemos que poner en práctica el uso de la locale con la que queremos hacer la transformación. Eso sí, en lugar de un string, también tendremos que utilizar un wstring, porque en codificaciones como UTF-8, un solo byte no siempre equivale a un carácter. Necesitaremos alguien que sea capaz de leer caracteres de nuestros bytes (y programarlo nosotros es horrible). Por ejemplo:
O también:
O si queremos que el código quede más limpio, podemos sustituir esta línea tan larga por:
Caracteres al principio y al final
Para saber el primer carácter de una cadena s, podíamos utilizar:
- s[0] : Primera posición del string, que está muy bien, pero damos una posición numérica y no decimos de forma explícita "lo primero que hay".
- *s.begin() : El valor del iterador al primer elemento. Aunque como programadores de C++ y no de C, ver muchos asteriscos nos agobia...
Para la última letra, ya lo tendríamos más complicado, tendríamos que usar:
- *(s.end()-1) : El iterador al último carácter de la cadena será el terminador, pues resolvemos el puntero al carácter anterior. Esto queda un poco feo, hay asteriscos y hacemos cuentas con punteros. No es muy seguro.
- s[s.length()-1] : Aunque no tenemos asteriscos. pedimos la longitud de la cadena, lo que puede que implica una llamada, aunque muchos compiladores están muy bien optimizados.
En C++11 tenemos front():
Si queremos, podemos añadir o eliminar caracteres al final con las funciones push_back() y pop_back, así:
Comprobar que una cadena contiene un número
Vamos, lo que queremos hacer es saber si el contenido de una cadena, es numérico. Para ello, debemos asegurarnos de que todos los caracteres de la misma son números:
Sí, precioso, pero ¿qué pasa con los negativos? Bueno, podemos modificar isNumeric un poco para comprobar el primer carácter de la cadena:
Bueno, ya cubrimos los números enteros, y si comprobamos esto, podremos ver que daría igual la longitud de la cadena, es decir, pueden ser números muy grandes, números que desbordarían un long, pero al menos podemos comprobar su validez.
Pero, ¿qué pasaría con los números hexadecimales? ¿o los binarios? Aquí podemos especificar la base:
Eso sí, ¿qué pasaría con los decimales? Nos estamos poniendo caprichosos ya, pero podríamos comprobar la existencia de la coma decimal y ver si hay más números a partir de ahí:
Comprobar que una cadena contiene un número II
Bueno, y como es C++ moderno, tenemos un extra muy interesante, ¡expresiones regulares! Pues nada, creemos una expresión regular para comprobar cadenas numéricas. Total, en otros lenguajes lo hacemos sin miedo, aunque, ¡cuidado! El tamaño del ejecutable puede crecer mucho.
Convertir de cadena a número
Si pensamos en C. Aunque tenemos atoi(), atod(), strtol() y demás familiares y derivados, debemos recorrer la cadena por completo y analizar carácter a carácter si es numérico y si lo es, darle un valor con respecto a la posición que ocupa dentro de la cadena. También es cierto que atoi() y demás pueden ser inseguras ya que C no comprueba tamaños de cadenas y si no controlamos el dato, los resultados pueden ser inesperados. Es más, atoi() es la función que nunca se queja, tanto si le pasas una cadena que no contiene un número (que devuelve 0) como si le pasas una cadena muy larga con números muy largos que desbordan la variable (ok, se desborda, devuelve lo que quiere, pero no falla). Aunque strtol() / strtod() nos dan más control, pero no son estilo C++.
En C++ podemos hacer lo siguiente:
Eso sí, stoi(), stol() y stoll() funcionan con strtol() y strtoll() por detrás, para eso de tener excepciones. Si queremos velocidad, seguramente nos vaya mejor utilizando las funciones de C, pero si queremos estilo y excepciones, las funciones de C++ nos vendrán bien.
Al igual que stoi(), tenemos también stof() para float, stod() para double y stold() para long double. Eso sí, ¿quieres números extremadamente grandes? Puedes probar GMP, que tiene una interfaz para C++. Aquí tienes un ejemplo de uso en C.
Convertir de número a cadena
En C, los que aprendimos hace mucho tiempo teníamos una función, itoa(), pero no era ANSI-C. De hecho, yo la conocí en Turbo C, y no la vemos en muchos compiladores. Aunque tenía el mismo problema de siempre: no hay comprobación del tamaño de cadenas, porque en C no es muy fácil.
De todas formas, aunque en C terminábamos haciendo las conversiones número-cadena con sprintf() o snprintf() (mejor esta segunda), antiguamente en C++, podíamos utilizar sstream, pero muchísimas veces da una pereza enorme y nos obligaba a tener wrappers a meno con estas funciones siempre cerca. Pero desde C++11 tenemos una nueva función para sacarnos las castañas del fuego, to_string(), fácil y preciosa, a la que le da igual que le pasemos un int, double, uint16_t, char o lo que queramos, siempre que sea numérico:
Dividir cadenas (split, tokenize...)
Una ardua tarea es siempre separar una cadena en varios trozos, o bien por palabras, o con algún carácter comodín, o cualquier otro criterio. En C tenemos strtok(), con la inseguridad que conlleva, además porque en un mundo multihilo puede darnos problemas (y por eso vino su hermano strtok_r()), pero es otro cantar. En C++ tenemos a nuestra disposición multitud de estructuras de datos, y algo que hacemos en otros lenguajes como Javascript de forma muy fácil (un split de toda la vida), en C++ se nos puede atravesar un poco. Y tenemos varias opciones:
Separar palabras por espacios
En realidad, por los caracteres que utiliza cin para separar elementos. Lo haremos con istream_iterator y meteremos los valores en un vector. Lo bueno es que podemos utilizar strings, int, o cualquier cosa que podamos utilizar en un stream:
...¡¡con expresiones regulares!!
Desde que el soporte de expresiones regulares es nativo. C++ se ha convertido en un lenguaje adulto. Es cierto que antes podíamos hacer multitud de cosas, y que seguro que muchos aprovechamos ese soporte de expresiones regulares para complicarnos la vida y hacer cosas menos eficientes, pero se agradece mucho en ocasiones y seguro que simplifica nuestros códigos.
...separadas con un carácter
Otro ejemplo más, puede ser este:
Aunque si queremos utilizar las nuevas herramientas que nos da C++ como lambdas o iteradores, que pueden llegar a ser muy interesantes, podemos utilizar esto:
En este punto, dependiendo de nuestras necesidades podríamos utilizar find() y derivados. Si os apetece también podemos recurrir a strtok() de C (aunque si no tenemos que hacer varios millones de operaciones como esta por segundo, mejor nos quedamos con las herramientas de C++ que serán algo más seguras y podríamos implementarlas fácilmente con wstring. (Si os queréis aventurar con strtok(), mejor utilizar strtok_r(), porque en un mundo multihilo sería normal que dos hilos llamasen a strtok() al mismo tiempo, y eso puede causarnos problemas.
Reemplazar subcadenas dentro de cadenas
Varios ejemplos de esto los encontramos en estos posts: Reemplazar cadenas de texto en C++ (string y Glib::ustring) y Reemplazar cadenas en C++, esta vez desde un map, para múltiples sustituciones.
Rot13
Una manera sencilla de hacer cifrados, pero no voy a eso. Es un cifrado tremensamente fácil de romper, pero transform() puede dar mucho juego, entre otras cosas, hacer un rot13 se vuelve muy sencillo:
Eliminar espacios de una cadena
¡En una sola línea de código, con posibilidad de eliminar no sólo los espacios sino cualquier carácter que cumpla una determinada condición. Con remove_if():
Y, en lugar de ::isspace (de cctype) podemos utilizar un lambda con una función que determine qué carácter eliminamos (no removemos), en dicha función podrán entrar otros delimitadores.
Y ya puestos, probemos con palíndromos
Este es un típico ejercicio de programación que se hace en todas las escuelas y universidades (bueno, seguro que en todas no, pero en la mayoría). Se utiliza para enseñar a los alumnos a utilizar bucles y cadenas de caracteres. Aunque aquí daremos un paso más en la abstracción y como antes, con una línea comprobaremos si la cadena es palindrómica o no. O, lo que es lo mismo, que podemos leerla tanto de izquierda a derecha como de derecha a izquierda:
Esto sólo es el principio
Estas son unas pocas operaciones con cadenas en C++. ¿Se te ocurren algunas más? Seguro que sí, y podrán dar para otro post con muchos más ejemplos. Déjame un comentario con tus sugerencias.
Foto principal: Katie Chase