Ir al contenido principal

Array en C. Parte 3: Cadenas de caracteres...

En esta ocasión hablaremos acerca de las cadenas de caracteres en C (también llamadas strings) que son vectores de tipo char y que tendrán muchas particularidades respecto de los array que vamos viendo.

Una de las primeras particularidades que veremos es que toda cadena termina en el caracter nulo '\0' que en código ASCII vale 0, o también puede terminar en el entero 0 (sin comillas). Este caracter nos permitirá conocer el final de una cadena y modificarla sin problemas mientras respetemos la capacidad de la misma.

Declarando una cadena de caracteres:

Declarar una cadena no representa nada nuevo respecto a lo que ya vimos en entradas anteriores, ya que lo haremos como declararíamos un vector de tipo char.



La capacidad de la cadena es la cantidad de caracteres que podrá almacenar nuestra cadena. Si queremos guardar una determinada palabra o frase hay que darle una capacidad adecuada para ello y recordar que el caracter nulo ocupa el espacio de un elemento en ella.

Es importante recordar ese detalle y que la longitud de la cadena que guardemos no supere la capacidad de la misma ya que si no podríamos tener errores de todos los colores del arcoiris incluso sin que el compilador se de cuenta de ello.

Inicializando una cadena de caracteres:

Inicializaremos una cadena de caracteres de la siguiente manera:





En este ejemplo, no utilizamos la capacidad máxima de nuestra cadena ya que la palabra que almacenamos tiene una longitud menor. Si recordamos cómo se almacenaban en memoria los elementos de un vector y hacemos lo mismo para esta cadena de ejemplo tendríamos algo así:


Otra forma de inicializar nuestra cadena, sería hacerlo como vimos para vectores, siempre recordando colocar el caracter nulo al final y colocar cada caracter entre comillas simples:


O incluso si seguimos la lógica que nos indica que es un vector de caracteres podríamos inicializar cada elemento de nuestra cadena de acuerdo a su índice:

Aunque obviamente esto no es lo que recomendamos para inicializar una cadena es posible hacerlo así y hasta lo usaremos en algunas ocasiones en que necesitemos extraer un caracter específico de la misma.

SALIDA:

Usando printf con cadenas de caracteres:

Para mostrar por pantalla una cadena de caracteres usando printf, tendremos en cuenta dos nuevos detalles:

  • La marca de formato de una cadena de caracteres será %s.
  • Al indicar la cadena que mostraremos lo haremos con el nombre de la misma sin índice.
Con lo que usar printf para mostrar nuestra cadena de ejemplo será tan sencillo como poner :



Recordemos que nuestra cadena es un vector de tipo char, por lo que si queremos mostrar sólo un caracter en particular de la misma usaremos el índice y la marca de formato correspondiente (%c). Por lo que si quisiéramos mostrar sólo la j de la palabra ejemplo tendríamos:


ENTRADA:

Usando scanf para leer cadenas de caracteres:

Para leer cadenas de caracteres, scanf muestra su punto débil ya que sólo nos permite leer palabras y no oraciones completas, por lo que si ingresamos una frase sólo leerá hasta el primer espacio (blanco) de la misma.

Aún así mostraremos cómo leer una palabra a través de scanf en el siguiente ejemplo:


Y vemos que es igual de simple que con printf, sólo que notemos que colocamos sólo el nombre de la cadena sin el & ya que en una cadena el nombre guarda la dirección de memoria del primer elemento.

En caso que quisiéramos leer toda una cadena de caracteres con scanf deberíamos agregar varios scanf para ir leyendo cada palabra... en fin, nada óptimo ni mucho menos.

Usando gets para leer cadenas de caracteres:

Como vimos scanf muestra su debilidad ante las cadenas de caracteres, por lo que se necesita una función que pueda leer una cadena completa sin importar si es una palabra o toda una frase, otra función que puede utilizarse en este caso es gets que sí permite leer frases.

Si usar printf y scanf con cadenas de caracteres les pareció fácil, entonces usar gets les parecerá un juego de niños:

Se los dije... tan simple como escribir gets y pasar como argumento el nombre de la cadena. 

Pero no todo lo que brilla es oro y ésta no es la excepción. Gets también tiene sus problemas ya que no verifica que la cadena que ingresamos no sobrepase la capacidad de nuestra cadena, por lo que es una función que fácilmente puede provocar un buffer overflow (desbordamiento).

Usando getchar para leer cadenas de caracteres:

Para leer un solo caracter podemos utilizar getchar que es una función que debe asignarse a una variable de tipo char de la manera que vemos a continuación:

Por lo que si hacemos memoria y pensamos que en un array cada elemento con su índice se comporta como una variable, se nos podría venir a la mente la idea de realizar un bucle para leer una cadena completa usando getchar. 

Pero veamos ¿cómo podríamos implementarlo sin tener mil y un problemas? Porque si nos ponemos a pensar en todo lo que podría pasar sin condicionar esto tendríamos tema de sobra para hacer una película de terror para frikis.

A ver, pensemos todo lo que debemos tener en cuenta: la cadena no debe superar su capacidad incluyendo el caracter nulo y si presionamos enter se termina la cadena. 

Así que si creamos una variable char auxiliar y agregamos todas las condiciones necesarias podríamos conseguir lo deseado en este while:



