lunes, 1 de junio de 2009

Salida con formato

printf es la funciòn bàsica utilizada para salida con formato; es llamada en la forma:

printf(control, argl, arg2,...)

char *control;

Esta funciòn acepta un nùmero variable de argumentos, convirtièndolos, formateàndolos e imprimièndolos bajo las especificaciones incluidas en "control". Los caracteres del string de control se vuelcan en el fichero de salida estàndar, salvo el metacaràcter "%", que se emplea como una especificaciòn de formato para controlar la impresiòn de los argumentos.

• un signo menos "-", indicando que el siguiente argumento debe imprimirse justificando a la izquierda en su campo de salida;

• una cadena de dìgitos, especificando el tamaño mìnimo del campo de salida para ese argumento. Si el argumento ya formateado ocupa menos caracteres que la anchura del campo que le ha sido asignado se completará con caracteres por la izquierda (o por la derecha si se ha especificado justificación a la izquierda).

Si la especificación de la anchura del campo contiene un cero como primer carácter (por ejemplo OSd), la longitud total del campo se completará con ceros en lugar de con espacios en blanco. La inclusión de este cero por la izquierda no debe confundirse en ningún caso con la expresión de una constante en octal (por ejemplo, el carácter espacio en blanco, ASCII 32 en decimal, se expresa como 040 en octal).

A diferencia de la salida con formato en Fortran, printf no trunca la conversión de argumentos en caso de que necesiten una extensión mayor que la especificada para el campo de salida;

• un punto, separando el ancho del campo de salida, de la precisiòn;

• una cadena de dìgitos que puede especificar tanto el nùmero màximo de caracteres a imprimir de una cadena, como el nùmero de dìgitos a imprimir a la derecha del punto decimal cuando se están imprimiendo números en coma flotante (float o double);

• una ele "l", que indica que el siguiente argumento es un long it, en lugar de un int;

• algunas implementaciones de printf interpretan el caràcter asterisco "*" como una anchura de campo o precisión, con el significado de que el siguiente argumento indicará realmente la anchura del campo o precisión del argumento a imprimir. Por ejemplo:

printf("%.*s", longitud, cadena);

imprimirá al menos "longitud" caracteres de la cadena de caracteres "cadena".


Los caracteres de conversiòn de salida son:

d- Convierte el argumento entero a un número decimal con signo.

o- Convierte su argumento (entero) a base octal.

x- El argumento entero se convierte a hexadecimal.

u- El argumento entero se convierte a notación decimal sin signo.

c- Interpreta su argumento como un único carácter.

s- Considera su argumento una cadena (string).

e- Convierte su argumento en coma flotante (interpretado como double, porque C siempre pasa los números en coma flotante como double) a notación exponencial, con signo, mantisa y exponente:

[-]m.nnnnE[-]xx

La longitud de la cadena de enes "n" viene determinada por la precisión, que es de 6 por defecto. (Recordemos que float proporcionaba 6 dígitos de precisión y double 15.) * • ' - El argumento en coma flotante se expresa en notación de coma fija:

