Está en la página 1de 53

EL MANUAL DEFINITIVO DE C

TEMA 0: Introduccin 0.1 Orgenes del C El lenguaje C cuando trabajaba, junto operativo UNIX. fue inventado por Dennis Ritchie en 1972 con Ken Thompson, en el diseo del sistema

El lenguaje C deriva del lenguaje B de Thompson, el cual, a su vez, deriva del lenguaje BCPL desarrollado por Martin Richards. Durante muchos aos el estndar de C fue la versin proporcionada con el sistema operativo UNIX versin 5. Pero pronto empezaron a surgir muchas implementaciones del C a raz de la popularidad creciente de los microordenadores. Por este motivo, se hizo necesario definir un C estndar que est representado hoy por el ANSI C. 0.2 Caractersticas del lenguaje C Algunas caractersticas del lenguaje C son las siguientes: - Es un lenguaje de propsito general. Este lenguaje se ha utilizado para el desarrollo de aplicaciones tan dispares como: hojas de clculos, gestores de bases de datos, compiladores, sistemas operativos, ... - Es un lenguaje de medio nivel. Este lenguaje permite programar a alto nivel (pensando a nivel lgico y no en la mquina fsica) y a bajo nivel (con lo que se puede obtener la mxima eficiencia y un control absoluto de cuanto sucede en el interior del ordenador). - Es un lenguaje porttil. Los programas fcilmente transportables a otros sistemas. escritos en C son

- Es un lenguaje potente y eficiente. Usando C, un programador puede casi alcanzar la eficiencia del cdigo ensamblador junto con la estructura del Algol o Pascal. Como desventajas habra que resear que es ms complicado de aprender que otros lenguajes como Pascal o Basic y que requiere una cierta experiencia para poder aprovecharlo a fondo. 0.3 Uso del C Los pasos a seguir desde el momento que se comienza a escribir el programa C hasta que se ejecuta son los siguientes: 1.2.3.4.Escribirlo en un editor. Compilarlo en un compilador. Enlazarlo en un enlazador. Ejecutarlo.

Paso 1: ESCRIBIRLO El programa se puede escribir en cualquier editor que genere ficheros de texto estndar, esto es, que los ficheros generados no incluyan cdigos de control y caracteres no imprimibles.

Estos ficheros que contienen cdigo C se llaman ficheros fuentes. Los ficheros fuentes son aquellos que contienen cdigo fuente, es decir, ficheros con texto que el usuario puede leer y que son utilizados como entrada al compilador de C. Los programas pequeos suelen ocupar un solo fichero fuente; pero a medida que el programa crece, se va haciendo necesario distribuirlo en ms ficheros fuentes. Paso 2: COMPILARLO El compilador produce ficheros objetos a partir de los ficheros fuentes. Los ficheros objetos son los ficheros que contienen cdigo objeto, es decir, ficheros con cdigo mquina (nmero binarios que tiene significado para el microprocesador) y que son utilizados como entrada al enlazador. La extensin de estos ficheros es OBJ, aunque tambin los hay con extensin LIB. A estos ltimos se les llama tambin ficheros de librera o biblioteca; contienen cdigo mquina perteneciente a cdigo compilado suministrado por el compilador. Paso 3: ENLAZARLO El enlazador ficheros objetos. produce un fichero ejecutable a partir de los

Los ficheros ejecutables son aquellos que contienen cdigo mquina y se pueden ejecutar directamente por el sistema operativo. La extensin estos ficheros es EXE o COM. Al proceso de enlazado tambin se le suele llamar el proceso de linkado. Paso 4: EJECUTARLO El programa se puede ejecutar simplemente tecleando su nombre desde la lnea de comandos del sistema operativo. ESQUEMA Los pasos anteriores se resumen en el siguiente esquema: f1.c f2.c . . . fn.c ----> ----> ----> f1.obj f2.obj . . . fn.obj f1.lib f2.lib . . fm.lib ---------+ | ---------| | | | | | ---------| |---------------> f.exe ---------| | ---------+ | | | ---------+

Hoy da los compiladores de C son muy sofisticados e incluyen entornos integrados desde los cuales editamos, compilamos, enlazamos, y podemos realizar una multitud de servicios ms. En algunos de ellos se pueden realizar los pasos de compilado, enlazado y ejecutado con la pulsacin de una sola tecla. En programacin, la experiencia es el gran maestro. Por ello es conveniente empezar a hacer programas en C cuanto antes. TEMA 1 : Conceptos bsicos 1.0 Introduccin En este segundo tema se describir la estructura bsica de un programa en lenguaje C as como la forma de visualizar distintos tipos de datos en pantalla. Se introducirn los conceptos de tipos de datos bsicos y su utilidad. 1.1 El programa HOLA MUNDO Este programa se ha convertido en un clsico dentro de los libros de programacin. Simplemente muestra en pantalla el mensaje HOLA MUNDO, esto que puede parecer muy tonto es algo fundamental puesto que si no sabemos imprimir mensajes datos en la pantalla difcilmente nuestro programa se podr comunicar con el usuario que lo utilice. Mostraremos el programa y a continuacin describiremos cada una de las instrucciones que lo forman. /* Programa : HOLA MUNDO */ #include <stdio.h> main() { } Como podemos observar se trata de un programa muy sencillo. La primera lnea es lo que se conoce como un comentario, un mensaje que el programa aade al cdigo del programa para explicar o aclarar su funcionamiento o el de una parte de l. Los comentarios se pueden situar en cualquier parte de nuestro cdigo y se considerar como comentarios cualquier mensaje que se encuentre entre los caracteres /* y */. Los "/*" y "*/" no son caracteres, sino smbolos o banderas. La siguiente lnea es lo que se conoce como directiva del preprocesador, todos los compiladores de C disponen de un preprocesador, un programa que examina el cdigo antes de compilarlo y que permite modificarlo de cara al compilador en distintos sentidos. En temas posteriores trataremos en profundidad estas directivas del preprocesador, pero para el desarrollo de los temas anteriores a este debemos conocer al menos la directiva #include. Las directivas se caracterizan por comenzar con el carcter # y se deben incluir al comienzo de la lnea aunque es probable que esto dependa de la implementacin del compilador con el que estemos trabajando. La directiva include permite aadir a nuestro cdigo

printf ("\nHola mundo");

algn fichero de texto, de tal forma que la directiva es sustituida por el texto que contiene el fichero indicado. En general los ficheros que acompaan a esta directiva son ficheros .H (Header Cabecera), en los que se incluyen definiciones de funciones que deseamos utilizar en nuestros programas, constantes o tipos complejos de datos. La librera stdio.h (STandarD Input/Output) con tiene las funciones estndar de entrada salida, y en ella se encuentra la funcin printf que utilizamos en nuestro programa. Como se observa en el cdigo el nombre de la funcin a incluir debe ir entre los caracteres <...>. A medida que vayan surgiendo iremos indicando las funciones estndar que deberan incorporar todos los compiladores C y cual es su fichero de definicin .H. En la siguiente lnea nos encontramos con main(). Esto indica que aqu comienza nuestro programa, en realidad estamos definiendo una funcin (esto se indica con los parntesis al final de la lnea) pero esto lo discutiremos en temas posteriores. La funcin main (principal en ingls) siempre debe existir y contendr el programa principal. Finalmente nos encontramos el programa principal, una sentencia printf entre llaves ({, }). Las llaves en C representan bloques, y encierran un conjunto de sentencias o instrucciones (lo que el computador ejecutar), considerando todas ellas como una sola, permitiendo una definicin homognea de los distintos bloques que constituyen el programa. En nuestro caso tenemos un slo bloque que no es ni ms ni menos que el programa principal, que en nuestro caso est compuesto por una sola sentencia (la lnea que contiene el printf). Como se observa en el cdigo las sentencias en C deben terminar con un punto y coma (;), #include <stdio.h> y main() no son sentencias, dan informacin sobre la estructura del programa, y por tanto no finalizan con un punto y coma. NOTA: La razn de que la lnea "main()" no tenga un punto y coma ";" al final es debido a que la sentencia en s termina al cerrar el corchete, no en que dicha lnea proporcione informacin sobre la estructura del programa. De hecho, si el "main()" constituyese una lnea de prototipo tendra un ";" al final. La funcin printf permite visualizar datos formateados en pantalla, es decir, permite indicar un formato como si de un impreso o formulario se tratase indicando donde se deben visualizar cada uno. En el siguiente tema cuando se introduzcan los tipos bsicos de datos se comprender mejor sto. Por ahora slo nos interesa conocer que printf visualiza mensajes en pantalla. El mensaje debe ir entre comillas dobles (") y dentro de las comillas se puede mostrar cualquier secuencia de caracteres. El formato de esta funcin para este segundo tema ser: printf ("mensaje"); En el siguiente tema, cuando expliquemos instruccin, ampliaremos esta definicin. en profundidad la

En nuestro programa observamos que el mensaje de texto que visualiza la instruccin printf comienza con los caracteres \n. Estos caracteres nos permiten algunas funciones especiales para controlar la forma de visualizar los mensajes, la ms utilizada en

\n que significa nueva lnea, as nuestra sentencia printf ("\nHOLA MUNDO"); mover el cursor (la posicin de la pantalla donde actualmente se visualizan los datos) a una nueva lnea situndolo a la izquierda de la pantalla y visualizar el mensaje HOLA MUNDO. Para finalizar con este punto se indicar las secuencias antecedidas por \ que se pueden incluir en una instruccin printf: +-------------------------+------+ | Nueva lnea | \n | | Tabulador horizontal | \t | | Tabulador vertical | \v | | Backspace (<-) | \b | | Retorno de carro | \r | | Avance de pgina | \f | | Pitido (alerta) | \a | | Caracter \ | \\ | | Caracter ? | \? | | Caracter ' | \' | | Caracter " | \" | | Nmero Octal (ooo) | \ooo | | Nmero Hexadecimal (hh) | \xhh | +-------------------------+------+ Algunos comentarios sobre estos cdigos. En primer lugar el primer grupo (hasta carcter \), eran utilizados para mover el cursor en terminales. Los terminales podan ser una pantalla o una impresora, esta es la razn por la que nos encontramos cosas como avance de pgina o retorno de carro. Los caracteres \ ? ' " son especiales puesto que se utilizan dentro del mensaje a visualizar para indicar como se visualiza, o sea, si escribimos \ el compilador buscar el siguiente carcter y si es alguno de los anteriores los visualiza sino corresponde con ninguno simplemente lo ignora, con lo cual no podramos visualizar el carcter \. Otro tanto sucede con las comillas puesto que para C, las comillas representan una cadena de caracteres, si escribimos " en nuestro mensaje se considerar que ste termina ah con lo que lo que se encuentre despus no tendr sentido para el compilador. Por ltimo Nmero octal y Nmero hexadecimal nos permite introducir directamente el cdigo numrico en octal o hexadecimal del carcter que deseemos visualizar, dependiendo del que est activo en nuestro computador. En general el cdigo utilizado para representar internamente los caracteres por los computadores es el cdigo ASCII (American Standard Code for Information Interchange). En este segundo tema hemos aprendido a escribir programas que visualicen en pantalla mensajes utilizando la funcin estndar de la librera stdio. Antes de terminar unos breves comentarios. El lenguaje C diferencia entre maysculas y minsculas con lo cual main, MAIN, MaIn, seran identificadores distintos para el compilador, en general en C se suele escribir todo en minsculas a excepcin de los mensajes a visualizar (cuyo uso depende del programador) y de las constantes, esto no tiene por que hacerse as pero digamos que se trata de una tradicin de la programacin en C. Las separaciones entre lneas tambin son arbitrarias y su nica funcin es facilitar la legibilidad del cdigo, sirviendo de separadores entre fragmentos de programa relacionados entre s. Y esto es todo por ahora.

1.2 Tipos de datos bsicos del C La mayora de los programas realizan algo til y generalmente para ello es necesario trabajar con grandes cantidades de datos, si queremos realizar un programa que nos calcule un sistema de ecuaciones tenemos que indicar cuales son las ecuaciones para que el programa las resuelva. Por tanto un programa estar constituido por una serie de datos y una serie de sentencias o instrucciones que le dicen lo que tiene que hacer con esos datos. Los lenguajes de programacin disponen de una serie de tipos de datos bsicos, y proporcionan herramientas para crear estructuras a medida que faciliten el acceso a la informacin. As en nuestro caso ficticio de resolver un sistema de ecuaciones podemos almacenar los coeficientes de cada ecuacin con lo que utilizaramos como tipo de dato los nmeros, si plantesemos el problema desde un punto de vista matricial nos interesara tener un tipo de datos matriz y lo ideal sera tener un tipo de datos ecuacin. En este apartado describiremos los tipos bsicos que proporciona el lenguaje C y dejaremos para temas posteriores la declaracin de tipos complejos. Estos tipos bsicos son los siguientes: +--------+---------------------------------------+ | int | Representa nmeros enteros. | | float | Representa nmeros decimales. | | double | Representa nmeros decimales de mayor | | | precisin. | | char | Representa caracteres. | +--------+---------------------------------------+ Aunque el tipo char represente caracteres internamente para el computador no es ms que un nmero comprendido entre 0 y 255 que identifica un carcter dentro de el cdigo especificado para tal propsito en el sistema en el que nos encontremos trabajando. El cdigo ms utilizado para este tipo de representacin es el ASCII ya mencionado anteriormente. NOTA: Segn la mquina, el compilador empleado y las opciones de compilacin activas, "char" puede interpretarse con signo o sin signo. Esto es, de -128 a 127 o desde 0 a 255. Si se requiere una representacin que no dependa de las opciones del compilador, etc., se puede poner "signed char" o "unsigned char", segn el caso. Como decamos antes el ordenador debe de disponer de los datos necesarios para resolver el problema para el que lo queramos programar. Difcilmente se podra resolver un sistema de ecuaciones si no se dispone de stas. Para ello podemos definir variables. Las variables almacenan valores de un tipo especificado y en ellas almacenamos los datos de nuestro problema, se denominan variables por que su valor puede cambiar a lo largo del programa. Para referenciar una variable especificada es necesario que la podamos identificar para ello se utiliza un nombre o identificador que no es ms que una secuencia de caracteres, esta secuencia no puede contener caracteres espaoles (acentos y ees), ni caracteres que tengan alguna funcin especial en el C como por ejemplo los caracteres que representan operaciones matemticas +, -, etc..., tampoco pueden contener espacios por lo que se suele utilizar el carcter subrayado (_) si el identificador que deseamos asignarle a nuestra variable est formado por varias palabras, pero en general con los caracteres y nmeros tenemos suficiente para dar nombres