Pero esto de tener que usar un while con tantas condiciones, cuántos i++, agregar el caracter nulo y cuánto más ya no es tan sencillo como vimos con otras funciones y encima cuando veamos complejidad de algoritmos veremos que con la manera anterior se nos va a las nubes comparado con usar scanf o gets.

Usando fgets para leer cadenas de caracteres:

Hasta ahora por uno u otro motivo ninguna función nos ayuda a leer una cadena de caracteres sin problemas y de manera sencilla, pero antes de darnos por vencidos volvamos a la esencia de lo que queremos hacer y analicemos.

Queremos ingresar una frase a través del teclado... pero el teclado en sí para C es lo que llamamos un fichero, explicando muy superficialmente y para que se entienda el fichero de entrada estándar stdin es lo que representa lo que llamamos teclado. Y es uno de los ficheros que se encuentran abiertos cuando ejecutamos nuestro programa. Éste se encuentra declarado dentro de stdio.h, cuando veamos más adelante el uso de ficheros veremos la sintaxis de esta declaración pero por ahora lo importante es que quede claro esto de que el teclado es un fichero.

Entonces si el teclado es un fichero podemos hacer uso de una función llamada fgets que nos permitirá leer cadenas de caracteres especificando el máximo tamaño que podrá tener y de esta manera evitar los problemas de desbordamiento que tanto nos vienen molestando en las otras funciones.

El uso de fgets vuelve a ser tan sencillo como las primeras opciones que vimos:

Vemos que fgets recibe como argumentos el nombre de la cadena, la capacidad de la misma y el fichero en cuestión. Por lo que esta función nunca leerá más caracteres de los que nos permite la capacidad de la cadena, incluyendo el caracter nulo (lee hasta llegar a ese límite o hasta que el usuario presione enter); lo que nos quita un problema de encima.

Por el momento no analizaremos más de esta función ya que eso queda para cuando veamos ficheros, pero con esto ya podemos leer cadenas de caracteres con más tranquilidad.



Ya que este post se extendió bastante llegaremos hasta aquí por ahora y en el próximo trataremos exclusivamente algunas de las funciones que utilizaremos para trabajar con estas cadenas de caracteres. 


Comentarios

Entradas populares de este blog

C: Conversiones de tipo (casting) en C...

El casting o simplemente cast  nos permite hacer una conversión explícita de un tipo de dato a otro, a criterio del programador siempre y cuando estos tipos sean compatibles. Este cast se realiza a través de un operador de conversión de tipos (type casting operator) y es un recurso a tener en cuenta ya que hay situaciones en que nos puede resultar de gran utilidad. Hacer uso de un cast es tan sencillo como poner (tipo de dato)  delante de la expresión o variable a convertir. Veamos un ejemplo: Declaramos una variable de tipo int con un identificador tan creativo como "a" y le realizamos diferentes cast a a para mostrarlo como si fuera un float, un double y un char en un printf. Lo que obtendríamos en pantalla sería lo siguiente: Donde tenemos el valor de nuestro a, a convertido en float y double (mostrándolo con 3 cifras decimales) y a convertido en char. Si vemos este último caso, al hacer la conversión de "a" a char toma a como el código ascii de

C: Ejemplos: Congruencia de Zeller (nivel básico) ...

La Congruencia de Zeller es un algoritmo que se atribuye al matemático alemán Julius Christian Johannes Zeller que vivió en el siglo XIX. Este algoritmo nos permite determinar el día de la semana que le corresponde a una fecha determinada del calendario Gregoriano. La fórmula que nosotros usaremos (con algunas modificaciones respecto de la original para poder usarla en  informática) es la siguiente: Donde h es el día de la semana (entre 0 y 6), J es año/100 (la centuria) y K es año mod 100 (el año de la centuria). Y hay que tener en cuenta que los meses de enero y febrero cuentan como el mes 13 y 14 del año anterior. Ahora que tenemos la fórmula, programemos el algoritmo en C mediante el uso de una función: Analicemos el código paso a paso: Tenemos en cuenta el caso de enero y febrero: Dijimos que estos meses corresponden a los meses 13 y 14 del año anterior por lo que los asignamos como corresponde (mes + 12 , que dará 13 para enero y 14 para febrero) y le rest

Algoritmos: Resolución de problemas y refinamientos en pseudocódigo...

En otras entradas, vimos las partes que debe tener nuestro algoritmo en pseudocódigo y las estructuras que utilizaremos para resolverlo. Ahora llega el turno de implementar todo en conjunto para dar origen a nuestra creación. Pero ¿cómo resolvemos un problema así? Para hacerlo, utilizaremos lo que llamamos refinamientos sucesivos. Este concepto consiste en dividir el problema en subproblemas más pequeños y a estos, a su vez, en otros más pequeños; y así sucesivamente hasta que la solución de los últimos sea trivial, sencillo de resolver. Luego usaremos todas las soluciones obtenidas para armar la solución de nuestro problema mayor. Este principio, tiene base en parte de la técnica divide and conquer (dependiendo de la traducción: "divide y vencerás") que es una de las muchas técnicas de resolución de algoritmos existentes. Como vemos, al dividir el problema en otros más pequeños y más fáciles de resolver, podemos pasar de un problema complicado a uno cuya solución es much