martes, 1 de abril de 2008

Capítulo 2

.
Manejo de Cadenas de Caracteres

En C no existe el tipo de datos string. Los string se implementan utilizando arrays de caracteres en los que cada elemento del array contiene cada caracter del string. Además el array debe contener un caracter adicional llamado "caracter nulo" o "barra cero" que determina el final del string contenido en dicho array.



El gráfico muestra un array de 7 elementos (en C los arrays se numeran desde cero) que contiene la cadena "Hola" más el caracter nulo barra cero que marca el fin de la cadena.

Para contener la cadena "Hola" se necesitan 5 caracteres (4 para la cadena en si y uno para el caracter nulo). Esto no quiere decir que el array no pueda tener elementos "de sobra". Simplemente será memoria desaprovechada. En este ejemplo sobran 2 elementos en el array.

El hecho de manejar strings sobre arrays de caracteres complica un poco las operaciones de manejo de cadenas, sobre todo si el programador está acostumbrado a trabajar con lenguajes de más alto nivel como Pascal.

Veamos algunos ejemplos:

1 - No podemos asignar una cadena literal a un array dimensionado.

char s1[50];
s1 = "Esto es una Prueba"; // incorrecto

2 - No podemos asignar un string a otro (un "char array" a otro).

char s1[50], s2[50];
s1 = s2; // incorrecto

3 - No podemos concatenar un string a otro.

char s1[50], s2[50];
// supongamos que s1 y s2 tienen valor...
s1 = s1 + s2; // incorrecto

4 - No podemos comparar alfabeticamente dos strings

char s1[50], s2[50];
// supongamos que s1 y s2 tienen valor...
if( s1 == s2 ) // incorrecto

Cualquiera de estas operaciones tendremos que realizarlas programando funciones que asignen (o comparen) caracter a caracter en los arrays que corresponda.

Todas las operaciones (copiar un string a otro, concatenar, calcular la longitud de un string, comparar dos string, etc) ya vienen como parte de la librería estandar de C pero vamos a programar algunas de ellas para analizarlas como ejemplo.


Contar los Caracteres de un String

La función original se llama strlen y lo que hace es iterar el array contando las iteraciones hasta que el caracter "sub i" sea '\0' . Se considera que el array (string) que se recibe como parámetro terminará con '\0'. De lo contrario el resultado será cualquier cosa.

testStrlen.c
   1:
2:#include<stdio.h>
3:
4:int hmStrlen(char s[])
5:{
6: int i=0;
7: while( s[i]!='\0' )
8: {
9: i++;
10: }
11: return i;
12:}
13:
14:int main()
15:{
16: // podemos asignar directamente porque
17: // p no esta dimensionado (ya veremos este tema)
18: char p[]="HolaMundo.C";
19: printf("longitud de [%s] = %d\n",p, hmStrlen(p));
20:}
21:

Otra versión (más compacta de la función hmStrlen podría ser la siguiente:
   1:
2:int hmStrlen(char s[])
3:{
4: int i=0;
5: while( s[i++]!='\0' );
6: return i-1;
7:}
8:

En la línea 5 cuando se evalúa s[i++] se considera el valor de i (de entrada vale cero) y luego se incrementa. El while no tiene cuerpo (finaliza con punto y coma) porque lo único que tiene que hacer es incrementar la variable i y esto lo hace dentro de la condición. Cuando llega el caracter barra cero la variable i estará incrementada por este motivo la función retorna el valor de i-1.


Copiar un Sring a Otro

La función original se llama strcpy, recibe dos strings: s1, s2. Copia el valor de s2 en s1. Asume que s2 termina con '\0' y que s1 tiene suficiente capacidad como para contener el valor de s2.

testStrcpy.c
   1:
2:#include<stdio.h>
3:
4:void hmStrcpy(char s1[], char s2[])
5:{
6: int i=0;
7: while( s2[i]!='\0' )
8: {
9: s1[i]=s2[i++];
10: }
11: s1[i]='\0';
12:}
13:
14:
15:int main()
16:{
17: char s[50];
18: char p[]="HolaMundo.C";
19: hmStrcpy(s,p);
20: printf("p vale = [%s]\n",p);
21:}
22:

Notemos que en la línea 11 tenemos que asignar "a mano" el '\0' a s1. Esto se debe que no lo asignamos dentro del while porque este corta cuando llega el caracter nulo en s2[i]. Si no asignamos el '\0' al final de s1 el printf no podrá determinar cual es el final del string que le mandamos a imprimir y la salida podrá ser desastrosa.


Concatener un String en Otro

La función original se llama strcat y recibe dos arrays: s1, s2. Copiará al final de s1 todos los caracteres contenidos en s2 agregando un '\0' al final.

testStrcat.
   1:
2:#include<stdio.h>
3:
4:int hmStrlen(char s[])
5:{
6: int i=0;
7: while( s[i++]!='\0' );
8: return i-1;
9:}
10:
11:void hmStrcat(char s1[], char s2[])
12:{
13: // obtengo la posicion del '\0' de s1
14: int i=hmStrlen(s1);
15: int j=0;
16:
17: // recorro s2 desde cero mientras no llegue '\0'
18: while( s2[j]!='\0' )
19: {
20: // recordemos...
21: // i comenzaba en la pos del '\0' de s1
22: s1[i++]=s2[j++];
23: }
24: // agrego el nuevo '\0' de s1
25: s1[i]='\0';
26:}
27:
28:int main()
29:{
30: char s[50];
31: char p[]=" que tal";
32:
33: s[0]='\0';
34: hmStrcat(s,"Hola");
35: hmStrcat(s,p);
36: printf("s vale = [%s]\n",s);
37:}
38:

En este ejemplo es muy importante notar como tuvimos que asignar "a mano" el barra cero a s[0] en la línea 33.


Comparar dos String

La función original se llama strcmp, recibe dos arrays: s1 y s2 para compararlos caracter a caracter. Retorna un valor menor, mayor o igual a cero según s1 sea alfabeticamente menor, mayor o igual a s2.

testStrcmp.c
   1:
2:#include<stdio.h>
3:
4:int hmStrlen(char s[])
5:{
6: int i=0;
7: while( s[i++]!='\0' );
8: return i-1;
9:}
10:
11:int hmStrcmp(char s1[], char s2[])
12:{
13: int i=0;
14: int lenS1=hmStrlen(s1);
15: int lenS2=hmStrlen(s2);
16:
17: // aca tenemos un "if in line"
18: // obtenemos menor valor entre lenS1 y lenS2
19: int minLen=(lenS1<lenS2)?lenS1:lenS2;
20:
21: while( i<minLen )
22: {
23: if( s1[i]<s2[i] )
24: {
25: return -1;
26: }
27: else
28: {
29: if( s1[i]>s2[i] )
30: {
31: return 1;
32: }
33: else
34: {
35: i++;
36: }
37: }
38: }
39: //
40: // La siguiente sentencia es
41: // un "if en linea" anidado, comparable
42: // a escribir el siguiente codigo:
43: //
44: // if( lenS1>lenS2 )
45: // {
46: // return 1;
47: // }
48: // else
49: // {
50: // if( lenS1<lenS2 )
51: // {
52: // return -1;
53: // }
54: // else
55: // {
56: // return 0;
57: // }
58: // }
59: //
60: return (lenS1>lenS2)?1:(lenS1<lenS2)?-1:0;
61:}
62:
63:int main()
64:{
65: char s1[]="Pablo";
66: char s2[]="Andres";
67:
68: printf("%d\n",hmStrcmp(s1,s2));
69:}
70:

La lógica para resolver este algoritmos es la siguiente: tomamos el primer caracter de cada string y lo comparamos. Si no son iguales entonces ya podemos determinar cual es el string alfabeticamente mayor o menor. Si son iguales entonces pasamos al segundo carecter y así hasta finalizar. Si todos los caracteres resultaron iguales entonces los dos string son idénticos.

En las líneas 19 y 60 utilizamos un recurso disponible en C: el "if in line". Se trata de un if que se resuelve en la misma línea de código. Por ejemplo:

min = (a<b) ? a:b;

En esta línea comparamos el valor de a con el valor de b. Si la expresión a<b resulta verdadera entonces el resultado será el valor que se encuentra ubicado a la izquierda de "los dos puntos". Si la expresión no es verdadera entonces el resultado será el valor ubicado a la derecha de "los dos puntos".


Convertir entre Números y Strings

Para convertir entre enteros y strings podemos utilizar las funciones atoi (array to integer) e itoa (integer to array). Analogamente las funciones ftoa y atof convierten entre flotantes y strings.

El siguiente programa muestra como utilizar estas funciones.

cadenas1.c
   1:
2:#include <stdio.h>
3:
4:int main()
5:{
6: char s1[20], s2[20];
7: int i=42;
8: double f=0;
9:
10: // almacena en s1 un string "42"
11: itoa(i,s1,10); // base 10
12: printf("%s\n",s1);
13:
14: // almacena en s1 un string "101010"
15: itoa(i,s1,2); // base 2
16: printf("%s\n",s1);
17:
18: // almacena en s1 un string "2A"
19: itoa(i,s1,16); // base 16
20: printf("%s\n",s1);
21:
22: // asignamos un valor a s2
23: strcpy(s2, "23.59");
24:
25: // convertimos a flotante
26: f=atof(s2);
27: printf("%f \n",f);
28:}
29:
30:


Longitud de un String y acceso a sus Caracteres

Ya analizamos la función strlen que retorna la longitud del string. Esta longitud coincide con la posición del caracter '\0'.

cadenas2.c
   1:
2:#include<stdio.h>
3:
4:int main()
5:{
6: char s[20];
7: int i,n;
8:
9: strcpy(s,"Hola, soy Pablo");
10: n=strlen(s);
11:
12: // recordemos que los arrays se numeran desde 0
13: printf("Cantidad de Caracteres: %d\n",n);
14: printf("En la posicion 3 hay un %c\n",s[3]);
15: printf("En la posicion 10 hay un %c\n",s[10]);
16:
17: // modifico el caracter en la posicion 10
18: s[10]='H';
19:
20: for( i=0; i<n; i++ )
21: {
22: printf("%c\n",s[i]);
23: }
24:}
25:


Substrings

No existe una función de librería que permita obtener un substring pero la podemos programar muy facilmente como vemos a continuación.

testSubstring.c
   1:
2:#include<stdio.h>
3:
4:// retorna la subcadena comprendida entre los
5:// caracteres desde y hasta (hasta no inclusive)
6:char* substring(char s[], int desde, int hasta)
7:{
8: int i,j;
9: int len=hasta-desde+1;
10:
11: // alocamos dinamicamente un string (o un char[])
12: // del tamanio que necesitamos
13: char *ret=(char*) malloc(sizeof(char)*len);
14:
15: // el for puede controlar mas de una variable
16: for( j=0,i=desde; i<hasta; i++,j++ )
17: {
18: ret[j]=s[i];
19: }
20: ret[j]='\0';
21:
22: return ret;
23:}
24:
25:int main()
26:{
27: char s[20],aux1[20];
28:
29: // asigno valor a s
30: strcpy(s,"HolaMundo.c");
31:
32: // el resultado sera "Mundo"
33: // lo asigno a aux1
34: strcpy(aux1,substring(s,4,9));
35: printf("%s\n",aux1);
36:}
37:

Además de programar la función substring este ejemplo muestra varias cosas que son fundamentales para trabajar con C y que hasta el momento no las hemos considerado.

Un array es en realidad un puntero al primer elemento de un conjunto de variables cuyas direcciones de memoria son consecutivas. Si la función tiene que retornar un string (un array de caracteres) lo que realmente estará retornando será un puntero a char. Un puntero al primer caracter del array.

En C los punteros se identifican con " * " por este motivo la función retorna un char* (puntero a caracter).

En la línea 13 estamos alocando dinamicamente el array que contendrá el resultado de la función. Para esto utilizamos la función malloc pero este tema lo estudiaremos más adelante, en el capítulo de punteros.


Convertir entre Mayúsculas y Minúsculas

En el siguiente ejemplo vemos como facilmente podemos desarrollar funciones para convertir entre mayúsculas y minúsculas.

cadenas3.c
   1:
2:#include<stdio.h>
3:
4:// convierte un caracter a minuscula
5:char toLower(char c)
6:{
7: return ( c>='A' && c<='Z' ) ? c-'A'+'a' : c;
8:}
9:
10:// convierte un caracter a mayuscula
11:char toUpper(char c)
12:{
13: return ( c>='a' && c<='z' ) ? c-'a'+'A' : c;
14:}
15:
16:// convierte un string a mayuscula
17:char *toUpperCase(char *s)
18:{
19: int i=0;
20: char *ret=(char*)malloc(sizeof(char*)*strlen(s)+1);
21:
22: while( s[i]!='\0' )
23: {
24: ret[i]=toUpper(s[i]);
25: i++;
26: }
27: ret[i]='\0';
28: return ret;
29:}
30:
31:// convierte un string a munuscula
32:char *toLowerCase(char *s)
33:{
34: int i=0;
35: char *ret=(char*)malloc(sizeof(char*)*strlen(s)+1);
36:
37: while( s[i]!='\0' )
38: {
39: ret[i]=toLower(s[i]);
40: i++;
41: }
42: ret[i]='\0';
43: return ret;
44:}
45:
46:int main()
47:{
48: char s[20], may[20], min[20];
49:
50: // asigno valor a s
51: strcpy(s,"Hola, Que Tal?");
52:
53: // convierto a mayuscula
54: strcpy(may,toUpperCase(s));
55:
56: // convierto a minuscula
57: strcpy(min,toLowerCase(s));
58:
59: printf("%s\n",may);
60: printf("%s\n",min);
61:}
62:

La función toLower (por ejemplo) recibe un caracter c y lo tiene que convertir a minúscula siempre y cuando el caracter corresponda a una letra mayúscula, si no, no tiene nada que hacer.

Si c es una letra mayúscula entonces se verificará que 'A' <= c <= 'Z'. Los caracteres tienen un valor numérico que corresponde a la tabla de códigos ASCII. En esta tabla el caracter 'A' tiene el valor 65, el caracter 'B' tiene el valor 66 y así. Si c está entre 'A' y 'Z' entonces la resta entre c y 'A' corresponderá al orden alfabético del caracter. Supongamos que c vale 'A' entonces c-'A' será cero (el primer caracter del alfabeto), si c es 'B' entonces c-'A' será 66-65 = 1 (segundo caracter). Luego, al hacer c-'A'+'a' estamos obteniendo el código ASCII del caracter (en minúscula) que corresponde a la posición que obtuvimos. Esto es: Si c es 'A' entonces c-'A' es cero, cero+ 'a' es 'a' con lo que "dado el caracter 'A' retornamos el caracter 'a'.








.

1 comentario:

Anónimo dijo...

Pablo gracias por la info.
Lucas Riccombene