autoexplicativos, aunque los nmeros no pueden comenzar el nombre de una variable. Las variables se suelen escribir con letra minscula aunque no es necesario y es aconsejable que los nombres sean auto explicativos para que resulte ms sencillo leer el programa (es mejor llamar resultado a una variable que x). Las variables se declaran indicando el tipo que van a tener seguido de su identificador y terminando la lnea con un punto y coma. Algunos ejemplos: int numero; /* nmero no sera un nombre vlido */ float mi_variable; char letra; Si necesitamos declarar varias variables de un mismo tipo se pueden incluir en la misma lnea todos los nombres que deseemos separndolos por una coma: int float numero1,numero2,numero3; coordenada_x,coordenada_y;

El compilador tiene que conocer las variables que va ha utilizar cada bloque para reservarles sitio, por ello las variables se suelen declarar al principio de cada bloque. Puesto que an no sabemos como definir funciones nuestro programa slo dispone de un bloque (el main()) con lo que nuestras variables deben de declararse al comienzo del main() dentro del bloque, es decir, inmediatamente a continuacin de la llave abierta ({). Un ejemplo: NOTA: Aunque el prrafo anterior da a entender que se puede declarar una variable en cualquier momento, el estndar ANSI C obliga a realizar las declaraciones al principio de cada bloque. En el caso se variables globales la sintaxis es ms flexible, para poder utilizar el "Scope" en nuestro provecho. main() { int numero; numero =20; } Podemos tambin declarar variables fuera del bloque main(). Estas variables se conocen como variables globales y cualquier funcin puede acceder a ellas, como slo tenemos una funcin (main) en este caso nos dara igual declarar las variables dentro o fuera de main. De poco nos serviran estos datos numricos si no pudisemos realizar operaciones con ellos, el lenguaje C permite realizar las operaciones bsicas con estas variables de tipo numrico, estas son: +---+---------------------------------------------+ | + | para indicar suma | | - | para indicar resta | | * | para indicar producto | | / | para indicar divisin | | % | para indicar mdulo (resto divisin entera) | +---+---------------------------------------------+

Podemos combinar estas operaciones en la forma que nos plazca con variables o constantes (podemos operar variables con nmeros fijos) y utilizar los parntesis, caracteres ( y ) para indicar precedencia de las operaciones como lo haramos en una expresin matemtica normal. En principio slo podemos realizar operaciones con variables que sean del mismo tipo, aunque en general podemos operar los tipos float y double con tipos int o incluso char, en principio no podramos almacenar un valor float (un nmero real) en una variable int (entera), para ello tendramos que convertir ese nmero real en entero de alguna forma. Podemos convertir tipos bsicos utilizando la facilidad del C conocida como cast. Esta facilidad simplemente consiste en indicar antes de una variable o constante el tipo al que lo deseamos convertir entre parntesis y el compilador se encargar del resto. Un ejemplo: NOTA: El C tambin define la conversin automtica de tipos. float int a; b;

b=30; a=(float)b; Para ejemplificar todo esto vamos a realizar un programa que nos calcule el espacio recorrido por un mvil con velocidad uniforme durante un tiempo determinado. El programa sera algo as: #include <stdio.h> main() { float

e,v,t;

v = 30; /* Velocidad del mvil en Km/h */ t = 5; /* Tiempo durante el cual se mueve */ e = v*t; printf ("\nVelocidad : %f\nTiempo : %f",v,t); printf ("\nEspacio recorrido : %f",e); } Este programa calcula el espacio recorrido por un mvil en movimiento uniforme a una velocidad indicada por la variable v, durante un tiempo indicado por la variable t. Lo ms interesante de este programa es que hemos utilizado la funcin printf para visualizar valores de variables. Como decamos ms arriba la funcin printf permite visualizar mensajes formateados, es decir, en la cadena de caracteres entre comillas dentro del parntesis nos indica la forma en la que se visualizarn los datos. Ya hablamos de los caracteres especiales como \n, ahora nos ocuparemos de la visualizacin de las variables. Cuando deseamos visualizar una variable debemos indicarlo en la cadena de formateo (la secuencia de caracteres entre comillas) mediante el carcter % seguido de un carcter que nos indica el tipo de dato a visualizar. Estos tipos son los siguientes:

+-----+------------------------------------------------+ | d,i | Entero en notacin decimal con signo. | | o | Entero notacin octal sin signo. | | x,X | Entero en notacin hexadecimal sin signo. | | u | Entero en notacin decimal sin signo. | | c | Entero como caracter simple. | | s | Cadena de caracteres. | | f | Tipo double ( float) de la forma [-]mmmm.ddd. | | e,E | Tipo double ( float) en notacin cientfica o | | | exponencial [-]m.dddde+-xx [-]m.ddddE+-xx. | | p | Tipo puntero. | +-----+------------------------------------------------+ Podemos as mismo indicar el nmero de cifras a visualizar, intercalando entre el % y la letra que identifica el tipo de dato un nmero. Si indicamos dos nmeros separados por un punto, el primer nmero indica el nmero total de caracteres a visualizar y el segundo el nmero de cifras decimales que queremos que se muestren, obviamente este formato slo se utilizar con tipo float o double. Algunos ejemplos: printf ("%f",numero); Visualiza un nmero real en el formato normal, parte entera y parte decimal separadas por un punto. printf ("%5.2f",numero); Visualiza un nmero entero en el mismo formato que la anterior, pero slo visualizando 5 cifras y siendo dos de ellas reservadas para la parte decimal. Hasta ahora hemos visto como decir a la funcin printf el formato en el que debe visualizar los datos, pero no le hemos dicho que datos debe visualizar. Lo que se escribir en el lugar indicado por el % est especificado a continuacin de la cadena de formateo entre comillas, separando cada una de ellas por comas. El primer % coincide con el primer parmetro despus de las comillas, el segundo con el segundo y as sucesivamente. De esta forma un programa como este: #include <stdio.h> main() { int i; float a,b; i = 10; a = 30.456; b = 678.12; printf ("\nvar1:%d var2:%6.2f var3:%6.1f",i,a,b); } Tendr como salida: var1:10 var2:30.46 var3:678,1

Como se puede observar en el ejemplo si la precisin especificada en la cadena de formateo es menor que la real del nmero, la funcin printf aproxima a la precisin especificada. 1.3 Entrada de datos por teclado El programa anterior para el clculo de el espacio funciona correctamente, pero cada vez que deseemos calcular un nuevo espacio debemos dar valores a las variables v y t y recompilar nuestro programa. Sera estupendo poder leer por teclado los datos de la velocidad y del tiempo y as permitir a nuestro programa trabajar con cualquier valor de velocidad y tiempo. Aqu es donde realmente se comprende de donde viene el nombre de variables. Para leer los datos por teclado se utiliza la funcin scanf cuya definicin se encuentra tambin en el fichero stdio.h. El formato es idntico al de printf utilizando una cadena de formateo con los caracteres % y la letra que indica el tipo, ahora del dato que vamos a leer, seguido de la variable en la que deseamos que se lea antecedida por el carcter &. Nuestro programa del mvil se convierte ahora en: #include <stdio.h> main() { float

v,t,e;

printf ("\nDime la velocidad de el mvil:"); scanf ("%f",&v); printf ("\Dime el tiempo :"); scanf ("%f",&t); e = v*t; printf ("\nUn mvil desplazndose a %5.2f Km/h durante %5.2f horas, recorre una distancia de %5.2f Km",v,t,e); } Ahora cada vez que el programa se ejecute nos pedir que introduzcamos los valores de la velocidad y el tiempo y nos proporcionar el espacio que recorreramos, evitando de esta forma la necesidad de recompilar el programa cada vez que deseemos realizar un nuevo clculo. TEMA 2: Control de Flujo de programa 2.0 Introduccin En tema anterior aprendimos a trabajar con variables, leer sus valores por teclado, visualizarlas en pantalla y realizar operaciones elementales con ellas. Los programas que escribimos hasta ahora se ejecutaban secuencialmente, es decir, instruccin tras instruccin, sin posibilidad de volver a ejecutar una instruccin ya ejecutada o evitar la ejecucin de un grupo de instrucciones si se dan unas caractersticas determinadas.

En este tercer tema se describirn las instrucciones que nospermiten escribir programas que no se ejecuten de una forma secuencial en el sentido explicado en el prrafo anterior. 2.1 Expresiones condicionales. En ciertas ocasiones nos puede interesar que un programa llegado a un punto de su ejecucin vuelva hacia atrs y se ejecute de nuevo, pero lo que en general nos interesar ser que este regreso a una lnea de terminada de nuestro cdigo se realice si se cumple una cierta condicin. Por esta razn es necesario explicar, antes de comenzar con las instrucciones propiamente dichas de control de flujo de programa, como le indicamos al ordenador que deseamos evaluar una condicin. Las expresiones que nos permiten realizar sto reciben el nombre de expresiones condicionales o booleanas. Estas expresiones slo pueden tomar dos valores: VERDADERO (TRUE) o FALSO (FALSE). En general un valor de 0 indica que la expresin es falsa y un valor distinto de 0 indica que la expresin es verdadera. Como hemos indicado se trata de expresiones condicionales, y anlogamente a las expresiones aritmticas podemos comparar variables entre s, constantes entre s (lo cual no es muy til puesto que si conocemos los dos valores ya sabemos la relacin que existe entre ambas constantes) y por supuesto variables y constantes. Adems podemos agrupar condiciones entre s formando expresiones ms complejas y ayudarnos de los parntesis para indicar el orden de evaluacin. Los operadores condicionales son: == != > < >= <= Representa igualdad. Representa desigualdad Mayor que. Menor que. Mayor o igual que. Menor o igual que.

Podemos encadenar distintas expresiones condicionales, las cuales deben de ir entre parntesis (comparamos de dos en dos) utilizando los operadores: && || Y lgico. O lgico.

Veamos un ejemplo de expresin condicional (a==2)||((b>=0)&&(b<=20)) la expresin ser cierta si la variable a es igual a dos o si la variable b tiene un valor comprendido entre 0 y 20. 2.1.1 La instruccin if... else. En ingls if significa si condicional, por ejemplo, si llueve me llevar el paraguas, else significa sino, sino llueve me ir a la playa. Este es el significado que poseen en programacin. Su sintaxis es: if (condicin) instruccin;else instruccin;

NOTA: La sintaxis real del bloque else bloque.

IF es la siguiente: if (condicin)

Un programa ejemplo nos indicar su funcionamiento con claridad. Supongamos que deseamos dividir dos nmeros. El nmero por el que dividimos no puede ser cero, sto nos dara un valor de infinito, provocando un error en el ordenador. Por tanto antes de dividir deberamos de comprobar si el divisor es cero. El programa sera algo como esto: #include <stdio.h> main() { float

dividendo,divisor;

printf ("\nDime el dividendo:"); scanf ("%f",ndo); printf ("\nDime el divisor:"); scanf ("%f",&divisor); if (divisor==0) printf ("\nNo podemos dividir un nmero por 0"); else printf ("\nEl resultado es: %f",dividendo/divisor); } Como en todas los comandos del lenguaje C una instruccin, en general, puede ser solamente una o un conjunto de ellas incluidas entre llaves. Por ltimo el lenguaje C dispone de un operador ternario (de tres elementos) que permite construir determinadas estructuras if-else, en concreto toma un valor u otro dependiendo de una expresin condicional. Su sintaxis es: exp1 ? exp2 : exp3 Si exp1 es cierta la expresin tomar el valor exp2, sino tomar el valor exp3. Un ejemplo de su utilizacin: /* La variable z toma el valor mximo entre a y b */ z = ( (a>b) ? a : b); Como se puede observar se trata de una secuencia if else pero muy concreta, probablemente el compilador generar un cdigo mucho ms eficiente para este tipo de secuencia de ah su inclusin en el juego de operadores del C. A continuacin se describirn las instrucciones que nos permiten controlar el flujo de programa, en las cuales tendremos que utilizar expresiones condicionales continuamente, por lo cual no insistiremos ms en este tema. 2.2 Control del flujo de programa 2.2.0 Introduccin

A estas alturas el lector ya debera conocer lo que es el flujo de programa. El flujo de programa es la secuencia de instrucciones que un programa ejecuta desde su comienzo hasta que finaliza. En principio la ejecucin es secuencial, comienza con la primera instruccin y termina con la ltima. Sin embargo es comn que nos interese que nuestro programa no termine con la ltima de las instrucciones (si por ejemplo no podemos abrir un fichero y la funcin del programa es modificar ese fichero, el programa no debera realizar ninguna operacin y terminar al detectar el error), o puede que nos interese que un grupo de instrucciones se ejecute repetidamente hasta que le indiquemos que pare. Todo esto se puede conseguir con las instrucciones que se describirn a continuacin. 2.2.1 Creacin de bucles de ejecucin. 2.2.1.0 Concepto de bucle En la introduccin ya se ha mencionado lo que es un bucle. Una secuencia de instrucciones que se repite un nmero determinado de veces o hasta que se cumplan unas determinadas condiciones. Los bucles son extremadamente algunos ejemplos son: tiles en nuestros programas, de datos,

* Lectura/Visualizacin de un nmero determinado como por ejemplo una matriz.

* A veces se hace necesario introducir esperas en nuestros programas ya sea por trabajar con un perifrico lento o simplemente por ralentizar su ejecucin. Los primeros se llaman bucles de espera activa y los segundo bucles vacos. * En aplicaciones grficas rellenado de polgonos. como trazado de lneas o

