Beruflich Dokumente
Kultur Dokumente
doc
Pgina 1 de 59
HVF
INTRODUCCIN AL LENGUAGE C
Para iniciar este curso de programacin es necesario establecer unas cuantas bases tiles que se aplicarn a lo largo de todos los temas tratados. En primer lugar veamos como nombrar un identificador, ste es utilizado por cualquier variable, funcin, definicin de datos, etc. En C, un identificador es una combinacin de caractres siendo el primero una letra del alfabeto o un smbolo de subrayado y el resto cualquier letra del alfabeto, cualquier dgito numrico o smbolo de subrayado. Dos reglas debemos tener en mente cuando nombramos identificadores: 1. El tamao de los caracteres alfabticos es importante. Usar PRINCIPAL para el nombre de una variable no es lo mismo que usar principal como tampoco es lo mismo que usar PrInCiPaL. Los tres se refieren a variables diferentes. 2. De acuerdo al estndar ANSI-C, al darle nombre a un identificador solo sern significativos los primeros 31 caractres, todo carcter mas all de este lmite ser ignorado por cualquier compilador que cumpla la norma ANSI-C Un elemento importante es el smbolo de subrayado que puede utilizarse como parte del nombre de una variable, contribuyendo notablemente a la legibilidad del cdigo resultante. Es utilizado por algunos, pero no por todos los programadores en C experimentados. Algunos subrayados sern utilizados en esta tutorial a manera de ilustracin. Debido a que una gran parte de los escritores de compiladores utilizan el subrayado como primer carcter para los nombres de variables internas de sistema, es aconsejable evitar el uso del subrayado para iniciar un identificador para evitar la posibilidad de una confusin de nombres en la etapa de compilacin, ms especfico, los identificadores con doble subrayado estn reservados para uso del compilador as como los identificadores que empiezan con un subrayado seguido de una letra mayscula. Esto es importante. La legibilidad de un programa se incrementa notablemente al utilizar nombres descriptivos para las variables y esto puede ser ventajoso para Usted. Programadores de Pascal y Ada tienden a utilizar nombres descriptivos largos, pero la mayora de los programadores de C tienden a usar nombres cortos y crpticos. Por esta razn la mayora de los programas de ejemplo de este tutorial utilizan nombres muy cortos, pero se usan algunos nombres largos a manera de ilustracin. Sin embargo insistimos en la importancia de utilizar nombres descriptivos que a su vez eviten comentarios redundantes
Palabras clave
Existen 32 palabras definidas como palabras clave en C. Estas tienen usos predefinidos y no pueden ser utilizadas para ningn otro propsito en un programa en C. Estas son utilizadas exclusivamente por el compilador. Afortunadamente para los programadores en espaol, las palabras clave estn definidas en Ingls por lo que resulta til al momento de evitar confusin con los identificadores que utilicemos para nuestros programas, adems siempre estn escritas en letra minscula, la lista completa es la siguiente: auto double int struct break else long Switch case enum register typedef char extern return Union const float short unsigned continue for signed Void default goto sizeof volatile do if static While Adicionalmente su compilador puede definir algunas palabras clave, mismas que estarn enlistadas en la documentacin del mismo. Cada una de las palabras clave arriba mencionadas sern definidas, ilustradas y utilizadas a lo largo de este curso.
Datos y programa.
Todo programa de computadora tiene dos entidades a considerar: los datos, y el programa en s. Estos son altamente dependientes uno del otro y una cuidadosa planeacin de ambos conducir a un programa bien escrito. Desgraciadamente no es posible estudiar cualquiera de estas sin un conocimiento en la otra parte, por esta razn este curso tratar de mostrar tanto mtodos de escritura de programas como mtodos de definicin de datos. Simplemente siga adelante y Usted tendr un buen conocimiento de ambos. Conforme avance por los programas de ejemplo encontrar que cada uno est completo, por lo que no hay fragmentos que resulten confusos, esto le permitir ver cada requerimiento necesario para utilizar cualquiera de las caractersticas de C conforme se vayan presentando. A lo largo de este curso, las palabras clave, los nombres de variables y los nombres de funciones estarn escritas en negrita y todas ellas sern completamente definidas a lo largo de este curso. Cada cdigo presentado en este tutorial ha sido probado utilizando Symantec C++ version 7.5. El resultado de la ejecucin de cada programa lo mostramos con una imagen capturada directamente del monitor al momento en que probamos el respectivo cdigo, en otros casos mostraremos el resultado en forma de comentario al final del cdigo fuente una vez que demos la definicin de comentario mas adelante. Si Usted piensa que entiende completamente el programa, puede consultar simplemente el resultado de la ejecucin, en este caso no es necesario compilar y ejecutar cada programa, sin embargo es aconsejable la compilacin de algunos de los programas debido a que diferentes compiladores no producen exactamente los mismos resultados y es necesario que Usted se familiarice con su propio compilador. Adems es posible seleccionar el cdigo directamente del navegador, copiarlo y pegarlo en el editor de texto del compilador que Usted utilice. Para probar que su compilador C est funcionando adecuadamente compile y ejecute el siguiente programa:
76312839.doc
Pgina 2 de 59
HVF
# include <stdio.h> int main () { int index ; for (index = 0; index = 7; index = index + 1) printf ("Primer programa de ejemplo.\n") ; return 0 ; } No se preocupe si no entiende que hace este programa, a su debido tiempo lo entender completamente. Volver al principio
76312839.doc
Pgina 3 de 59
HVF
manera dividir la palabra entre dos lneas. Ahora es posible una descripcin detallada del programa. La primer printf ( ) despliega una lnea de texto y regresa el cursor. La segunda printf ( ) despliega otra lnea de texto pero sin regresar el cursor de tal manera que la tercera lnea aparece al final de la segunda, entonces le siguen dos retornos de cursor dando como resultado un espacio en blanco. Finalmente la cuarta instruccin printf ( ) despliega una nueva lnea de texto seguida por el retorno del cursor finalizando el programa. Esta sera la salida mostrada en su monitor: Es buena idea experimentar con este programa agregando instrucciones printf ( ) para asegurarnos de entender como trabaja esta funcin, cuanto mas modifique y compile los ejemplos dados en este tutorial tanto ms aprender conforme avance en su trabajo.
VISUALIZACION NUMERICA.
Este es el cdigo que utilizaremos como primer ejemplo de cmo trabajar con datos en un programa C: # include <stdio.h> int main() { int indice; indice = 13; printf("El valor de indice es %d\n", indice); indice = 27; printf("El valor de indice es %d\n", indice); indice = 10; printf("El valor de indice es %d\n", indice); return 0; } El punto de entrada main ( ) debe resultarle claro as como la primera llave. Lo nuevo que encontramos est en la lnea 4 que nos dice int indice; la cual se utiliza para definir una variable de tipo entero llamada indice. La palabra int es una palabra clave de C y no puede ser utilizada con otros fines, define una variable que almacena un nmero entero dentro de un rango predefinido de valores, definiremos el actual rango posteriormente. El nombre de la variable, indice, puede ser cualquier nombre que siga las reglas dadas para un identificador. El punto y coma al final de la lnea es un terminador de enunciado como se explic al principio. Observe que, aunque hemos definido una variable no le asignamos a sta un valor por lo que se dice que contiene un valor indefinido, posteriormente veremos cmo definir varias variables en la misma lnea de instrucciones. Veamos el cuerpo principal del programa, notar que hay tres enunciados que asignan un valor a la variable indice, pero solo uno a la vez. El enunciado en la lnea 5 asigna a indice el valor de 13, y este valor es desplegado en la lnea 6. Despus se asigna a indice el valor de 27, y finalmente le asignamos el valor de 10. Esta claro que indice es una variable que puede almacenar muchos valores diferentes pero solo uno a la vez. El programa una vez compilado aparece de la siguiente forma: Continuando con el analisis del programa veamos los enunciados que contienen printf ( ). Todos son idnticos e inician de la misma forma que los printf ( ) que habamos visto anteriormente, la primera diferencia la encontramos en el carcter %, este seala a la rutina de salida para detener el despliegue de caracteres y hacer algo diferente, generalmente mostrar el valor de una variable. El smbolo % se utiliza para sealar el despliegue de muchos tipos diferentes de variables, pero nos concentraremos en uno solo en este ejemplo. El carcter que le sigue al smbolo % es una d, la cual indica a la rutina de salida tomar un valor decimal y desplegarlo en el monitor. Despus de la d encontramos a la ahora familiar \n para el retorno del cursor y por ltimo el cierre de parntesis. Todos los caracteres entre parntesis definen el patrn de datos a desplegar por el enunciado, luego est una coma seguida por el nombre de la variable indice. Aqu es donde la funcin printf ( ) obtiene el valor decimal como se lo indic %d segn vimos. El sistema sustituye el valor actual de la variable llamada indice por los smbolos %d y los muestra en el monitor.
76312839.doc
Pgina 4 de 59
HVF
COMENTARIOS EN UN PROGRAMA C.
Agregamos comentarios al cdigo C de un programa para hacerlo mas entendible para Usted pero carente de significado para el compilador, por lo que le indicamos al compilador ignorar completamente los comentarios encerrndolos en caracteres especiales. La combinacin de lnea diagonal y asterisco se usa en C para delimitar comentarios como podemos ver en el siguiente cdigo, observe que este programa ilustra una mala tcnica al hacer comentarios pero a su vez muestra donde pueden situarse los comentarios. # include <stdio.h> /* Este es un comentario que el compilador ignora */ int main() /* Este es otro comentario ignorado por el compilador*/ { printf("Buscamos como son utilizados los comentarios "); /* Un comentario esta permitido continuar en otra lnea */ printf ("en C.\n"); return 0; } /* Agregamos aqu un comentario ms... */ La combinacin de lnea diagonal y asterisco en la lnea 2 introduce el primer comentario mientras que la combinacin de asterisco y lnea diagonal finaliza el comentario en esa lnea. Observe que este comentario est antes del principio del programa lo que ilustra que un comentario puede preceder al programa en s. Una buena prctica de programacin es incluir un comentario antes del inicio del programa con una breve descripcin del mismo. Observe que el comentario inicia con la combinacin de lnea diagonal y asterisco finalizando con la combinacin de asterisco y lnea diagonal, as en ese orden ( /* Texto del comentario */ ). Es muy importante que no deje espacio alguno entre el asterisco y la lnea diagonal pues de lo contrario el compilador no sabr que se trata de un comentario y por ende se generan mensajes de error. En el siguiente cdigo podemos ver un ejemplo de un programa con un formato de comentarios bien hecho. Con la experiencia que Usted ha ganado hasta el momento es fcil comprender el programa en su totalidad, el compilador ignora todo el espacio extra en tanto que el retorno de cursor le da amplia libertad al momento de darle formato al cdigo del programa. Compile el programa y observe el resultado. #include <stdio.h> int main() / * Aqu empieza el programa* / { printf ("Un buen formato"); printf ("puede ayudar a "); printf ("entender un programa.\n"); printf ("Y un mal formato "); printf ("puede convertir un programa"); printf ("en algo difcil de comprender.\n"); return 0; } Ahora observe este otro cdigo. En cuanto tiempo comprendi su funcionamiento ? Para el compilador no importa el formato que Usted utilice, pero a Usted s que le importar cuando trate de resolver un problema relacionado con el cdigo de su programa (Tcnica conocida como debbuging). Compile y ejecute el programa, se sorprender que hace lo mismo que el programa anterior, la nica diferencia est en el formato de la escritura del cdigo. # include <stdio.h> int main() /* main inicia aqui * / {printf ("Un buen formato ");printf ("puede ayudar a"); printf ("entender un progama.\n") ;printf ("Y un mal formato ");printf("puede convertir un programa "); printf ("en algo difcil de entender.\n");return 0;} En estos momentos no se preocupe mucho por el formato de sus programas. Tiene mucho tiempo para desarrollar un estilo propio conforme avance en su aprendizaje del lenguaje C. Sea Usted crtico de los estilos que vea en programas C en libros y revistas.
76312839.doc
Pgina 5 de 59
HVF
EL BUCLE while
El lenguaje de programacin C contiene varias instrucciones condicionales y de bucle, en este captulo las trataremos todas ellas empezando con el bucle while. El bucle while se ejecuta mientras una condicin es cierta. Cuando esta condicin se torna falsa, el bucle termina su operacin. Veamos el siguiente ejemplo: /* Este es un ejemplo del bucle while. */ # include <stdio.h int main() { int contador; contador = 0; while (contador < 6) { printf ("El valor de contador es %d\n", contador); contador = contador + 1; } return 0 ; } / * Resultado de la ejecucin del programa El valor de contador es 0 El valor de contador es 1 El valor de contador es 2 El valor de contador es 3 El valor de contador es 4 El valor de contador es 5 */ En este programa empezamos con un comentario y el punto de entrada main ( ), despus definimos una variable de tipo entero a la que llamamos contador dentro del cuerpo del programa, esta variable es inicializada a cero para despus entrar en el bucle while. La sintaxis del bucle while es justamente como se muestra en el programa. A la palabra clave while le sigue una expresin de algo entre parntesis y luego una serie de enunciados encerrados entre llaves. Tan pronto como la expresin entre parntesis es verdadera todos los enunciados entre las llaves se ejecutarn repetidamente. En este caso, debido a que la variable contador es incrementada en 1 cada que los enunciados entre llaves son ejecutados, eventualmente se alcanzar el valor de 6. En este punto los enunciados no se ejecutarn mas porque contador ya no es menor que 6 finalizando as el bucle. El programa continuar entonces con los enunciados que siguen a las llaves. La expresin de comparacin entre parntesis de la instruccin while la trataremos en el siguiente captulo, antes debemos hacer algunas observaciones respecto al bucle. Primero, si la variable contador fuera inicializada a un valor mayor de 5, los enunciados dentro de las llaves podran no ejecutarse por lo que es posible tener un bucle que jams se ejecute. Segundo, si la variable no se incrementa dentro del bucle este jams terminara y por ende el programa. Finalmente, en el caso de existir un solo enunciado por ejecutar entonces no es necesario el uso de llaves. A partir de este programa veremos el resultado de la ejecucin del mismo en forma de comentarios y en algunas veces mostraremos imgenes del programa ejecutado al final del cdigo. Tambin continuaremos ignorando el significado de los enunciados #include y return ya que se explicarn posteriormente.
EL BUCLE do-while
Tenemos ahora una variacin del bucle while en nuestro siguiente ejemplo, este programa es casi idntico al ejemplo anterior excepto que el bucle inicia con la palabra clave do, seguida por una serie de enunciados compuestos entre llaves, despus viene la palabra clave while y finalmente la expresin de evaluacin entre parntesis. /* Este es un ejemplo del bucle do-while */ # include <stdio.h int main() {
76312839.doc int i; i = 0; do { printf ( "El valor de i es ahora %d\n", i ); i = i + 1; } while (i < 5); return 0; }
Pgina 6 de 59
HVF
Los enunciados entre llaves se ejecutan repetidamente en tanto que la expresin entre parntesis sea verdadera. Cuando la expresin es falsa, la ejecucin del bucle termina y el control del programa pasa a los enunciados siguientes. Respecto al bucle dowhile debemos apuntar lo siguiente. En primer lugar, debido a que la prueba verdadero-falso se hace al final del bucle, los enunciados dentro de las llaves se ejecutan al menos una vez. En segundo, si la variable i no cambia dentro del bucle entonces el programa jams terminara. Observe adems que los bucles pueden anidarse, esto es, un bucle puede contener dentro de sus enunciados otro bucle. El nivel de anidamiento no tiene lmite y esto lo ilustraremos mas adelante.
EL BUCLE for
/* Este es un ejemplo del bucle for */ #include <stdio.h int main() { int indice; for(indice = 0 ; indice /* Resultado de la ejecucin: El valor de indice es 0 El valor de indice es 1 El valor de indice es 2 El valor de indice es 3 El valor de indice es 4 El valor de indice es 5 */ El bucle for consiste de la palabra clave for seguida de una expresin entre parntesis. Esta expresin se compone realmente de tres campos cada uno separado por un punto y coma. El primer campo contiene la expresin "indice = 0" y se le llama campo de inicializacin. Cualquier expresin en este campo se ejecuta antes del inicio del bucle, en trminos generales se puede decir que no existe lmite en el contenido del primer campo ya que es posible contener varios enunciados separados por comas sin embargo es buena prctica de programacin mantener las cosas simples. El segundo campo, que en este caso contiene "indice " es la prueba que se hace al principio de cada ciclo del bucle y puede ser cualquier expresin que pueda evaluarse a verdadero falso. La expresin del tercer campo se ejecuta en cada ciclo del bucle pero solo hasta que se hayan ejecutado todas las instrucciones contenidas dentro del cuerpo principal del bucle, en este campo, como en el primero es posible contener varias expresiones separadas por comas. En seguida de la expresin for ( ) estn uno o varios enunciados que conforman el cuerpo ejecutable del bucle. Un enunciado compuesto es cualquier grupo de instrucciones vlidas en C encerradas entre llaves. Un bucle while es til cuando se desconoce cuantas veces ser ejecutado un bucle, en tanto que la instruccin for se usa generalmente en aquellos casos en donde debe existir un nmero fijo de interacciones, adems, el bucle for es conveniente porque contiene toda la informacin del control del bucle en un solo lugar, dentro de un parntesis. Es de su eleccin utilizar uno u otro bucle y dependiendo de cmo sean utilizados cabe la posibilidad con cada uno de estos bucles de no ejecutar las instrucciones dentro del cuerpo del bucle, esto es porque la prueba se hace al principio del bucle y en la primera interaccin puede fallar, sin
76312839.doc
Pgina 7 de 59
HVF
embargo con la instruccin do-while tenemos la seguridad de ejecutar el cuerpo del bucle al menos una sola vez porque la prueba en este bucle se hace al final del ciclo.
EL ENUNCIADO if
Con la instruccin if tenemos el primer ejemplo de un enunciado condicional. Observe en primera instancia la presencia de un bucle for con un enunciado compuesto que contiene dos instrucciones if. Este es a su vez un ejemplo de instrucciones anidadas, est claro que cada una de las instrucciones if ser ejecutada 8 veces. /* Ejemplo de los enunciados if e if-else */ # include <stdio.h int main() { int valor; for(valor = 0 ; valor < 5) printf("Este mensaje se muestra cuando valor que es %d es menor que 5\n", valor); else printf("Este mensaje se muestra cuando valor que es %d es mayor que 4\n", valor); } /* Fin del bucle */ printf("Este mensaje se mostrara solo cuando finalice el bucle \n"); return 0; } Veamos el primer enunciado if, este empieza con la palabra clave if seguida de una expresin entre parntesis, si esta es evaluada a verdadero se ejecuta la instruccin que le sigue, pero si es falso se brinca esta instruccin y contina la ejecucin en los siguientes enunciados. La expresin "valor == 2" est simplemente preguntando si el valor de valor es igual a 2, observe que se est utilizando un doble signo de igual para evaluar a verdadero cuando valor vale 2, utilizar "valor == 2" tiene un significado completamente diferente que explicaremos mas adelante. La segunda instruccin if es similar a la primera excepto por la adicin de la palabra clave else en la lnea 14, esto significa que si el enunciado entre parntesis se evala a verdadero se ejecuta la primera expresin, de lo contrario la instruccin que sigue a else ser ejecutada, por lo que una de las dos instrucciones ser siempre ejecutada. Observe adems que el programa imprime dos mensajes diferentes cuando valor vale 2, esto es as porque tenemos dos condiciones verdaderas cuando valor es 2, esto es, cuando valor es exactamente 2 y a la vez es menor que 5. Compile este programa y observe su funcionamiento.
76312839.doc
Pgina 8 de 59
HVF
Ahora xx es diferente de 8, xx tiene el valor de 12 Ahora xx es diferente de 8, xx tiene el valor de 13 Ahora xx es diferente de 8, xx tiene el valor de 14 */ Observe que en el primer bucle for, existe una instruccin if que llama a un break si xx es igual a 8. La instruccin break lleva al programa a salirse del bucle que se estaba ejecutando para continuar con los enunciados inmediatos al bucle, terminando ste en forma efectiva. Se trata de una instruccin muy til cuando se desea salir del bucle dependiendo de los resultados que se obtengan dentro del bucle. En este caso, cuando xx alcanza el valor de 8, el bucle termina imprimiendo el ltimo valor vlido, 7. La instruccin break brinca inmediatamente despus de la llave que cierra el bucle. En el siguiente bucle for que empieza en la lnea 12, contiene un enunciado continue el cual no finaliza el bucle pero suspende el presente ciclo. Cuando el valor de xx alcanza como en este caso, el valor de 8, el programa brincar al final del bucle para continuar la ejecucin del mismo, eliminando as la instruccin printf ( ) cuando en el bucle xx alcanza el valor de 8. El enunciado continue siempre brinca al final del bucle, justo antes de la llave que indica el fin del bucle.
LA INSTRUCCIN switch
Estudiaremos ahora una de las instrucciones mas importantes del lenguaje C, el enunciado switch, sta no es difcil as que no permita que lo intimide. Empieza con la palabra clave switch seguida por una variable entre parntesis la cual es la variable de conmutacin, en este ejemplo truck. Las condiciones de conmutacin se encierran entre llaves. La palabra reservada case se utiliza para empezar cada condicin, le sigue el valor de la variable para la condicin seleccionada, despus un smbolo de colon (dos puntos) y por ltimo los enunciados a ser ejecutados. # include <stdio.h int main() { int pato; for (pato = 3 ; pato La mejor manera de entender el funcionamiento de la instruccin switch es compilando y ejecutando el programa de este ejemplo, cuando la variable pato vale 3 la instruccin switch causa que el programa brinque directamente a la lnea 9 donde printf ( ) despliega "pato vale tres" y el enunciado break hace brincar la ejecucin del programa fuera del bucle de instrucciones de switch. Cuando el valor de la variable pato est especificado en una instruccin case dentro del bucle de instrucciones de switch, los enunciados del programa sern ejecutados en orden hasta encontrar una instruccin break cuyo funcionamiento se explic en el prrafo anterior. En el ejemplo que presentamos, cuando pato vale 5, este valor est asociado a una instruccin case pero como no est asociada ninguna instruccin para ejecutar, el programa contina hasta encontrar una instruccin ejecutable, que en el ejemplo es printf ( ) de la lnea 15. En el caso en que el valor de pato no est asociado con una instruccin case se ejecuta el enunciado especificado en la instruccin default. La instruccin switch no se usa con la misma frecuencia que el bucle o el enunciado if, de hecho se usa muy poco, sin embargo debe ser comprendida completamente por el programador C serio.
EL ENUNCIADO goto
Para utilizar este enunciado simplemente use la palabra clave goto seguida por el nombre simblico a donde se quiera hacer el salto, este nombre se coloca en cualquier parte del cdigo seguido de un smbolo de colon (dos puntos). Usted puede brincar a donde quiera, pero no est permitido hacerlo hacia el interior de un bucle, aunque si esta permitido brincar fuera del bucle. # include <stdio.h int main () { int uno, dos, tres; casa_del_lobo: { printf("Auuuuuuuuuuu \n"); if(uno==1) if(dos==2) if(tres==3) {
76312839.doc
Pgina 9 de 59
HVF
printf("Este es el ultimo mensaje... \n"); printf("La suma de uno, dos y tres es %d \n", (uno+dos+tres)); goto fin_del_programa; } printf("Todavia no termina el programa... \n"); } goto inicio; otro_lugar: { printf("Estamos perdidos... \n"); tres=3; printf("tres vale %d \n", tres); goto un_lugar_mas; } un_lugar_desconocido: { dos=2; printf("Este no es el inicio del programa... \n"); printf("Con goto se puede brincar fuera de un bucle... \n"); goto casa_del_lobo; } goto fin_del_programa; inicio: { uno=1; printf("Ahora uno vale %d \n", uno); } goto otro_lugar; un_lugar_mas: printf("Este lugar es solo para brincar a otro lado... \n"); goto un_lugar_desconocido; fin_del_programa: return 0; } Este programa en particular es un verdadero embrollo pero es un buen ejemplo de porque los desarrolladores de software estn tratando de eliminar el uso del enunciado goto tanto como sea posible. Algunas personas opinan que goto no debera utilizarse nunca, esto es un criterio reducido pues si Usted se llegara a encontrar en una situacin donde el uso de goto facilita la ejecucin del programa sienta plena libertad de utilizar la sentencia goto. A los cdigos escritos sin sentencias goto se les suele conocer con el nombre de "Programacin Estructurada". Un buen ejercicio podra consistir en re-escribir el cdigo para ver en que manera se vuelven los enunciados mas legibles cuando estn enlistados en orden. A lo largo de este captulo nos referiremos al rango de una variable, esto significa los lmites de valores que pueden ser almacenadas en una variable dada. Su compilador puede usar rangos diferentes para algunas variables debido a que el estndar ANSI no define lmites especficos para todos los tipos de datos. Consulte la documentacin de su compilador para saber el rango exacto para cada tipo de variable.
ASIGNANDO ENTEROS.
En el primer programa de este captulo veremos ejemplos de enunciados de asignacin. Tres variables estn definidas para usarse en este programa y el resto del cdigo consiste en una serie de ilustraciones de varios tipos de asignacin. Las tres variables estn definidas en una sola lnea e inicialmente almacenan valores desconocidos. Las primeras dos lneas de asignacin, lneas 6 y 7, asignan valores numricos a las variables llamadas a y b, las cinco lneas siguientes ilustran las cinco funciones aritmticas bsicas y cmo usarlas. La quinta funcin se llama operador modulo y devuelve el resto cuando las dos variables son divididas, solo se puede aplicar a variables de tipo entero mismas que definiremos mas adelante. Las siguientes dos lneas demuestran cmo combinar algunas variables en expresiones matemticas relativamente complejas. Todos estos ejemplos tienen un uso meramente ilustrativo.
76312839.doc
Pgina 10 de 59
HVF
/* Este programa ilustra diversos enunciados de asignacin */ int main() { int a, b, c; /* Variables de tipo entero para los ejemplos */ a = 12; b = 3; c = a + b; /* suma simple */ c = a - b; /* substraccin resta */ c = a * b; /* multiplicacin */ c = a / b; /* divisin */ c = a % b; /* modulo (resto) */ c = 12*a + b/2 - a*b*2/(a*c + b*2); c = c/4+13*(a + b)/3 - a*b + 2*a*a; a = a + 1; /* incremento de variable */ b = b * 5; a = b = c = 20; /* asignacin mltiple */ a = b = c = a + b * c/ 3; a = (b = (c = 20)); /* Igual a la lnea 18 */ return 0 ; } /* Resultado de la ejecucin: (Este programa no tiene salida.) */ La procedencia de los operadores es un tpico muy importante que Usted necesita estudiar en detalle en algn momento, por lo pronto necesitamos unas cuantas reglas. Cuando se tienen expresiones aritmticas combinadas, los operadores de multiplicacin y divisin se completan antes que los operadores de suma y resta estando en el mismo nivel lgico, as cuando evaluamos a*b+c/d, la multiplicacin y la divisin se ejecutan primero y despus la suma. Sin embargo, en la expresin a*(b+c/d), la suma sigue de la divisin y posteriormente la multiplicacin porque las operaciones estn en niveles lgicos diferentes como lo define el uso del parntesis. Los enunciados en las lneas 15 y 16 son perfectamente aceptables como estn, pero como veremos mas adelante en este captulo, hay otra forma de escribir estas sentencias con un cdigo mas compacto. En las lneas 17 y 18 se dan ejemplos de asignacin mltiple. El compilador C rastrea el enunciado de asignacin de derecha a izquierda, resultando un constructor muy til. El compilador encuentra el valor 20, lo asigna a c, entonces contina a la izquierda encontrando que el resultado del ltimo clculo debe asignarse a b. Como este resultado fue 20 asigna a su vez ste valor a b y contina rastreando a la izquierda asignando el valor de 20 a la variable a. Este es un constructor muy til cuando Usted inicializa un grupo de variables. La lnea 18 ilustra que es posible efectuar algunos clculos antes de asignar el resultado a un grupo de variables. Los valores de a, b y c, antes del principio de la lnea 18 son utilizados para el clculo, el resultado se asigna posteriormente a cada una de las tres variables.
76312839.doc
Pgina 11 de 59
HVF
tipo de dato char fue diseado para almacenar representaciones de caracteres ASCII, puede ser utilizado a su vez para almacenar datos de valor pequeo, veremos mas de este tema cuando estudiemos cadenas en un captulo posterior. /* Tipos de datos nuevos */ int main() { int a, b, c; /* Entero, de -32768 a 32767 sin punto decimal */ char x, y, z; /* De -128 a 127 sin punto decimal */ float numero, gato, casa; /* De 3.4E-38 a 3.4E+38 con punto decimal */ a = b = c = -27; x = y = z = 'A'; numero = gato = casa = 3.6792; a = y; /* a es ahora 65 (carcter A) */ x = b; /* x es ahora -27 */ numero = b; /* num ser -27.00 */ a = gato; /* a tomar el valor de 3 */ return 0; } /* Resultado de la ejecucin: (Este programa no tiene salida.) */
76312839.doc int main() { int a; /* entero simple */ long int b; /* entero largo */ short int c; /* entero corto */ unsigned int d; /* entero unsigned */ char e; /* carcter */ float f; /* punto flotante */ double g; /* punto flotante de doble precisin */ a = 1023; b = 2222; c = 123; d = 1234; e = 'X'; f = 3.14159; g = 3.1415926535898; printf("a = %d\n", a); /* salida decimal */ printf("a = %o\n", a); /* salida octal */ printf("a = %x\n", a); /* salida hexadecimal */ printf("b = %ld\n", b); /* salida decimal largo */ printf("c = %d\n", c); /* salida decimal corto */ printf("d = %u\n", d); /* salida unsigned */ printf("e = %c\n", e); /* salida carcter */ printf("f = %f\n", f); /* salida flotante */ printf("g = %f\n", g); /* salida flotante doble */
Pgina 12 de 59
HVF
printf("\n"); printf("a = %d\n", a); /* salida entero simple */ printf("a = %7d\n", a); /* usa una amplitud de 7 campos */ printf("a = %-7d\n", a); /* justificado por la izquierda con 7 campos */ c = 5; d = 8; printf("a = %*d\n", c, a); /* utiliza 5 campos */ printf("a = %*d\n", d, a); /* utiliza 8 campos */ printf("\n"); printf("f = %f\n", f); /* salida flotante */ printf("f = %12f\n", f); /* 12 campos */ printf("f = %12.3f\n", f); /* 12 campos y 3 decimales */ printf("f = %12.5f\n", f); /* 5 decimales */ printf("f = %-12.5f\n", f); /* justificado a la izquierda */ return 0; } Con la introduccin del estndard ANSI-C dos palabras clave han sido agregadas a C, estas no estn ilustradas en los ejemplos pero las discutiremos aqu, estas son const y volatile y se utilizan para decirle al compilador que las variables de estos tipos necesitarn especial consideracin. Cuando una variable es declarada como const su valor no padr ser cambiado por el programa, si Usted trata inadvertidamente de modificar una entidad const el compilador generar un mensaje de error. Cuando utilizamos volatile declaramos que el valor puede ser cambiado por el programa y adems puede ser cambiado por una entidad externa como puede ser un pulso de actualizacin de reloj almacenado en una variable. Ejemplos: const int index1 = 5; /* Una variable const debe inicializarse siempre */ const index2 = 6; const float valor_grandote = 1245.12; volatile const int index3 = 45; volatile int index4;
76312839.doc
Pgina 13 de 59
HVF
CARACTERES DE CONVERSION.
Enseguida tenemos una lista de algunos de los caracteres de conversin y la forma en que son utilizados con la instruccin printf ( ), una lista completa de los caracteres de conversin debe estar includa en la documentacin de su compilador, no se preocupe si por el momento no los entiende, es suficiente saber que cuenta con una gran flexibilidad disponible para cuando Usted est listo para utilizarlas. d i o x u c s f Notacin decimal Notacin decimal (Nueva extensin ANSI) Notacin octal Notacin hexadecimal Notacin unsigned Notacin carcter Notacin de cadena Notacin de punto flotante
Cada uno de estos caracteres de conversin se utilizan despus del signo de porcentaje (%) para indicar el tipo de salida deseada, los siguientes campos pueden agregarse entre estos dos caracteres: Justificacin por la izquierda en su campo (n) Amplitud de campo Separa (n) de (m) (m) Dgitos significativos en punto flotante l Largo Todos estos caracteres de conversin se utilizaron en el ejemplo anterior excepto la notacin de cadena, misma que ser cubierta mas adelante.
COMPARACIONES LGICAS.
En el siguiente cdigo mostramos una gran variedad de enunciados de comparacin, empezamos definiendo e inicializando nueve variables para ser utilizados en las comparaciones. El primer grupo es el mas simple porque la comparacin se d solo entre dos variables, cualquier variable puede ser reemplazada por una constante y aun seguir siendo vlida la comparacin, pero utilizar dos variables es el caso mas general. Observe que en este ejemplo hemos introducido el operador de negacin que se representa con el smbolo de admiracin ! , observe adems que los comparadores lgicos "menor que" y "mayor igual que" estn tambin disponibles pero no se ilustran en el ejemplo. Para comprender algunos de los enunciados del ejemplo debemos entender lo que significa verdadero o falso en lenguaje C. Falso est definido como cero, y verdadero es cualquier valor diferente de cero, en los compiladores ANSI-C este valor es 1, sin embargo es recomendable como buena prctica de programacin no utilizar este valor para ningn clculo sino solo para propsitos de control. Cualquier variable de tipo int o char puede utilizarse para una evaluacin de verdadero-falso. En el tercer grupo del ejemplo se introducen los conceptos de los operadores lgicos "and" ( && ) en el cual el resultado de la comparacin es verdadero si ambas partes del enunciado && son verdaderas, y "or" ( || ) en donde la expresin se evala como verdadera si alguna de las dos partes de || es verdadera. Veamos el ejemplo. int main() /* Comparaciones lgicas */ { int x = 11, y = 11, z = 11; char a = 40, b = 40, c = 40; float r = 12.987, s = 12.987, t = 12.987; /* Primer grupo de enunciados de comparacin */ if (x == y) z = -13; /* z = -13 */ if (x > z) a = 'A'; /* a = 65 */ if (!(x > z)) a = 'B'; /* No habr cambios */ if (b <= c) r = 0.0; /* r = 0.0 */ if (r != s) t = c/2; /* t = 20 */
76312839.doc
Pgina 14 de 59
HVF
/* Segundo grupo de enunciados de comparacin */ if (x = (r != s)) z = 1000; /* x = algn nmero positivo, z = 1000 */ if (x = y) z = 222; /* x = y, y z = 222 */ if (x != 0) z = 333; /* z = 333 */ if (x) z = 444; /* z = 444 */ /* Tercer grupo de comparacin */ x = y = z = 77; if ((x == y) && (x == 77)) z = 33; /* z = 33 */ if ((x > y) || (z > 12)) z = 22; /* z = 22 */ if (x && y && z) z = 11; /* z = 11 */ if ((x = 1) && (y = 2) && (z = 3)) r = 12.00; /* x = 1, y = 2, z = 3, r = 12.00 */ if ((x == 2) && (y = 3) && (z = 4)) r = 14.56; /* Ningn cambio */ /* Cuarto grupo de comparacin */ if (x == x); z = 27.345; /* z siempre cambia */ if (x != x) z = 27.345; /* Nada cambia */ if (x = 0) z = 27.345; /* x = 0, z no cambia */ return 0; } /* Resultado de la ejecucin: (Este programa no tiene salida.) */
CONSTRUCCIONES TILES EN C.
Existen tres constructores en C que a primera vista no tienen sentido porque no son intuitivos, pero pueden incrementar la eficiencia del cdigo compilado y son utilizados extensivamente por los programadores de C experimentados, Usted debe aprender a utilizarlos debido a que aparecen en prcticamente todos los programas que Usted ver en publicaciones, veamos estos nuevos constructores: int main() { int x = 0, y = 2, z = 1025; float a = 0.0, b = 3.14159, c = -37.234; /* incremento */ x = x + 1; /* incremento de x */ x++; /* post-incremento de x */ ++x; /* pre-incremento de x */ z = y++; /* z = 2, y = 3 */ z = ++y; /* z = 4, y = 4 */ /* decremento */ y = y - 1; /* decremento de y */ y--; /* post-decremento de y */ --y; /* pre-decremento de y */ y = 3; z = y--; /* z = 3, y = 2 */ z = --y; /* z = 1, y = 1 */
76312839.doc
Pgina 15 de 59
HVF
/* operaciones aritmticas */ a = a + 12; /* Se suma 12 a la variable a */ a += 12; /* Se suman otros 12 a la variable a */ a *= 3.2; /* Multiplica a por 3.2 */ a -= b; /* Resta b de a */ a /= 10.0; /* Divide a entre 10.0 */ /* enunciados condicionales */ a = (b >= 3.0 ? 2.0 : 10.5 ); /* Esta expresin */ if (b >= 3.0) /* Y esta expresin */ a = 2.0; /* son idnticas, ambas */ else /* causarn el mismo */ a = 10.5; /* resultado */ c = (a > b ? a : b); /* c tendr el mayor valor de a b */ c = (a > b ? b : a); /* c tendr el valor menor de a b */ return 0; } /* Resultado de la ejecucin: (Este programa no tiene salida.) */ En la lnea 8 simplemente se agrega 1 al valor de x, los siguientes dos enunciados tambin agregan uno al valor de x, pero no es tan intuitivo respecto a su funcionamiento. Por definicin del lenguaje C un doble signo de mas (++) ya sea antes despus de la variable, incrementa sta en uno, adicionalmente si los signos mas estn despus de la variable, sta se incrementa despus de utilizarla, por el contrario, si los signos mas estn antes de la variable, sta se incrementa y despus se utiliza. En el siguiente grupo se analiza el decremento de la variable aplicandose las mismas reglas que para el incremento de la variable. Los operadores aritmticos por su parte se utilizan para modificar cualquier variable por algn valor constante, en la lnea 25 se suma 12 a la variable a, en tanto que en la lnea 26 el resultado es el mismo, solo que no es tan intuitiva como la instruccin anterior. Colocando el operador deseado antes del signo igual y eliminando la segunda referencia a la variable, esto se puede hacer con los cuatro operadores aritmticos. Al igual que los operadores de incremento y decremento, los operadores aritmticos son utilizados con frecuencia por los programadores experimentados por lo que es muy recomendable su familiarizacin con el uso de estos operadores. El operador condicional consiste de tres expresiones separadas por un signo de interrogacin y por un signo colon (dos puntos). El enunciado previo al signo de interrogacin es evaluada a falso-verdadero, si es verdadero, el enunciado que est entre el signo de interrogacin y el signo colon se valora, por el contrario, la expresin posterior al signo colon es valorada. El resultado es idntico si se utiliza una expresin if con una clausula else pero la expresin condicional tiene la ventaja de ser mas compacta y por lo tanto compilar pocas instrucciones en el programa final. Este ha sido un captulo largo, sin embargo contiene informacin importante para ser un buen programador C, en el siguiente captulo analizaremos la construccin de bloques de C, las funciones, en ese punto Usted tendr a su alcance los materiales bsicos que el permitirn escribir programas tiles y aplicables a la vida real.
76312839.doc
Pgina 16 de 59
HVF
encabezado(); /* Aqu se llama a la funcin llamada header */ for (indice = 1 ; indice <= 7 ; indice ++) cuadrado (indice); /* Llama a la funcin cuadrado */ final(); /* Llama a la funcin final */ return 0; } encabezado () /* Esta es la funcin llamada encabezado */ { suma = 0; /* Inicializa la variable "suma" */ printf("Este es el encabezado para el programa cuadratico \n\n"); } cuadrado (numero) /* Esta es la funcin cuadrado */ int numero; { int numero_cuadrado; numero_cuadrado = numero * numero; /* Esta genera el valor cuadrtico */ suma += numero_cuadrado; printf("El cuadrado de %d es %d\n", numero, numero_cuadrado); } final () /* Esta es la funcin final */ { printf("\nLa suma de los cuadrados es %d\n", suma); } Note la parte ejecutable de este programa que empieza en la lnea 9 con un enunciado que dice simplemente "encabezado ( ) ;", la cual es la manera de llamar a una funcin. El parntesis es necesario porque el compilador C lo utiliza para determinar que se trata de una llamada a funcin y no simplemente una variable mal colocada. Cuando el programa llega a esta lnea de cdigo la funcin llamada encabezado ( ) es llamada, sus enunciados son ejecutados y el control regresa a los enunciados que le siguen a la llamada. Continuando nos encontramos con un bucle for que ser ejecutado siete veces en donde est otra llamada a una funcin denominada cuadrado( ). Finalmente encontramos otra funcin llamada final ( ) que ser llamada y ejecutada. Por el momento ignoraremos la variable indice en el parntesis de la llamada a cuadrado ( ). En seguida del programa principal podemos ver el principio de una funcin en la lnea 18 que cumple con las reglas establecidas para el programa principal excepto que su nombre es encabezado ( ). Esta es la funcin que llamamos desde la lnea 9 del programa principal. Cada uno de sus enunciados sern ejecutados y una vez completos el control retorna al programa principal, o mas propiamente dicho, a la funcin main ( ). El primer enunciado le asigna a la variable llamada suma el valor de cero ya que planeamos utilizarla para acumular la suma de los cuadrados. Como la variable llamada suma fue definida antes del programa principal est disponible para utilizarla en cualquiera de las funciones que se han definido posteriormente. A una variable definida de esta manera se el llama global y su alcance es el programa completo incluyendo todas las funciones. En la lnea 21 se despliega un mensaje en el monitor y despus el control retorna a la funcin main ( ). En la llamada a la funcin cuadrado ( ), hemos agregado una nueva caracterstica, el nombre de la variable indice dentro del parntesis. Esta es una indicacin al compilador para que cuando brinque a la funcin Usted desea tomar el valor de la variable indice para utlizarlo durante la ejecucin de la funcin. Observando la funcin cuadrado ( ) en la lnea 25 encontramos otro nombre de variable encerrado entre parntesis, la variable numero. Este es el nombre que preferimos para llamar a la variable pasada a la funcin cuando ejecutemos el cdigo dentro de la funcin. Debido a que la funcin necesita saber el tipo de variable, esta se define inmediatamente despus del nombre de la funcin y antes de la llave de apertura de la funcin. En la lnea 26, la expresin "int numero;" le indica a la funcin que el valor que le ha sido pasado ser una variable de tipo int. De esta manera el valor de la variable indice del programa principal pasado a la funcin cuadrado ( ) pero renombrada numero y disponible para utilizarse dentro de la funcin. Este es el estilo clsico para definir variables dentro de una funcin y ha estado en uso desde que fue definido por primera vez el lenguaje C. Un nuevo y mejor mtodo est ganando popularidad debido a sus beneficios y lo discutiremos mas adelante en este captulo. En seguida de la llave de apertura de la funcin definimos otra variable llamada numero_cuadrado para utilizarla dentro de la funcin en s. Establecemos la variable llamada numero_cuadrado como el cuadrado del valor almacenado en numero, despus agregamos numero_cuadrado al total almacenado en suma. De la pasada leccin recordar que "suma += numero_cuadrado;" tiene el mismo significado de "suma = suma + numero_cuadrado;", imprimimos el nmero y su cuadrado en la lnea 32 y retornamos al programa principal.
76312839.doc
Pgina 17 de 59
HVF
Cuando pasamos el valor de la variable indice a la funcin debemos puntualizar lo siguiente: Nosotros no pasamos a la funcin la variable indice, lo que pasamos es una copia del valor, de esta manera el valor original se protege de cambios accidentales dentro de la funcin. Podemos modificar la variable numero como lo requiera la funcin cuadrado( ) y al retornar a la funcin principal la variable indice no ha sido modificada, de esta manera no podemos retornar un valor a la funcin que llama ( main ( ) ) de la funcin llamada (square ( ) ) utilizando este mtodo. Encontraremos un mtodo bien definido para retornar valores a main ( ) o a cualquier funcin que hace la llamada cuando estudiemos arrays y punteros. Hasta entonces la unica manera que tenemos para comunicarnos con la funcin que llama son las variables globales. Continuando en la funcin main ( ) llegamos a la ltima llamada a una funcin denominada final ( ) en la lnea 12. En esta lnea llamamos a la ltima funcin que no tiene variables locales definidas, esta funcin despliega un mensaje con el valor almacenado en suma para finalizar el programa. El programa termina al retornar a la funcin main ( ) y como ya no hay nada que hacer, el programa termina.
76312839.doc
Pgina 18 de 59
HVF
de dos variables de punto flotante siguiendoles dos definiciones de extrao aspecto. Las expresiones cuadrado( ) y glcuadrado( ) en la lnea 8 parecen llamadas a funcin. Esta es la forma adecuada para definir que una funcin regresar un valor que no es de tipo int sino de otro tipo, en este caso de tipo flotante. Observe que ninguna funcin es llamada en esta lnea de cdigo, simplemente se declara el tipo de dato que retornarn estas dos funciones. Refiriendonos a la funcin cuadrado( ) que empieza en la lnea 28 ver que el nombre es precedido por la palabra clave float, esto lo indica al compilador que esta funcin retornar un valor de tipo float a cualquier programa que las llame. El tipo de dato que retorna la funcin es ahora compatible con la llamada a esta funcin. La siguiente lnea de cdigo contiene "float valor_interno;" lo que le indica al compilador que la variable pasada a esta funcin desde el programa que la llama ser de tipo flotante. La funcin glcuadrado ( ) empieza en la lnea 38 retornar una variable de tipo float pero adems utiliza una variable global para la entrada. El clculo cuadrtico lo hace en el enunciado return y por lo tanto no requiere definir una variable separada para almacenar el producto. La funcin cuadrado ( ) pudo ejecutar el clculo cuadrtico en la instruccin return pero se hizo en forma separada a manera de ilustracin. # include <stdio.h> float z; /* Variable global */ int main() { int indice; float x, y, cuadrado(), glcuadrado(); for(indice = 0 ; indice <= 7 ; indice ++) { x = indice; /* convierte int en float */ y = cuadrado(x); /* el cuadrado de x a una variable de punto flotante */ printf("El cuadrado de %d es %10.4f\n", indice, y); } for (indice = 0 ; indice <= 7 ; indice ++) { z = indice; y = glcuadrado (); printf("El cuadrado de %d es %10.4f\n", indice, y); } return 0; } float cuadrado (valor_interno) /* Eleva al cuadrado un tipo float, retorna un tipo float */ float valor_interno; { float cuadratica; cuadratica = valor_interno * valor_interno; return(cuadratica); } float glcuadrado () /* Eleva al cuadrado un tipo float, retorna un tipo float */ { return(z * z); } En los tres programas que hemos estudiado en este captulo se ha utilizado el estilo clsico para definir funciones, si bien, este fue el primer estilo definido en C existe un mtodo mas reciente que le permite detectar errores con mayor facilidad. Cuando Usted lea artculos de C se encontrar programas que utilizan el estilo clsico por lo que Usted debe estar preparado para interpretarlos correctamente, esta es la razn por lo que incluimos el estilo clsico en este tutorial sin embargo, se recomienda ampliamente que Usted adopte y use el mtodo moderno tal y como est definido por el estndar ANSI-C, mismo que empezaremos a tratar desde este momento y hasta el final de este tutorial.
76312839.doc
Pgina 19 de 59
HVF
76312839.doc } return 0; }
Pgina 20 de 59
HVF
int counter; /* Variable disponible a partir de este momento */ void head1(void) { int index; /* Esta variable est disponible solo en head1 */ index = 23; printf("El valor de header1 es %d\n", index); } void head2(void) { int count; /* Esta variable est disponible solo en head2 */ /* y desplaza a la variable global del mismo nombre */ count = 53; printf("El valor de header2 es %d\n", count); counter = 77; } void head3(void) { printf("El valor de header3 es %d\n", counter); } /* Resultado de la ejecucin: El valor de header1 es 23 El valor de header2 es 53 El valor de header3 es 77 0 1 2 3 4 5 6 index es ahora 8 0 1 2 3 4 5 6 index es ahora 7 0 1 2 3 4 5 6 index es ahora 6 0 1 2 3 4 5 6 index es ahora 5 0 1 2 3 4 5 6 index es ahora 4 0 1 2 3 4 5 6 index es ahora 3 0 1 2 3 4 5 6 index es ahora 2 0 1 2 3 4 5 6 index es ahora 1 */
76312839.doc
Pgina 21 de 59
HVF
Observe la funcin llamada head1 ( ) en la lnea 29. El uso de la palabra void lo explicaremos en breve. La funcin contiene una variable llamada index que no tiene nada en comn con la variable del mismo nombre de la funcin main ( ) en la lnea 10, excepto que ambas son variables automticas. Mientras el programa no ejecute sentencias de esta funcin esta variable no existir. Cuando head1 ( ) es llamada se genera la variable y cuando head1 ( ) termina su trabajo la variable llamada index de la funcin es eliminada por completo. Tenga en mente que esto no afecta la variable del mismo nombre en la funcin main ( ) porque se trata de entidades diferentes. Es importante recordar que de una llamada a la siguiente, el valor de una variable no se conserva y por lo tanto debe reinicializarse.
VARIABLES ESTTICAS
Al colocar la palabra clave static antes de la definicin de una variable dentro de una funcin, la las variables definidas son variables estticas y existirn de una llamada a otra en una particular funcin. Una variable esttica es inicializada una vez al cargar un programa y nunca es reinicializada durante la ejecucin del programa. Si colocamos la palabra clave static antes de una variable externa hacemos la variable privada lo que significa que esta variable no ser posible utilizarla con ningn otro archivo, ejemplos de esto se darn en el captulo 14.
LA VARIABLE register
Una computadora puede almacenar datos en un registro o en memoria. Un registro es mucho mas rpido en operacin que una memoria paro hay pocos registros disponibles para uso del programador. Si en un programa existen ciertas variables que son utilizadas extensivamente, Usted puede designar que estas variables sean almacenadas en un registro para acelerar la ejecucin de un programa, esto se ilustra en la lnea 10. Su compilador probablemente le permita utilizar una o mas variables de registro, si su compilador no le permite el uso de este tipo de variables la peticin de registro ser ignorada.
PROTOTIPADO DE FUNCIONES
Un prototipo es un modelo de un objeto real y cuando Usted programa en ANSI-C, Usted tiene la habilidad para definir un modelo de cada funcin para el compilador. El compilador puede entonces usar el modelo para checar cada una de las llamadas a la funcin y determinar si Usted ha utilizado el nmero correcto de argumentos en la llamada a la funcin y si son del tipo correcto. El estndar ANSI-C contiene el prototipado como parte de sus recomendaciones, a lo largo de este estudio se tratar ampliamente el prototipado. Volviendo a las lneas 2, 3, y 4 del ejemplo que estamos estudiando, tenemos el prototipo para cada una de las tres funciones contenidas en el programa. El primer void le indica al compilador que esta funcin en particular no tiene valor de retorno. La palabra void dentro del parntesis le indica al compilador que esta funcin no tiene parmetros y si una variable fuera incluida ocurrira un error que el compilador indicara en un mensaje de advertencia. En este momento Usted empezar a utilizar el chequeo de prototipo para todas las funciones que Usted defina. La lnea 1 del programa le dice al sistema que obtenga una copia del archivo llamado stdio.h localizado en el directorio include. El archivo stdio.h contiene los prototipos para las funciones estndar de entrada/salida de tal manera que pueda ser posible checar los tipos adecuados de variables, mas adelante cubriremos en detalle el directorio include.
76312839.doc
Pgina 22 de 59
HVF
RECURSIVIDAD
La recursividad es otra de esas tecnicas de programacin que cuando las vemos por vez primera parecen muy intimidantes pero en el siguiente ejemplo descubriremos el misterio en un programa muy simple pero para propsitos de ilustracin resulta excelente. # include <stdio.h /* Contiene el prototipo para printf */ void count_dn(int count) ; /* Prototipo para count_dn */ int main( ) { int index ; index = 8 ; count_dn(index) ; return 0 ; } void count_dn(int count) { count -- ; printf ( "El valor de la cuenta es %d\n", count ) ; if (count > 0) count_dn(count) ; printf ( "Ahora la cuenta es %d\n", count ) ; } /* Resultado de la ejecucin: El valor de la cuenta es 7 El valor de la cuenta es 6 El valor de la cuenta es 5 El valor de la cuenta es 4 El valor de la cuenta es 3 El valor de la cuenta es 2 El valor de la cuenta es 1 El valor de la cuenta es 0 Ahora la cuenta es 0 Ahora la cuenta es 1 Ahora la cuenta es 2 Ahora la cuenta es 3 Ahora la cuenta es 4 Ahora la cuenta es 5 Ahora la cuenta es 6 Ahora la cuenta es 7 */ La recursividad no es otra cosa mas que una funcin que se llama a s misma, es por lo tanto un bucle que debe tener una manera de terminar. En el programa, la variable index es colocada en 8 en la lnea 8 y es utilizada como el argumento de la funcin llamada count_dn ( ). La funcin simplemente decrementa la variable, despliega un mensaje, y si la variable es mayor que cero, se llama a s misma donde decrementa la variable una vez ms, despliega un mensaje, etc, etc,etc. Finalmente la variable alcanza el valor de cero y la funcin ya no se llama a s misma, en lugar de esto retorna al punto previo a su llamada, y retorna de nueva cuenta, y de nuevo, hasta que finalmente retorna a la funcin main ( ) y de aqu retorna al sistema operativo. Para que le resulte mas claro piense como si tuviera ocho funciones llamadas count_dn disponible y que llama una a la vez manteniendo un registro de en cual copia estuvo en determinado momento, esto no es en realidad lo que sucede en el programa pero a manera de comparacin resulta til para comprender el funcionamiento del programa. Cuando Usted llama a la funcin desde la misma funcin, esta alamacena todas la variables y demas datos que necesita para completar la funcin en un bloque interno. La siguiente vez que es llamada la funcin hace exactamente lo mismo creando otro bloque interno, este ciclo se repite hasta alcanzar la ltima llamada a la funcin, entonces empieza a regresar los bloques utilizando estos para completar cada llamada de funcin. Los bloques son almacenados en una parte interna de la computadora llamada stack, esta es una parte de la memoria cuidadosamente organizada para almacenar datos de la manera ya descrita.
76312839.doc
Pgina 23 de 59
HVF
Al utilizar la recursividad es posible que Usted desee escribir un programa con recursividad indirecta, opuesta a la recursividad directa descrita arriba. La recursividad indirecta puede ser cuando una funcin A llama a una funcin B, la cual a su vez llama a la funcin A, etc. Esto es completamente permisible ya que el sistema tomar cuidado de almacenar en stack los datos para regresarlos cuando sea necesario. Recuerde que en la recursividad, en algn punto algo debe llegar a cero o alcanzar un punto predefinido para terminar el bucle. Si esto no es as, Usted tendr un bucle infinito, en determinado momento el stack se saturar resultando en un mensaje de error y terminando el programa abruptamente.
76312839.doc
Pgina 24 de 59
HVF
QU ES UNA MACRO?
Una macro no es otra cosa que una definicin, pero como parece ser capaz de ejecutar algunas decisiones lgicas operaciones matemticas, tiene un nombre nico. En la lnea 5 del programa podemos ver un ejemplo de una macro, en este caso, cada vez que el preprocesador encuentra la palabra MAX seguida por un grupo de parntesis espera encontrar dos trminos en el parntesis y har el reemplazo de los trminos en la segunda parte de la definicin, as el primer trmino reemplazar cada A en la segunda parte de la definicin, y el segundo trmino reemplazar cada B en la segunda parte de la definicin. Cuando el programa alcanza la lnea 15, indice ser sustituda por cada A, y contador ser sustituida por cada B. Por lo tanto, antes de que la lnea 15 sea entregada al compilador, esta ser modificada por lo siguiente: mx = ((index)>(count) ? (index) : (count)) Recuerde que ni los comentarios ni las cadenas sern afectadas. Recordando las construcciones ya estudiadas vemos que mx recibir el valor mximo de indice contador. De la misma manera, la macro MIN resulta en mn recibiendo el valor mnimo de indice contador. Estas dos macros se utilizan con frecuencia en los programas C. Al definir una macro es imperativo que no haya espacio entre el nombre de la macro y el parntesis de apertura, de lo contrario, el compilador no podr determinar la existencia de una macro pero s har la sustitucin definida. Los resultados de la macro se imprimen en la lnea 17.
int main( ) { int i, offset ; offset = 5 ; for (i = INICIO ; i <= FINAL ; i++) { printf ("El cuadrado de %3d es %4d, y su cubo es %6d\n", i+offset, CUADRADO(i+offset), CUBO(i+offset)) ; printf ("El cubo equivocado de %3d es %6d\n", i+offset, EQUIVOCADA(i+offset)) ; } printf ("\nProbamos la macro de suma\n") ; for (i = INICIO ; i <= FINAL ; i++) { printf ("La macro de suma EQUIVOCADA = %6d, y la correcta = %6d\n", 5*SUMA_EQUIVOCADA(i), 5*SUMA_CORRECTA(i)) ; } return 0 ; } Considere el programa mismo donde el CUBO de i+offset se calcula en la lnea 19. Si i es 1, entonces estaremos buscando el cubo de 1+5=6, lo cual resulta en 216. Cuando se usa CUBO, los valores se agrupan as, (1+5)*(1+5)*(1+5)=6*6*6=216. Sin embargo, al utilizar EQUIVOCADA tenemos el siguiente agrupamiento, 1+5*1+5*1+5=1+5+5+5=16 lo que d un resultado errneo. Los parntesis son necesarios para agrupar adecuadamente las variables. En la lnea 6 definimos la macro SUMA_EQUIVOCADA de acuerdo a las reglas dadas pero an tenemos problemas cuando tratamos de utilizar esta macro en las lneas 27 y 28. En la lnea 28, cuando queremos que el programa calcule 5*SUMA_EQUIVOCADA(i) con i=1, obtenemos como resultado 5*1+1, lo que se evala como 5+1 6, y esto seguramente no
76312839.doc
Pgina 25 de 59
HVF
es lo que tenemos en mente, el resultado que realmente deseamos es 5*(1+1) = 5*2 = 10 que es la respuesta que obtenemos al utilizar la macro llamada SUMA_CORRECTA, esto se debe a los parntesis extra que agregamos en la definicin dada en la lnea 7. Dedicarle un poco de tiempo para estudiar este programa nos ayudar a comprender el funcionamiento de las macros. Para prevenir los problemas que hemos visto en el ejemplo, los programadores experimentados de C incluyen un parntesis en torno a cada variable en una macro y un parntesis adicional en torno a la totalidad de la expresin, esto permitir a cualquier macro trabajar adecuadamente y esta es la razn por la que la macro CUBO arroja ciertos resultados errneos, necesita un parntesis en torno a la expresin.
int main( ) { int indice ; for (indice = 0 ; indice < 6 ; indice++) { printf ("En el bucle, indice = %d", indice) ; # ifdef OPCION_1 printf (" contador_1 = %d", contador_1) ; /* Esto puede ser desplegado */ # endif printf ("\n") ; } return 0 ; } # undef OPCION_1 /* Resultado de la ejecucin: (Con OPCION_1 definido) En el bucle, indice = 0 contador_1 = 17 En el bucle, indice = 1 contador_1 = 17 En el bucle, indice = 2 contador_1 = 17 En el bucle, indice = 3 contador_1 = 17 En el bucle, indice = 4 contador_1 = 17 En el bucle, indice = 5 contador_1 = 17 (Comentando removiendo la lnea 3) En el bucle, indice = 0 En el bucle, indice = 1 En el bucle, indice = 2 En el bucle, indice = 3 En el bucle, indice = 4 En el bucle, indice = 5 */
76312839.doc
Pgina 26 de 59
HVF
Compile y ejecute el programa como est, despus comente la lnea 3 de tal manera que OPCION_1 no sea definida entonces recompile y ejecute el programa, ver como la lnea extra no se imprimir porque el preprocesador se la brinc. En la lnea 25 ilustramos el comando al preprocesador undefine. Este remueve el hecho de que OPCION_1 fue definido y desde este punto el programa acta como si nunca hubiera sido definido, por supuesto que la instruccin undefine nada tiene que hacer en este punto del programa ya que ste est completo y no siguen mas enunciados ejecutables, como experimento coloque la instruccin undefine en la lnea 4, recompile y ejecute el programa y ver que acta como si OPCION_1 jams hubiera sido definido.
int main( ) { int indice ; # ifndef MUESTRA_DATO printf ("MUESTRA_DATO no est definido en " " el codigo\n") ; # endif for (indice = 0 ; indice < 6 ; indice++) { # ifdef MUESTRA_DATO printf ("En el bucle, indice = %d", indice) ; # ifndef OPCION_1 printf (" contador_1 = %d", contador_1); # endif printf ("\n") ; # endif } return 0 ; } /* Resultado de la ejecucin: (Con OPCION_1 definido) En el bucle, indice = 0 En el bucle, indice = 1 En el bucle, indice = 2 En el bucle, indice = 3 En el bucle, indice = 4 En el bucle, indice = 5 (Removiendo comentando la lnea 3) En el bucle, indice = 0 contador_1 = 17 En el bucle, indice = 1 contador_1 = 17 En el bucle, indice = 2 contador_1 = 17 En el bucle, indice = 3 contador_1 = 17 En el bucle, indice = 4 contador_1 = 17 En el bucle, indice = 5 contador_1 = 17 */
76312839.doc
Pgina 27 de 59
HVF
76312839.doc
Pgina 28 de 59
HVF
int valor ; int main () ; static void uno () ; void dos () ; void tres () ; La variable llamada indice definida en Archivo1.c est disponible para utilizarse por cualquier otro archivo porque est definida globalmente. Los otros dos archivos hacen uso de la misma variable al declararla variable de tipo extern. En escencia se le est diciendo al compilador, "deseo utilizar la variable llamada indice la cual est definida en algn lugar". Cada vez que indice sea referenciada en los otros dos archivos, la variable de ese nombre es utilizada de Archivo1.c, y puede ser leda y modificada por cualquiera de los tres archivos, esto provee una manera fcil para intercambiar datos de un archivo a otro pero puede causar problemas. La variable llamada contador esta definida en Archivo2.c y esta referida en Archivo1.c como explicamos arriba, pero no puede utilizarse en Archivo3.c porque aqu no est declarada. Una variable esttica, como valor en Archivo2.c no puede ser referenciada por ningn otro archivo. Otra variable llamada valor est definida en Archivo3.c, esta no tiene ninguna relacin con la variable del mismo nombre en Archivo2.c. En este caso, Archivo1.c puede declarar una variable externa valor y hacer referencia a esta variable en Archivo3.c si se desea. El punto de entrada main ( ) solo puede ser llamado por el sistema operativo para iniciar el programa, pero las funciones dos ( ) y tres ( ) pueden ser llamadas desde cualquier punto dentro de los tres archivos ya que son funciones globales. Sin embargo, como la funcin uno ( ) esta declarada como de tipo esttica solo puede ser llamada dentro del archivo en la cual esta declarada.
76312839.doc
Pgina 29 de 59
HVF
ejemplo los das de la semana, y de esta manera se puede manejar una sola variable la cual puede tomar cualquiera de sus valores predeterminados, mismos que pueden estar representados por nombres significativos. El resultado de la ejecucin del programa es el siguiente:
Pgina 30 de 59
HVF
if(strcmp(cadena1, cadena2) 0) strcpy(prueba, cadena1); else strcpy(prueba, cadena2); printf("La cadena mas grande es: %s\n\n", prueba); strcpy(prueba, cadena1); strcat(prueba, " y "); strcat(prueba, cadena2); printf("%s son vecinos\n", prueba); return 0; } Como puede ver, en este programa se han definido cuatro arrays de tipo char de diferente longitud, enseguida nos encontramos con la funcin strcpy( ) que sirve para copiar la cadena especificada en la segunda entidad dentro del parntesis de la funcin en un array de tipo char especificado por la primera entidad dentro del parntesis de la funcin strcpy, de esta forma, por ejemplo, la cadena "Pedro Picapiedra" se copia en el array de tipo char llamado cadena1. Mas adelante en el cdigo nos encontramos con la funcin strcmp( ) que como es fcil adivinar, sirve para comparar, letra por letra, dos cadenas especificadas dentro del parntesis. Esta funcin devuelve 1 si la primera cadena es mayor que la segunda, es decir, si tiene mayor cantidad de letras. Si ambas cadenas son iguales la funcin devuelve 0, en tanto que si la primera cadena es menor que la segunda entonces el valor devuelto es -1. Por ltimo tenemos la funcin strcat( ) que ejecuta una concatenacin de cadenas, es decir, copia la segunda cadena especificada dentro del parntesis de la funcin enseguida de la primera cadena especificada, agregando un caracter nulo al final de la cadena resultante. Naturalmente existen mas funciones para el manejo de cadenas, todas ellas fciles de implementar, lo mas recomendable en este caso es consultar la informacin de su compilador en particular.
76312839.doc
Pgina 31 de 59
HVF
un segundo bucle para desplegar en orden cada uno de los valores almacenados en los elementos del array, el resultado de la ejecucin de este programa es el siguiente:
ARRAYS Y FUNCIONES
En la leccin Funciones mencionamos que haba una forma de obtener datos de una funcin utilizando un array, esto lo podemos ver en el cdigo siguiente en donde se ha definido un array de 15 variables llamado matriz, luego asignamos algunos datos a estas variables y desplegamos en pantalla las primeras cinco. En la lnea 17 llamamos a la funcin denominada una_funcion tomando todo el array como parmetro poniendo el nombre del array en el parntesis de la funcin. #include <stdio.h void una_funcion(int nombre_interno[]); int main() { int indice; int matriz[15]; for (indice = 0; indice < 15; indice++) matriz[indice] = indice + 1; for (indice = 0; indice < 5; indice++) printf("Valor inicial asignado a matriz[%d] = %d\n", indice, matriz[indice]); printf("\n"); una_funcion(matriz); /*Llama a la funcin denominada una_funcion*/
for (indice = 0; indice < 5; indice++) printf("Nuevo valor asignado a matriz[%d] = %d\n", indice, matriz[indice]); return 0; } void una_funcion(int nombre_interno[]) { int i; for (i = 0 ; i < 5; i++) printf("Valor de matriz[%d] al salir de la funcion= %d\n", i, nombre_interno[i]); printf("\n"); } La funcin una_funcion empieza en la lnea 25 y como se puede ver, prefiere llamar internamente a la matriz con el nombre de nombre_interno, es adems necesario declarar el array como de tipo int y especificar que se trata de un array incluyendo los corchetes, en este caso dejamos que el sistema determine el tamao del array al no especificar ningn valor entre los corchetes. Al regresar a la funcin principal main ( ) podemos comprobar lo que hemos dicho al desplegar los nuevos valores asignados a las variables del array denominado matriz. Otra forma de obtener datos de una funcin hacia el programa que la llama es utilizando un puntero, tema que estudiaremos en la siguiente leccin, ah veremos que el nombre de un array es en realidad un puntero hacia una lista de valores, pero antes de avanzar a la siguiente leccin veamos la manera de trabajar con arrays mltiples. Volver al principio
76312839.doc
Pgina 32 de 59
HVF
arrays MULTIPLES
Ya mencionamos que un array es un conjunto de datos almacenados en variables adyacentes en memoria, todos del mismo tipo. Siguiendo esta definicin podemos imaginarnos a un array como un conjunto de cajas apiladas una encima de la otra, de la misma manera podemos juntar dos o ms conjuntos de cajas apiladas unas encima de las otras en donde cada conjunto de cajas no es necesariamente del mismo nmero (tamao), se puede decir pues, que un array mltiple no es otra cosa que un array de arrays. En el cdigo de ejemplo generamos un array doblemente dimensionado. La variable multiplica es un array de 11 por 11 elementos, o sea un total de 121, el primer elemento es multiplica[0][0], y el ltimo es multiplica[11][11]. #include <stdio.h int main() { int i, j; int multiplica[11][11]; for (i = 0 ; i En este ejemplo se generan las diez tablas de multiplicar y se despliegan en pantalla en forma de matriz de 11 elementos para facilitar la comprensin del concepto. Por supuesto, es posible asignar valores a cada elemento del array en forma individual como queda demostrado en el cdigo del ejemplo que he modificado para que Usted lo compile y vea los resultados a manera de ejercicio: #include <stdio.h int main() { int i, j, valor1=8, valor2=9; int multiplica[11][11]; for (i = 0 ; i
Definicin
Dicho simplemente, un puntero es una direccin en memoria. Como es costumbre en este tutorial, los conceptos se explican mejor por s mismos, el cdigo es el siguiente: #include <stdio.h> main () { int almacen, *puntero; /* Una variable normal y un puntero de tipo int */ almacen=45; /* Se asigna un valor cualquiera a variable */ puntero=&almacen; /* La direccion de almacen */ printf("El contenido de la variable llamada almacen\n" "y que esta ubicada en %xh es de %d\n", puntero, *puntero); return 0; } /* Resultado de la ejecucin del programa: El contenido de la variable llamada almacen y que esta ubicada en 2796h es de 45 */
76312839.doc
Pgina 33 de 59
HVF
En primer lugar podemos ver una variable esttica llamada almacen y una ms que lleva un asterisco al principio llamada *puntero, por el momento no se fije en este detalle, lo explicaremos mas adelante. En la lnea 7 se le asigna a la variable almacen el valor de 45 tal y como lo hemos hecho en los programas vistos hasta ahora. En la lnea 8 se aprecia una forma de asignar un valor extrao a la variable llamada puntero, se trata del operador de direccin ampersand &, que se utiliza en C para acceder a la direccion en memoria de una variable, de aqu salen dos puntos muy importantes: 1. Cuando al nombre de una variable le precede el operador ampersand, ste define la direccin de la variable y por lo tanto se dice que apunta hacia la variable. En el cdigo de arriba se asigna a la variable llamada puntero la direccin de la variable llamada almacen. En efecto, la variable puntero es un puntero propiamente dicho. 2. Para saber el contenido de una variable sealada por un puntero utilizamos el asterisco antes del nombre de la variable puntero, en el cdigo de ejemplo observe en la instruccin printf( ) la manera en que se despliegan la direccin y el contenido de la variable llamada puntero. Como su nombre lo indica, decimos, refiriendonos a la lnea 4 del cdigo de ejemplo, que la direccin de memoria sealada por *puntero corresponde a una variable de tipo int, por lo tanto *puntero es un puntero a una variable de tipo int. Un puntero debe definirse para sealar a un tipo especfico de variable y por lo tanto no deber utilizarse en el mismo programa para sealar a una variable diferente pues esto produce errores de incompatibilidad de cdigo. Como se puede ver en el cdigo de arriba, es posible conocer el contenido de la variable almacen de dos formas diferentes, utilizando el nombre de la variable directamente, o bien con un puntero que seale a la direccin de almacen. Es comn en el estudio de punteros utilizar algunos grficos para comprender este importante tema de la programacin en C. Un rectngulo representa a la variable esttica almacen, en tanto que un rectngulo con un punto en su interior representa a un puntero, en nuestro ejemplo, el llamado a su vez puntero. Este diagrama representa el programa en el punto correspondiente a la lnea 5 en donde an no se le ha asignado valor alguno a las variables, observe que el puntero en este momento no apunta a ningn lado y la variable almacen no tiene asignado an un valor determinado. Siguiendo la ejecucin del programa, en la lnea 7 asignamos a almacen el valor de 45, en tanto que en la lnea 8 indicamos que puntero guarde la direccin de almacen. En la lnea 9 utilizamos la instruccin printf( ) para demostrar los dos importantes conceptos estudiados hasta ahora, en primer lugar indicamos desplegar el valor hexadecimal correspondiente a la direccin en memoria ocupada por la variable almacen y posteriormente desplegamos el valor almacenado en la variable en s, que en este caso es 45. Aunque en apariencia tenemos dos variables, en realidad se trata de una sola, en este caso almacen, solo que se hace uso del puntero para desplegar los valores mencionados. Es importante que observe detenidamente la instruccin printf( ) y que adems compile y ejecute el cdigo de ejemplo para un mejor entendimiento de estos conceptos.
Punteros y arrays
En el siguiente cdigo de ejemplo se han definido algunas variables y dos punteros. El primer puntero llamado alla es un puntero a una variable de tipo char y el segundo llamado pt apunta a una variable de tipo int. Tambin se han definido dos arrays llamados cadena y lista, los utilizaremos para demostrar la correspondencia entre punteros y los nombres de los arrays. El nuevo cdigo es el siguiente: #include <stdio.h> #include <string.h> int main() { char cadena[30], *alla, primera, segunda; int *pt, lista[100], indice; strcpy(cadena, "Esta es una cadena de texto."); primera = cadena[0]; segunda = *cadena; /* primera y segunda son iguales */ printf("La primera salida es %c %c\n", primera, segunda); primera = cadena[8]; segunda = *(cadena+8); /* primera y segunda son iguales */ printf("La segunda salida es %c %c\n", primera, segunda);
76312839.doc
Pgina 34 de 59
HVF
alla = cadena+10; /* cadena+10 es igual a &cadena[10] */ printf("La tercera salida es %c\n", cadena[10]); printf("La cuarta salida es %c\n", *alla); for (indice = 0 ; indice < 100 ; indice++) lista[indice] = indice + 100; pt = lista + 27; printf("La quinta salida es %d\n", lista[27]); printf("La sexta salida es %d\n", *pt); return 0; } Utilizaremos un dibujo para representar la condicin inicial de nuestro programa. Se puede observar que tenemos tres variables, dos punteros, una cadena y un array de enteros, tambin podramos decir que tenemos tres variables, dos punteros y dos arrays. Cada array est compuesto por el array en s y un puntero que seala al inicio del array de acuerdo a la definicin de un array en C, esto quedar completamente aclarado en el siguiente prrafo. Cada array est compuesto de un nmero idntico de elementos de los cuales solo unos cuantos al principio y al final son mostrados para mayor claridad del dibujo. En C, el nombre de un array est definido como un puntero constante que seala al principio del array. En el cdigo de ejemplo se observa que en la lnea 8 asignamos una cadena constante a la variable llamada cadena simplemente para tener algunos datos con los cuales poder trabajar, enseguida asignamos a la variable de tipo char llamada primera el valor contenido en el primer elemento. Como el nombre de una cadena es un puntero constante al primer elemento de la cadena podemos asignarle el mismo valor a segunda utilizando el asterisco y el nombre de la cadena (*cadena). Tenga presente que en la lnea 8 sera incorrecto escribir segunda = *cadena[0]; porque el asterisco toma el lugar de los corchetes, o sea hacen el mismo trabajo. Para todo propsito prctico, cadena es un puntero a una variable de tipo char, esto tiene una restriccin que un puntero real no tiene, no puede ser cambiado como una variable ya que siempre contiene la direccin del primer elemento de la cadena y por lo tanto siempre apunta hacia el principio de la cadena. An y cuando no puede ser cambiado, se puede utilizar para referirse a otros elemntos de la cadena como veremos en la siguiente seccin del programa. En la lnea 14 se ha asignado a la variable primera el valor del noveno caracter de la cadena (recuerde que en C los ndices empiezan en 0) en tanto que a segunda se le asigna el mismo valor porque hemos permitido que el puntero seale ms all del principio de la cadena, en la lnea 15 dice que se debe sumar 8 al valor del puntero cadena, entonces obtener el valor almacenado en aquella locacin y almacenarlo en la variable segunda. Es muy recomendable que Usted compile y ejecute este segundo programa de ejemplo ya que adems de los conceptos tratados en los prrafos anteriores a su vez demuestra el concepto de la aritmtica de punteros, experimentar con el cdigo nos har ms familiar el manejo de los punteros y a la vez aportar nuevos elementos a lo ya visto en materia de arrays, es importante tener en cuenta que C maneja de forma automtica la organizacin de los punteros de acuerdo al tipo de variable que sealan, esto dependiendo a su vez de la forma en que el compilador defina los diferentes tipos de variables, este concepto quedar ms claro cuando en una leccin posterior estudiemos el concepto de las estructuras, por lo pronto estudie detenidamente la ltima parte del cdigo y observe como el sistema ajusta automticamente el ndice cuando utilizamos un puntero a una variable de tipo int. El resultado de la ejecucin del programa es el siguiente:
Punteros y funciones
Recordar que en la leccin Funciones mencionamos que haba dos maneras de obtener datos provenientes de una funcin. Una era a travs de un array y la otra es utilizando un puntero, demostramos este concepto en el siguiente programa de ejemplo: #include <stdio.h> void reparar(int tuercas, int *tornillos); int main() { int pernos, rondanas; pernos = 100; rondanas = 101; printf("Los valores iniciales son %d %d\n", pernos, rondanas); /* Cuando se llama a "reparar" */
76312839.doc
Pgina 35 de 59
HVF
reparar(pernos, &rondanas); /* tomamos el valor de pernos */ /* y la direccion de rondanas */ printf("Los valores finales son %d %d\n", pernos, rondanas); return 0; } void reparar(int tuercas, int *tornillos) /* tuercas es un valor entero */ /* tornillos apunta a un entero */ { printf("Los valores inicilales en la funcion son %d %d\n", tuercas, *tornillos); tuercas = 135; *tornillos = 172; printf("Ahora los valores en la funcion son %d %d\n" ,tuercas, *tornillos); } En este programa tenemos dos variables declaradas en el programa principal, pernos y rondanas, ninguna fu declarada como puntero. Enseguida asignamos valores a ambas y las desplegamos en pantalla y entonces llamamos a la funcin llamada reparar ( ) tomando ambos valores, a la variable pernos simplemente la enviamos como parmetro a la funcin, pero en cambio tomamos la direccin de la variable rondanas como segundo parmetro de la funcin. Ahora tenemos un problema. Los dos argumentos no son iguales ya que el segundo es un puntero a una variable. De alguna manera debemos alertarle a la funcin que recibir una variable entera y un puntero a una variable de tipo int, esto se hace de una manera simple. En la lnea 19 vemos que la funcin declara como primer parmetro a una variable de tipo int llamada tuercas y a un puntero a una variable de tipo int llamado tornillos, por tanto la llamada a la funcin en el programa principal est acorde con el encabezado de la misma. En el cuerpo de la funcin desplegamos los valores pasados como parmetros luego los modificamos y desplegamos los nuevos valores. Hasta este momento las cosas estn lo suficientemente claras, la sorpresa viene cuando regresamos a la funcin principal main ( ) y desplegamos los valores una vez ms. Encontramos que el valor de pernos se restaura al valor que tena antes de la llamada a la funcin reparar, esto es as porque C hace una copia de la variable en cuestin y lleva la copia a la funcin llamada, dejando a la variable original intacta tal y como lo explicamos anteriormente. En el caso de la variable rondanas hacemos una copia del puntero a la variable y llevamos la copia a la funcin. Como tenemos un puntero a la variable original, an y cuando el puntero es una copia local, sigue sealando a la variable original por lo tanto podemos cambiar el valor almacenado en rondanas desde el interior de la funcin reparar. Cuando regresamos al programa principal, encontramos a la variable rondanas con un nuevo valor. En el ejemplo no existe un puntero en el programa principal porque enviamos simplemente la direccin de la variable a la funcin reparar. El resultado de este programa es este:
76312839.doc
Pgina 36 de 59
HVF
puntero_a_funcion(two_pi); /* Se muestra en pantalla */ puntero_a_funcion(13.0); /* Se muestra en pantalla */ puntero_a_funcion = despliega_numero; puntero_a_funcion(pi); /* Se muestra en pantalla */ despliega_numero(pi); /* Se muestra en pantalla */ return 0; } void despliega_cosas(float ignorar_datos) { printf("Esta es la funcion ignorar_datos\n"); } void despliega_mensaje(float mostrar_datos) { printf("El dato a mostrar es %f\n", mostrar_datos); } void despliega_numero(float numero_flotante) { printf("El numero a desplegar es %f\n", numero_flotante); } Observe los prototipos dados en las lineas 3 a 6 que declaran tres funciones que utilizan el mismo parmetro y regresa nada (void) al igual que el puntero. Como son similares, es posible utilizar el puntero para referirse a las funciones como lo demuestra la parte ejecutable del programa. En la lnea 14 contiene una llamada a la funcin despliega_cosas y la lnea 15 asigna el valor de despliega_cosas a puntero_a_funcion. Como el nombre de la funcin est definido como un puntero a esa funcin, su nombre puede ser asignado a una variable puntero hacia la funcin. Recordar que el nombre de un array es en realidad un puntero constante al primer elemento del array, de la misma manera, el nombre de una funcin es en realidad un puntero constante el cual seala a la funcin misma. El puntero es sucesivamente asignado a la direccin de cada una de las tres funciones y cada una es llamada una dos veces a manera de ilustracin de cmo se utiliza un puntero a una funcin. El resultado de la ejecucin del programa es el siguiente: Un puntero a una funcin no es utilizado a menudo pero es una construccin muy poderosa cuando se utiliza, continuaremos estudiando el uso de los punteros examinando los programas que veremos en las siguientes lecciones.
76312839.doc
Pgina 37 de 59
HVF
} La primera cosa por estudiar la tenemos en la primera lnea. La instruccin #include <stdio.h> es muy parecida a la instruccin #define que ya hemos estudiado excepto que en lugar de una simple sustitucin se lee todo un archivo en este punto. El sistema encontrar el archivo llamado stdio.h y leer la totalidad del contenido reemplazando la instruccin #include stdio.h. Obviamente el archivo debe contener enunciados vlidos de C que pueden ser compilados como parte del programa. Este archivo en particular contiene varias definiciones y prototipos para las operaciones de E/S estndar. El archivo es llamado archivo de cabecera y su compilador incluye una gran variedad de ellos, cada uno con un propsito especfico y cualquiera de ellos puede ser incluido en cualquier programa. El compilador C utiliza el smbolo de doble comilla para indicar que la bsqueda del archivo include empieza en el directorio actual de trabajo, y si no se encuentra ah, la bsqueda continuar en el directorio include tal y como est especificado en el ambiente de su compilador. Adems se utilizan los smbolos de "menor que" y "mayor que" para indicar que la bsqueda empieza directamente en el directorio include del compilador. Se pueden utilizar tantos include como sea necesario, y es perfectamente vlido que un archivo de cabecera incluya uno o ms archivos de cabecera adicionales. Comprender a su vez que cuando escriba programas largos, es posible incluir ciertas rutinas comunes en un archivo de cabecera e incluir el mismo como ya se ha descrito. Continuando con el cdigo de ejemplo. Se define la variable llamada c y se despliega un mensaje en pantalla con la y conocida funcin printf ( ), entramos en un bucle que no termina sino hasta que el caracter introducido sea una X mayscula, dentro del bucle nos encontramos con dos nuevas funciones, una que sirve para leer un caracter desde el teclado y otra que despliega dicho caracter en pantalla. La funcin getchar ( ) lee un solo caracter desde el dispositivo estndar de entrada, o sea, el teclado, y lo asigna a la variable llamada c. La siguiente funcin llamada putchar ( ) utiliza el dispositivo de salida estndar, es decir, el monitor de video, para desplegar el caracter contenido en c. El caracter se despliega en la posicin actual del cursor y ste avanza un espacio para el siguiente caracter, por lo tanto el sistema se ocupa del orden de despliegue de los caracteres. Compile y ejecute este programa para descubrir algunas caractersticas adicionales, como el hecho que conforme escriba en el teclado, lo escrito se despliega en el monitor y al presionar la tecla enter se repite la lnea completa de texto, tal parece que memoriza los caracteres y luego los vuelve a deplegar.
76312839.doc
Pgina 38 de 59
HVF
est incluida en el estndar ANSI-C y por lo tanto puede no estar disponible en todos los compiladores, adems utilizar sta funcin puede hacer el progama menos portable a otras mquinas, si su compilador soporta la funcin, compile este programa y observe su funcionamiento comparado con el programa anterior, ver que al presionar la tecla enter no coloca una nueva lnea con el retorno de carro, para corregir esta situacin tenemos el siguiente programa: #include <stdio.h> #include <conio.h> #define RC 13 #define AL 10 int main() { int c; printf("Introduzca cualquier caracter, X para terminar.\n"); do { c = _getch(); /* Se obtiene un caracter */ putchar(c); /* Despliega la tecla presionada */ if (c == RC) putchar(AL); /* Si es retorno de carro */ /* coloca una nueva linea */ } while (c != 'X'); printf("\nFin del programa.\n"); return 0; } Tenemos dos nuevos enunciados que definen los cdigos de caracter para la nueva lnea (linefeed en ingls, traducido en este programa como "alimentar lnea", AL), y para el retorno de carro (carriage return, "retorno de carro", RC), si Usted consulta una tabla de cdigos ASCII ver por qu stos trminos se han definido como 10 y 13. En el programa principal, despus de desplegar el caracter introducido en pantalla lo comparamos con RC, y si es igual adems desplegamos una nueva lnea, AL. En los programas presentados hasta este momento no hemos puesto, como es usual en este curso, las pantallas que demuestran la salida del programa, esto se debe al hecho de que los programas de este captulo depende su salida enteramente de lo que Usted teclee, por lo que le recomiendo ampliamente la compilacin y ejecucin de cada programa de ejemplo para un mejor entendimiento de la mecnica de las funciones aqu presentadas. Le toca ahora el turno a los nmeros enteros.
Entrada numrica
Estudie el siguiente programa: #include <stdio.h> int main() { int numero; printf("Introduzca un numero de 0 a 32767, el programa finaliza con un 100.\n"); do { scanf("%d", &numero); /* Lee un valor entero */ printf("El numero es %d\n", numero); } while (numero != 100); printf("Adios!\n"); return 0; } La mecnica del programa es bastante similar a lo que hemos estado trabajando, excepto que definimos una variable de tipo int llamada numero y el bucle continua hasta que el valor introducido sea 100. En lugar de leer un solo caracter tal y como lo hemos hecho en los programas anteriores, ahora leemos un nmero completo con una sola llamada a la funcin llamada scanf ( ), esta
76312839.doc
Pgina 39 de 59
HVF
funcin es muy similar a la conocida printf ( ) solo que se utiliza para introducir datos en lugar de desplegarlos. Observe en la lnea donde se encuentra la funcin scanf ( ) que sta no refiere directamente a la variable llamada numero, en lugar de esto, utiliza la direccin de la misma (o sea, un puntero a la variable), ya que espera le sea retornado un valor. La funcin scanf ( ) busca en la lnea de entrada hasta encontrar el primer campo de datos, lee los caracteres enteros hasta encontrar un espacio en blanco un caracter decimal invlido, en este punto detiene la lectura y retorna el valor encontrado. Si su sistema utiliza enteros de 2 bytes y Usted introduce un nmero hasta 32767 inclusive ste se despliega correctamente, pero con nmeros mayores parece haber un error. Por ejemplo, si Usted introduce 32768 se despliega -32768, e introduciendo 65536 el valor desplegado es cero. La explicacin de ste fenmeno est en la manera en que est definido una variable de tipo int, el bit ms significativo para un patrn disponible de 16 bits para una variable entera es el bit de signo por lo que slo nos quedan 15 bits para representar el valor, por lo tanto la variable slo puede tomar valores comprendidos entre -32768 y 32767. Este detalle debe tomarlo en cuenta al momento de hacer sus programas. Lo dicho es vlido slo para los compiladores de 16 bits, aunque existe la cada vez mayor posibilidad de que su compilador utilice valores enteros almacenados en campos mayores de 16 bits, en este caso se aplican los mismos principios excepto que el rango de valores es mayor. Compile y ejecute el programa anterior y experimente con lo anteriormente dicho.
Entrada de cadenas
Ahora veremos cmo introducir una cadena de caracteres en nuestro siguiente programa, el cdigo es el siguiente: #include <stdio.h> int main() { char cadena[25]; printf("Introduzca una cadena de caracteres, maximo 25 caracteres.\n"); printf("Una X en la columna 1 termina el programa.\n"); do { scanf("%s", cadena); printf("La cadena es -> %s\n", cadena); } while (cadena[0] != 'X'); printf("Adios!.\n"); return 0; } Este programa es similar al ltimo cdigo que estudiamos, excepto que en lugar de definir una variable de tipo int, definimos una variable de tipo string con un lmite de 24 caracteres (recuerde que las cadenas de caracteres deben incluir un caracter nulo al final de la cadena). La variable en la funcin scanf ( ) no requiere un smbolo de & porque cadena es un array y por definicin incluye un puntero. Este programa no requiere mayor explicacin. Cuando compile y ejecute ste programa notar que los enunciados son separados en palabras. Cuando scanf ( ) se utiliza en el modo de entrada de cadena de caracteres lee los caracteres hasta que encuentra el final de la linea un caracter en blanco, por lo tanto, lee una palabra a la vez. Experimente introduciendo ms de 24 caracteres y observe cmo el sistema maneja una situacin de error. Como scanf ( ) no tiene manera de parar la introduccin de caracteres cuando el array est lleno, por lo tanto no lo utilice para introducir cadenas de caracteres en un programa importante, aqu lo usamos solamente para propsitos de ilustracin.
Entrada/Salida en memoria
Hablemos ahora de otro tipo de E/S, uno que no tiene salida al mundo exterior pero que permanece en la computadora, el cdigo es este: #include <stdio.h> int main() { int numeros[5], resultado[5], indice; char linea[80];
76312839.doc
Pgina 40 de 59
HVF
numeros[0] = 74; numeros[1] = 18; numeros[2] = 33; numeros[3] = 30; numeros[4] = 97; sprintf(linea,"%d %d %d %d %d\n", numeros[0], numeros[1], numeros[2], numeros[3], numeros[4]); printf("%s", linea); sscanf(linea,"%d %d %d %d %d", &resultado[4], &resultado[3], (resultado+2), (resultado+1), resultado); for (indice = 0 ; indice < 5 ; indice++) printf("El resultado final es %d\n", resultado[indice]); return 0; } En este programa definimos algunas variables, despus asignamos algunos valores a las llamadas numeros para propsitos de ilustracin y entonces utilizamos la funcin sprintf ( ), sta acta similar a la funcin printf ( ) pero en lugar de desplegar los datos a un dispositivo de salida, imprime la cadena formateada en una cadena en memoria. En este caso la cadena v a la variable llamada linea, porque esta es la cadena que introducimos como primer argumento de la funcin sprintf ( ). Como la cadena generada contina en memoria, podemos leerla utilizando la funcin sscanf ( ), le decimos a la funcin en el primer argumento que linea es la cadena a utilizar para su entrada, el resto de los argumentos se manejan igual que en la funcin scanf ( ). Observe que en este caso si utilizamos punteros porque necesitamos regresar datos de la funcin y observe adems que utilizamos varias formas para declarar punteros, las primeras dos simplemente declaran la direccin de los elementos del array, mientras que los ltimos tres aprovechan el hecho que resultado, sin el subndice, es un puntero. Finalmente y para agregarle ms inters, los datos se despliegan en orden inverso.
Escritura de un archivo
A lo largo de sta leccin veremos la mecnica necesaria para escribir y leer datos a un archivo, empezaremos con la escritura. Como siempre, los cdigos especifican en primer lugar algunas sentencias #include, y en el caso concreto del primer cdigo de ejemplo se ha declarado un nuevo tipo de variable. Estudie el siguiente cdigo: #include <stdio.h> #include <string.h> int main() { FILE *fp; fp = fopen("prueba.htm", "w"); /* Abrir archivo para escritura */ fprintf(fp, "<HTML> \n"); fprintf(fp, "<BODY> \n"); fprintf(fp, "Esta es la primera linea de texto. \n"); fprintf(fp, "<CENTER>Esta es la segunda linea</CENTER> \n"); fprintf(fp, "Y esta es la <B>tercera linea de texto.</B> \n"); fclose(fp); /* Cerrar el archivo antes de terminar el programa */ printf("Se ha creado el archivo: prueba.htm \n"); return 0; } El tipo FILE es una estructura (misma que estudiaremos en la siguiente leccin) que est definida en el archivo de cabecera stdio.h, se usa para definir un puntero que se utilizar en operaciones con archivos. Por definicin, C requiere para accesar a un archivo de un puntero de tipo FILE, como es normal, se puede utilizar cualquier nombre para representar dicho puntero, es comn utilizar fp, as que ste nombre utilizamos en el primer cdigo.
76312839.doc
Pgina 41 de 59
HVF
Lectura ("r")
El segundo parmetro es el atributo del archivo y puede ser cualquiera de stas tres letras, "r", "w", "a", y deben estar en letra minscula. Existen atributos adicionales en C que permiten operaciones de Entrada/Salida (E/S) ms flexibles por lo que es recomendable la consulta de la documentacin del compilador. Cuando se utiliza "r" el archivo se abre para operaciones de lectura, para operaciones de escritura utilizamos "w" y cuando se especifica "a" es porque deseamos agregar datos adicionales a los ya existentes en el archivo, o sea concatenar datos. Abrir un archivo para lectura implica la existencia del mismo, si sta condicin no es vlida el puntero de archivo ser igual a NULL y sto puede ser verificado utilizando el siguiente cdigo: if (fp==NULL) { printf("Error al abrir el archivo \n"); exit (1); } Es una buena prctica de programacin checar todos los punteros de archivo en una forma similar al cdigo de arriba, el valor de 1 utilizado como parmetro de exit ( ) ser explicado ms adelante.
Escritura ("w")
Cuando un archivo se abre para operaciones de escritura, si ste no existe entonces ser creado, y si existe ser reescrito dando como resultado la prdida de los datos existentes en el archivo previo. Si ocurre por alguna razn un error al abrir el archivo, el puntero de archivo retorna un valor de NULL que puede ser checado como se especific arriba.
Concatenar ("a")
Cuando un archivo se abre para concatenar datos, si no existe ser creado inicialmente vaco. Si el archivo existe, el punto de entrada de datos se situa al final de los datos existentes en el archivo, de sta manera es como se agregan nuevos datos al archivo. El puntero de archivo se puede verificar como y se explic.
Salida al archivo
La salida de datos hacia un archivo es prcticamente idntica a la forma en que desplegamos datos en el dispositivo estndar de salida, las nicas diferencias reales son el nombre de una nueva funcin y la adicin del puntero de archivo como uno de los argumentos de la funcin. En el cdigo de ejemplo, la funcin fprintf ( ) reemplaza a la familiar printf ( ) y el puntero de archivo v como argumento dentro del parntesis de la funcin, como se aprecia en las lneas 9 a la 13 del cdigo de ejemplo.
Cerrando el archivo
Para cerrar un archivo se utiliza la funcin fclose ( ) con el puntero de archivo dentro del parntesis. En algunos programas sencillos no es necesario cerrar el archivo ya que el sistema operativo se encarga de cerrar los archivos que hayan quedado abiertos antes de retornar el control al usuario, sin embargo es buena prctica cerrar en cdigo todo aquel archivo que se abra. Compile y ejecute el programa, la nica salida que ver en pantalla es la lnea que indica la creacin del archivo especificado, despus de correr el programa verifique en su directorio de trabajo la existencia del archivo prueba.htm. Por la extensin utilizada es fcil suponer que se trata de un pequeo archivo web, su navegador lo puede visualizar de la forma convencional, pero tambin puede abrir ste archivo con un editor de texto comn (como Notepad), entonces se dar cuenta que el cdigo HTML est inconcluso, este "problemita" lo resolveremos ms adelante por lo que le recomiendo que conserve ste archivo pues se utilizar en las prcticas que siguen.
76312839.doc
Pgina 42 de 59
HVF
Concatenar datos
Como vimos en el programa anterior, el archivo generado llamado prueba.htm est inconcluso as que es hora de corregir sta situacin, lo haremos utilizando el cdigo que sigue el cual hace uso del atributo para concatenar datos y adems utilizaremos una nueva funcin para escribir en el archivo un solo dato a la vez: #include <stdio.h> #include <string.h> #include <stdlib.h> int main() { FILE *final; final = fopen("Prueba.htm", "a"); /* Abrir archivo para concatenar */ if (final == NULL) { printf("Falla al abrir el archivo \n"); exit (EXIT_FAILURE); } putc('\n', final); putc('<', final); putc('/', final); putc('B', final); putc('O', final); putc('D', final); putc('Y', final); putc('>', final); putc('\n', final); putc('<', final); putc('/', final); putc('H', final); putc('T', final); putc('M', final); putc('L', final); putc('>', final); putc('\n', final); fclose(final); return EXIT_SUCCESS; } En primer lugar observe que en este programa se efecta la verificacin del xito al abrir el archivo, la constante llamada EXIT_FAILURE est definida en el archivo de cabecera stdlib.h generalmente con el valor de 1. La constante llamda EXIT_SUCESS a su vez est definida generalmente con el valor de 0. El sistema operativo puede utilizar el valor retornado para determinar si el programa est operando normalmente si es necesario tomar alguna accin correctiva, por ejemplo, si un programa se ejecuta en dos partes y la primera de ellas retorna un valor de error, entonces no hay necesidad de ejecutar la segunda parte del programa.
La funcin putc ( )
La parte del programa que nos interesa es la funcin llamada putc ( ) ejemplificada de la lnea 16 a la 32, sta funcin extrae al archivo un caracter a la vez, el caracter en cuestin es el primer argumento de la funcin y el puntero de archivo el segundo y ltimo argumento dentro del parntesis. Observe que para especificar un caracter determinado se utiliza la comilla sencilla, incluyendo el caso del caracter de retorno de carro '\n'. Compile y ejecute el programa. Antes de correr el programa asegurese de la existencia del archivo prueba.htm en su directorio de trabajo, generado en el programa anterior, despus de correr el programa, abra el archivo con un editor de texto y observe que ahora el documento web est completo.
76312839.doc
Pgina 43 de 59
HVF
Lectura de un archivo
Como ya tenemos un archivo para leer podemos utilizar un nuevo programa, como en los programas anteriores, ste empieza con algunas declaraciones y abriendo el archivo prueba.htm especificando que deseamos efectuar operaciones de lectura mediante el atributo "r", el programa ejecuta un bucle do while para leer del archivo un slo caracter a la vez y desplegarlo en pantalla hasta detectar un caracter EOF (End Of File, Fin de Archivo). Por ltimo cerramos el archivo y el programa termina. #include <stdio.h> #include <stdlib.h> int main() { FILE *nombre; int c; nombre = fopen("Prueba.htm", "r"); if (nombre == NULL) { printf("El archivo no existe \n"); exit (EXIT_FAILURE); } else { do { c = getc(nombre); /* Obtiene un caracter del archivo */ putchar(c); /* Lo despliega en pantalla y continua... */ } while (c != EOF); /* hasta encontrar EOF (el final del archivo) */ } fclose(nombre); return EXIT_SUCCESS; } En este punto afrontamos un problema comn en programacin C. La variable regresada por la funcin getc ( ) es un caracter, por lo que podemos utilizar para el propsito una variable de tipo char, el problema empieza si tratamos de utilizar una variable de tipo unsigned char, ya que C regresa -1 para EOF. Una variable de tipo unsigned char no puede contener valores negativos ya que su rango est entre 0 y 255 por lo que retornar 255 para un valor negativo valor que no compara con EOF, en este caso el programa nunca terminar. Para prevenir esta situacin, utilice una variable de tipo int, ya que este tipo de variable siempre incluye el signo. En este programa leimos del archivo un caracter a la vez, en el siguiente leeremos una palabra a la vez.
76312839.doc
Pgina 44 de 59
HVF
do { c = fscanf(fp1, "%s", palabra); /* Obtiene una palabra del archivo */ printf("%s\n", palabra); /* la despliega en pantalla */ } while (c != EOF); fclose(fp1); return 0; } Al ejecutar ste programa la salida es la siguiente: El problema es que se imprime dos veces la ltima palabra, para resolver este detalle modificamos el anterior cdigo as: #include <stdio.h> int main() { FILE *fp1; char palabra[100]; int c; fp1 = fopen("Prueba.htm", "r"); do { c = fscanf(fp1, "%s", palabra); /* Obtiene una palabra del archivo */ if (c != EOF) printf("%s\n", palabra); /* La despliega en pantalla */ } while (c != EOF); fclose(fp1); return 0; } Es bueno hacer notar que un programador experimentado no escribira el cdigo como lo hicimos en el ejemplo ya que compara c con EOF dos veces por cada ciclo del bucle y esto es ineficiente. Utilizamos cdigo que trabaja y es fcil de leer pero conforme Usted gane experiencia en C, Usted utilizar mtodos ms eficientes de codificar, aunque sean ms difciles de leer, por ejemplo: while((c = fscanf(fp1, "%s", palabra) != EOF) { printf("%s\n", palabra); } /* Se repite hasta encontrar EOF */ /* Se repite hasta encontrar EOF */
76312839.doc
Pgina 45 de 59
HVF
fp1 = fopen("Prueba.htm", "r"); if (fp1 == NULL) { printf("Error al abrir el archivo \n"); exit (EXIT_FAILURE); } do { c = fgets(palabra, 100, fp1); /* Obtiene una linea del archivo */ if (c != NULL) printf("%s", palabra); /* La despliega en pantalla */ } while (c != NULL); fclose(fp1); return EXIT_SUCCESS; } Ahora utilizamos la funcin fgets ( ) la cual lee una lnea completa, incluyendo el caracter de nueva lnea y coloca los datos en un buffer (espacio de memoria RAM temporal). El buffer a ser ledo es el primer argumento en la llamada a la funcin en tanto que el mximo nmero de caracteres a ser ledos es el segundo argumento, seguido por el puntero de archivo. Esta funcin leer caracteres en el buffer hasta que encuentre el caracter de nueva lnea, lea el mximo nmero de caracteres menos uno, lo que ocurra primero. El espacio final se reserva para el caracter nulo (NULL) del fin de la cadena. Adems, si se encuentra un EOF, la funcin retorna NULL. NULL est definido a cero en el archivo stdio.h Los ejemplos de ste captulo realmente no requieren de mucha explicacin, el cdigo lo podemos modificar para introducir el nombre del archivo que deseamos abrir de sta forma: #include <stdio.h> #include <stdlib.h> int main() { FILE *fp1; char palabra[100], nombre[25]; char *c; printf("Introduzca el nombre del archivo -> "); scanf("%s", nombre); /* Lee el archivo deseado */ fp1 = fopen(nombre, "r"); if (fp1 == NULL) { printf("Error al abrir el archivo \n"); exit (EXIT_FAILURE); } do { c = fgets(palabra, 100, fp1); /* Obtiene una linea del archivo */ if (c != NULL) printf("%s", palabra); /* La despliega en pantalla */ } while (c != NULL); fclose(fp1); return EXIT_SUCCESS; } /* Hasta encontrar NULL */ /* Se repite hasta encontrar NULL */
Pgina 46 de 59
HVF
Asignacin especial
A lo largo de este captulo hemos tratado el uso de diferentes funciones para operaciones de lectura y escritura, se trata de un tema particularmente til en el desarrollo de un programa. Como seguramente habr notado, utilizamos para los ejemplos un archivo llamado Prueba.htm, por la extensin utilizada y por la naturaleza del texto includo en el mismo sabemos que se trata de un documento web que puede ser visualizado en su navegador. Para terminar este captulo y a manera de resumen que a la vez nos sirva de introduccin al siguiente captulo, le presento el siguiente cdigo que Yo espero despierte en Usted un poco ( un mucho) de curiosidad, experimente con el programa y si Usted desea, mndeme su opinin por correo electrnico. #include <stdio.h> #include <stdlib.h> enum HTMLid { HTML_NINGUNO, HTML_BODY, HTML_cBODY, HTML_B, HTML_cB, HTML_HTML, HTML_cHTML, HTML_CENTER, HTML_cCENTER }; static struct { char *htmlcodigo; enum HTMLid id; } lista_de_codigos[]= { {"<HTML>", HTML_HTML}, {"</HTML>", HTML_cHTML}, {"<BODY>", HTML_BODY}, {"</BODY>", HTML_cBODY}, {"<CENTER>", HTML_CENTER}, {"</CENTER>", HTML_cCENTER}, {"<B>", HTML_B}, {"</B>", HTML_cB}, {NULL, HTML_NINGUNO} }; char texto[128]; int itexto=0, c; int main() { int i, ietiqueta=0; char etiqueta[64]; FILE *archivo; enum HTMLid codigo; archivo = fopen("Prueba.htm", "r"); if (archivo == NULL) { /* Abre el archivo para lectura */
Pgina 47 de 59
HVF
/* Checa todos los caracteres del archivo */ c=getc(archivo); if (c=='<') /* Lee la etiqueta html */ { etiqueta[ietiqueta++]=c; /* incluye el principio de la etiqueta */ do { c=getc(archivo); etiqueta[ietiqueta++]=c; } while(c!='>'); etiqueta[ietiqueta]=0; codigo=HTML_NINGUNO; for(i=0; lista_de_codigos[i].htmlcodigo!=NULL; i++) { if(stricmp(etiqueta, lista_de_codigos[i].htmlcodigo)==0) { codigo=lista_de_codigos[i].id; break; } } switch (codigo) { case HTML_NINGUNO: break; case HTML_HTML: printf("Empieza el documento web \n"); break; case HTML_cHTML: printf("Fin del documento web \n"); break; case HTML_B: printf("Empieza la etiqueta B \n"); break; case HTML_cB: printf("Termina la etiqueta B \n"); break; case HTML_BODY: printf("Empieza la etiqueta BODY \n"); break; case HTML_cBODY: printf("Termina la etiqueta BODY \n"); break; case HTML_CENTER: printf("Empieza la etiqueta CENTER \n"); break; case HTML_cCENTER: printf("Termina la etiqueta CENTER \n"); break; } ietiqueta=0; } else
76312839.doc rollo(); } while(c!=EOF); } fclose(archivo); texto[itexto]=0; printf(texto); return EXIT_SUCCESS; } rollo() { texto[itexto++]=c; } La salida del programa es la siguiente:
Pgina 48 de 59
HVF
Qu es una estructura?
Una estructura es un tipo de dato definido por el usuario, al utilizar una estructura Usted tiene la habilidad para definir un nuevo tipo de dato considerablemente ms complejo que los tipos que hemos utilizado hasta ahora. Una estructura es una combinacin de varios tipos de datos previamente definidos, incluyendo otras estructuras que hayamos definido previamente. Una definicin simple es, "una estructura es un grupo de datos relacionados en una forma conveniente al programador y/o al usuario del programa". Como es costumbre, un ejemplo nos clarifica los conceptos: #include <stdio.h> struct { char inicial; /* Letra inicial del apellido */ int edad; /* Edad */ int calificacion; /* Aprovechamiento */ } chico, chica; int main() { chico.inicial = 'R'; chico.edad = 15; chico.calificacion = 75; chica.edad = chico.edad - 1; /* Ella es un ao menor que l */ chica.calificacion = 82; chica.inicial = 'H'; printf("%c tiene %d anos y su calificacion es de %d\n", chica.inicial, chica.edad, chica.calificacion); printf("%c tiene %d anos y su calificacion es de %d\n", chico.inicial, chico.edad, chico.calificacion); return 0; } El programa empieza definiendo una estructura utilizando la palabra clave struct seguida de tres variables sencillas encerradas entre llaves, las cuales son los componentes de la estructura, despues de la llave de cierre tenemos enlistadas dos variables llamadas chico y chica. De acuerdo a la definicin de una estructura, chico es una variable compuesta de tres elementos, inicial, edad y, calificacion. Cada uno de los tres campos estn asociados a chico y cada uno almacena una variable de su respectivo tipo, lo mismo se puede decir para chica pero sus variables son diferentes por lo tanto tenemos 6 variables agrupadas en dos, de tipo struct.
76312839.doc
Pgina 49 de 59
HVF
Un array de estructuras
El siguiente programa es bsicamente el mismo que el anterior, pero esta vez definimos un array de 12 variables llamadas chicos, est claro que ste programa contiene 12 veces 3=36 variables sencillas cada una de las cuales puede almacenar un tem de dato siempre y cuando sea del tipo adecuado, se define adems una variable comn llamada indice para utilizarla en los bucles, estudie el cdigo: #include <stdio.h> struct { char inicial; int edad; int calificacion; } chicos[12]; int main() { int indice; for (indice = 0; indice < 12; indice++) { chicos[indice].inicial = 'A' + indice; chicos[indice].edad = 16; chicos[indice].calificacion = 84; } chicos[3].edad = chicos[5].edad = 17; chicos[2].calificacion = chicos[6].calificacion = 92; chicos[4].calificacion = 57; chicos[10] = chicos[4]; /* Asignacion de estructura solo en compiladores ANSI-C */
for (indice = 0; indice < 12; indice++) printf("%c tiene %d anos y una calificacion de %d\n", chicos[indice].inicial, chicos[indice].edad, chicos[indice].calificacion); return 0; }
76312839.doc
Pgina 50 de 59
HVF
Para asignar un valor a cada uno de los campos utilizamos un bucle for, en cada ciclo del bucle se asignan todos los valores para uno de los chicos, en una situacin real sta podra no ser la mejor manera de asignar datos, pero un bucle puede leer los datos de un archivo y almacenarlos en la correcta ubicacin en un programa real, considere ste ejemplo como un inicio burdo a una base da datos, pues eso es justamente nuestro ejemplo. El cdigo resulta fcil de entender, solo har un comentario respecto a la lnea 26 en donde podemos ver una asgnacin de estructura, en ste enunciado los tres campos de chicos[4] son copiados en los respectivos campos de chicos{10], esto no siempre est permitido en el lenguaje C, solo en los compiladores que cumplen con la norma ANSIC, si su compilador no es ANSI-C encierre en comentarios la lnea 26. El resultado de la ejecucin delprograma es el siguiente:
Estructuras y punteros
Ahora modificamos nuevamente el programa del ejemplo anterior para utilizar punteros en algunas de las operaciones, la primera diferencia se muestra en la definicin de las variables enseguida de la definicinde la estructura, tenemos un puntero llamado puntero el cual seala a la estructura, sera ilegal tratar de utilizar ste puntero para sealar a cualquier otro tipo de variable por una fuerte razn que estudiaremos un poco ms adelante en sta misma leccin, entre tanto le presento el cdigo: #include <stdio.h> struct { char inicial; int edad; int calificacion; } chicos[12], *puntero, extra; int main() { int indice; for (indice = 0; indice < 12; indice++) { puntero = chicos + indice; puntero->inicial = 'A' + indice; puntero->edad = 16; puntero->calificacion = 84; } chicos[3].edad = chicos[5].edad = 17; chicos[2].calificacion = chicos[6].calificacion = 92; chicos[4].calificacion = 57; for (indice = 0; indice < 12; indice++) { puntero = chicos + indice; printf("%c tiene %d anos y su calificacion es %d\n", (*puntero).inicial, chicos[indice].edad, puntero->calificacion); } extra = chicos[2]; extra = *puntero; /* Asignacion de estructura */ /* Asignacion de estructura */
return 0; } La siguiente diferencia la encontramos en el bucle for donde utilizamos el puntero para acceder a los campos de datos, recuerde que el nombre de un array es en realidad un puntero al primer elemento del array, como chicos es un puntero constante que seala al primer elemento del array el cual es una estructura, podemos definir a puntero en trminos de chicos. El elemento llamado chicos es constante por lo que no puede alterarse su valor, pero puntero es una variable puntero que se le puede asignar cualquier valor consistente requerido para sealar a la estructura. Si asignamos el valor de chicos a puntero entonces est claro que puntero
76312839.doc
Pgina 51 de 59
HVF
adems sealar al primer elemento del array que es una estructura que contiene tres campos. Es muy til para comprender el funcionamiento del programa utilizar un debbuger que nos permita ejecutar el programa paso a paso.
Aritmtica de punteros
Sumando 1 a puntero causar que seale al segundo campo del array debido a la manera en que los punteros son manejados en C. El sistema sabe que la estructura contiene tres variables y sabe cuntos elementos de memoria son requeridos para almacenar la estructura completa, por tanto si le indicamos al sistema que agregue 1 al puntero, agregar los elementos de memoria necesarios para obtener el siguiente elemento del array. Si, por ejemplo, sumamos 4 al puntero, el sistema avanzar el valor del puntero 4 veces el tamao de la estructura dando como resultado que el puntero seale 4 elementos ms all del array. Esta es la razn por la cual un puntero no puede utilizarse para sealar a otro tipo de dato excepto al que fu definido. Del prrafo anterior est claro que conforme avanzamos en el bucle el puntero sealar a uno de los elementos del array en cada ciclo, podemos por lo tanto utilizar el puntero para referenciar a varios elementos de cada una de las estructuras conforme avanzamos por el bucle. Referenciar a los elementos de una estructura con un puntero ocurre tan a menudo en C que se utiliza una notacin especial. Utilizar puntero->inicial es lo mismo que utilizar (*puntero).inicial lo cual es lo que hicimos en el programa. El smbolo de "->" se hace con el signo de menos y el de mayor que. Como el puntero seala a la estructura, debemos definir una vez ms cul de los elementos deseamos referenciar cada vez que utilizamos uno de los elementos de la estructura. Existen, como podemos ver, varios mtodos diferentes para referirnos a los miembros de la estructura. Cuando ejecutamos el bucle for para desplegar los datos al final del programa, utilizamos tres mtodos diferentes para referenciar los elementos de la estructura. Esto puede considerarse una prctica pobre de programacin pero la utilizamos para fines de ilustracin.
Estructuras anidadas
El siguiente ejemplo muestra una estructura anidada. Las estructuras que hemos visto han sido muy sencillas aunque tiles. Es posible definir estructuras conteniendo docenas y an cientos miles de elementos pero sera ventajoso para el programador no definir todos los elementos en una pasada sino utilizar una definicin de estructura jerrquica. #include <string.h> struct persona { char nombre[25]; int edad; char estado; /* C = casado, S = soltero */ }; struct datos { int calificacion; struct persona descripcion; char comida[25]; }; int main() { struct datos estudiante[53]; struct datos maestro, sub; maestro.calificacion = 94; maestro.descripcion.edad = 23; maestro.descripcion.estado = 'M'; strcpy(maestro.descripcion.nombre, "Lisseth Gil"); strcpy(maestro.comida, "Chocolates de Ron"); sub.descripcion.edad = 87; sub.descripcion.estado = 'M'; strcpy(sub.descripcion.nombre, "Abuela Pata"); sub.calificacion = 73;
Pgina 52 de 59
HVF
estudiante[1].descripcion.edad = 15; estudiante[1].descripcion.estado = 'S'; strcpy(estudiante[1].descripcion.nombre, "Bill Griton"); strcpy(estudiante[1].comida, "Crema de cacahuate"); estudiante[1].calificacion = 77; estudiante[7].descripcion.edad = 14; estudiante[12].calificacion = 87; return 0; } La primera estructura contiene tres elementos pero no le sigue ninguna variable definida, slo una estructura, pero como incluimos un nombre al principio de la estructura, la estructura es llamada persona. La palabra persona puede utilizarse para referirse a la estructura pero no a cualquier variable de ste tipo de estructura, se trata por lo tanto de un nuevo tipo que hemos definido y lo podemos utilizar de la misma manera en que usamos un int, char o cualquier otro tipo que existe en C. La nica restriccin es que ste nuevo tipo debe estar siempre asociado con la palabra clave struct. La siguiente definicin de estructura contiene tres campos siendo el segundo la estructura previamente definida la cual llamamos persona. La variable de tipo persona se llama descripcion, as la nueva estructura contiene dos variables simples, calificacion y una cadena llamada comida, y la estructura llamada descripcion. Como descripcion contiene tres variables, la nueva estructura tiene entonces cinco variables, a sta estructura le hemos dado el nombre de datos, lo cual es otro tipo definido. Finalmente, dentro de la funcin main ( ) definimos un array de 53 variables cada una con la estructura definida por el tipo datos, y cada una con el nombre estudiante, en total hemos definido 53 veces 5 variables, cada una de las cuales es capaz de almacenar datos. Como tenemos la definicin de un nuevo tipo podemos utilizarla para a su vez definir dos variables. Las variables maestro y sub estn definidas en la lnea 20 como variables de tipo datos por lo que cada una de stas dos variables contienen 5 campos en los cuales podemos almacenar datos. En las lneas 22 a 26 del programa asignamos valores a cada uno de los campos de maestro. El primero es el campo calificacion y es manejado como las otras estructuras que hemos estudiado porque no forma parte de la estructura anidada. Enseguida deseamos asignar un valor a edad el cual es parte de la estructura anidada. Para acceder a ste campo empezamos con el nombre de la variable maestro al cual le concatenamos el nombre del grupo descripcion, y entonces debemos definir en cul campo de la estructura anidada estamos interesados por lo que concatenamos el nombre de la variable edad. El estado de los maestros se manejan de la misma manera que su edad pero los ltimos dos campos son cadenas asignadas utilizando la funcin strcpy ( ). Observe que los nombres de las variables en la funcin strcpy ( ) se consideran como una unidad aunque estn compuestas de varias partes. Compile y ejecute el programa, probablemente obtenga un aviso sea de error advertencia respecto a un desbordamiento de memoria similar a ste: Lo que sto significa es que el programa requiere ms memoria que la asignada por el compilador por lo que es necesario incrementar el tamao de stacks, el mtodo para hacer sto vara de un compilador a otro, en el caso concreto del compilador de Symantec que utilizo para los programas de ste tutorial, se cumple el objetivo asignando un modelo de memoria mayor:
Uniones
Dicho de una forma simple, una unin le permite manejar los mismos datos con diferentes tipos, utilizar el mismo dato con diferente nombre, a continuacin le presento un ejemplo: #include <stdio.h> int main() { union { int valor; /* Esta es la primera parte de la union */ struct { char primero; /* Esta es la segunda parte */ char segundo;
Pgina 53 de 59
HVF
long indice; for (indice = 12; indice < 300000L; indice += 35231L) { numero.valor = indice; printf("%8x %6x %6x\n", numero.valor, numero.mitad.primero, numero.mitad.segundo); } return 0; } En ste ejemplo tenemos dos elementos en la unin, la primera parte es el entero llamado valor el cual es almacenado en algn lugar de la memoria de la computadora como una variable de dos bytes. El segundo elemento est compuesto de dos variables de tipo char llamadas primero y segundo. Estas dos variables son almacenadas en la misma ubicacin de almacenamiento que valor porque sto es precisamente lo que una unin hace, le permite almacenar diferentes tipos de datos en la misma ubicacin fsica. En ste caso Usted puede poner un valor de tipo entero en valor y despus recobrarlo en dos partes utilizando primero y segundo, sta tcnica es utilizada a menudo para empaquetar bytes cuando, por ejemplo, combine bytes para utilizarlos en los registros del microprocesador. La unin no es utilizada frecuentemente y casi nunca por programadores principiantes, en este momento no necesita profundizar en el empleo de la unin as que no dedique mucho tiempo a su estudio, sin embargo no tome a la ligera el concepto de la unin, podra utilizarlo a menudo.
Qu es un campo de bits?
Para finalizar la presente leccin estudiaremos el concepto de campo de bits, en el siguiente cdigo podemos ver la manera de definirlo y utilizarlo, en el programa tenemos una unin de una variable sencilla de tipo int en la lnea 5 y la estructura definida en las lneas 6 a la 12: #include <stdio.h> union { int indice; struct { unsigned int x : 1; unsigned int y : 2; unsigned int z : 2; } bits; } numero; int main() { for (numero.indice = 0; numero.indice < 20; numero.indice++) { printf("indice = %3d, bits = %3d%3d%3d\n", numero.indice, numero.bits.z, numero.bits.y, numero.bits.x); } return 0; } La estructura est compuesta de tres campos de bits llamados x, y, y z. La variable llamada x es de un solo bit, la variable y es de dos bits y es adyacente a la variable x, y la variable z es de dos bits y adyacente a la variable y. Como la unin causa que los bits sean almacenados en la misma ubicacin en memoria que la variable indice, la variable x es el bit menos significante de la variable indice, y conforma los siguientes dos bits, y z es almacenado en los siguientes dos bits de indice. Compile y ejecute el programa y ver que al ser incrementada la variable indice en cada ciclo del bucle, ver los campos de bits incrementarse en sus respectivas
76312839.doc
Pgina 54 de 59
HVF
ubicaciones. Una cosa debemos sealar, los campos de bits deben definirse como partes de un tipo unsigned int de lo contrario el compilador marcar error. El resultado de la ejecucin del programa es:
Qu es la asignacin dinmica?
Hasta este punto del tutorial de C las variables que se han utilizado en los programas son de tipo estticas. (Algunas de ellas han sido automticas y fueron asignadas dinmicamente para Usted por el sistema pero esta operacin pas inadvertida para Usted). En este captulo estudiaremos algunas variables asignadas dinmicamente, stas son variables que no existen cuando se carga el programa pero se crean dinmicamente cuando son necesarias al correr el programa. Es posible, utilizando stas tcnicas crear tantas variables como sea necesario, utilizarlas y removerlas de su espacio en memoria para que sea utilizado por otras variables, como es costumbre, un ejemplo habla bin del concepto: #include <stdio.h> #include <string.h> #include <stdlib.h> struct animal { char nombre[25]; char raza[25]; int edad; } *mascota1, *mascota2, *mascota3; int main() { mascota1 = (struct animal *)malloc(sizeof(struct animal)); /* Es un error no checar la asignacion, consulte el texto. */ /* Checaremos la asignacion en el siguiente programa. */ strcpy(mascota1->nombre, "General"); strcpy(mascota1->raza, "Colicondela"); mascota1->edad = 1; mascota2 = mascota1; /* mascota2 ahora seala a la construccion de arriba */ mascota1 = (struct animal *)malloc(sizeof(struct animal)); strcpy(mascota1->nombre, "Francisco"); strcpy(mascota1->raza, "Labrador Cobrador"); mascota1->edad = 3; mascota3 = (struct animal *)malloc(sizeof(struct animal)); strcpy(mascota3->nombre, "Cristal"); strcpy(mascota3->raza, "Pastor Aleman"); mascota3->edad = 4; /* Desplegamos los datos */ printf("%s es un %s, y su edad es de %d.\n", mascota1->nombre, mascota1->raza, mascota1->edad); printf("%s es un %s, y su edad es de %d.\n", mascota2->nombre, mascota2->raza, mascota2->edad); printf("%s es un %s, y su edad es de %d.\n", mascota3->nombre, mascota3->raza, mascota3->edad); mascota1 = mascota3; /* mascota1 seala a la misma estructura que mascota3 */ free(mascota3); /* Esto libera una estructura */ free(mascota2); /* Esto libera otra estructura*/ /* free(mascota1); esto no se puede hacer, consulte el texto */ return 0;
76312839.doc
Pgina 55 de 59
HVF
} Empezamos definiendo una estructura llamada animal con algunos campos en relacin a unos perros, no definimos ninguna variable de ste tipo slo tres punteros, si Usted busca en el resto del cdigo del programa no encontrar ninguna variable definida por lo que no tenemos en donde almacenar datos, todo lo que tenemos para trabajar son tres punteros, cada uno de los cuales es capaz de sealar a variables de la estructura definida llamada animal. Para hacer cualquier cosa con el programa necesitamos algunas variables por lo que las crearemos dinmicamente
Qu es la pila (heap)?
La pila (heap) es una rea predefinida de memoria que puede ser accesada por los programas para almacenar datos y variables, stos son asignados a la pila (heap) por el sistema cuando se realizan llamadas a malloc ( ). El sistema mantiene un registro del lugar donde los datos son almacenados, los datos y las variables pueden ser desasignadas al gusto dejando "agujeros" en la pila, el sistema sabe la ubicacin de los agujeros y los puede utilizar para almacenamiento adicional de datos con llamadas adicionales a malloc ( ), la estructura de la pila (heap) es por tanto una entidad en constante cambio, dinmica. Espero que la breve explicacin de la pila y la asignacin dinmica sea suficiente para entender lo que estamos haciendo con la funcin malloc ( ). Simplemente le solicita al sistema un bloque de memoria del tamao especificado regresando un puntero que seala al primer elemento del bloque, el nico argumento en el parntesis es el tamao deseado del bloque que en nuestro caso, deseamos un bloque que almacene una de las estructuras que definimos al principio del programa. El operador sizeof es nuevo, al menos para nosotros en ste curso, regresa el tamao en bytes del argumento entre parntesis, regresa por lo tanto, el tamao de la estructura llamada animal y se nmero es utilizado como parmetro en la llamada a malloc ( ), al completar sta llamada tenemos un bloque asignado en la memoria con el puntero llamado mascota1 sealando el principio de ste bloque.
Qu es el reparto (cast)?
Al principio de la llamada a la funcin malloc ( ) podemos observar el llamado reparto (Traduccin en ste tutorial del trmino en ingls "cast"). La funcin malloc ( ) retorna por defecto un puntero de tipo void, como no se puede utilizar un puntero que seale a nada, debe ser cambiado a otro tipo. Usted puede definir el tipo de puntero con la construccin dada en el ejemplo, en ste caso deseamos un puntero que seale a una estructura de tipo animal, as que se lo decimos al compilador con ste reparto. An en el caso de omitir el reparto, la mayora de los compiladores retornan un puntero correctamente dando un mensaje de advertencia y produciendo un ejecutable de todas maneras, es mejor prctica indicarle al compilador el reparto adecuado.
76312839.doc
Pgina 56 de 59
HVF
cual es parte de la pila, se ha desperdiciado, sto no debe hacerse en un programa, lo mostramos a manera de ilustracin. La primera llamada a la funcin free ( ) remueve el bloque de datos sealado por mascota1 y mascota3 lo que desasigna la estructura y libera el espacio de memoria para su uso posterior. La segunda llamada a free ( ) remueve el bloque sealado por mascota2, en este punto del programa hemos perdido todo acceso a los datos generados al principio del programa.
Un array de punteros
Parece que la explicacin de la asignacin dinmica en el anterior ejemplo fu muy extensa, la buena noticia es que se ha dicho prcticamente todo al respecto, claro est que an falta por aprender algunas tcnicas en el uso de la asignacin dinmica y esto es lo que haremos en los siguientes ejemplos, empezando con el siguiente cdigo: #include <stdio.h> #include <string.h> #include <stdlib.h> struct animal { char nombre[25]; char raza[25]; int edad; } *mascota[12], *puntero; int main() { int indice; /* primero rellenar dinamicamente las estructuras con cualquier cosa */ for (indice = 0 ; indice < 12 ; indice++) { mascota[indice] = (struct animal *)malloc(sizeof(struct animal)); if (mascota[indice] == NULL) { printf("Falla en la asignacion de memoria.\n"); exit (EXIT_FAILURE); } strcpy(mascota[indice]->nombre, "Fido"); strcpy(mascota[indice]->raza, "Pastor para tacos"); mascota[indice]->edad = 4; } mascota[4]->edad = 12; mascota[5]->edad = 15; mascota[6]->edad = 10; /* estas lineas son para */ /* poner algunos datos */ /* en algunos de los campos */
/* Desplegamos los datos */ for (indice = 0 ; indice < 12 ; indice++) { puntero = mascota[indice]; printf("%s Es un %s, y tiene %d anos.\n", puntero->nombre, puntero->raza, puntero->edad); } /* es buena practica de programacion liberar el espacio */ /* dinamicamente asignado en el programa */ for (indice = 0 ; indice < 12 ; indice++) free(mascota[indice]);
76312839.doc
Pgina 57 de 59
HVF
return EXIT_SUCCESS; } Este programa es muy parecido al ejemplo anterior ya que se utiliza la misma estructura pero en el presente caso definimos un array de punteros para ilustrar los mecanismos para construir una base de datos grande utilizando un array de punteros en lugar de un puntero sencillo a cada elemento, para mantener el ejemplo sencillo definimos 12 elementos en el array y otro puntero adicional llamado puntero. El enunciado *mascota[12] es nuevo as que es conveniente hacer algunos comentarios. Lo que hemos definido es un array de 12 punteros siendo el primero mascota[0] y el ltimo mascota[11], como un array es en s un puntero, el nombre mascota es un puntero constante a otro puntero, sto es vlido en C, no hay limites en cuanto al nmero de niveles sealados, as, por ejemplo, la expresin int ****pt es legal ya que se trata de un puntero a un puntero a un puntero a un puntero a una variable de tipo entero, se recomienda tal uso de los punteros hasta haber ganado suficiente experiencia en su manejo. Ahora que tenemos 12 punteros los cuales pueden usarse como cualquier otro, es sencillo escribir un bucle para asignar dinmicamente un bloque de datos para cada puntero y rellenar los respectivos campos con los datos deseados, en ste caso rellenamos los espacios con datos de propsito ilustrativo pero bin podra tratarse de una base de datos, de informacin proveniente de algn equipo de prueba de laboratorio cualquier otra fuente de datos. Notar que en el ejemplo checamos cuidadosamente el valor retornado por la funcin malloc ( ) para verificar que no contenga un valor de cero, si retorna un valor NULL, desplegamos un mensaje indicandolo y terminando el programa, recuerde que en un programa real no basta con terminar el programa, es aconsejable generar un reporte de errores y darle al usuario la oportunidad de corregirlos y continuar el proceso iniciado antes de cerrar el programa. Volviendo al anlisis del cdigo, en las lneas 31 a la 33 escogimos algunos campos al azar para ilustrar el uso de enunciados sencillos y que los datos son desplegados en el monitor. El puntero puntero se utiliza en el bucle para desplegar datos slo para ilustracin, fcilmente se hubiera podido utilizar mascota[indice] en su lugar. Finalmente los 12 bloques son liberados de la memoria antes de terminar el programa, el resultado de la ejecucin es el siguiente, tenga en cuenta que mi compilador no maneja la letra ee:
#define REGISTROS 6 struct animal { char nombre[25]; /* El nombre del animal */ char raza[25]; /* El tipo de animal */ int edad; /* La edad del animal */ struct animal *siguiente; /* seala a otra estructura de este tipo */ } *puntero, *inicio, *previo; /* Se definen tres punteros, no variables */ int indice; /* Una variable global */
int main() { /* El primer registro siempre es un caso especial */ inicio = (struct animal *)malloc(sizeof(struct animal)); if (inicio == NULL)
76312839.doc { printf("Falla en la asignacion de memoria\n"); exit (EXIT_FAILURE); } strcpy(inicio->nombre, "General"); strcpy(inicio->raza, "Pastor para tacos"); inicio->edad = 4; inicio->siguiente = NULL; previo = inicio;
Pgina 58 de 59
HVF
/* Una vez iniciada la lista, se puede utilizar un bucle para rellenar los bloques */ for (indice = 0 ; indice < REGISTROS ; indice++) { puntero = (struct animal *)malloc(sizeof(struct animal)); if (puntero == NULL) { printf("Falla en la asignacion de memoria\n"); exit (EXIT_FAILURE); } strcpy(puntero->nombre, "Pancho"); strcpy(puntero->raza, "Labrador"); puntero->edad = 3; previo->siguiente = puntero; /* seala al ultimo "siguiente" de este registro */ puntero->siguiente = NULL; /* seala este "siguiente" a NULL */ previo = puntero; /* Este es ahora el registro previo */ } /* Se despliegan los datos descritos */ puntero = inicio; do { previo = puntero->siguiente; printf("%s es un %s, y tiene %d anos de edad.\n", puntero->nombre, puntero->raza, puntero->edad); puntero = puntero->siguiente; } while (previo != NULL); /* Es buena practica liberar el espacio utilizado */ puntero = inicio; /* primer bloque del grupo */ do { previo = puntero->siguiente; /* siguiente bloque */ free(puntero); /* libera el actual bloque */ puntero = previo; /* seala al siguiente */ } while (previo != NULL); /* termina hasta que el siguiente sea NULL */ return EXIT_SUCCESS; } Este programa inicia de una manera similar a los ejemplos anteriores con la adicin de una declaracin constante para uso posterior, la estructura es casi la misma excepto por la adicin de otro campo dentro de la estructura en la lnea 12, se trata de un puntero a otra estructura del mismo tipo y ser utilizada para sealar a la siguiente estructura en orden, de acuerdo a la analoga de arriba, ste puntero sealar a la siguiente nota, que a su vez contendr un puntero a la siguiente nota. Definimos tres punteros y una variable para utilizarla como contador y con sto estamos listos para empezar a utilizar la estructura definida, una vez ms con datos sin sentido, slo para propsitos de ilustracin.
76312839.doc
Pgina 59 de 59
HVF
El primer campo
Utilizando la funcin malloc ( ) solicitamos un bloque de almacenamiento en memoria (en el heap) y lo rellenamos con datos, checando la correcta asignacin. Al campo adicional, en ste ejemplo llamado siguiente, se le asigna el valor de NULL, el cual es utilizado para indicar que ste es el final de la lista, dejaremos el puntero llamado inicio sealando a sta estructura de tal manera que siempre sealar a la primera estructura de la lista. Asignamos adems a previo el valor de inicio por razones que pronto veremos. Tenga en cuenta que los extremos de la lista enlazada sern siempre manejados de forma diferente a los elementos centrales de la lista, en ste punto tenemos un elemento en nuestra lista relleno de datos significativos.
Dos funciones ms
Debemos mencionar un par de funciones, calloc ( ) y realloc ( ). La funcin calloc ( ) asigna un bloque de memoria inicializado todo en ceros lo cual puede ser til en algunas circunstancias. Generalmente asignamos memoria e inmediatamente la rellenamos con datos as que es una prdida de tiempo rellenar primero conceros el bloque de memoria y despus con los datos del programa, por sta razn, la funcin calloc ( ) se utiliza muy raramente. por otra parte, la funcin realloc ( ) se utiliza para cambiar el tamao de un bloque asignado de memoria, a su vez es raramente utilizada, an por programadores experimentados.