[-jmmm.nnnn

La longitud de la cadena de enes "n" se toma nuevamente de 6 por defecto.

g- Convierte su argumento en coma flotante a formato "%f o "%e", dependiendo de cuál de los dos proporcione una cadena de menor longitud.

Cualquier otro caràcter distinto de los especificados anteriormente y que siga a un signo "%" se imprimirá literalmente. Así:

printf("%%");

Imprimirá un único carácter"'%".

La apariciòn de los anteriores caracteres en mayùsculas (D, O, X, U, E, F o G) se interpreta como si una ele "l" precediese al caràcter correspondiente en, minúculas. Así

printf("%D", valor);

es equivalente a

printf("%ld", valor);

La salida con formato especificada por printf y enviada por esta función al fichero de salida estándar tambièn se puede enviar a otro fichero o a una cadena de caracteres, por medio de las funciones fprintf y sprintf, respectivamente.

fprintf(fp, control, argl, arg2,...)

FILE *fp;

char *cntrol;

realiza su salida sobre el fichero especificado por fp.

sprintf(string, control, argl, arg2,...)

lleva su salida al array string de acuerdo con las especificaciones de formato incluidas, en lugar de poner la salida en un fichero.

Entrada con formato

scanf es la funciòn bàsica de entrada con formato.

int scanf (control, argl, arg2, ...)

char *control;

Esta funciòn lee caracteres con formato a partir del fichero de entrada estàndar "stdin", interpretàndolos de acuerdo a las especificaciones de la cadena de control.

Cada uno de sus argumentos debe ser un puntero, especificando la dirección de la variable donde deberá almacenarse el resultado. Obviamente, scanf es una función que necesita "llamada por referencia", pues va a modificar las variables que se le pasan como argumentos. Un error frecuente en su uso consiste en la omisión de los "&" en cada uno de los argumentos.

Los caracteres de separación como espacios en blanco, fabuladores y caracteres de salto de línea que figuren en la cadena de control, serán ignorados.

Los restantes caracteres (excepto el"%") se corresponden con una especificación de formato de entrada, pudiendo consistir en:

• el carácter asterisco "*", indicando que el valor leído no debe almacenarse en la variable;

• una cadena de dìgitos, especificando la longitud màxima del campo de entrada (el número de caracteres a convertir);

• carácter "h , para indicar que el siguiente argumento debe ser considerado como un puntero a un short int, o el carácter "i' indicando que debe ser tratado como un puntero a un long int, o a un double (equivalente a long float);

• carácter de conversión.

Las conversiones de caracteres de entrada permitidas son:

d - Lee un entero decimal. El argumento debe ser un puntero a un entero (o un puntero a un short int o long int, si se hubiesen antepuesto "h" o "1" a la especificación de formato "d").

o - Lee un número entero octal.

x - Lee un número entero hexadecimal.

c - Lee un único carácter de* la entrada. El argumento debe ser un puntero a un carácter.

s - Acepta una cadena de caracteres terminada por un espacio en blanco o un carácter de salto de línea. El array de caracteres donde se guarde el resultado debejser lo bastante grande como para contener todos los caracteres y el carácter nulo terminador de cadena.

e - Lee un número en coma flotante. El argumento debeser un float (o un double, con especificación "le").

f - Análogo al anterior. Acepta un número en coma flotante.

[ - Lee una cadena de caracteres limitada por separadores arbitrarios, almacenándola en el array apuntado por el argumento. El corchete de apertura "[" va seguido por un conjunto de caracteres y un corchete de cierre "]". Si el carácter inmediatamente posterior a "[" no es un circunflejo "ó", entonces scanf leerá solamente los caracteres que aparezcan en el conjunto de caracteres entre corchetes. Por el contrario, si el carácter inmediatemente posterior a"[" es un circunflejoentonces scanf leerá caracteres hasta que encuentre algún carácter de los contenidos en el conjunto de caracteres entre corchetes.

Los caracteres de conversión d, o, x, e, y f pueden aparecer en mayúsculas o precedidos por una ele "i", para indicar que el correspondiente puntero apunta a un long int o long float.

scanf interrumpe la lectura cuando llega al final.de la cadena de control, o cuando la entrada no cumple la especificación, de formato impuesta (por ejemplo, intentar leer un literal con una especificación %d de número entero).

scanf devuelve un número entero indicando el número de asignaciones que ha realizado o EOF si ha encontrado un final de fichero y esperaba leer más caracteres.

Es importante aclarar que el valor cero no quiere decir que se haya alcanzado el final de fichero, sino que no se ha convertido correctamente ningún campo de entrada.

Análogamente a lo descrito para printf, scanf también permite que su entrada provenga de una cadena de caracteres o de un fichero.

int fscanf(fd, control, argl, arg2,...)

FILE *fd;

char *control;

es similar a scanf, con la diferencia de que toma su entrada del fichero fd, en lugar de "stdin".

int sscanf(control, argl, arg2,...)

char *control;

tomaría su entrada de una cadena de caracteres. Como la lectura de una cadena no viene acompañada de efectos laterales (side effects), sscanf es generalmente más útil que scanf o fscanf.

E/S orientada a lìneas

La funciòn fgets leerà la siguiente lìnea de entrada (incluido el carácter \n) del fichero "fp", guardando su entrada en el array "line" que deberá estar terminado en un carácter nulo. Se leerán al menos "n-1" caracteres, fgets devuelve line, salvó que se haya alcanzado el final de fichero antes de llegar a leer ningún carácter.

char *fgets(line, nchars, fp)

char *line;

int nchars;

FILE *fp;

La funciòn fputs escribe la cadena de caracteres "line" sobre el fichero especificado por fp.

fputs(line, fp)

char *line;

FILE *fp;

La librerìa estàndar tambièn incluye kas funciones gets y puts que leen y escriben de stdin y stdout, respectivamente. Sin embargo, no son equivalentes a fgets y fputs dirigidas a stdin y stdout.

La funciòn gets

char *gets(line)

char *line;

lee la siguiente lìnea de entrada en stdin (incluyendo el \n) sobre el array de caracteres "line", que se supone tendrá suficiente espacio para contener la entrada. El carácter \n no se almacena en "line". A diferencia de fgets, gets devuelve "line" a menos que se alcance el final de fichero antes de que se hayja podido leer ningún carácter.

La función puts

puts(line)

char *line;

escribe la cadena de caracteres "line" sobre stdout seguido por un caràcter \n.

Entrada/Salida binaria

Las funciones bàsicas de E/S gets y putc son capaces de leer y escribir cualquier tipo de fichero (al menos bajo Unix), no sòlo ficheros de texto.

Se puede escribir una "palabra" (tipo de dato entero) sobre un fichero utilizando:

int putw(word, fp)

int word;

FILE *fp;

putw devuelve la palabra escrita en el fichero, o EOF, si se ha producido algún error.

Se puede leer una palabra de un fichero utilizando

int-getw(fp)

FILE *fp;

getw devuelve la palabra, o EOF, si se ha alcanzado el final de fichero antes de leer la totalidad de la palabra. Como EOF es un valor permitido para un int "(pero no para un char), debería utilizarse feof para comprobar la condición de final de fichero después de getw.

Cualquier variable de C puede escribirse como si se tratase de un array de caractores. Por ejemplo, dada la declaraciòn:

union {

long int val;

char c[sizeof long];

} x;

Leyendo o escribiendo el array de caracteres x.c se transferirá exactamente el valor de x.val.

La funciòn general de lectura binaria es fread:

int fwrite(pointer, size, number, fp)

char *pointer;

unsigned int size;

unsigned int number;

FILE *fp;

Esta funciòn lee un nùmero "number" de objetos del fichero "fp", de un tamaño de "size" bytes cada uno de ellos, en el àrea de datos apuntada por "pointer" fread devuelve el número de objetos completos que ha podido leer. Así, cero equivale a un fin de fichero.

La funciòn general de escritura binaria es fwrite

int fread(pointer, size, number, fp)

char *pointer;

unsigned int size;

unsigned int number;

FILE *fp;

que escribe "number" objetos del fichero "fp", de "size" bytes cada uno de ellos, en el àrea apuntada por "pointer" fwrite devuelve el número de objetos completos escritos, que coincidirá con num-ber a menos que se haya producido algún error. Un ejemplo típico de utilización sería:

long int

FILE «írom, *to;

while (f read ( (char *)!

fwriteUchar *)&x, sizeof x, 1, to) ;

copiando un long del fichero "from" al fichero "to".

Es importante observar el empleo del cast &x para obtener un char *. La expresión &(char *)x que fuerza a que "x" sea del tipo char, no funcionaría porque (char)x no es una variable, y no se puede aplicar un &.

Aunque la utilización de fread y fwrite es portable, los ficheros manejados por ellos no tienen porqué serlo, pues estos ficheros son fiel reflejo del tamaño y formato de los objetos de datos representados por el ordenador en cuestión.

Si se necesita escribir ficheros portables es mejor emplear funciones de desplazamiento y máscaras de bits o utilizar datos con formato.

Acceso aleatorio a ficheros

La E/S de ficheros en lenguaje C es habitualmente secuen-cial. Sin embargo, un fichero puede ser leído o escrito en cualquier orden.

La función fseek

fseek(fp, offset, origin)

FILE *fp;

long int offset;

int origin;

obliga a que la siguiente llamada a getc o putc tenga lugar sobre la posiciòn offset del fichero. El origen es 0, 1 ó 2 indicando si el offset es relativo al principio del fichero (0), a la posición actual (1) o al final del fichero (2)

La función

long int ftell(fp)

FILE *fp;

devuelve la posición actual del offset a partir del principio o del final del fichero fp.

La función

rewind(fp)

FILE *fp

es equivalente a:

fseek(fp, 0L, 0);

Entrada/Salida con buffer

Las llamadas al nùcleo del sistema (system calls) para realizar operaciones de E/S ocupan una gran cantidad de tiempo, comparadas con una llamada a función. Una llamada al núcleo consiste en utilizar funciones del núcleo del sistema operativo de una forma semejante a como se manejan las funciones "normales" en un programa.

Para realizar una llamada al núcleo del sistema se requiere un trabajo extra, comparándolo con una llamada a función, necesitándose almacenar información adicional durante la llamada y el retorno, además de la sobrecarga necesaria para encontrar un manejador de la llamada al núcleo, traspasar argumentos, etc.

La sobrecarga en las llamadas al nùcleo se puede disminuir mediante la "bufferizaciòn": se puede transferir un ùnico caràcter de/hacia un buffer desde el proceso de usuario; una vez que el buffer se ha llenado (para ficheros de salida) o vaciado (para ficheros de entrada) se puede transferir la totalidad del buffer lleno de caracteres.

Este procedimiento reduce el tiempo de ejecución de un programa, pero puede tener otros efectos. En particular, si putchar pone caracteres en un buffer, estos caracteres no aparecerán en la pantalla a medida que vayan siendo enviados por putchar; es más, ni siquiera lo harán después de que se haya producido el retorno de putchar, con la molestia de que.no aparece eco inmediato en pantalla. "

Si un programa termina anormalmente por cualquier causa, la salida que tenía bufferizada se quedará sin enviar al fichero de salida.

Del mismo modo, los errores que se estén produciendo en la escritura no aparecerán hasta que se hayan escrito los datos del buffer, lo cual podría suceder bastante después de que la llamada a putc haya enviado los datos.

La "bufferización" de entrada causa generalmente pocos problemas, salvo en el caso particular de que más de un proceso esté leyendo simultáneamente el mismo fichero.

Realmente no hace demasiada falta controlar la bufferización de los datos; dejándolo en manos de la librería estándar de E/S podemos confiar en que la librería "hará las cosas bien" sin necesidad de control (por ejemplo no bufferizará los datos de salida a un terminal).

No obstante, la librerìa stàndar de E/S posee funciones que permiten validar o invalidar la "bufferizaciòn" sobre un fichero, asì como una funciòn que fuerza a la librerìa de E/S a que envìe de inmediato los datos retenidos en el buffer. La función setbuf

setbuf(fp, buffer)

FILE *fp;

char *buffer;


informa a la librería estándar de E/S de que utilice el array buffer de BUFSIZ caracteres (definido en la ) para bufferización. Si buffer es igual a NULL entonces el fichero no estará bufferizado. setbuf debería llamarse antes de que se vaya a realizar cualquier operación de E/S sobre el fichero.
La función

fflush(fp)
FILE *fp;

fuerza que cualquier dato bufferizado del fichero fp sea escrito en el fichero, fflush devuelve EOF para indicar un error (por ejemplo, la imposibilidad de grabar los datos en el fichero) o cero en el caso contrario.

EFICIENCIA Y PORTABILIDAD EN EL C

La capacidad dé escribir programas que utilicen con eficiencia los recursos del sistema, estén libres de errores (error-free) y sean fácilmente transportables a otros computadores son los signos que indican el paso de un buen programador y el uso de un lenguaje adecuado.

EFICIENCIA

Cuando hablamos de eficiencia en un programa de ordenador nos referimos tanto a su velocidad de ejecuciòn como a la utilizaciòn òptima de los recursos crìticos del sistema (memoria y ocupaciòn en disco, basicamente). No basta con que los programas funcionen, sino que además deben hacerlo de manera que supongan la menor carga posible para el ordenador y no gasten sus recursos inútilmente.

Normalmente, la optimización de uñ aspecto del programa suele degradar otros. Por ejemplo, hacer que un programa se ejecute más rápido normalmente requiere que se duplique el código fuente en algunos casos, en lugar de realizar llamadas a funciones. También se puede conseguir un ahorro de ocupación en disco, utilizando empaquetado de datos, a costa de degradar la velocidad de acceso a los datos. Estas y otras posibilidades de mejora de la eficiencia pueden ser frustrantes para los no-programadores, a quienes les puede costar comprender cómo la mejora de una característica de un programa puede afectar negativamente a otras, llegando incluso a empeorar el resultado final.

Afortunadamente, existen algunas técnicas de programación que mejoran siempre la eficiencia en cualquier sistema que consideremos.

Por ejemplo, el uso de operadores de incremento ++ y decremento — es más rápido y compacto que la utilización de operaciones como x = x + 1 (frente a x++).

La utilización de funciones puede resultar perjudicial en ocasiones, como es en el caso de una función que sea llamada desde el interior de un bucle un gran número de veces. Pensemos en el siguiente ejemplo:

/* Función que evalúa el valor del polinomio

* x + 3x -1 en el intervalo de valores de O a 100

*/

for (i =0; i <>

x = polin((double) i);

printf < " d f n" , i , x > ;

double polin(z)

double z;

return <

Sería más eficiente la escritura del programa en la forma:

for (i =0; i <>

x = (double) i;

x = ( (x + 3) * x -1) ;

printf <" d f n", i , x) ;

}

donde hemos eliminado la llamada a la función polin, que debería realizarse 100 veces, con lo que esto significa en cuanto a secuencias de llamada y retorno de función y manejo de stack: la llamada requiere salvar el estado de determinados registros de la CPU mediante la instrucción push de ensamblador, y el retorno requiere la recuperación de sus valores briginales mediante la instrucción pop; en ocasiones la función exige además la utilización de variables automáticas que han de ser definidas en cada llamada a función.

La utilización de punteros en lugar de índices de arrays genera un código más compacto en todos los casos, si tenemos en cuenta que internamente el compilador convierte todas las referencias a subíndices en manejo de punteros.

Por ejemplo accedería al elemento "j" del array creando internamente un puntero al elemento 0 del array y desplazando el puntero en "j" elementos, incrementando posteriormente "j" en 1.

Sería más eficiente porque "p" ya es directamente un puntero al elemento "j" del array al que deseamos acceder. El manejo de punteros es muy utilizado en los casos en que se desea acceder secuencialmente a todos los elejnentos de un array.

En algunos círculos C es considerado como un lenguaje críp-tico, difícil de leer y de escribir. Esta mala reputación es debida Integramente al estilo de algunos programadores, amigos de escribir programas difíciles de comprender incluso para ellos mismos cuando ha transcurrido algún tiempo desde su escritura, debido a la utilización de nombres de variables inadecuados, problemas muy complejos llenos de "trucos" y utilización de expresiones complicadas en la misma línea sin hacer uso de variables intermedias. Todo esto puede.hacer en ocasiones que los programas sean un poco más rápidos, al precio de hacerlos ininteligibles, siendo más razonable plantearse de antemano la idea de si no sería posible mejorar el algoritmo empleado, reducir el núme-i o de llamadas a funciones o, en casos muy extremos, codificar alguna subrutina en lenguaje ensamblador, aunque esto requiere una muy buena razón para hacerlo.

Portabilidad

La portabilidad en lenguaje C se refiere a la posibilidad de transportar programas de uno a otro ordenador y ponerlos en funcionamiento con sòlo volver a compilarlos. La portabilidad se refiere sòlo a los programas fuente; no tiene sentido alguno intentar transportar programas compilados de uno a otro ordenador, pues han sido compilados haciendo uso de las facilidades suministradas por un determinado hardware, que no tiene porqué estar presente sobre otro, salvo que expresamente nos estemos refiriendo <» ordenadores compatibles y funcionando bajo un mismo sistema operativo.

Posibles causas de pérdida de portabilidad son la utilización de "números mágicos" o constantes dependientes del ordenadorconsiderado, como pueden ser el tamaño en bytes de los buffers del sistema, la utilización de secuencias de escape y control particulares para determinados periféricos y el empleo de funciones específicas de un sistema operativo que no tienen por qué estar presentes o funcionar igual en otros sistemas.

Para evitar el empleo indiscriminado de "números mágicos" es conveniente el empleo de las instrucciones #define e #ifdef del preprocesador de lenguaje C, de manera que permita la modificación de determinadas constantes antes de proceder a la compilación del programa.

De este modo podríamos emplear parámetros y funciones propios de cada sistema operativo con la seguridad de que los programas serían independientes del sistema considerado.
Una causa posible (y peligrosa) de pèrdida de portabilidad es la redefiniciòn de nombres de funciones, como los contenidos en la librería de C estándar. Si redefinimos funciones como printf, strepy, strlen, etc., por otras funciones propias que teniendo el mismo nombre manejen distintos argumentos de llamada y retorno, tendríamos problemas de funcionamiento obvios.

Otra causa de pérdida de portabilidad es la utilización de secuencias de escape y control específicas para determinados periféricos, como pantallas e impresoras de una determinada marca. Si nuestro programa ha de funcionar algún día con atros periféricos tendremos serios problemas de ftmeionamiento. La adopción de las secuencias de escape y control apropiadas para cada periférico ha de efectuarse en tiempo de ejecución del programa, en lugar de en tiempo de compilación. Lo más normal es proceder a la lectura de un fichero de parámetros conteniendo las características de cada periférico considerado. Otra posibilidad (mejor) es la utilización de funciones de librería que hagan uso de esta posibilidad (lectura de un fichero de parámetros) y además utilicen algún tipo de optimización, como el empleo de determinadas funciones que no están presentes en todos los terminales (borrado e inserción de líneas, por ejemplo), o manejen por software otras posibilidades, como ventanas de texto. Estas posibilidades y otras son manejadas por un paquete estándar como es el curses de la Universidad de Berkeley, existente en la mayoría de las instalaciones que trabajan con lenguaje C.

UN EJEMPLO COMPLETO: EL PROGRAMA "calles.c"

A modo de glosario de lo visto hasta ahora vamos a desarrollar un programa que maneje muchos aspectos interesantes del lenguaje C, presentes en la mayoría de los programas que vayamos a realizar.

El programa 'calles.c' tiene como misión modificar el nombre de una calle (que se pasará como argumento de entrada) realizando sobre él una serie de abreviaturas (por ejemplo, cam-, biar 'teniente' por 'tte' o 'señor' por 'sr') y eliminando palabras no deseadas ('de', 'el', 'la',...). Se podría utilizar para minimizar el efecto de posibles duplicaciones en una base de datos que contuviese nombres de calles, con entradas como:

"SANTA CATALINA, PARQUE DE" "SAN ANDRES, CALLE"

El programa devolvería como salida los nombres:

"STA CATALINA PQE"

"S ANDRES CALLE"

donde ha reducido las palabras 'SANTA' y 'SAN' cambiándolas por 'STA' y 'S', y eliminado la partícula 'DE' y las comas de separación. Pensemos ahora en una gran base de datos, con 50.000 o más nombres de calles, que constituyan un índice, y el efecto de entradas como:

"SAN ANDRES, CALLE"

"S. ANDRES CALLE DE"

"DE SAN ANDRES, CALLE"

"SAN ANDRES CALLE"

Nuestro programa evitaría esta dispersión de nombres y los transformaría en el único nombre:

"S ANDRES CALLE"

facilitando su localización y eliminando la posibilidad de claves duplicadas por error.

El ejemplo no es muy importante en sí mismo, pero maneja conceptos aislados que sí son de interés; argumentos en la línea de llamada a un programa, manejo de arrays y estructuras con punteros, funciones propias y de la librería C estándar, y estructuras de control muy empleadas en C (for, if, if else, switch).

Para compilar el programa 'calles.c' habremos de utilizar el comando de sistema operativo:

ce -o calles calles.c y ejecutar el programa como:

calles 'SAN ANDRES, CALLE DE'

obteniendo como resultado:

Entrada: ->SAN ANDRES, CALLE DE <-Salida: ->S ANDRES CALLE<-

El listado del programa, casi sin ningún tipo de comentario para facilitar su seguimiento y el de su' estructura, es el que sigue (al final del capítulo se incluye con todos los comentarios"'autoex-plicativos).

Al principio del programa se declaran las variables globales calle[30] y palabra[30], como cadenas de caracteres, y se declara tabla como una función que devuelve un puntero a una cadena de caracteres.

Luego se define la estructura mapa, compuesta por carJn y car_out, punteros a las cadenas de caracteres de entrada y salida, respectivamente, calles se define como una estructura mapa, y se procede a inicializarla.

El número de elementos que contiene fe estructura calles se calcula como

short hi_calle=sizeof(calles) / sizeof(struct mapa);

Es decir, el número de elementos (hi_calle) equivale al tamaño (sizeof) de la estructura calles, dividido por el tamaño de un elemento (mapa).

En este caso main aparece como main(argc, argv), debida a que va a manejar argumentos en la línea de llamada (el nombre de la calle a modificar). El argumento se pasa entre comillas simples para evitar que los espacios en blanco intercalados hagan que sea considerado como varios argumentos.

Mediante un bucle for y el desplazamiento de un puntero arg[l], se va utilizando el argumento de entrada carácter a carácter. Si se encuentra un carácter distinto del separador (los separadores son '', V y ','), se carga en el array "palabra" y se incrementa el subíndice "i".

Si, por el contrario, se alcanza un separador entonces se añade un carácter nulo a «palabra», como terminador de cadena de caracteres, y se envía cornos-argumento a la funcióh-tabla, que nos devolverá un puntero a una 'cadena, bien apuntando a uña cadena distinta de la de entrada, si la encuentra en la estructura calles, o la misma cadena de entrada si no la ha encontrado. Al final le añade un espacio en blanco' como separador, y reinicializa el subíndice "i" a 0, para preparar la búsqueda de otra palabra.

Esta secuencia se repite hasta alcanzar el final de la cadena de entrada argv[l].

La función tabla tiene como misión efectuar la búsqueda de una palabra en la estructura calles, devolviendo la palabra modificada en caso de encontrarla. La búsqueda en la estructura se im-plementa mediante una búsqueda binaria.

Se compara la cadena a buscar con la correspondiente a la mitad de la tabla (que ha de estar ordenada), si es mayor la cárdena buscada se repite la búsqueda en la mitad superior de la tabla, si no en la inferior; y así sucesivamente hasta encontrar el elemento buscado o finalizar con una comparación entre dos posibles elementos. Si al final del proceso no se ha encontrado la cadena buscada, se devuelve un puntero a la misma cadena, para que el programa principal la añada a la cadena obtenida como resultado de salida.

Al final, se llama a la función trimar para que elimine los espacios en blanco que pudiera haber ál principio, final, y embebidos en la cadena obtenida como resultado del programa y se imprimen para su comparación las cadenas de entrada y salida.

La función trimar elimina los caracteres en blanco que pudiera haber en una cadena de caracteres, mediante el desplazamiento de un puntero. Cuando encuentra un blanco intermedio, pone a uno un indicador (dbl) y continúa la exploración de la cadena sin copiar ningún carácter a su salida. Cuando se encuentre un carácter distinto de blanco, si tiene a 1 el indicador dbl, entonces pone un blanco a su salida, y reinicializa dbl a 0. Luego copia sobre su salida el carácter distinto de blanco que acababa de encontrar. De este modo, ha convertido la aparición de múltiples blancos en uno solo. Al finalizar la exploración de la cadena, sitúa un carácter nulo como terminador de cadena de caracteres.