* Lectura de datos de un fichero... A continuacin describiremos las opciones que nos proporciona el lenguaje de programacin C para crear y gestionar los bucles. 2.2.1.1 Bucle for La primera opcin de que disponemos es el bucle for. Este tipo de instruccin se halla presente en la mayora de los lenguajes de programacin estructurados, y permite repetir una instruccin o conjunto de instrucciones un nmero determinado de veces. Su sintaxis es como sigue: for (exp1;exp2;exp3) instruccin; exp1 es una expresin que slo se ejecuta una vez al principio del bucle. El bucle for suele utilizarse en combinacin con un contador. Un contador es una variable que lleva la cuenta de las veces que se han ejecutado las instrucciones sobre las que acta el comando for. Por tanto exp1 suele contener una expresin que nos permite inicializar ese contador generalmente a 0 aunque eso depende de para qu deseemos utilizar el bucle.

exp2 es la expresin que nos indica cuando debe finalizar el bucle, por tanto se tratar de una expresin condicional. Su interpretacin sera algo como; repite la instruccin (o instrucciones) mientras se cumpla exp2. Esta expresin se evaluar en cada ciclo del bucle para determinar si se debe realizar una nueva iteracin. NOTA: Hay que recordar que bucle, y no al final. Por tanto NINGUNA vez. exp2 se evala al principio del es posible no ejecutar el bucle

exp3 es una expresin que se ejecuta en cada iteracin. Puesto que como ya indicamos el bucle for se utiliza junto a un contador, exp3 en general contiene una instruccin que actualiza nuestro contador. Por tanto en un bucle diferenciadas: con contador distinguimos tres partes

* La inicializacin del contador (exp1). * La condicin de fin de bucle (exp2). * Y la actualizacin del contador (exp3). El bucle for esta especialmente pensado para realizar bucles basados en contadores. Se puede utilizar en bucle del tipo "repite esto hasta que se pulse una tecla", pero para estos tenemos instrucciones ms apropiadas. Veamos unos ejemplos que nos permitan comprender ms fcilmente el funcionamiento del comando for. Ejemplo 1: Contar hasta diez. #include <stdio.h> main() { int i; /* Esta variable la utilizaremos como contador*/ for (i=0;i<10;i++) printf ("\n%d",i); } Este programa mostrar en pantalla numeros de 0 a 9 (diez en total). exp1 inicializa nuestro contador que en este caso es una variable de tipo entero, con el valor 0, exp2 nos dice que nuestra instruccin (la funcin printf) debe repetirse mientras el contador sea menor que diez y finalmente exp3 indica que el contador debe de incrementarse en una unidad en cada ciclo del bucle. Nos podra interesar contar desde diez hasta 1, en este caso el comando for debera de ser: for (i=10;i>0;i--) printf ("\n%d",i); Ejemplo 2: Visualizar dos tablas de multiplicar en pantalla. #include <stdio.h> main() {

int int

i; tabla1,tabla2;

tabla1 = 2; /* Primero la tabla del dos */ tabla2 = 5; /* y a continuacin la tabla del cinco*/ for (i=1;i<11;i++) { printf ("\n %2dx%2d=%3d",tabla1,i,tabla1*i); printf (" %2dx%2d=%3d",tabla2,i,tabla2*i); } } El ejemplo es anlogo al anterior, pero en este caso visualizamos valores desde uno a diez, en lugar de visualizarlos de 0 a 9. En este ejemplo, el bucle acta sobre un conjunto de instrucciones, no sobre una sola, por tanto debemos introducirlas entre las llaves para indicar al compilador que la instruccin for acta sobre las dos instrucciones. Estamos considerando todo lo que se encuentre entre las llaves como una sola instruccin. Para terminar con los bucles de tipo for un leve comentario sobre los bucles aadidos, simplemente lo que se hace es incluir un bucle dentro de otro. Supongamos que deseamos introducir los datos de nuestros jugadores preferidos por teclado para almacenarlos en el ordenador, y que de cada jugador queremos almacenar por ejemplo, su nacionalidad, su peso y su altura. En este caso nos sera til un bucle anidado. El bucle exterior nos contara jugadores, mientras que para cada jugador tendramos otro bucle que nos leyera los tres datos que necesitamos. Si tenemos veinte jugadores preferidos, incluiramos una unas instrucciones como estas: for (i=0;i<20;i++) { printf ("Jugador preferido %d",i); for (j=0;j<3;j++) { leo caracterstica j; la almaceno donde sea; } } Nada ms en lo que a bucles de tipo for respecta. A continuacin veremos las otras estructuras que nos proporciona el lenguaje C para la realizacin de bucles. 2.2.1.2 Bucles while. La sintaxis de este bucle ser algo as: while (exp2) instruccin; En ingls while significa mientras, por tanto la lnea anterior significara mientras de cumpla exp2 ejecuta la instruccin. Obviamente la instruccin que ejecutemos debe de permitir que en algn caso se cumpla exp2, de lo contraro el ordenador permanecera eternamente ejecutando instruccin. Tambin es evidente que exp2 debe ser una expresin condicional. Como vemos este tipo de bucles no est orientado a contadores, es mucho ms

genrico, sin embargo se puede utilizar de forma anloga a for. Con la nomenclatura utilizada anteriormente tendramos algo como esto: exp1; while (exp2) { }

instruccin; exp3;

Con este esquema se hace patente la utilidad de la instruccin for para bucles con contador puesto que se "centraliza" todo el proceso de gestin del contador (inicializacin y actualizacin) en una sola instruccin. Un error comn al escribir un bucle con contador con una estructura while es olvidar introducir exp3, con lo cual nunca se cumple exp2 y nunca salimos del bucle. De nuevo un bucle infinito aunque a veces nos interesa tener un bucle infinito. La forma ms sencilla de realizar un bucle infinito es con la expresin: while (1) instruccin; Como indicamos exp2 es una expresin condicional y para estas expresiones un valor distinto de 0 es verdadero por tanto un 1 es siempre cierto y no hay manera de salir del bucle puesto que es una constante y ninguna modificacin de variables por parte de instruccin tendra repercusiones sobre ella. Los bucle while son tiles en aplicaciones como; lee datos de este fichero mientras no llegues al final muestra estos datos en la pantalla mientras no pulse una tecla. Una peculiaridad de esta instruccin es que puede no ejecutarse nunca la instruccin afectada por el while. Algunos ejemplos: Ejemplo 1: Contar hasta diez. #include <stdio.h> main() { int

i;

i = 0; while (i<10) { printf ("\n%d",i); i++; } }

El primer valor que visualizaremos ser el 0. Cuando i tenga el valor nueve la condicin i<10 todava se cumple por lo que entraremos en el bucle de nuevo, visualizaremos el nueve e incrementamos i con lo que pasa a tener el valor 10 momento en el cual se vuelve a evaluar la expresin i<10 que en este caso sera falsa y no volveramos a entrar en el bloque de instrucciones (las que estn entre llaves). As visualizamos nueve nmero de 0 a 9 como antes. Si incluysemos una instruccin para visualizar el valor de

antes de abandonar el programa (justo antes de las ltimas llaves el valor que se mostrara sera 10. Ejemplo 2: Lee nmeros enteros hasta que se valor hasta que se introduzca el valor 0. #include <stdio.h> main() { int introduzca el

numero;

numero = 10; while (numero!=0) { printf ("\nDime un nmero:"); scanf ("%d",&numero); } } En este ejemplo tenemos que introducir en la variable nmero un valor distinto de cero antes de entrar en el bucle, puesto que en principio al declarar una variable el valor de sta no est determinado y podra valer cero, en cuyo caso nunca se ejecutara el bucle. Esta es la misin de la instruccin numero = 10;. 2.2.1.3 Bucle do.. while Su funcionamiento es anlogo al anterior, con la nica salvedad de que la condicin ahora se evala despus de ejecutar la instruccin su sintaxis sera: do instruccin while (exp2); Si en el ejemplo anterior utilizamos esta estructura no sera necesario actualizar numero con un valor distinto de cero, puesto que antes de comprobar si es cero leeramos el valor. #include <stdio.h> main() { int do

numero; { printf ("\nDime un numero :"); scanf ("%d",&numero); } while (numero !=0);

La diferencia fundamental con la instruccin anterior es que esta estructura ejecuta la instruccin sobre la que acta al menos una vez. 2.2.1.4 Modificadores del flujo de programa. Puede que en ciertas ocasiones no nos interese que si se da alguna condicin slo se ejecute un conjunto de todas las instrucciones sobre las que acta el bucle o simplemente salir del

bucle antes de llegar a la condicin que hayamos indicado en el comando for o en while. Esto es posible mediante dos modificadores: continue y break. El primero de ellos, continue, permite volver a reevaluar la condicin de salida, es decir, despus de ejecutar continue la siguiente instruccin que se ejecutar ser el for o el while. Veamos un ejemplo de aplicacin para comprender mejor como funciona este comando. #include <stdio.h> main() { int int

numero; contador;

contador =0; do { printf ("\nIntroduce el nmero %2d:",contador); scanf ("%d",&numero); if ((numero<0)||(numero>20)) continue; contador++; } while (contador<50); } Este programa lee nmeros en una variable hasta un mximo de 50, alcanzado este mximo el programa termina. Adems si el nmero no est entre 0 y 20 (si es menor que 0 o mayor que 20) vuelve a pedir que lo introduzcamos. El comando continue en la instruccin if obliga al programa a saltar a la instruccin while donde se vuelve a evaluar la condicin, sin pasar por la lnea en la que se incrementa el contador. De esta forma se nos vuelve a pedir el mismo nmero y la entrada incorrecta no es tenida en cuenta. La funcin de break es ligeramente distinta no salta a la instruccin en la que se evala la condicin sino que simplemente abandona el bucle y contina la ejecucin en la lnea inmediatamente siguiente al bucle. #include <stdio.h> main() { int

i;

for (i=0;i<20;i++) { if (i==5) break; printf ("\n%d",i); } printf ("\n\n%d",i); } La salida de este programa sera algo como esto: 0 1 2

3 4 5 Y con esto terminamos todo lo relacionado con los bucles en lenguaje C. Los bucles son una estructura bsica y es necesario utilizarla en la inmensa mayora de los programas. 2.2.2 Mens de Opciones. 2.2.2.1 Introduccin La mayora de los programas permiten realizar una serie de operaciones sobre datos. Lo ideal sera poder indicarle al ordenador que operacin deseamos realizar sobre estos datos en lenguaje natural. Por ejemplo, para una base de datos nos podra interesar decirle al ordenador: "Bscame todas la fichas de libros que traten sobre informtica" "Borra de la base de datos el libro tal". Existen en la actualidad herramientas de este tipo, pero an distan bastante del lenguaje natural, por ello una de las formas ms sencillas de indicar al ordenador la operacin que deseamos realizar es utilizar un men de opciones. La otra solucin comnmente adoptada es una lnea de comandos, es decir, escribimos una frase en un lenguaje muy reducido indicando al ordenador lo que deseamos hacer (de una forma similar a como lo hacemos en MS-DOS). Esta solucin tiene la desventaja de tener que aprender complicados comandos y distintas secuencias para distintos programas. Un men nos muestra en pantalla todas las opciones que podemos realizar con nuestro programa de forma que no es necesario que el usuario conozca ninguna serie de ordenes complejas, simplificando por tanto el uso de los programas por parte de los usuarios. 2.2.2.2 Sentencia switch-case-default La mayora de los lenguajes de alto nivel disponen de alguna instruccin que permite gestionar el valor de una variable de una forma estructurada y sencilla, permitiendo por tanto la creacin de mens de programa de una forma sencilla. En lenguaje C esta instruccin es switch-case-default. Veamos un ejemplo de como funciona mediante un pequeo programita. #include <stdio.h> main() { int printf printf printf printf printf

opcion; ("\nEjemplo de Men de Programa"); ("\n1.-Cargar fichero de datos"); ("\n2.-Almacenar fichero de datos"); ("\n3.-Modificar datos"); ("\n4.-Salir");

printf ("\n\nDime tu opcin :");scanf ("%d",&opcion);

switch (opcion) { case 1: /* Cdigo para cargar fichero de datos*/ break; case 2: /* Cdigo para almacenar datos */ break; case 3: /* Cdigo para modificar datos */ break; case 4: /* Salir del programa */ exit (0); default : printf ("\nSu opcin no est disponible"); printf ("\nIntntelo con otra");

Del ejemplo se deduce fcilmente el funcionamiento de esta secuencia. El comando switch (var) realizar una bifurcacin o salto a la lnea indicada por la variable var. Si var vale 2, el programa se seguir ejecutando a partir de la lnea marcada con case 2. Todos los separadores case estn separador por un comando break, ya que de no ser as la ejecucin seguira lineal hasta encontrar la llave que termina el comando switch. La palabra clave default indica el cdigo que se debera ejecutar si el valor de la variable especificada en el switch no corresponde con ninguno de los indicados por los case dentro del switch. As en nuestro ejemplo si opcion tiene un valor distinto de 1, 2, 3 4, se mostrar en pantalla un mensaje indicando que la opcin indicada no es correcta. La sintaxis de esta estructura sera: switch (variable) { case valor1-variable: cdigo asociado; case valor2-variable: cdigo asociado; . . case valorN-variable: cdigo asociado; default: cdigo asociado; } Dentro del cdigo asociado a cada opcin se debern incluir las instrucciones break adecuadas. Ya se explic el funcionamiento de break y continue cuando se habl de bucles. Su funcionamiento es anlogo para los comandos for, while, do-while, y switch. Un fragmento de completo sera: cdigo para la implementacin de un men

while (Opcion!=0) { /* Secuencia de printfs que muestran en pantalla el men. En este caso la opcin 0 debera ser salir */ switch (opcion)

{ /* Secuencia de cases */ default : /* Mostrar mensaje de error */

} }

Por su puesto las aplicaciones del comando switch van mucho ms all de la simple creacin de mens. Puede ser utilizada en cualquier aplicacin en la que se necesite realizar distintas operaciones dependiendo de un valor. Un ejemplo sencillo podra ser un programa que imprimiese textos en impresora. Podramos marcar en el texto mediante una secuencia especial como debera ser impreso el texto a continuacin. Por ejemplo: @N @n @C,@c @S,@s etc... Activa Negrita. Desactiva Negrita. Activa/desactiva cursiva. idem sibrayado

Leeramos estos valores provenientes del teclado o de un fichero y con algo de procesamiento y una instruccin switch con dicha variable, en cada case enviaramos a la impresora la secuencia adecuada para realizar cada una de la opciones. TEMA 3 : Estructuras de datos estticas 3.0 Introduccin. En este tema se describirn las herramientas que proporciona el lenguaje C para trabajar con tipos y estructuras de datos, flexibilizando de esta forma la creacin de programas por parte del programador. 3.1 Matrices Estticas. La matriz es una estructura de datos bsica dentro de los lenguajes de programacin y conceptualmente son identicas a sus homnimas matemticas. Por tanto una matriz es un conjunto de datos de un tamao definido que se encuentran consecutivos en memoria y en la que es posible el acceso al elemento que deseemos simplemente con indicar su posicin. La declaracin de una matriz en lenguaje C es como sigue: tipo_de_dato identificador[tamao1][tamao2]...; Dnde : tipo_de_dato: Es el tipo de datos que contendr la matriz. Hasta ahora slo conocemos los tipos bsicos de datos; int, float, double, char. Posteriormente veremos como definir nuestros propios tipos de datos. identificador: Es el nombre que le damos a la variable matriz y po el cual la referenciaremos en nuestro programa. [tamao] : Indica el nmero de elementos de tipo tipo_de_datos contendr la matriz identificador. Si definimos dos tamaos [tamao1][tamao2] nuestra matriz ser bidimensional.

Algunas declaraciones de matrices seran: /* Matriz de nmeros reales de 10x10 */ float matriz[10][10]; /* Matriz tridimensional de nmeros enteros 20x20x10 */ int Tridimensional[20][20][10]; Como ya se supondr el acceso a cada elemento de la matriz se realiza especificando su posicin, pero sta comienza a contarse desde el valor 0, es decir, la primera matriz que hemos definido (matriz) tendr elementos desde el [0][0] al [9][9]. Esto puede causar algunos mal entendidos cuando se trabaja con matrices estticas. Por ejemplo: a = matriz [2][1]; /* A toma el valor del elemento (2,1) comenzando a contar desde 0 o del (3,2) si consideramos que el primer valor de la matriz es el (1,1) */ tridimensional [5][16][1] = 67; /* Introduce el valor 67 en especificada */ Las variables de declaraciones, se pueden declaracin, ayudndose de inicializaciones mltiples. int matriz[2][3] = { }; la entrada de la matriz

tipo matriz como el resto de las inicializar en el momento de su las llaves ({}) para la inclusin de

{ 1,2,3 }, { 4,5,6 }

Estas lneas nos declararan una matriz llamada "matriz" de 2x3 elementos inicializada con los valores indicados. Las matrices son extremadamente tiles para trabajar con multitud de problemas matemticos que se formulan de esta forma o para mantener tablas de datos a los que se accede con frecuencia y por tanto su referencia tiene que ser muy rpida. Supongamos que estamos desarrollando un programa para dibujar objetos en tres dimensiones y ms que la exactitud de la representacin (aunque esto es muy relativo), nos interesa la velocidad. En la representacin de objetos tridimensionales se hace continua referencia a las funciones trigonomtricas seno y coseno. El clculo de un seno y un coseno puede llevar bastante tiempo as que antes de comenzar la representacin calculamos todos los senos y cosenos que necesitemos (por ejemplo con una resolucin de 1 grado -360 valores-) cada vez que necesitemos uno de estos valores accedemos a la matriz en lugar de llamar a la funcin que nos lo calcula. Veamos como sera nuestro programa (las funciones sin y cos se encuentran en la librera estndar math.h y sus paramtros estn en radianes). #include <stdio.h> #include <math.h> main() { float float int

senos[360]; /* Almacenamos senos */ cosenos[360]; i;

/* Inicializamos las matrices */ for (i=0;i<360;i++) { seno[i] = sin (3.14159*i/180); coseno[i] = cos (3.14159*i/180); } printf ("\nEl coseno de 30 es : %f",coseno[30]); printf ("\nEl seno de 30 es : %f",seno[30]); } 3.2 Tipos compuestos 3.2.0 Introduccin En muchas ocasiones nos interesara disponer de variables compuestas de otras variables y trabajar con ellas como si se tratasen de una sola. Un ejemplo tpico es una ficha de datos de una agenda. Necesitaramos una variable que nos almacenase el nombre, otra variable que nos almacenase la direccin, otra para el telfono y as sucesivamente para todos los datos que deseemos mantener. Podramos disponer de una variable para cada campo (cada una de las informaciones que componen nuestra ficha) pero esto resultara un tanto engorroso a la hora de su programacin. El lenguaje C dispone de mecanismos para trabajar con variables compuestas de otras variables con suma facilidad. Existen dos tipos bsicos: estructuras y uniones. 3.2.1 Estructuras de datos. Se trata de la forma ms verstil de trabajar con fichas de informacin. Veamos como se definen y posteriormente comentaremos todos los aspectos relevantes de ellas. struct [Nombre_de_la_estructura] { tipo1 campo1; tipo2 campo2; . . tipoN campoN; } [variable]; La palabra clave struct define una estructura. Por tratarse de un tipo de datos puede utilizarse directamente para definir una variable. La variable aparece entre corchetes puesto que puede ser omitida. Si se especifica una variable, estaremos definiendo una variable cuyo tipo ser la estructura que la precede. Si la variable no es indicada definimos un nuevo tipo de datos (struct Nombre_de_la_estructura), que podremos utilizar posteriormente. Si es el nombre de la estructura lo que se omite, tendremos que especificar obligatoriamente una variable que tendr esa estructura y no podremos definir otras variables con esa estructura sin tener que volver a especificar todos los campos. Lo que se encuentra dentro de las llaves es una definicin tpica de variables con su tipo y su identificador. Todo esto puede parecer un poco confuso pero lo aclararemos con unos ejemplos.

struct punto {

float x; float y; int color; } punto_de_fuga;

Aqu estamos definiendo una variable llamada punto_de_fuga cuyo tipo es una estructura de datos formada por tres campos y a la que hemos llamado punto. Dos de ellos son de tipo float y representan las coordenadas del punto, el tercer valor es un entero que indica el color de ese punto. En este caso hemos definido una variable y una estructura. Al disponer de un identificador para esta ltima podemos definir nuevas variables de esta estructura. struct punto struct punto origen1; final1;

Donde origen1 y final1 son variables de tipo struct punto que hemos definido anteriormente. Si en la definicin de punto_de_fuga no se hubiese incluido un identificador para la estructura (en este caso el identificador es punto), no podramos definir nuevas variables con esa estructura ya que no estara identificada por ningn nombre. Tambin podramos haber excluido el nombre de la variable (punto_de_fuga). En este caso lo que definiramos sera una estructura llamada punto que pasara a ser un nuevo tipo disponible por el usuario. As los tipos de variables de que dispondramos ahora seran: int float double char struct punto Por tanto podramos definir cualquier tipos o incluso definir matriz de estos tipos. struct punto matriz_de_puntos[30]; variable con estos

As estaramos definiendo una matriz de 30 elementos en la que cada elemento es una struct punto con sus tres campos. Lo que ahora nos interesa es saber como referenciar esos campos y acceder o modificar, por tanto la informacin que contienen. Esto se consigue separando el identificador del campo de la variable mediante un punto. As: punto_de_fuga.x = 0; punto_de_fuga.y = 0; punto_de_fuga.color = 10; inicializa la cada uno de los campos de la variable punto de fuga con sus valores correspondientes. Est claro que para acceder a los campos necesitamos alguna variable cuyo tipo sea nuestra estructura. Si no tenemos variable no tenemos informacin (sera como hacer int = 6).

En el caso de la matriz tenemos tantas variables de tipo struct punto como las indicadas, puesto que el punto separa el nombre de la variable del campo al que queremos acceder, la forma de modificar una entrada de la matriz sera: matriz_de_puntos[4].x = 6; matriz_de_puntos.x[4] = 6; /* No sera correcto */ Esta ltima declaracin estructura de un tipo como: struct otra { se podra utilizar con una

float x[10]; } matriz_de_puntos;

Con lo cual accederamos al cuarto elemento del campo x de matriz_de_puntos que es una variable de tipo struct otra constituida por una matriz de diez floats. Para terminar con la declaracin struct indicar que es posible la declaracin de estructuras anidadas, es decir, un campo de una estructura puede ser otra estructura. struct vector { float float float };

x; y; z;

struct poligono_cuadrado { struct vector p1; struct vector p2; struct vector p3; struct vecto p4; }; struct cubo { struct poligono_cuadrado int struct vector }; struct cubo mi_cubo;

cara[6]; color; posicion;

Hemos declarado una variable (mi_cubo) de tipo struct cubo que es una estructura conteniendo un valor entero que nos indica el color de nuestro objeto, una variable de tipo struct vector (posicin) indicando la posicin del objeto en un espacio de tres dimensiones (posicin tiene tres campos x,y,z por tratarse de una struct vector) y una matriz de seis elemento en la que cada elemento es un struct poligono_cuadrado, el cual est formado por cuadro vectores que indican los cuatro vrtices del cuadrado en 3D. Para acceder a todos los campos de esta variable necesitaramos sentencias del tipo.

mi_cubo.color = 0; mi_cubo.posicion.x = 3; mi_cubo.posicion.y = 2; mi_cubo.posicion.z = 6; mi_cubo.cara[0].p1.x = 5; /* Ahora acedemos a la coordenada 0 del tercer polgono de la cara 0 de mi_cubo*/ mi_cubo.cara[0].p3.z = 6; .... 3.2.2 Estructuras solapadas. Unin La definicin de una unin es anloga a la definicin de una estructura. La diferencia entre ambas es que los campos que especifiquemos en una unin ocupan todos la misma posicin de memoria. Cuando se declara una unin se reserva espacio para poder almacenar el campo de mayor tamao de los declarados y como ya se dijo todos los campos ocupan la misma posicin en la memoria. Veamos un ejemplo. union ejemplo { char int } mi_var; caracter; entero;

mi_var es una variable cuyo tipo es union ejemplo, y el acceso a cada campo de los definidos se realiza igual que en las struct mediante la utilizacin de un punto. Hasta aqu nada nuevo lo que sucede es que carcter y entero (los dos campos) ocupan la misma posicin de memoria. As: mi_var.entero = 0; /* Como el tipo int ocupa ms que el tipo char ponemos a 0 toda la union */ mi_var.caracter = 'A'; /* El cdigo ASCII de A es 65, por tanto ahora mi_var.entero = 65 */ mi_var.entero = 0x00f10; Esta ltima instruccin introduce un valor en hexadecimal en la variable mi_var.entero. El cdigo hexadecimal se representa en C anteponiendo al nmero los caracteres 0x. Para comprender lo que realiza esta instruccin veamos un poco como el ordenador representa los nmeros internamente. Todos hemos odo alguna vez que el ordenador slo entiende ceros y unos, pues bien, lo nico que significa sto es que el ordenador cuenta en base dos en lugar de hacerlo en base diez como nosotros. Cuando contamos en base diez comenzamos en 0 y al llegar a nueve aadimos una unidad a la izquierda para indicar que llegamos a las centenas y as consecutivamente. Cada cifra de un nmero en base diez representa esa cifra multiplicada por una potencia de diez que depende de la posicin del dgito. Es lo que se llama descomposicin factorial de un nmero. 63452 = 6*10^4+3*10^3+4*10^2+5*10^1+2*10^0= = 60000+3000+400+50+2 Como nuestro ordenador en lugar de contar de diez en diez cuenta de dos en dos cada cifra es una potencia de dos. El sistema de numeracin en base dos se denomina sistema binario.

b100101 = 1*2^5+0*2^4+0*2^3+1*2^2+0*2^1+1*2^0= = 32 + 0 + 0 + 4 + 1 = 37 As es como representa el ordenador el nmero 37 en su sistema binario. Cada una de las cifras de un nmero binario se denomina BIT (BInary digiT) y los ordenadores los suelen agrupar el grupos de 8. As 8 bits se denomina un byte, 16bits seran 2 bytes y se denomina word o palabra y as sucesivamente. El mayor nmero que podramos representar byte (8bits) sera: b11111111 = 255 Este es el tamao que el lenguaje C asigna al tipo char, que slo puede representar 256 valores distintos, desde 0 a 255. El tipo int short suele ocupar una palabra es decir, 16 bits. As con 16 bits el mayor nmero que podemos representar es: b1111111111111111 = 65535 NOTA: El tamao asociado a cada tipo de especfico de cada compilador/ordenador. No debera supuesto... datos es muy darse nada por en binario con 1

Los nmeros en binario rpidamente se hacen muy largos por ello se utilizan otros sistemas de numeracin que permitan una escritura ms compacta sin perter la informacin binaria en gran medida. Esto sistemas son en general sistemas con bases que son potencias de dos. As tenemos el sistema octal (base 8) y el sistema hexadecimal (base 16). Este ltimo es el ms ampliamente usado, disponemos de 16 cifras de 0 a F(15) y la caracterstica ms importante de este sistema es que cada cifra hexadecimal, representa cuatro bits binarios, con lo cual el paso de un sistema al otro es extremadamente fcil. Volvamos ahora a la instruccin anteriormente indicada mi_var.entero = 0x00f10; Si pasamos este nmero a binario obtenemos: 0 -> 0000 f -> 1111 -> 15 en decimal 1 -> 0001 -> 1 en decimal 0f10 <-> 0000111100010000 -> 3856 en decimal Como dijimos anteriormente un char ocupa 8 bits y un int ocupa 16, como la unin los solapa tendramos un esquema en la memoria del ordenador como ste: int char 0000111100010000 00010000 -> 3856 -> 65 ('A')

As mi_var.caracter contendr el valor A, pero mi_var.entero contendr el valor 3856.

NOTA: Como ya se indic en la nota anterior, el tamao asignado a cada tipo depende del ordenador y del compilador. Adems, algunos ordenadores almacenan los nmeros en formato Bajo/Alto (los 8 bits e Intel) y otros en formato Alto/Bajo (Motorola, Sparc, etc.). Este tipo de estructura se suele utilizar en aplicaciones a bajo nivel en la que es necesario poder utilizar este tipo de solapamiento de bits. Como ya se habr podido comprobar para comprender mnimamente como funciona esto es necesario bajar mucho al nivel de la mquina con la consiguiente complicacin de la explicacin. 3.2.3 Tipos definidos por el usuario. Con las palabras clave struct y union, podemos definir nuevos tipos de variables pero tenemos que indicar estos tipos con todo su nombre, es decir, struct mi_struct. El lenguaje C dispone de un comando que nos permite dar el nombre que nosotros deseemos a cualquier tipo de variable. El comando es typedef y su forma de utilizacin es como sigue: typedef tipo nuevo_tipo Algunos ejemplos para aclarar las cosas: typedef unsigned char typedef struct cubo BYTE; HEXAHEDRO;

As con estas definiciones una declaracin de las siguientes variables: BYTE HEXAEDRO var1,var2; var3;

Sera equivalente a: unsigned char struct cubo var1,var2; var3;

TEMA 4 : Punteros y funciones 4.0 Introduccin En este tema estudiaremos el tipo de dato ms importante dentro del lenguaje C. Los punteros. Absolutamente todos los datos en C pueden ser tratados como punteros y por ello este lenguaje proporciona una serie de importantes herramientas para trabajar con ellos. Adems introduciremos el concepto de funcin asociado estrechamente a la llamada programacin modular que nos permite crear un programa mucho ms claro y fcil de corregir a la hora de encontrar errores. 4.1 Punteros 4.1.1 Qu son los punteros ?

Como su nombre indica un puntero es algo que apunta, es decir, nos indica dnde se encuentra una cierta cosa. Supongamos (como otras tantas veces) que disponemos de un gran archivo en el que almacenamos informes. Este fichero est dividido en compartimientos, cada uno de los cuales contiene uno de nuestros informes (esto sera equivalente a las variables con las que hemos trabajado hasta ahora -informes-, la cuales contienen informacin, y el archivo representa la memoria de nuestro ordenador, obviamente las variables se almacenan en la memoria). Sin embargo otros compartimientos no contienen informes, sino que lo que contienen es una nota que nos dice dnde est ese informe. Supongamos que como mximo trabajamos con tres informes a la vez, digamos que no nos gusta leer demasiado, y reservamos, por tanto, tres compartimientos en los indicamos en que compartimiento se encuentran esos tres informes. Estos tres compartimientos seran nuestros punteros y como ocupan un compartimiento en el archivo (nuestra memoria) son realmente variables, pero variables muy especiales. Estas variables punteros ocupan siempre un tamao fijo, simplemente contienen el nmero de compartimiento en el que se encuentra la informacin. No contienen la informacin en s. Si en nuestro archivo pudisemos almacenar un mximo de 20.000 hojas, esta sera la capacidad de nuestra memoria (unos 19 Kilobytes). Estas hojas de nuestros informes las agruparamos de distintas formas. Quiz un informe slo ocupe 5 pginas mientras que otro puede ocupar 100. Podemos ver esto como los distintos tipos de datos del C, es lgico pensar que necesitamos ms espacio para almacenar un nmero real que uno entero o que una matriz de 20x20 elemento. Estos son nuestros informes en nuestro archivo. Sin embargo los punteros siempre ocupan lo mismo, en nuestro ejemplo nos llegara con una pgina para poder escribir el nmero del compartimiento en el que se encuentra el inicio del informe. As en nuestro supuesto de que slo trabajemos con tres informes a la vez, dispondramos de tres compartimientos en los que indicaramos dnde se encuentran esos informes que buscamos y de esta forma cuando terminemos con ellos y deseemos trabajar con otros slo tendremos que cambiar el contenido de esos tres compartimientos diciendo donde se encuentran los nuevos informes. De esta forma no es necesario reservar unos compartimientos para trabajar y cada vez que cambiemos de trabajo llevar los informes viejos a su compartimiento anterior y traer los nuevos informes a estos compartimientos. Esto es lo que en programacin se conoce como referencia indirecta o indireccin. Accedemos a la informacin a travs de un puntero que nos dice dnde se encuentra sta. Y a grandes rasgos esto son los punteros, referencias indirectas a datos en la memoria del ordenador. Los punteros en C son muy importantes puesto que su utilizacin es bsica para la realizacin de numerosas operaciones. Entre ellas: paso de parmetros que deseamos sean modificados, tratamiento de estructuras dinmicas de datos (esto es, variables que no se declaran en el programa y se crean durante la ejecucin del programa), cadenas de caracteres ... 4.1.2 Operadores que actan sobre punteros.

El lenguaje C proporciona dos operadores relacionados directamente con los punteros. El primero de ellos es el operador &. Ya hemos visto este operador antes en las llamadas a la funcin scanf, posteriormente explicaremos por que la funcin scanf necesita ser llamada con el operador &. El operador &, es un operador unario, es decir, acta sobre un slo operando. Este operando tiene que ser obligatoriamente una estructura direccionable, es decir, que se encuentre en la memoria del ordenador. Estas estructuras son fundamentalmente las variables y las funciones, de las que hablaremos posteriormente. Decimos que slo se puede aplicar sobre estructuras direccionables porque su funcin es devolver la posicin de memoria en la que se encuentra dicha estructura. En nuestro ejemplo nos indicara cual sera el compartimiento en el que se encuentra el informe que le indiquemos. El segundo operador es el *. Tambin se trata de un operador unario como el anterior y su funcin en este caso es la de permitir el acceso al contenido de la posicin indicada por un puntero. En nuestro ejemplo el operador * nos permitira leer o escribir el informe al que apunta uno de nuestros compartimientos punteros. Adems el carcter * se utiliza para declarar punteros los cuales como ya dijimos tienen que ser declarados (tienen su propio compartimiento en el archivo). Por supuesto el operador * debe ser aplicado sobre un puntero, mientras que el operador & sobre una estructura direccionable (variable o funcin). Veamos un ejemplo de su utilizacin: main () { int x,y; int *px;

/* Variables de tipo entero */ /* Puntero a una variable de tipo entero */

/* Leemos la direccin -compartimiento- de la variable -informe- x mediante & y lo almacenamos en la variable puntero px */ px = &x; /* px contiene la direccin en la que se encuentra x */ /* Utilizando el operador *, podemos acceder a su informacin. *px representa ahora el valor de la variable x */ *px = 10; /* Ahora x contiene el valor 10 */ y = 15; /* Si ahora hacemos que nuestro puntero apunte a la variable y utilizando de nuevo el operador & */ px = &y; /* El valor que ahora toma *px ser el valor de y puesto que es el compartimiento al que ahora estamos apuntando */ *px = 125; /* Ahora y contiene el valor 125 */ x = *px /* Ahora x contiene tambin 125 */ } Como hemos visto en este ejemplo es exactamente igual acceder a una variable que utilizar un puntero que apunte a ella (hacemos que apunte a ella mediante el operador &) junto con el operador *. trabajar Pero el lenguaje C an ofrece otra herramienta ms para con punteros. Es lo que se suele llamar aritmtica de

punteros. apartado.

Este tema

lo trataremos

en profundidad

en el siguiente

4.1.3 Punteros y matrices Ya hemos hablado de las matrices en el tema anterior. Se trataba de un conjunto de un nmero de terminado de variables de un mismo tipo que se referenciaban con un nombre comn seguido de su posicin entre corchetes con relacin al primer elemento. Todas las entradas de una matriz estn consecutivas en memoria, por eso es muy sencillo acceder al elemento que queramos en cada momento simplemente indicando su posicin. Slo se le suma a la posicin inicial ese ndice que indicamos. Es un ejemplo que casa perfectamente con nuestro ejemplo de los informes, cada informe podra ser considerado como una matriz de tantos elementos como pginas tenga el informe y en los que cada uno de ellos es un tipo de datos llamado pgina. Las matrices son realmente punteros al inicio de una zona consecutiva de los elementos indicados en su declaracin, por lo cual podemos acceder a la matriz utilizando los corchetes como ya vimos o utilizando el operador *. elemento[i] <=> *(elemento +i) Como ya se ha comentado todos los punteros ocupan lo mismo en memoria, el espacio suficiente para contener una direccin, sin embargo cuando se declaran es necesario indicar cual es el tipo de datos al que van a apuntar (entero, real, alguna estructura definida por el usuario). En nuestro ejemplo tendramos un tipo de puntero por cada tipo de informe distinto, un puntero para informes de una pgina, otro puntero para informes de 2 pginas y as sucesivamente. En principio esto es irrelevante por que una direccin de memoria es una direccin de memoria, independientemente de lo que contenga con lo cual no sera necesario declarar ningn tipo, pero esta informacin es necesaria para implementar la aritmtica de punteros que ejemplificaremos a continuacin. Supongamos que hemos definido un tipo de datos en nuestro programa que fuese pgina, si cada pgina puede contener 80 caracteres de ancho por 25 de alto, podra ser algo como sto: typedef char pgina[80][25]; Y supongamos tambin que slo tenemos tres informes, de 1 pgina, de 5 pginas y de 25 pginas: typedef pgina typedef pgina typedef pgina Y en nuestro siguientes variables: main() { pgina informe1 informe2 informe3 informe1; informe2[5]; informe3[25]; programa principal hemos declarado las tipos de

*punt_pgina; i1[10],*punt1; i3[5],*punt2; i4[15],*punt3;

Por tanto disponemos de un puntero a pginas y tres punteros, uno para cada tipo de informe y tres matrices de distintos tipos de informes que nos permiten almacenar en nuestro archivo un mximo de 30 informes (10 de 1 pgina, 5 de 5 pginas y 15 de 25 pginas). Supongamos que en el programa principal se llenan esas matrices con datos (por teclado o leyendo de un fichero, por ejemplo) y realizamos las siguientes operaciones: punt_pgina = (pgina *) &i4[0]; punt3 = (informe3 *)&i4[0]; Los cast (que comentamos en el tema 1) convierten las direcciones al tipo apropiado, las direcciones que contendrn punt_pgina y punt3 sern exactamente iguales, apuntarn al principio del primer informe de tipo3. Sin embargo punt_pgina es un puntero de tipo pgina y punt3 es un puntero de tipo informe3, qu significa esto?. Si ejecutsemos una instruccin como sta: punt_pgina = punt_pgina + 5; punt_pgina pasara a apuntar a la quinta pgina del primer informe de tipo 3 (i4[0]), puesto que punt_pgina es un puntero de pginas. Mientras que si la operacin fuese: punt3 = punt3 + 5; punt3 pasara a apuntar al quinto informe de tipo 3 (i4[5]), puesto que punt3 es un puntero a informes de tipo tres. Si ahora realizsemos la operacin: punt_pgina = (pgina *)punt3; Ahora punt pgina apuntara a la primera pgina del quinto informe de tipo 3. En esto consiste la aritmtica de punteros, cuando se realiza una operacin aritmtica sobre un puntero las unidades de sta son el tipo que se le ha asociado a dicho puntero. Si el puntero es de tipo pgina operamos con pginas, si es de tipo informes operamos con informes. Es evidente que un informe de tipo 3 y una pgina tienen distintos tamaos (un informe de tipo 3 son 25 pginas por definicin). Como hemos visto las matrices se pueden considerar como punteros y las operaciones con esos punteros depende del tipo asociado al puntero, adems es muy recomendable utilizar el cast cuando se realizan conversiones de un tipo de puntero a otro. 4.1.4 Punteros y cadenas de caracteres Como su propio nombre indica una cadena de caracteres es precisamente eso un conjunto consecutivo de caracteres. Como ya habamos comentado los caracteres se codifican utilizando el cdigo ASCII que asigna un nmero desde 0 hasta 255 a cada uno de los smbolos representables en nuestro ordenador. Las cadenas de caracteres utilizan el valor 0 ('\0') para indicar su final. A este tipo de codificacin se le ha llamado alguna vez ASCIIZ (la Z es de zero).

Las cadenas de caracteres se representan entre comillas dobles (") y los caracteres simples, como ya habamos indicado con comillas simples ('). Puesto que son un conjunto consecutivo de caracteres la forma de definirlas es como una matriz de caracteres. char identificador[tamao_de_la_cadena];

Y por ser en esencia una matriz todo lo comentado anteriormente para matrices y punteros puede ser aplicado a ellas. As la siguiente definicin constituye tambin una cadena de caracteres: char *identificador;

La diferencia entre ambas declaraciones es que la primera reserva una zona de memoria de tamao_de_la_cadena para almacenar el mensaje que deseemos mientras que la segunda slo genera un puntero. La primera por tratarse de una matriz siempre tiene un puntero asociado al inicio del bloque del tamao especificado. Podemos tratar a las cadenas como punteros a caracteres (char *) pero tenemos que recordar siempre que un puntero no contiene informacin slo nos indica dnde se encuentra sta, por tanto con la segunda definicin no podramos hacer gran cosa puesto que no tenemos memoria reservada para ninguna informacin. Veamos un ejemplo para comprender mejor la diferencia entra ambas declaraciones. Utilizaremos dos funciones especiales de stdio.h para trabajar con cadenas. Estas son puts y gets que definiramos como un printf y un scanf exclusivo para cadenas. #include main() { char char char <stdio.h> cadena1[10]; cadena2[10]; *cadena;

gets(cadena1); /* Leemos un texto por teclado y lo almacenamos en cadena 1 */ gets(cadena2); /* Idem cadena2 */ puts (cadena1); /* Lo mostramos en pantalla */ puts (cadena2); cadena = cadena1; /* cadena que slo es un puntero ahora apunta a cadena1 en donde tenemos 10 caracteres reservados por la definicin */ puts (cadena); /* Mostrara en pantalla el mensaje contenido en cadena1 */ cadena = cadena2; /* Ahora cadena apunta a la segunda matriz de caracteres */ gets(cadena); /* Cuando llenos sobre cadena ahora estamos leyendo sobre cadena2, debido al efecto de la instruccin anterior */ puts(cadena2); /* SI imprimimos ahora cadena2 la pantalla nos mostrar la cadena que acabamos de leer por teclado */ } un En el programa vemos como utilizamos cadena que solamente es puntero para apuntar a distintas zonas de memoria y utilizar

cadena1 o cadena2 como destino de nuestras operaciones. Como podemos ver cuando cambiamos el valor de cadena a cadena1 o cadena2 no utilizamos el operador de direccin &, puesto que como ya hemos dicho una matriz es en s un puntero (si slo indicamos su nombre) y por tanto una matriz o cadena de caracteres sigue siendo un puntero, con lo cual los dos miembros de la igualdad son del mismo tipo y por tanto no hay ningn problema. 4.2 Funciones 4.2.1 Introduccin Hasta el momento hemos utilizado ya numerosas funciones, como printf o scanf, las cuales forman parte de la librera estndar de entrada/salida (stdio.h). Sin embargo el lenguaje C nos permite definir nuestras propias funciones, es decir, podemos aadir al lenguaje tantos comandos como deseemos. Las funciones son bsicas en el desarrollo de un programa cuyo tamao sea considerable, puesto que en este tipo de programas es comn que se repitan fragmentos de cdigo, los cuales se pueden incluir en una funcin con el consiguiente ahorro de memoria. Por otra parte el uso de funciones divide un programa de gran tamao en subprogramas ms pequeos (las funciones), facilitando su comprensin, as como la correccin de errores. Cuando llamamos a una funcin desde nuestra funcin principal main() o desde otra funcin lo que estamos haciendo realmente es un salto o bifurcacin al cdigo que le hayamos asignado, en cierto modo es una forma de modificar el flujo de control del programa como lo hacamos con los comandos while y for. 4.2.2 Definicin de funciones Ya hemos visto cual es la estructura general de una funcin puesto que nuestro programa principal, main() no es otra cosa que una funcin. Veamos cual es el esquema genrico: tipo_a_devolver identificador (tipo1 parmetro1, tipo2 ...) { tipo1 Variable_Local1; tipo2 Variable_Local2; ... Cdigo de la funcin return valor del tipo valor a devolver; } Lo primero con lo que nos encontramos es la cabecera de la funcin. Esta cabecera est formada por una serie de declaraciones. En primer lugar el tipo_a_devolver. Todas las funciones tienen la posibilidad de devolver un valor, aunque pueden no hacerlo. Si definimos una funcin que nos calcula el coseno de un cierto ngulo nos interesara que nuestra funcin devolviese ese valor. Si por el contrario nuestra funcin realiza el proceso de borrar la pantalla no existira ningn valor que nos interesase conocer sobre esa funcin. Si no se especifica

ningn parmetro el compilador devuelve un valor entero (int).

supondr

que

nuestra

funcin

A continuacin nos encontramos con el identificador de la funcin, es decir, el nombre con el que la vamos a referenciar en nuestro programas, seguido de una lista de parmetros entre parntesis y separados por comas sobre los que actuar el cdigo que escribamos para esa funcin. En el caso de la funcin coseno a la que antes aludamos, el parmetro sera el ngulo calculamos el coseno de un cierto ngulo que en cada llamada a la funcin probablemente sea distinto. Vase la importancia de los parmetros, si no pudisemos definir un parmetro para nuestra funcin coseno, tendramos que definir una funcin para cada ngulo, en la que obviamente no indicaramos ningn parmetro. A continuacin nos encontramos el cuerpo de la funcin. En primer lugar declaramos las variables locales de esa funcin. Estas variables solamente podrn ser accedidas dentro de la funcin, esto es, entre las llaves ({}). Los nombres de las variables locales pueden ser los mismos en distintas funciones puesto que slo son accesibles dentro de ellas. As si estamos acostumbrados a utilizar una variable entera llamada i como contador en nuestro bucles, podemos definir en distintas funciones esta variable y utilizarla dentro de cada funcin sin que haya interferencias entre las distintas funciones. Con respecto al cdigo de la funcin, pues simplemente se trata de un programa como todos los que hemos estado haciendo hasta ahora. La instruccin return del final puede omitirse si la funcin no devuelve ningn valor, su cometido es simplemente indicar que valor tomara esa funcin con los parmetros que le hemos pasado. En otros lenguajes las funciones que no devuelven valores se conocen como procedimientos. Veamos un ejemplo de definicin de una funcin. int { int busca_elemento (int *vector,int valor,int longitud) i;

for (i=0;i<longitud;i++) if (vector[i] == valor) break; return i; } Esta funcin busca un valor en un vector de nmeros enteros y devuelve el ndice dentro de la matriz de la entrada de sta que lo contiene. Puesto que devuelve el ndice de la matriz supondremos en principio un valor de retorno entero para ese ndice. Los parmetros que debe conocer la funcin son: la matriz en la que buscar, el valor que debemos buscar y la longitud de la matriz. Podramos haber realizado una funcin a medida para que utilizase una matriz de un nmero determinado de elementos (int vector[100], por ejemplo) y ahorrar el parmetro longitud, sin embargo con la definicin que hemos hecho nuestra funcin funcionar con matrices de cualquier longitud de enteros.

Hemos declarado adems una variable local que es necesaria para la realizacin del bucle actuando como contador y conteniendo adems el ndice dentro de la matriz que buscamos. Si la entrada i de nuestro vector corresponde con valor, salimos del bucle y la variable i contiene ese valor de la entrada, el cual es devuelto por la funcin mediante la instruccin return. Ahora veremos como utilizaramos esta funcin desde un programa: main () { int matriz1[20]; int matriz2[30]; int indice,dato; /* Aqu realizaramos alguna operacin sobre las matrices como por ejemplo inicializarlas */ indice = busca_elemento (matriz1,10,20); .... dato = 15; indice = busca_elemento (matriz2,dato,30); ..... } Como vemos en las llamadas a nuestra funcin podemos utilizar tanto variables o constantes como parmetros. 4.2.3 Ms sobre funciones Cuando el valor que retornan las funciones no es entero, es necesario que el compilador sepa de antemano su tipo por lo cual es necesario aadir al comienzo del programa lo que se llaman prototipos. Los prototipos simplemente son una predeclaracin de la funcin, solo indican el tipo que devuelve, su nombre y los tipos de los parmetros, no es necesario indicar un identificador para los parmetros. Un prototipo para la funcin anterior sera: int busca_elemento (int *, int, int); Los fichero .h que se incluyen con la directiva del procesador #include, contienen entre otras cosas los prototipos de las funciones a las que nos dan acceso. Para finalizar con las funciones vamos a explicar como pasar parmetros que deseamos que la funcin modifique. Cuando pasamos parmetros a una funcin sta realiza una copia de los valores de stos en una zona de memoria propia, con lo cual la funcin trabaja con estas copias de los valores y no hay peligro de que se modifique la variable original con la que llamamos a la funcin, forzando de esta forma a utilizar el valor retornado por la funcin como parmetro. Sin embargo es posible que nos interese que nuestra funcin nos devuelva ms de una valor o que uno de los parmetros con los que lo llamamos se modifique en funcin de las operaciones realizadas por la funcin. En este caso tenemos que pasar los parmetros como punteros. realiza Cuando pasamos una copia de los los valores valores como punteros la funcin de los parmetros de las

funciones en su zona propia de memoria, pero en este caso el valor que pasamos no es un valor en s, sino que es una direccin de memoria en la que se encuentra ese valor que deseamos se modifique, es decir, creamos un puntero que apunta a la posicin que deseamos modificar, con lo cual tenemos acceso a esos valores. Veamos un ejemplo tpico de parmetros que deben modificarse, este es la funcin swap(a,b) cuya misin es intercambiar los valores de los dos parmetros, es decir, el parmetro a toma el valor del parmetro b y viceversa. La primera codificacin que se nos ocurre sera sta: swap (int a,int b) { int t; t = a; a = b; b = t; } Y nuestro programa principal podra ser algo como sto: main () { int c,d; c = 5; d = 7; swap (c,d); } Veamos que pasa en la memoria de nuestro ordenador. -Funcin main() -Espacio para la variable c (Posicin de memoria x) -Espacio para la variable d (Posicin de memoria y) -Inicializacin de las variables -swap(c,d) -Fin de main() -Funcin swap -Cdigo de la funcin swap -Espacio privado para almacenar los parmetros (Posicin de memoria z) En este ltimo compartimiento es dnde almacenamos los valores de nuestros parmetros que sern respectivamente 5 y 7. Despus de la ejecucin de swap en esta zona de memoria los valores estn intercambiados, nuestro parmetro a que se corresponde con la variable c en la llamada a swap contendr el valor 7 y el parmetro b correspondiente a d en la funcin main contendr el valor 5. Esto es lo que se encuentra almacenado en la zona privada de memoria de la funcin. Con este esquema cuando la funcin swap termina su ejecucin y se devuelve el control al programa principal main, los valores de c y d no han cambiado, puesto que los compartimientos o posiciones de memoria x e y no han sido tocados por la funcin swap, la cual slo ha actuado sobre el compartimiento z. Si declaramos ahora nuestra funcin swap como sigue:

swap (int *p1,int *p2) { int t; t = *p1; *p1 = *p2; *p2 = t; } /*Metemos en t el contenido de p1 */ /* Contenido de p1 = contenido de p2 */

Tendremos el mismo esquema de nuestra memoria que antes pero en lugar de almacenar en la zona privada de la funcin swap para los parmetros los valores 5 y 7 tenemos almacenados en ella los compartimientos en los que se encuentran, esto es, hemos almacenado las posiciones x e y en lugar de 5 y 7. De esta forma accedemos mediante un puntero a las variables c y d del programa principal que se encuentran en las posiciones x e y modificndolas directamente as que al regresar al programa principal sus valores se encuentran ahora intercambiados. En resumen, cuando deseemos que una funcin modifique el valor de uno de los parmetros con los que es llamada debemos pasar un puntero a esa variable en lugar del valor de esa variable. Es evidente que si implementamos nuestra funcin de esta forma, los parmetros jams podrn ser constantes, puesto que difcilmente podramos modificar el valor de una constante. TEMA 5 : Asignacin dinmica de memoria 5.0 Introduccin En este tema estudiaremos las posibilidades que ofrece el lenguaje C a la hora de trabajar dinmicamente con la memoria dentro de nuestros programas, esto es, reservar y liberar bloques de memoria dentro de un programa. Adems en este tema se introducir el concepto de tipo abstracto de dato y la forma de dividir un gran programa en otros ms pequeos. 5.1 Asignacin dinmica y esttica de memoria.

Hasta este momento solamente hemos realizado asignaciones estticas del programa, y ms concretamente estas asignaciones estticas no eran otras que las declaraciones de variables en nuestro programa. Cuando declaramos una variable se reserva la memoria suficiente para contener la informacin que debe almacenar. Esta memoria permanece asignada a la variable hasta que termine la ejecucin del programa (funcin main). Realmente las variables locales de las funciones se crean cuando stas son llamadas pero nosotros no tenemos control sobre esa memoria, el compilador genera el cdigo para esta operacin automticamente. En este sentido las variables locales estn asociadas a asignaciones de memoria dinmicas, puesto que se crean y destruyen durante la ejecucin del programa. As entendemos por asignaciones de memoria dinmica, aquellas que son creadas por nuestro programa mientras se estn ejecutando y que por tanto, cuya gestin debe ser realizada por el programador.

5.2 Cmo se reserva memoria dinmicamente? El lenguaje C dispone, como ya indicamos con anterioridad, de una serie de libreras de funciones estndar. El fichero de cabeceras stdlib.h contiene las declaraciones de dos funciones que nos permiten reservar memoria, as como otra funcin que nos permite liberarla. 5.2.1 Reserva de memoria Las dos funciones que nos permiten reservar memoria son: malloc (cantidad_de_memoria); calloc (nmero_de_elementos, tamao_de_cada_elemento); Estas dos funciones reservan la memoria especificada y nos devuelven un puntero a la zona en cuestin. Si no se ha podido reservar el tamao de la memoria especificado devuelve un puntero con el valor 0 o NULL. El tipo del puntero es, en principio void, es decir, un puntero a cualquier cosa. Por tanto, a la hora de ejecutar ests funciones es aconsejable realizar una operacin cast (de conversin de tipo) de cara a la utilizacin de la aritmtica de punteros a la que aludamos anteriormente. Los compiladores modernos suelen realizar esta conversin automticamente. Antes de indicar como deben utilizarse las susodichas funciones tenemos que comentar el operador sizeof. Este operador es imprescindible a la hora de realizar programas portables, es decir, programas que puedan ejecutarse en cualquier mquina que disponga de un compilador de C. El operador sizeof(tipo_de_dato), nos devuelve el tamao que ocupa en memoria un cierto tipo de dato, de esta manera, podemos escribir programas independientes del tamao de los datos y de la longitud de palabra de la mquina. En resumen si no utilizamos este operador en conjuncin con las conversiones de tipo cast probablemente nuestro programa slo funciones en el ordenador sobre el que lo hemos programado. Por ejemplo, el los sistemas PC, la memoria est orientada a bytes y un entero ocupa 2 posiciones de memoria, sin embargo puede que en otro sistema la mquina est orientada a palabras (conjuntos de 2 bytes, aunque en general una mquina orientada a palabras tambin puede acceder a bytes) y por tanto el tamao de un entero sera de 1 posicin de memoria, suponiendo que ambas mquinas definan la misma precisin para este tipo. Con todo lo mencionado anteriormente veamos un ejemplo de un programa que reserva dinmicamente memoria para algn dato. #include <stdlib.h> #include <stdio.h> main() { int float

*p_int; *mat;

p_int = (int *) malloc(sizeof(int)); mat = (float *)calloc(20,sizeof(float));

if ((p_int==NULL)||(mat==NULL)) { printf ("\nNo hay memoria"); exit(1); } /* Aqu iran las operaciones sobre los datos */ /* Aqu ira el cdigo que libera la memoria */ } Este programa declara dos variables que son punteros a un entero y a un float. A estos punteros se le asigna una zona de memoria, para el primero se reserva memoria para almacenar una variable entera y en el segundo se crea una matriz de veinte elementos cada uno de ellos un float. Obsrvese el uso de los operadores cast para modificar el tipo del puntero devuelto por malloc y calloc, as como la utilizacin del operador sizeof. Como se puede observar no resulta rentable la declaracin de una variable simple (un entero, por ejemplo, como en el programa anterior) dinmicamente, en primer lugar por que aunque la variable slo se utilice en una pequea parte del programa, compensa tener menos memoria (2 bytes para un entero) que incluir todo el cdigo de llamada a malloc y comprobacin de que la asignacin fue correcta (sto seguro que ocupa ms de dos bytes). En segundo lugar tenemos que trabajar con un puntero con lo cual el programa ya aparece un poco ms engorroso puesto que para las lecturas y asignaciones de las variables tenemos que utilizar el operador *. Para termina un breve comentario sobre las funciones anteriormente descritas. Bsicamente da lo mismo utilizar malloc y calloc para reservar memoria es equivalente: mat = (float *)calloc (20,sizeof(float)); mat = (float *)malloc (20*sizeof(float)); La diferencia fundamental es que, a la hora de definir matrices dinmicas calloc es mucho ms claro y adems inicializa todos los elementos de la matriz a cero. Ntese tambin que puesto que las matrices se referencian como un puntero la asignacin dinmica de una matriz nos permite acceder a sus elementos con instrucciones de la forma: NOTA: En realidad existen algunas diferencias al trabajar sobre mquinas con alineamiento de palabras. mat[0] = 5; mat[2] = mat[1]*mat[6]/67; Con lo cual el comentario sobre lo engorroso que resultaba trabajar con un puntero a una variable simple, en el caso de las matrices dinmicas no existe diferencia alguna con una declaracin normal de matrices.

5.2.2 Liberacin de la memoria. La funcin que nos permite liberar la memoria asignada con malloc y calloc es free(puntero), donde puntero es el puntero devuelto por malloc o calloc. En nuestro ejemplo anterior, podemos ahora escribir cdigo etiquetado como : /* Ahora ira el cdigo que libera memoria */ free (p_int); free(mat); Hay que tener cuidado a la hora de liberar la memoria. Tenemos que liberar todos los bloques que hemos asignado, con lo cual siempre debemos tener almacenados los punteros al principio de la zona que reservamos. Si mientras actuamos sobre los datos modificamos el valor del puntero al inicio de la zona reservada, la funcin free probablemente no podr liberar el bloque de memoria. 5.2.3 Ventajas de la asignacin dinmica. Vamos a exponer un ejemplo en el que se aprecie claramente la utilidad de la asignacin dinmica de memoria. Supongamos que deseamos programar una serie de funciones para trabajar con matrices. Una primera solucin sera definir una estructura de datos matriz, compuesta por una matriz y sus dimensiones puesto que nos interesa que nuestras funciones trabajen con matrices de cualquier tamao. Por tanto la matriz dentro de la estructura tendr el tamao mximo de todas las matrices con las que queramos trabajar y como tenemos almacenadas las dimensiones trabajaremos con una matriz de cualquier tamao pero tendremos reservada memoria para una matriz de tamao mximo. Estamos desperdiciando memoria. Una definicin de este tipo sera: typedef struct { el la

float int } MATRIZ;

mat[1000][1000]; ancho,alto;

En principio esta es la nica forma de definir nuestro tipo de datos. Con esta definicin una matriz 3x3 ocupar 1000x1000 floats al igual que una matriz 50x50. Sin embargo podemos asignar memoria dinmicamente a la matriz y reservar slo el tamao que nos hace falta. La estructura sera sta. struct mat { };

float int

*datos; ancho,alto;

typedef struct mat *MATRIZ;

El tipo MATRIZ ahora debe ser un puntero puesto que tenemos que reservar memoria para la estructura que contiene la matriz en s y las dimensiones. Una funcin que nos crease una matriz sera algo as: MATRIZ { MATRIZ inic_matriz (int x,int y) temp;

temp = (MATRIZ) malloc (sizeof(struct mat)); temp->ancho = x; temp->alto = y; temp->datos = (float *) malloc (sizeof(float)*x*y); return temp; } En esta funcin se ha obviado el cdigo que comprueba si la asignacin ha sido correcta. Veamos como se organizan los datos con estas estructuras. temp------->datos---------->x*x elementos ancho,alto Esta estructura puede parecer en principio demasiado compleja, pero veremos en el siguiente captulo que es muy til en el encapsulado de los datos. En nuestro programa declararamos algo as: main() { MATRIZ principal, para utilizar la matriz

a;

a = inic_matriz (3,3); ... borrar_matriz(a); } Dnde borrar_matriz(a) libera la memoria reservada en inic_matriz, teniendo en cuenta que se realizaron dos asignaciones, una para la estructura mat y otra para la matriz en s. Otra definicin posible del problema podra ser as. typedef struct mat MATRIZ; void inic_matriz (MATRIZ *p,int x,int y) { p->ancho = x; p->alto = y; p->datos = (float *)malloc(sizeof(float)*x*y); } esto: Con este esquema el programa principal sera algo como

main() { MATRIZ

a;

inic_matriz (&a,3,3); ..... borrar_matriz (&a); } Con este esquema el acceso a la matriz sera *(a.datos+x+y*a.ancho), idntico al anterior sustituyendo los puntos por flechas ->. En el siguiente captulo se justificar la utilizacin de la primera forma de implementacin frente a esta segunda. En realidad se trata de la misma implementacin salvo que en la primera el tipo MATRIZ es un puntero a una estructura, por tanto es necesario primero reservar memoria para poder utilizar la estructura, mientras que en la segunda implementacin, el tipo MATRIZ es ya en si la estructura, con lo cual el compilador ya reserva la memoria necesaria. En el primer ejemplo MATRIZ a; define a como un puntero a una estructura struct mat, mientras que en la segunda MATRIZ a; define a como una variable cuyo tipo es struct mat. 5.3 Tipos de datos abstractos y encapsulado. 5.3.0 Introduccin En el apartado anterior se mostraba un ejemplo de la definicin de un nuevo tipo de datos. En general un nuevo tipo de datos es simplemente una estructura que contiene informacin, la cual es organizada de una forma determinada dependiendo de cual ser su aplicacin. Podemos adems de crear la estructura de datos asociar al nuevo tipo una serie de funciones que nos permitan actuar sobre esos datos, de forma que el usuario final que utilice nuestra estructura de datos no necesite saber como est organizada internamente. A grandes rasgos esto es lo que se entiende por encapsulamiento de la informacin. El usuario dispone de un tipo de datos abstracto, puesto que no tiene acceso directo a esa informacin y por ello se hace necesaria la utilizacin de funciones que nos permitan procesar esa informacin. Esta organizacin tiene numerosas ventajas. En primer lugar la depuracin de programas se hace ms sencilla, puesto que si se produce un error mientras trabajamos con nuestro tipo abstracto, necesariamente ese error se produce dentro de las funciones asociadas a l y no en otro lugar. Adems resulta sencilla la modificacin de la implementacin de las funciones o incluso de la estructura de datos asociada a nuestro tipo, sin necesidad de modificar los programas que utilizan nuestro tipo abstracto. Estamos aumentando la modularidad de nuestra programacin. La definicin de tipos abstractos es la base de la programacin orientada a objetos. Este tipo de programacin denomina los tipos abstractos como clases, una clase es una estructura de datos y una serie de funciones asociadas a esa

estructura de datos. Adems en general disponen de otras opciones tales como sobrecarga de operadores, herencia de clases, etc... En la actualidad el estndar en programacin orientada a objetos es el C++. Otros lenguajes como el C o el Mdula II no estn orientados a objetos pero disponen de facilidades para la definicin de tipos abstractos de datos. 5.3.1 Cdigo fuente, cdigo objeto. Libreras y enlazadores Los programas que hemos esta realizando hasta el momento se conocen como cdigo fuente, son ficheros de texto cuyas lneas contienen instrucciones de un determinado lenguaje de programacin. El ordenador slo puede ejecutar un nmero determinado de instrucciones, en general muy sencillas, por ello necesitamos utilizar un compilador para poder ejecutar nuestros programas. El compilador traduce nuestro cdigo fuente (un programa en C, por ejemplo) a un cdigo objeto que la mquina puede reconocer. En principio este cdigo tal y como es generado por el compilador no puede ser ejecutado por el sistema operativo, puesto que en nuestro cdigo fuente hacemos referencia a funciones como printf o malloc que nosotros no hemos definido. Estas funciones se encuentran ya compiladas en forma de cdigo objeto y agrupadas en lo que se conoce como una librera. As se hace necesaria la utilizacin de otro programa conocido como enlazador o linker. El enlazador toma como entrada una serie de mdulos objeto o libreras y enlaza todos los mdulos utilizados por el programa en un slo programa que pueda ser ejecutado por el sistema operativo. De todo esto se deduce que resulta muy interesante disponer de libreras para distintas operaciones que pueden ser utilizadas en cualquiera de nuestros programas. Adems a medida que los programas se van haciendo cada vez ms grandes e recomendable dividir el programa en varios ficheros que contengan, cada uno de ellos, funciones que guarden cierta relacin o que acten sobre un determinado tipo de datos, siendo sta la finalidad que perseguimos en este captulo. 5.3.2 Los ficheros de proyectos de Borland Los compiladores de lenguaje C de Borland ofrecen un entorno integrado para el desarrollo de programas. Nos ofrece un editor para escribir nuestro cdigo fuente, un compilador para obtener el cdigo objeto y un enlazador para construir el programa ejecutable final. NOTA: esto es slo aplicable a compiladores C con entornos integrados. Por consiguiente, no es aplicable al GCC, pero conviene saber algo del tema para posteriores trabajos con otros compiladores. Aqu se hace referencia al compilador Borland C. Hasta ahora nuestros programas eran muy sencillos y ocupaban un slo fichero, con lo que el enlazador slo tena que vrselas con las libreras del sistema. En este captulo nuestros programas van a estar formados por varios ficheros y por ello tendremos que utilizar los ficheros de proyectos.

Estos ficheros simplemente contienen los nombres de los ficheros que son necesarios para crear el programa final. Se pueden crear utilizando la opcin Proyect del men principal del compilador y seleccionando posteriormente la opcin Open. Una ventana aparecer en pantalla y en ella tendremos que introducir los nombres de los ficheros que compondrn nuestro programa. En la lnea inferior de la pantalla siempre aparece una ayuda con las teclas que se pueden utilizar para trabajar en la ventana que se encuentre activa. La tecla INSERT nos permite seleccionar los ficheros a incluir mediante un selector de ficheros semejante al que aparece al cargar un programa. 5.3.4 Tipos abstractos de datos. Ya hemos explicado el concepto de tipo abstracto de datos en la introduccin de este captulo, y en esta seccin nos centraremos en el modo de implementacin. Fundamentalmente vamos a tener tres ficheros: 1.-El programa principal: Este programa utilizar el tipo de datos abstractos que implementaremos como un mdulo independiente. Adems debe incluir un fichero .H con las definiciones de los tipos abstractos y de las funciones de gestin de los datos. 2.-El fichero de cabecera conteniendo la definicin del tipo abstracto y de los prototipos de las funciones. 3.-La estructura real del tipo de permanecer oculta al programa principal, y funciones definidas para trabajar con el tipo. datos, la cual el cdigo de las

Nuestro tipo abstracto no es tal. En el fichero 3, el que implementa las funciones y tipos de datos, nos encontramos con una definicin de un tipo de dato como podra ser el utilizado en el ejemplo de las matrices. Sin embargo en el programa principal no tendremos acceso a los distintos campos de esa estructura de datos, sta es la razn de que se llamen tipos abstractos. La mejor forma para expresar un tipo abstracto es el tipo void. Void representa cualquier cosa, de esta manera para el programa principal cualquier tipo abstracto ser un puntero a void. La necesidad de que se trate con punteros, deriva del hecho de que tenemos que mantener las estructuras de datos ocultas, lo que nos obliga a asignar dinmicamente las variables del tipo abstracto, hacindose necesario el uso de punteros. Veremos un ejemplo de implementacin de un tipo abstracto de datos y lo comentaremos. El tipo que implementaremos ser el tipo vector y definiremos una serie de funciones para el trabajo con vectores. Comenzaremos con el fichero que implementa de datos y las funciones asociadas a l. el nuevo tipo

VECTOR.C #include<stdio.h> #include<stdlib.h> #define VECTOR_C struct vector { float int };

*componentes; dimension;

typedef struct vector *VECTOR; #include "vector.h" VECTOR crear_vector(int dimension) { VECTOR temp; temp=(VECTOR)malloc(sizeof( struct vector)); if (temp==0) { printf("\nno hay sitio en memoria");exit(1); } temp->dimension=dimension; temp->componentes=(float *)calloc(dimension,sizeof(float)); if (temp->componentes==0) { printf("\nno hay sitio en memoria");exit(1); } return temp; } void borrar_vector(VECTOR vec) { free(vec->componentes); free(vec); } float leer_componente(VECTOR vec,int componente) { float x; x=*(vec->componentes+componente); return x ; } void escribir_componente(VECTOR vec,int componente,float valor) { *(vec->componentes+componente)=valor; } void suma(VECTOR uno,VECTOR dos,VECTOR tres) { int i; if (uno->dimension!=dos->dimension) printf("\nerror"); else if (uno->dimension!=tres->dimension) printf("\nerror"); else for(i=0;i<uno->dimension;i++) *(tres->componentes+i)=*(dos->componentes+i)+ *(uno->componentes+i); }

En primer lugar se indican las libreras que necesita utilizar el fichero en s. En este caso la stdlib para las asignaciones y liberaciones de memoria, y la stdio para sacar mensajes de error por pantalla. A continuacin definimos un tipo de datos para trabajar con vectores, se trata de un vector y un entero que nos indica la longitud, y finalmente el tipo de dato abstracto VECTOR como un puntero a esta estructura. Como vemos dentro del fichero VECTOR.C el dato VECTOR est perfectamente definido y la ocultacin de ste se realiza en el fichero VECTOR.H. En el fichero VECTOR.C podemos dividir las funciones en varios tipos. En primer lugar estn las funciones crear_vector y borrar_vector, las cuales se encargan de reservar y liberar la memoria necesaria para cada elemento. Por otra parte tenemos las funciones leer_componente y escribir_componente que nos permiten acceder a la estructura de datos, dependiendo del tipo de datos puede que no sea necesario que el programador acceda a los datos en la estructura. Finalmente tenemos las funciones que realizan operaciones con los vectores, sumar_vectores. Vemos que antes de que comience el cdigo de las funciones tenemos un include de el fichero VECTOR.H, esta lnea lo que hace es incorporar los prototipos de las funciones para poder llevar a cabo la compilacin, ya se explic la funcin de los prototipos de funciones. Adems al principio de el fichero se ha definido un constante VECTOR_C, en el fichero .H veremos su utilidad. VECTOR.H #ifndef VECTOR_H #define VECTOR_H #ifndef VECTOR_C typedef void *VECTOR; #endif VECTOR crear_vector(int); void borrar_vector(VECTOR); float leer_componente(VECTOR,int); void escribir_componente(VECTOR,int,float); void suma(VECTOR,VECTOR,VECTOR); #endif En primer lugar se comprueba si se ha definido la constante VECTOR_H, para evitar incluir el fichero ms de una vez. Si est definida se ignora el fichero, si no est definida la define y comprueba si el fichero que llama a VECTOR.H es VECTOR.C mediante la constante VECTOR_C que se defina al comienzo del fichero VECTOR.C. Si no est definida se declara el tipo abstracto VECTOR como un puntero a void, eliminando de esta forma cualquier informacin sobre la forma de el tipo asociado a VECTOR en VECTOR.C. Si est definida no se realiza la definicin para evitar una redeclaracin de VECTOR dentro de VECTOR.C. Finalmente se encuentran los prototipos de las funciones asociadas al tipo vector.

Hasta el momento slo conocamos la directiva del preprocesador #include. En este ejemplo se incluye dos ms, cuyo significado es evidente. La directiva #define permite definir una constante en general realiza una macro sustitucin, vemoslo con unos ejemplos: #define PI 3.14159

Esta sentencia simplemente define una constante, en cualquier parte de el programa en la que aparezca PI, este smbolo se sustituir por 3.14159. #define COMP vector->datos

Con esta definicin podramos escribir instrucciones como: *(COMP+1) = 10; Las directivas #ifndef y #endif van siempre asociadas sealando bloques de programa y permiten la compilacin condicional de bloques de programa. Significan algo como: Si no est definida tal cosa compila (#ifndef) esto Hasta aqu (#endif) Finalmente veamos como utilizaramos abstracto en un programa, para sumar dos vectores. #include"stdio.h" #include"vector.h" main() { VECTOR x,y,z; int i; x=crear_vector(3); y=crear_vector(3); z=crear_vector(3); printf ("\n"); for(i=0;i<3;i++) escribir_componente(x,i,i); for(i=0;i<3;i++) escribir_componente(y,i,i+2); suma(x,y,z); for(i=0;i<3;i++) { printf("\n%f + %f = %f",leer_componente(x,i), leer_componente(y,i),leer_componente(z,i)); } borrar_vector (x); borrar_vector (y); borrar_vector (z); } TEMA 6 : Entrada y salida (E/S) de datos 6.0 Introduccin nuestro tipo

El objetivo de este tema es hacer un estudio completo en todo lo referente a la entrada y, salida (E/S) en C, estudiando tambin los dos sistemas de ficheros que existen en este lenguaje. 6.1 Entrada y salida (E/S) Las operaciones de entrada y salida (abreviadamente E/S) no forman parte del lenguaje C propiamente dicho, sino que estn en una biblioteca o librera: <stdio.h>. Todo programa que utilice funciones de entrada y salida estndar deber contener la lnea: #include <stdio.h> 6.1.1 E/S estndar Por defecto, la entrada estndar es el teclado y la salida estndar es la pantalla o monitor. Hay dos formas bsicas de cambiar la entrada y la salida estndar: 1. Con los smbolos de redireccin (<, >, <<, >>) o de tubera (|) del sistema operativo al ejecutar el programa desde la lnea de rdenes. 2. Con determinadas funciones y variables que se encuentran en la librera <stdio.h> en el cdigo fuente del programa. 6.2 Flujos y ficheros Hay dos conceptos muy importantes en C relacionados con la E/S: flujos (streams, en ingls) y ficheros. Los flujos son sucesiones de caracteres a travs de los cuales realizamos las operaciones de E/S. Para el programador todos los flujos son iguales. Para el C (en general para el sistema operativo) un fichero es un concepto lgico que puede ser aplicado a cualquier cosa desde ficheros de discos a terminales. A cada fichero se le asigna un flujo al realizar la operacin de apertura sobre l. Para el programador un fichero es un dispositivo externo capaz de una E/S. Todos los ficheros no son iguales pero todos los flujos s. Esto supone una gran simplificacin para el usuario, ya que slo tiene que pensar en trminos de flujo y no de dispositivos concretos. Por ejemplo, si el usuario hace: printf ("mensaje"); sabe que mensaje se escribe en el flujo estndar de salida, ya sea la pantalla, un fichero de disco, una cinta, ... 6.3 Tipos de flujos: flujos de texto y flujos binarios Cuando hemos dicho que todos los flujos son iguales, es cierto que lo son en su utilizacin por parte del programador, pero en realidad, podemos distinguir dos tipos: - Flujos de texto: son una sucesin de caracteres originado en lneas que finalizan con un carcter de nueva-lnea. En estos flujos puede no haber una relacin de uno a uno entre los caracteres que son escritos (ledos) y los del dispositivo externo, por ejemplo, una nueva-lnea puede transformarse en un par de caracteres (un retorno de carro y un carcter de salto de lnea).

- Flujos binarios: son flujos de bytes que tienen una correspondencia uno a uno con los que estn almacenados en el dispositivo externo. Esto es, no se presentan desplazamientos de caracteres. Adems el nmero de bytes escritos (ledos) es el mismo que los almacenados en el dispositivo externo. Esta diferencia de flujos es importante tenerla en cuenta al leer ficheros de discos. Supongamos que tenemos un fichero de disco con 7 caracteres donde el cuarto carcter es el carcter fin de fichero (en sistema operativo DOS es el carcter con cdigo ASCII 26). Si abrimos el fichero en modo texto, slo podemos leer los 3 primeros caracteres, sin embargo, si lo abrimos en modo binario, leeremos los 7 caracteres ya que el carcter con cdigo ASCII 26 es un carcter como cualquier otro. 6.4 Programas C con flujos Al principio de la ejecucin de flujos de tipo texto predefinidos: un programa C se abren tres

stdin : dispositivo de entrada estndar stdout: dispositivo de salida estndar stderr: dispositivo de salidad de error estndar Al finalizar el programa, bien volviendo de la funcin main al sistema operativo o bien por una llamada a exit(), todos los ficheros se cierran automticamente. No se cerrarn si el programa termina a travs de una llamada a abort() o abortando el programa. Estos tres explcitamente. ficheros no pueden abrirse ni cerrarse

6.5 Resumen de lo anterior Como todo lo que acabamos de decir puede resultar un poco confuso a las personas que tienen poca experiencia en C, vamos a hacer un pequeo resumen en trminos generales: 1. En C, cualquier cosa externa de la que podemos podemos escribir datos es un fichero. leer o en la que

2. El programador escribe (lee) datos en estos ficheros a travs de los flujos de cada fichero. De esta forma el programador escribe (lee) los datos de la misma forma en todos los tipos de ficheros independientemente del tipo de fichero que sea. 3. Aunque conceptualmente todos los flujos son iguales, en realidad hay dos tipos: flujos de texto y flujos binarios. 4. Hay tres flujos de texto predefinidos que se abren automticamente al principio del programa: stdin, stdout y stderr. Estos tres flujos se cierran automticamente al final del programa. 6.6 Pasos para operar con un fichero Los pasos a realizar fichero son los siguientes: para realizar operaciones con un

1) Crear un nombre interno de fichero. Esto se hace en C declarando un puntero de fichero (o puntero a fichero). Un puntero de fichero es una variable puntero que apunta a una estructura llamada FILE. Esta estructura est definida en el fichero stdio.h y contiene toda la informacin necesaria para poder trabajar con un fichero. El contenido de esta estructura es dependiente de la implementacin de C y del sistema, y no es interesante saberlo para el programador. Ejemplo: FILE *pf; /* pf es un puntero de fichero */ 2) Abrir el fichero. Esto se hace con la funcin fopen() prototipo se encuentra en el fichero stdio.h y es: FILE *fopen (char *nombre_fichero, char *modo); Si el fichero con nombre devuelve NULL. nombre_fichero no se puede abrir cuyo

El parmetro nombre_fichero puede contener la ruta completa de fichero pero teniendo en cuenta que la barra invertida (\) hay que repetirla en una cadena de caracteres. Los valores vlidos para el parmetro modo son: +-------+-------------------------------------------------+ | Modo | Interpretacin | +-------+-------------------------------------------------+ | "r" | Abrir un fichero texto para lectura | | "w" | Crear un fichero texto para escritura | | "a" | Aadir a un fichero texto | | "rb" | Abrir un fichero binario para lectura | | "wb" | Crear un fichero binario para escritura | | "ab" | Aadir a un fichero binario | | "r+" | Abrir un fichero texto para lectura/escritura | | "w+" | Crear un fichero texto para lectura/escritura | | "a+" | Abrir un fichero texto para lectura/escritura | | "rb+" | Abrir un fichero binario para lectura/escritura | | "wb+" | Crear un fichero binario para lectura/escritura | | "ab+" | Abrir un fichero binario para lectura/escritura | +-------+-------------------------------------------------+ Si se utiliza fopen() para abrir un fichero para escritura, entonces cualquier fichero que exista con ese nombre es borrado y se comienza con un fichero nuevo. Si no existe un fichero con ese nombre, entonces se crea uno. Si lo que se quiere es aadir al final del fichero, se debe utilizar el modo "a". Si no existe el fichero, devuelve error. Abrir un fichero para operaciones de lectura necesita que el fichero exista. Si no existe devuelve error. Finalmente, si se abre un fichero para operaciones de lectura/escritura, no se borra en caso de existir. Sin embargo, si no existe se crea. Ejemplo: FILE *pf; pf = fopen ("c:\\autoexec.bat", "r"); if (pf == NULL) /* siempre se debe hacer esta comprobacin*/ { puts ("No se puede abrir fichero.");

exit (1); } 3) Realizar las operaciones deseadas con el fichero como pueden ser la escritura en l y la lectura de l. Las funciones que disponemos para hacer esto las veremos un poco ms adelante. 4) Cerrar el fichero. Aunque el C cierra automticamente todos los ficheros abiertos al terminar el programa, es muy aconsejable cerrarlos explcitamente. Esto se hace con la funcin fclose() cuyo prototipo es: int fclose (FILE *pf); La funcin fclose() cierra el pf y vuelca su buffer. fichero asociado con el flujo

Si fclose() se ejecuta correctamente devuelve el valor 0. La comprobacin del valor devuelto no se hace muchas veces porque no suele fallar. Ejemplo: FILE *pf; if ((pf = fopen ("prueba", "rb")) == NULL) { puts ("Error al intentar abrir el fichero."); exit (1); } /* ... */ if (fclose (pf) != 0) { puts ("Error al intentar cerrar el fichero."); exit (1); } Resumen de los 4 pasos para la manipulacin de un fichero: ---------------------------------------------------------1) Declarar un puntero de fichero. FILE *pf; 2) Abrirlo el fichero. if ((pf = fopen ("nombre_fichero", "modo_apertura")) == NULL) error (); else /* ... */ 3) Realizar las operaciones deseadas con el fichero. /* En las siguientes ventanas veremos las funciones que tenemos para ello. */ 4) Cerrar el fichero. if (fclose (pf) != 0) error (); else /* ... */

6.7 Sistema de ficheros tipo UNIX Debido a que el lenguaje C se desarroll inicialmente bajo el sistema operativo UNIX, se cre un segundo sistema de ficheros. A este sistema se le llama E/S sin buffer, E/S de bajo nivel o E/S tipo UNIX. Hoy en da, este sistema de ficheros est totalmente en desuso y se considera obsoleto, adems el nuevo estndar ANSI ha decidido no estandarizar el sistema de E/S sin buffer tipo UNIX. Por todo lo dicho no se puede recomendar este sistema a los nuevos programadores C. Sin embargo, todava existen programas que lo usan y es soportado por la mayora de los compiladores de C. As que incluimos una breve explicacin al respecto. 6.7.1 Descriptores de ficheros A diferencia del sistema de E/S de alto nivel, el sistema de bajo nivel no utiliza punteros a ficheros de tipo FILE; el sistema de bajo nivel utiliza descriptores de fichero de tipo int. A cada fichero se le asocia un nmero (su descriptor de fichero). Hay tres descriptores de fichero predefinidos: 0 1 2 entrada estndar salida estndar salida de errores estndar

Los cuatro pasos para la manipulacin de ficheros con E/S de alto nivel que vimos antes se transforman en la E/S de bajo nivel en los siguientes pasos: 1) Declarar un descriptor de fichero para el fichero a abrir. int fd; 2) Abrir el fichero. if ((fd = open (nombre_fichero, modo_apertura)) = -1) { printf ("Error al intentar abrir el fichero.\n"); exit (1); } 3) Manipular el para ello. fichero con las funciones que tenemos disponible

4) Cerrar el fichero. close (fd);

También podría gustarte