Curso de programaci�n en C. Miquel A Garcies

Indice

Introducci�n

Programas

El desarrollo de un programa

Tipos b�sicos y variables

Funciones

Expresiones y operadores

Conversi�n de tipos

Control del flujo

Definici�n de funciones y prototipos

Construcci�n de tipos

Ambito de funciones y variables

Punteros

El preprocesador

Funciones de entrada y salida por pantalla

Funciones de asignaci�n de memoria

Funciones matem�ticas

Operaciones con ficheros

Bibliograf�a y referencias

Introducci�n.

C es un lenguaje de programaci�n de prop�sito general que ofrece econom�a sint�ctica, control de flujo y estructuras sencillas y un buen conjunto de operadores. No es un lenguaje de muy alto nivel y m�s bien un lenguaje peque�o, sencillo y no est� especializado en ning�n tipo de aplicaci�n. Esto lo hace un lenguaje potente, con un campo de aplicaci�n ilimitado y sobre todo, se aprende r�pidamente. En poco tiempo, un programador puede utilizar la totalidad del lenguaje.

Este lenguaje ha sido estrechamente ligado al sistema operativo UNIX, puesto que fueron desarrollados conjuntamente. Sin embargo, este lenguaje no est� ligado a ning�n sistema operativo ni a ninguna m�quina concreta. Se le suele llamar lenguaje de programaci�n de sistemas debido a su utilidad para escribir compiladores y sistemas operativos, aunque de igual forma se pueden desarrollar cualquier tipo de aplicaci�n.

La base del C proviene del BCPL, escrito por Martin Richards, y del B escrito por Ken Thompson en 1970 para el primer sistema UNIX en un DEC PDP-7. Estos son lenguajes sin tipos, al contrario que el C que proporciona varios tipos de datos. Los tipos son caracteres, n�meros enteros y en coma flotante, de varios tama�os. Adem�s se pueden crear tipos derivados mediante la utilizaci�n de punteros, vectores, registros y uniones. El primer compilador de C fue escrito por Dennis Ritchie para un DEC PDP-11 y escribi� el propio sistema operativo en C.

C trabaja con tipos de datos que son directamente tratables por el hardware de la mayor�a de computadoras actuales, como son los caracteres, n�meros y direcciones. Estos tipos de datos pueden ser manipulados por las operaciones aritm�ticas que proporcionan las computadoras. No proporciona mecanismos para tratar tipos de datos que no sean los b�sicos, debiendo ser el programador el que los desarrolle. Esto permite que el c�digo generado sea muy eficiente y de ah� el �xito que ha tenido como lenguaje de desarrollo de sistemas. No proporciona otros mecanismos de almacenamiento de datos que no sea el est�tico y no proporciona mecanismos de entrada ni salida. Ello permite que el lenguaje sea reducido y los compiladores de f�cil implementaci�n en distintos sistemas. Por contra, estas carencias se compensan mediante la inclusi�n de funciones de librer�a para realizar todas estas tareas, que normalmente dependen del sistema operativo.

Originariamente, el manual de referencia del lenguaje para el gran p�blico fue el libro [1] de Kernighan y Ritchie, escrito en 1977. Es un libro que explica y justifica totalmente el desarrollo de aplicaciones en C, aunque en �l se utilizaban construcciones, en la definici�n de funciones, que pod�an provocar confusi�n y errores de programaci�n que no eran detectados por el compilador. Como los tiempos cambian y las necesidades tambi�n, en 1983 ANSI establece el comit� X3J11 para que desarrolle una definici�n moderna y comprensible del C. El est�ndar est� basado en el manual de referencia original de 1972 y se desarrolla con el mismo esp�ritu de sus creadores originales. La primera versi�n de est�ndar se public� en 1988 y actualmente todos los compiladores utilizan la nueva definici�n. Una aportaci�n muy importante de ANSI consiste en la definici�n de un conjunto de librer�as que acompa�an al compilador y de las funciones contenidas en ellas. Muchas de las operaciones comunes con el sistema operativo se realizan a trav�s de estas funciones. Una colecci�n de ficheros de encabezamiento, headers, en los que se definen los tipos de datos y funciones incluidas en cada librer�a. Los programas que utilizan estas bibliotecas para interactuar con el sistema operativo obtendr�n un comportamiento equivalente en otro sistema.

Programas.

La mejor forma de aprender un lenguaje es programando con �l. El programa m�s sencillo que se puede escribir en C es el siguiente:

main()

{

}

Como nos podemos imaginar, este programa no hace nada, pero contiene la parte m�s importante de cualquier programa C y adem�s, es el m�s peque�o que se puede escribir y que se compile correctamente. En el se define la funci�n main, que es la que ejecuta el sistema operativo al llamar a un programa C. El nombre de una funci�n C siempre va seguida de par�ntesis, tanto si tiene argumentos como si no. La definici�n de la funci�n est� formada por un bloque de sentencias, que esta encerrado entre llaves {}.

Un programa algo m�s complicado pero que hace algo, es el siguiente:

#include <stdio.h>

main()

{

	printf("Hola amigos!\n");

}



Con el visualizamos el mensaje Hola amigos! en el terminal. En la primera l�nea indica que se tengan en cuenta las funciones y tipos definidos en la librer�a stdio (standard input/output). Estas definiciones se encuentran en el fichero header stdio.h. Ahora, en la funci�n main se incluye una �nica sentencia que llama a la funci�n printf. Esta toma como argumento una cadena de caracteres, que se imprimen van encerradas entre dobles comillas " ". El s�mbolo \n indica un cambio de l�nea. Hay un grupo de s�mbolos, que son tratados como caracteres individuales, que especifican algunos caracteres especiales del c�digo ASCII. Los m�s importantes son:
\a alert
\b backspace
\f formfeed
\n newline
\r carriage return
\t horizontal tab
\v vertical tab
\\ backslash
\' single quote
\" double quote
\OOO visualiza un car�cter cuyo c�digo ASCII es OOO en octal.
\xHHH visualiza un car�cter cuyo c�digo ASCII es HHH en hexadecimal.

Las funciones de entrada y salida y los formatos utilizados los explicaremos con m�s detalle en otro cap�tulo.

El desarrollo de un programa.

Un programa C puede estar formado por diferentes m�dulos o fuentes. Es conveniente mantener los fuentes de un tama�o no muy grande, para que la compilaci�n sea r�pida. Tambi�n, al dividirse un programa en partes, puede facilitar la legibilidad del programa y su estructuraci�n. Los diferentes fuentes son compilados de forma separada, �nicamente los fuentes que han sido modificados desde la �ltima compilaci�n, y despu�s combinados con las librer�as necesarias para formar el programa en su versi�n ejecutable.

Los comandos necesarios para compilar, linkar y ejecutar un programa dependen del sistema operativo y debemos dirigirnos a los manuales correspondientes para conocer la sintaxis exacta. Como forma m�s com�n podemos dar la siguiente:

cc prog

cc modulo1, modulo2

link prog, modulo1, modulo2

prog



Creaci�n de un programa en C.

Tipos b�sicos y variables.

Los tipos de datos b�sicos definidos por C son caracteres, n�meros enteros y n�meros en coma flotante. Los caracteres son representados por char, los enteros por short, int, long y los n�meros en coma flotante por float y double. Los tipos b�sicos disponibles y su tama�o son:
char Car�cter (normalmente 8 bits)
short Entero corto con signo (normalmente 16 bits)
int Entero con signo (depende de la implementaci�n)
unsigned Entero sin signo (depende de la implementaci�n)
long Entero largo con signo (normalmente 32 bits)
float Flotante simple (normalmente 32 bits)
double Flotante doble (normalmente 64 bits)

La palabra unsigned en realidad es un modificador aplicable a tipos enteros, aunque si no se especifica un tipo se supone int. Un modificador es una palabra clave de C que indica que una variable, o funci�n, no se comporta de la forma normal. Hay tambi�n un modificador signed, pero como los tipos son por defecto con signo, casi no se utiliza.

Las variables son definidas utilizando un identificador de tipo seguido del nombre de la variable. Veamos el siguiente programa.

#include <stdio.h>

main()

{

	float	cels, farh;



	farh = 35.0;

	cels = 5.0 * ( farh - 32.0 ) / 9.0;

	printf(">>> %f F son %f C\n", farh, cels );

}



En el programa anterior se definen dos variables float, se asigna un valor a la primera y se calcula la segunda mediante una expresi�n aritm�tica. Las asignaciones en C tambi�n son una expresi�n, por lo que se pueden utilizar como parte de otra expresi�n, pero seg�n que pr�cticas de este tipo no son muy recomendables ya que reducen la legibilidad del programa. En la instrucci�n printf, el s�mbolo %f indica que se imprime un n�mero en coma flotante.

Hay un tipo muy importante que se representa por void que puede significar dos cosas distintas, seg�n su utilizaci�n. Puede significar nada, o sea que si una funci�n devuelve un valor de tipo void no devuelve ning�n resultado, o puede significar cualquier cosa, como puede ser un puntero a void es un puntero gen�rico a cualquier tipo de dato. M�s adelante veremos su utilizaci�n.

Funciones.

Un programa C est� formado por un conjunto de funciones que al menos contiene la funci�n main. Una funci�n se declara con el nombre de la funci�n precedido del tipo de valor que retorna y una lista de argumentos encerrados entre par�ntesis. El cuerpo de la funci�n est� formado por un conjunto de declaraciones y de sentencias comprendidas entre llaves. Veamos un ejemplo de utilizaci�n de funciones:

#include <stdio.h>

#define	VALOR	5

#define	FACT	120



int fact_i ( int v )

{

	int r = 1, i = 0;



	while ( i <= v ) {

		r = r * i;

		i = i + 1;

	}

	return r;

}



int fact_r ( int v )

{

	if ( v == 0 )

		return 1;

	else 

		return v * fact_r(v-1);

}



main()

{

	int	r, valor = VALOR;



	if ( (r = fact_i(valor)) != fact_r(valor) )

		printf("Codificaci�n err�nea!!.\n");

	else

		if ( r == FACT )

			printf("Codificaci�n correcta.\n");

		else

			printf("Algo falla!!.\n");

}

Se definen dos funciones, fact_i y fact_r, adem�s de la funci�n main. Ambas toman como par�metro un valor entero y devuelven otro entero. La primera calcula el factorial de un n�mero de forma iterativa, mientras que la segunda hace lo mismo de forma recursiva.

Todas las l�neas que comienzan con el s�mbolo # indican una directiva del precompilador. Antes de realizar la compilaci�n en C se llama a un precompilador cuya misi�n es procesar el texto y realizar ciertas sustituciones textuales. Hemos visto que la directiva #include incluye el texto contenido en un fichero en el fuente que estamos compilando. De forma parecida, #define nombre texto sustituye todas las apariciones de nombre por texto. As�, en el fuente, la palabra VALOR se sustituye por el n�mero 5.

El valor que debe devolver una funci�n se indica con la palabra return. La evaluaci�n de la expresi�n debe dar una valor del mismo tipo de dato que el que se ha definido como resultado. La declaraci�n de una variable puede incluir una inicializaci�n en la misma declaraci�n. Se debe tener muy en cuenta que en C todos los argumentos son pasados 'por valor'. No existe el concepto de paso de par�metros 'por variable' o 'por referencia'. Veamos un ejemplo:

int incr ( int v )

{

	return v + 1;

}



main()

{

	int	a, b;



	b = 3;

	a = incr(b);

/*

	a = 4 mientras que 

	b = 3. No ha cambiado despu�s de la llamada.

*/

}

En el ejemplo anterior el valor del par�metro de la funci�n incr, aunque se modifique dentro de la funci�n, no cambia el valor de la variable b de la funci�n main. Todo el texto comprendido entre los caracteres /* y */ son comentarios al programa y son ignorados por el compilador. En un fuente C los comentarios no se pueden anidar.

Expresiones y operadores.

Los distintos operadores permiten formar expresiones tanto aritm�ticas como l�gicas. Los operadores aritm�ticos y l�gicos son:
+, - suma, resta
++, -- incremento, decremento
*, /, % multiplicaci�n, divisi�n, m�dulo
>>, << rotaci�n de bits a la derecha, izquierda.
& AND booleano
| OR booleano
^ EXOR booleano
~ complemento a 1
! complemento a 2, NOT l�gico
==, != igualdad, desigualdad
&&, || AND, OR l�gico
<, <= menor, menor o igual
>, >= mayor, mayor o igual

En estos operadores deben tenerse en cuenta la precedencia de operadores y las reglas de asociatividad, que son las normales en la mayor�a de lenguajes. Se debe consultar el manual de referencia para obtener una explicaci�n de tallada. Adem�s hay toda una serie de operadores aritm�ticos con asignaci�n, como pueden ser += y ^=.

En la evaluaci�n de expresiones l�gicas, los compiladores normalmente utilizan t�cnicas de evaluaci�n r�pida. Para decidir si una expresi�n l�gica es cierta o falsa muchas veces no es necesario evaluarla completamente. Por ejemplo una expresi�n formada <exp1> || <exp2>, el compilador eval�a primero <exp1> y si es cierta, no eval�a <exp2>. Por ello se deben evitar construcciones en las que se modifiquen valores de datos en la propia expresi�n, pues su comportamiento puede depender de la implementaci�n del compilador o de la optimizaci�n utilizada en una compilaci�n o en otra. Estos son errores que se pueden cometer f�cilmente en C ya que una asignaci�n es tambi�n una expresi�n.

Debemos evitar:

if (( x++ > 3 ) || ( x < y ))

y escribir en su lugar:

x++;

if (( x > 3 ) || ( x < y ))

Hay un tipo especial de expresi�n en C que se denomina expresi�n condicional y est� representada por los operadores ? : . Su utilizaci�n es como sigue: <e> ? <x> : <y>. Se eval�a si e entonces x; si no, y.

int mayor ( int a, int b )

{

	return ( a > b ) ? TRUE : FALSE;

}



waste_time ()

{

	float a, b = 0.0;



	( b > 0.0 ) ? sin(M_PI / 8) : cos(M_PI / 4);

}

Conversi�n de tipos.

Cuando escribimos una expresi�n aritm�tica a+b, en la cual hay variables o valores de distintos tipos, el compilador realiza determinadas conversiones antes de que eval�e la expresi�n. Estas conversiones pueden ser para 'aumentar' o 'disminuir' la precisi�n del tipo al que se convierten los elementos de la expresi�n. Un ejemplo claro, es la comparaci�n de una variable de tipo int con una variable de tipo double. En este caso, la de tipo int es convertida a double para poder realizar la comparaci�n.

Los tipos peque�os son convertidos de la forma siguiente: un tipo char se convierte a int, con el modificador signed si los caracteres son con signo, o unsigned si los caracteres son sin signo. Un unsigned char es convertido a int con los bits m�s altos puestos a cero. Un signed char es convertido a int con los bits m�s altos puestos a uno o cero, dependiendo del valor de la variable.

Para los tipos de mayor tama�o: si un operando es de tipo double, el otro es convertido a double. Si un operando es de tipo float, el otro es convertido a float. Si un operando es de tipo unsigned long, el otro es convertido a unsigned long. Si un operando es de tipo long, el otro es convertido a long. Si un operando es de tipo unsigned, el otro es convertido a unsigned. Si no, los operandos son te tipo int.

Una variable o expresi�n de un tipo se puede convertir expl�citamente a otro tipo, anteponi�ndole el tipo entre par�ntesis.

cambio_tipo ()

{

	float	a;

	int		b;



	b = 10;

	a = 0.5;



	if ( a <= (float) b )

		menor();

}

Control de flujo.

La sentencia de control b�sica es if (<e>) then <s> else <t>. En ella se eval�a una expresi�n condicional y si se cumple, se ejecuta la sentencia s; si no, se ejecuta la sentencia t. La segunda parte de la condici�n, else <t>, es opcional.

int cero ( double a )

{

	if ( a == 0.0 )

		return (TRUE);

	else

		return (FALSE);

}

En el caso que <e> no sea una expresi�n condicional y sea aritm�tica, se considera falso si vale 0; y si no, verdadero. Hay casos en los que se deben evaluar m�ltiples condiciones y �nicamente se debe evaluar una de ellas. Se puede programar con un grupo de sentencias if then else anidadas, aunque ello puede ser farragoso y de complicada lectura. Para evitarlo nos puede ayudar la sentencia switch. Su utilizaci�n es:

switch (valor) {

	case	valor1:	<sentencias>

	case	valor2:	<sentencias>

	...

	default:		<sentencias>

}



Cuando se encuentra una sentencia case que concuerda con el valor del switch se ejecutan las sentencias que le siguen y todas las dem�s a partir de ah�, a no ser que se introduzca una sentencia break para salir de la sentencia switch. Por ejemplo,

ver_opcion ( char c )

{

	switch(c){

		case 'a':	printf("Op A\n");	break;

		case 'b':	printf("Op B\n");	break;

		case 'c':

		case 'd':	printf("Op C o D\n");	break;

		default:	printf("Op ?\n");

	}

}



Otras sentencias de control de flujo son las que nos permiten realizar iteraciones sobre un conjunto de sentencias. En C tenemos tres formas principales de realizar iteraciones. La sentencia while (<e>) <s> es seguramente la m�s utilizada. La sentencia, o grupo de sentencias <s> se ejecuta mientras la evaluaci�n de la expresi�n <e> sea verdadera.

long raiz ( long valor )

{

	long	r = 1;



	while ( r * r <= valor )

		r++;



	return r;

}

Otra sentencia iterativa, que permite inicializar los controles del bucle se la sentencia for ( <i>; <e>; <p> ) <s>. La sentencia for se puede escribir tambi�n como:

<i>;

while ( <e> ) {

	<s>;

	<p>;

}

El ejemplo anterior se podr�a escribir como:

long raiz ( long valor )

{

	long	r;



	for ( r = 1; r * r <= valor; r++ )

		;



	return r;

}

Una variaci�n de la sentencia while es: do <s> while ( <e> ); En ella la sentencia se ejecuta al menos una vez, antes de que se eval�e la expresi�n condicional.

Otras sentencias interesantes, aunque menos utilizadas son break y continue. break provoca que se termine la ejecuci�n de una iteraci�n o para salir de la sentencia switch, como ya hemos visto. En cambio, continue provoca que se comience una nueva iteraci�n, evalu�ndose la expresi�n de control. Veamos dos ejemplos:

final_countdown ()

{

	int	count = 10;



	while ( count-- > 1 ) {

		if ( count == 4 )

			start_engines();

		if ( status() == WARNING )

			break;

		printf("%d ", count );

	}

	if ( count == 0 ) {

		launch();

		printf("Shuttle launched\n");

	}

	else {

		printf("WARNING condition received.\n");

		printf("Count held at T - %d\n", count );

	}

}



d2 ()

{

	int	f;



	for ( f = 1; f <= 50; f++ ) {

		if ( f % 2 == 0 )

			continue;

		printf("%d", f );

	}

}

Definici�n y prototipos de funciones.

Los programas sencillos, como los ejemplo planteados hasta ahora, normalmente no necesitan un nivel de estructuraci�n elevado. Pero cuando �stos crecen un poco necesitamos estructurarlos adecuadamente para mantenerlos legibles, facilitar su mantenimiento y para poder reutilizar ciertas porciones de c�digo. El mecanismo C que nos permite esto son las funciones. Con los compiladores, los fabricantes nos proporcionan un conjunto importante de funciones de librer�a. A veces, nos puede interesar construir nuestras propias librer�as. Ya hemos utilizado funciones, pero veamos c�mo debemos definirlas.

Los prototipos de funciones son una caracter�stica clave de la recomendaci�n ANSI del C. Un prototipo es una declaraci�n que toma la forma:

tipo_resultado nombre_funci�n ( tipo_par�metro nombre_par�metro ... );

Aqu� tenemos varios ejemplos:

int fact_i ( int v );

int mayor ( int a, int b );

int cero ( double a );

long raiz ( long valor );

void final_countdown ( void );

int main ( int argc, char **argv );

Observando el prototipo de una funci�n podemos decir exactamente que tipo de par�metros necesita y que resultado devuelve. Si una funci�n tiene como argumento void, quiere decir que no tiene argumentos, al igual que si el resultado es void, no devuelve ning�n valor.

En la vieja definici�n de Kernighan y Ritchie el tipo que devolv�a una funci�n se declaraba �nicamente si era distinto de int. Similarmente, los par�metros eran declarados en el cuerpo de la funci�n, en lugar de utilizar la lista de par�metros. Por ejemplo:

mayor ( a, b )

int a;

int b;

{

...

}

Las funciones al viejo estilo se compilan correctamente en muchos compiladores actuales. Por contra, proporcionan menos informaci�n sobre sus par�metros y errores que afecten al tipo de par�metros de llamada a las funciones no pueden ser detectados autom�ticamente. Por tanto, la declaraci�n de una funci�n debe escribirse igual que su prototipo pero sin el punto y coma final. El cuerpo de la funci�n le sigue encerrado entre llaves.

En un programa que est� formado por distintas partes bien diferenciadas es conveniente utilizar m�ltiples ficheros fuente. Cada fuente agrupa las funciones semejantes, como por ejemplo en un compilador podr�amos tener un fuente para el an�lisis l�xico, otro para el sint�ctico y otro para la generaci�n de c�digo. Pero en un fuente necesitaremos funciones que se han definido en otro. Para ello, escribiremos un fichero de cabecera (header), que contendr� las declaraciones que podemos necesitar en otros fuente. As�, en el fuente que implementa el analizador sint�ctico pondremos una l�nea #include "lexic.h". De esta forma al compilar el m�dulo sint�ctico tendremos todos los prototipos de las funciones del l�xico y el compilador podr� detectar malas utilizaciones de las funciones all� definidas.

Construcci�n de tipos.

Los datos del mundo real, normalmente no est�n formados por variables escalares de tipos los tipos b�sicos. Por ejemplo, nos puede interesar cu�ntos m�dulos en C hemos escrito cada semana, a lo largo del a�o. O tambi�n nos interesa tener los datos de cada planeta del Sistema Solar, masa, posici�n, velocidad y aceleraci�n, para un programa de simulaci�n de la ley de gravitaci�n de Newton. Para resolver el primer caso, C nos permite declarar una variable que sea de tipo vector. Para el segundo, podemos definir un registro para cada elemento.

Un vector es una porci�n de memoria que es utilizada para almacenar un grupo de elementos del mismo tipo Un vector se declara: tipo nombre [tama�o];. Por ejemplo, int modulo[52];. Aqu� 'modulo' es un vector de 52 elementos enteros.

main()

{

	int f, modulo[52];



	for ( f = 0; f < 52; f++ )

		modulo[f] = 0;

	...

}

Cada elemento de un vector es accedido mediante un n�mero de �ndice y se comporta como una variable del tipo base del vector. Los elementos de un vector son accedidos por �ndices que van desde 0 hasta N-1 para un vector de N elementos. Los elementos de un vector pueden ser inicializados en la misma declaraci�n:

char vocal[5] = { 'a', 'e', 'i', 'o', 'u' };

float n_Bode[5] = { 0.4, 0.7, 1, 1.6, 2.8 };

Tambi�n podemos definir vectores multidimensionales. C no impone ninguna limitaci�n al n�mero de dimensiones de un vector. Existe, en cambio, la limitaci�n del tama�o de memoria que podamos utilizar en nuestro ordenador. Por ejemplo, para la declaraci�n de un vector multidimensional podemos escribir:

int video[25][80][2];

El tama�o de la variable video es proporcional al tama�o del tipo int y al tama�o de cada dimensi�n. Existe un operador C que nos permite obtener el tama�o de un tipo o de una variable. Este es sizeof() y nos proporciona el tama�o en bytes.

if ( sizeof(video) == 80 * 25 * 2 * sizeof(int) )

	printf("OK!\n");

else

	printf("Algo no funciona.\n");

Un tipo vector muy utilizado es la cadena de caracteres (string). Si queremos asignar espacio para un string podemos hacer:

char nombre[60], direccion[80];

Es un vector C pero con la particularidad que de el propio lenguaje utiliza un car�cter especial como marca de final de string. As� en un vector de caracteres de tama�o N podremos almacenar una cadena de N-1 caracteres, cuyo �ltimo car�cter estar� en la posici�n N-2 y la marca de final de string en la N-1. Veamos un ejemplo:

char servei[6] = "SCI";

La posici�n 0 contiene el car�cter 'S'; la 1 el 'C'; la 2 el 'I'; la 3 el '\0', marca de final de string y el resto de componentes no est�n definidas. En la inicializaci�n de strings no se debe indicar el final; ya lo hace el compilador. Para la manipulaci�n de cadenas de caracteres ANSI proporciona el fichero string.h que contiene las declaraciones de un conjunto de funciones proporcionadas con la librer�a del compilador.

Un registro agrupa distintos tipos de datos en una misma estructura. Los registros son definidos de la forma:

struct nombre { lista de declaraciones };

Los campos de cada registro pueden ser tipos b�sicos u otros registros. Por ejemplo:

struct planeta {

	struct co		r, v, a;

	double		masa;

	char			nom[10];

};



struct co {

	double	x,y,z;

};

Los campos de cada registro son accesibles mediante el nombre del registro seguido de punto y el nombre del campo, como por ejemplo venus.r.x = 1.0;. Cada campo se comporta como lo hace su tipo b�sico. C no proporciona mecanismos de inicializaci�n ni copia de registros, por lo que debe ser el programador el que los implemente.

A veces los datos se ajustan a series ordenadas en las cuales un elemento sigue, o precede, a otro. Un caso t�pico son los d�as de la semana. Si se desea realizar iteraciones con los d�as de la semana una forma es, por ejemplo, asignar un n�mero a cada d�a con #define. C proporciona un mecanismo compacto para realizar esto; son las enumeraciones. Una enumeraci�n toma la forma: enum nombre { lista de elementos };. Veamos un ejemplo:

void planning ( void )

{

	enum diasemana { lunes, martes, miercoles,

		jueves, viernes, sabado, domingo };

	int dia;



	for ( dia = lunes; dia <= viernes; dia++ )

		trabajar(dia);

	if ( dia == sabado )

		salir();

}

A cada elemento de la enumeraci�n se le asigna un valor consecutivo, comenzando por 0. Si se desea que el valor asignado sea distinto se puede hacer de la siguiente forma:

enum puntos { t_6_25 = 3, t_zona = 2, t_libre = 1 };

Muchas veces es conveniente renombrar tipos de datos para que la escritura del programa se nos haga m�s sencilla y la lectura tambi�n. Esto se puede conseguir con la palabra typedef. Con ella damos un nombre a cierto tipo, o combinaci�n de ellos.

typedef struct planeta PLANETA;



PLANETA mercurio, venus, tierra, marte;

Al igual que podemos inicializar las variables de tipos b�sicos en la misma declaraci�n, tambi�n lo podemos hacer con los registros. Los valores de cada campo de un registro van separados por comas y encerrados entre llaves.

PLANETA mercurio = { { 0.350, 0, 0 },

				  { 0, 0, 0 },

				  { 0, 0, 0 },

				100, "Mercurio" };

Ambito de funciones y variables.

El �mbito, o visibilidad, de una variable nos indica en que lugares del programa est� activa esa variable. Hasta ahora, en los ejemplos que hemos visto, se han utilizado variables definidas en el cuerpo de funciones. Estas variables se crean en la memoria del ordenador cuando se llama a la funci�n y se destruyen cuando se sale. Es necesario a veces, que una variable tenga un valor que pueda ser accesible desde todas las funciones de un mismo fuente, e incluso desde otros fuentes.

En C, el �mbito de las variables depende de d�nde han sido declaradas y si se les ha aplicado alg�n modificador. Una variable definida en una funci�n es, por defecto, una variable local. Esto es, que s�lo existe y puede ser accedida dentro de la funci�n. Para que una variable sea visible desde una funci�n cualquiera del mismo fuente debe declararse fuera de cualquier funci�n. Esta variable s�lo ser� visible en las funciones definidas despu�s de su declaraci�n. Por esto, el lugar m�s com�n para definir las variables globales es antes de la definici�n de ninguna funci�n. Por defecto, una variable global es visible desde otro fuente. Para definir que existe una variable global que est� definida en otro fuente tenemos que anteponer la palabra extern a su declaraci�n. Esta declaraci�n �nicamente indica al compilador que se har� referencia a una variable externa al m�dulo que se compila.

Las variables locales llevan impl�cito el modificador auto. Esto es que se crean al inicio de la ejecuci�n de la funci�n y se destruyen al final. En un programa ser�a muy ineficiente en t�rminos de almacenamiento que se crearan todas las variables al inicio de la ejecuci�n. Por contra, en algunos casos es deseable. Esto se consigue anteponiendo el modificador static a una variable local. Si una funci�n necesita una variable que �nicamente sea accedida por la misma funci�n y que conserve su valor a trav�s de sucesivas llamadas, es el caso adecuado para que sea declarada local a la funci�n con el modificador static. El modificador static se puede aplicar tambi�n a variables globales. Una variable global es por defecto accesible desde cualquier fuente del programa. Si, por cualquier motivo, se desea que una de estas variables no se visible desde otro fuente se le debe aplicar el modificador static. Lo mismo ocurre con las funciones. Las funciones definidas en un fuente son utilizables desde cualquier otro. En este caso conviene incluir los prototipos de las funciones del otro fuente. Si no se desea que alguna funci�n pueda ser llamada desde fuera del fuente en la que est� definida se le debe anteponer el modificador static.

void contar ( void )

{

	static long	cuenta = 0;



	cuenta++;

	printf("Llamada %ld veces\n", cuenta );

}

Otro modificador muy importante es const. Con �l se pueden definir variables cuyo valor debe permanecer constante durante toda la ejecuci�n del programa. Tambi�n se puede utilizar con argumentos de funciones. En esta caso se indica que el argumento en cuesti�n es un par�metro y su valor no debe ser modificado. En el caso que por error modifiquemos ese par�metro, el compilador nos indicar� el error.

#define EULER	2.71828

const double	pi = 3.14159;



double lcercle ( const double r )

{

	return 2 * pi * r;

}



double EXP ( const double x )

{

	return pow ( EULER, x );

}



double sinh ( const double x )

{

	return (exp(x) - exp(-x)) / 2;

}

Debemos fijarnos que en el ejemplo anterior pi es una variable, la cual no podemos modificar. Por ello pi s�lo puede aparecer en un �nico fuente. Si la definimos en varios, al linkar el programa se nos generar� un error por tener una variable duplicada.

Otro modificador utilizado algunas veces es el register. Este modificador es aplicable �nicamente a variables locales e indica al compilador que esta debe ser almacenada permanentemente en un registro del procesador del ordenador. Este modificador es herencia de los viejos tiempos, cuando las tecnolog�as de optimizaci�n de c�digo no estaban muy desarrolladas y se deb�a decir qu� variable era muy utilizada en la funci�n. Hoy en d�a casi todos los compiladores realizan un estudio de qu� variables locales son las m�s adecuadas para ser almacenadas en registros, y las asignan autom�ticamente. Con los compiladores modernos se puede dar el caso de que una declaraci�n register inadecuada disminuya la velocidad de ejecuci�n de la funci�n, en lugar de aumentarla. Por ello, hoy en d�a, la utilizaci�n de este modificador est� en desuso, hasta el punto de que algunos compiladores lo ignoran. Se debe tener en cuenta que de una variable declarada como register no se puede obtener su direcci�n, ya que est� almacenada en un registro y no en memoria.

Punteros.

Cada variable de un programa tiene una direcci�n en la memoria del ordenador. Esta direcci�n indica la posici�n del primer byte que la variable ocupa. En el caso de una estructura es la suma del tama�o de cada uno de sus campos. Como en cualquier caso las variables son almacenadas ordenadamente y de una forma predecible, es posible acceder a estas y manipularlas mediante otra variables que contenga su direcci�n. A este tipo de variables se les denomina punteros.

Los punteros C son el tipo m�s potente y seguramente la otra clave del �xito del lenguaje. La primera ventaja que obtenemos de los punteros es la posibilidad que nos dan de poder tratar con datos de un tama�o arbitrario sin tener que moverlos por la memoria. Esto puede ahorrar un tiempo de computaci�n muy importante en algunos tipos de aplicaciones. Tambi�n permiten que una funci�n reciba y cambie el valor de una variable. Recordemos que todas las funciones C �nicamente aceptan par�metros por valor. Mediante un puntero a una variable podemos modificarla indirectamente desde una funci�n cualquiera.

Un puntero se declara de la forma: tipo *nombre;

float	*pf;

PLANETA	*pp;

char	*pc;

Para manipular un puntero, como variable que es, se utiliza su nombre; pero para acceder a la variable a la que apunta se le debe preceder de *. A este proceso se le llama indirecci�n. Accedemos indirectamente a una variable. Para trabajar con punteros existe un operador, &, que indica 'direcci�n de'. Con �l se puede asignar a un puntero la direcci�n de una variable, o pasar como par�metro a una funci�n.

void prova_punter ( void )

{

	long	edat;

	long	*p;



	p = &edat;

	edad = 50;

	printf("La edat es %ld\n", edat );

	*p = *p / 2;

	printf("La edat es %ld\n", edat );

}



void imprimir_string ( char string[] )

{

	char	*p;



	for ( p = string; *p != '\0'; p++ )

		imprimir_char(*p);

}

Los punteros tambi�n se pueden utilizar con los registros. Para ello se utiliza la notaci�n -> en lugar del punto que utiliz�bamos anteriormente. Si p es un puntero a PLANETA, y queremos conocer su masa, debemos escribir p->masa. Un puntero se puede utilizar para almacenar la direcci�n de cualquier tipo de datos, tanto simple como un vector, como un registro. De c�mo lo definimos y lo utilizamos depende su comportamiento. Las componentes de un vector, por ejemplo pueden ser referenciadas por un puntero al tipo de cada componente. Veamos un ejemplo:

#define N_PLA		9



static PLANETA	SSolar[N_PLA];



void init_SistemaSolar ( void )

{

	PLANETA	*p;



	for ( p = SSolar; p < SSolar[N_PLA]; p++ )

		init_planeta(p);

}



void init_planeta ( PLANETA *p )

{

	p->masa = 0;

	p->nom = "";

	init_co(&(p->r));

	init_co(&(p->v));

	init_co(&(p->a));

}



void init_co ( struct co *c )

{

	c->x = c->y = c->z = 0;

}

Definimos un vector de N_PLA componentes de tipo PLANETA. Este tipo est� formado por un registro. Vemos que en la funci�n de inicializaci�n del vector el puntero a la primera componente se inicializa con el nombre del vector. Esto es una caracter�stica importante de C. La direcci�n de la primera componente de un vector se puede direccionar con el nombre del vector. Esto es debido a que en la memoria del ordenador, los distintos elementos est�n ordenados de forma ascendente. As�, SSolar se puede utilizar como &SSolar[0]. A cada iteraci�n llamamos a una funci�n que nos inicializar� los datos de cada planeta. A esta funci�n le pasamos como argumento el puntero a la componente en curso para que, utilizando la notaci�n ->, pueda asignar los valores adecuados a cada campo del registro. Debemos fijarnos en el incremento del puntero de control de la iteraci�n, p++. Con los punteros se pueden realizar determinadas operaciones aritm�ticas aunque, a parte del incremento y decremento, no son muy frecuentes. Cuando incrementamos un puntero el compilador le suma la cantidad necesaria para que apunte al siguiente elemento de la memoria. Debemos fijarnos que esto es aplicable s�lo siempre que haya distintas variables o elementos situados consecutivamente en la memoria, como ocurre con los vectores.

De forma similar se pueden utilizar funciones que tengan como par�metros punteros, para cambiar el valor de una variable. Veamos:

void intercambio ( void )

{

	int	a, b;



	a = 1;

	b = 2;

	swap( &a, &b );

	printf(" a = %d b = %d\n", a, b );

}



void swap ( int *x, int *y )

{

	int	tmp;



	tmp = *x;

	*x = *y;

	*y = tmp;

}

La sintaxis de C puede, a veces, provocar confusi�n. Se debe distinguir lo que es un prototipo de una funci�n de lo que es una declaraci�n de una variable. As� mismo, un puntero a un vector de punteros, etc...
int f1();

funci�n que devuelve un entero

int *p1;
puntero a entero

int *f2();
funci�n que devuelve un puntero a entero

int (*pf)(int);
puntero a funci�n que toma y devuelve un entero

int (*pf2)(int *pi);
puntero a funci�n que toma un puntero a entero y devuelve un entero

int a[3];
vector de tres enteros

int *ap[3];
vector de tres punteros a entero

int *(ap[3]);
vector de tres punteros a entero

int (*pa)[3];
puntero a vector de tres enteros

int (*apf[5])(int *pi);
vector de 5 punteros a funci�n que toman un puntero a entero y devuelven un entero

En los programas que se escriban se debe intentar evitar declaraciones complejas que dificulten la legibilidad del programa. Una forma de conseguirlo es utilizando typedef para redefinir/renombrar tipos.

typedef int *intptr;

typedef intptr (*fptr) ( intptr );

fptr f1, f2;

El preprocesador.

El preprocesador es una parte del compilador que se ejecuta en primer lugar, cuando se compila un fuente C y que realiza unas determinadas operaciones, independientes del propio lenguaje C. Estas operaciones se realizan a nivel l�xico y son la inclusi�n de otros textos en un punto del fuente, realizar sustituciones o eliminar ciertas partes del fuente. Debemos tener en cuenta que el preprocesador trabaja �nicamente con el texto del fuente y no tiene en cuenta ning�n aspecto sint�ctico ni sem�ntico del lenguaje.

El control del preprocesador se realiza mediante determinadas directivas incluidas en el fuente. Una directiva es una palabra que interpreta el preprocesador, que siempre va precedida por el s�mbolo # y que est� situada a principio de l�nea.

La directiva #define se utiliza para definir una macro. Las macros proporcionan principalmente un mecanismo para la sustituci�n l�xica. Una macro se define de la forma #define id secuencia. Cada ocurrencia de id en el fuente es sustituida por secuencia. Puede definirse una macro sin una secuencia de caracteres. Una macro se puede �indefinir� mediante la directiva #undef.

#define MSG01	"SCI-I-START: Starting system kernel\n"

#define MSG02	"SCI-I-STOP: Stopping system kernel\n"



void print_msg ( void )

{

	if ( check_state() == START )

		printf(MSG01);

	else

		printf(MSG02);

}

El estado de una macro, si est� definida o no, se puede comprobar mediante las directivas #ifdef y #ifndef. Estas dos directivas se deben completar con una #endif y, el texto comprendido entre ambas es procesado si la macro est� definida. Todas las directivas deben ser completadas en el mismo fuente y pueden ser anidadas.

#ifndef M_PI

#define M_PI 3.1415927

#endif

El preprocesador nos permite tambi�n incluir tambi�n otros ficheros en un fuente C. Esto se consigue con la directiva #include. Esta puede tomar tres formas: #include <fichero>, #include "fichero" y #include macro. La diferencia entre la primera y la segunda est� en el lugar d�nde se buscar� el fichero en cuesti�n. Normalmente se utiliza la primera para ficheros proporcionados por la librer�a del compilador, y la segunda, para ficheros creados por el programador.

Funciones de entrada y salida por pantalla.

En este apartado y los siguientes vamos a ver algunas de las funciones m�s importantes que nos proporcionan las librer�as definidas por ANSI y su utilizaci�n. Como hemos visto hasta ahora, el lenguaje C no proporciona ning�n mecanismo de comunicaci�n ni con el usuario ni con el sistema operativo. Ello es realizado a trav�s de las librer�as.

El fichero de declaraciones que normalmente m�s se utiliza es el stdio.h. Vamos a ver algunas funciones definidas en �l.

Una funci�n que ya hemos utilizado y que, ella y sus variantes, es la m�s utilizadas para la salida de informaci�n es printf. Esta permite formatear en enviar datos a la salida est�ndar del sistema operativo.

#include <stdio.h>

int printf ( const char *format [, argumentos, ...] );

Acepta un string de formato y cualquier n�mero de argumentos. Estos argumentos se aplican a cada uno de los especificadores de formato contenidos en format. Un especificador de formato toma la forma %[flags][width][.prec][h|l] type. El tipo puede ser:
d, i entero decimal con signo
o entero octal sin signo
u entero decimal sin signo
x entero hexadecimal sin signo (en min�sculas)
X entero hexadecimal sin signo (en may�sculas)
f coma flotante en la forma [-]dddd.dddd
e coma flotante en la forma [-]d.dddd e[+/-]ddd
g coma flotante seg�n el valor
E como e pero en may�sculas
G como g pero en may�sculas
c un car�cter
s cadena de caracteres terminada en '\0'
% imprime el car�cter %
p puntero

Los flags pueden ser los caracteres:
+ siempre se imprime el signo, tanto + como -
- justifica a la izquierda el resultado, a�adiendo espacios al final
blank si es positivo, imprime un espacio en lugar de un signo +
# especifica la forma alternativa

En el campo width se especifica la anchura m�nima de la forma:
n se imprimen al menos n caracteres.
0n se imprimen al menos n caracteres y si la salida es menor, se anteponen ceros
* la lista de par�metros proporciona el valor

Hay dos modificadores de tama�o, para los tipos enteros:
l imprime un entero long
h imprime un entero short

Otra funci�n similar a printf pero para la entrada de datos es scanf. Esta toma los datos de la entrada est�ndar del sistema operativo. En este caso, la lista de argumentos debe estar formada por punteros, que indican d�nde depositar los valores.

#include <stdio.h>

int scanf ( const char *format [, argumentos, ...] );

Hay dos funciones que trabajan con strings. La primera lee un string de la entrada est�ndar y la segunda lo imprime en el dispositivo de salida est�ndar.

#include <stdio.h>

char *gets ( char *s );

int puts ( char *s );

Tambi�n hay funciones de lectura y escritura de caracteres individuales.

#include <stdio.h>

int getchar ( void );

int putchar ( int c );

Veamos, por ejemplo, un programa que copia la entrada est�ndar a la salida.

#include <stdio.h>



main()

{

	int	c;



	while ( (c = getchar()) != EOF )

		putchar(c);

}

Funciones de asignaci�n de memoria.

Hemos visto que en C, las variables est�ticas pueden estar creadas al inicio de la ejecuci�n del programa, o bien son variables locales autom�ticas que se crean al iniciarse la ejecuci�n de una funci�n. En muchas aplicaciones en necesario la utilizaci�n de estructuras de datos din�micas. Para asignar memoria din�mica C utiliza varias funciones definidas en stdlib.h. Veamos la utilizaci�n de estas funciones.

La funci�n de este tipo m�s utilizada es malloc. Esta toma como par�metro un tama�o en bytes y devuelve un puntero al bloque de memoria asignado. Si no hay memoria suficiente para asignar el bloque devuelve NULL.

#include <stdlib.h>

void *malloc ( size_t size );

El tipo size_t est� definido normalmente como unsigned y se utiliza en todas las funciones que necesitan un tama�o en bytes. Otra funci�n similar es:

#include <stdlib.h>

void *calloc ( size_t nitems, size_t size );

En este caso se le pasa como par�metro el n�mero de elementos consecutivos que se desean. A diferencia de malloc, calloc inicializa el contenido de la memoria asignada a 0. La funci�n que se utiliza para devolver memoria din�mica previamente asignada es free. Esta toma como par�metro un puntero previamente obtenido con malloc o calloc

#include <stdlib.h>

void free ( void *block );

Hay que tener en cuenta, que la funci�n free no cambia el valor del par�metro. El puntero al cual se hab�a asignado memoria y ahora se ha liberado sigue almacenando la direcci�n de memoria, pero esta ya no existe despu�s de llamar a free. Es misi�n del programador actualizar el valor del puntero, si es necesario. Como ejemplo mostraremos una funci�n que asigna memoria para un vector, lo inicializa y lo libera.

void alloc_array ( void )

{

	int	*v, f;



	if ((v = (int *) calloc ( 10, sizeof(int) )) == NULL)

		printf("No hay memoria\n");

	else {

		for ( f = 0; f < 10; f++ )

			v[f] = 0;



		free(v);

		v = NULL;

	}

}

Debemos observar la conversi�n de tipo realizada al valor que devuelve calloc. Todas las funciones devuelven punteros a void, por lo que se deben convertir al tipo de nuestra variable.

Funciones matem�ticas.

La utilizaci�n de las funciones matem�ticas definidas en el ANSI C requieren la inclusi�n del fichero math.h. Todas ellas trabajan con el tipo double, por lo que si los argumentos o resultados son del tipo float el propio compilador se encarga de convertirlos al formato adecuado. En ANSI se est� trabajando para proporcionar funciones con argumentos de tipo float e introducir el tipo long float. Casi todas la funciones tienen la forma double nombre ( double x );.
atan2 toma dos argumentos x e y y devuelve la arcotangente de y/x en radianes.
exp devuelve el valor e elevado a x.
acos retorna el arco coseno del par�metro x.
asin retorna el arco seno del par�metro x.
atan retorna el valor de la arco tangente del par�metro x.
cos retorna el coseno del �ngulo x.
cosh retorna el coseno hiperb�lico del par�metro x.
sin retorna el seno del �ngulo x.
sinh retorna el seno hiperb�lico del par�metro x.
tan retorna la tangente del �ngulo x.
tanh retorna la tangente hiperb�lica del par�metro x.
log retorna el logaritmo natural del par�metro x.
log10 retorna el logaritmo en base 10 del par�metro x.
pow toma dos par�metros x e y y devuelve el valor xy
sqrt retorna la raiz cuadrada del par�metro x.

Operaciones con ficheros.

La entrada y salida a ficheros es uno de los aspectos m�s delicados de cualquier lenguaje de programaci�n, pues suelen estar estrechamente integradas con el sistema operativo. Los servicios ofrecidos por los sistemas operativos var�an enormemente de un sistema a otro. Las librer�as del C proporcionan un gran conjunto de funciones, muchas de ellas descritas en el libro de Kernighan y Ritchie y otras derivadas de los servicios que ofrece el Unix.

En C hay dos tipos de funciones de entrada/salida a ficheros. Las primeras son derivadas del SO Unix y trabajan sin buffer. Las segundas son las que fueron estandarizadas por ANSI y utilizan un buffer intermedio. Adem�s, hacen distinciones si trabajan con ficheros binarios o de texto. Veremos las segundas, que son las m�s utilizadas.

Las funciones del C no hacen distinci�n si trabajan con un terminal, cinta o ficheros situados en un disco. Todas las operaciones se realizan a trav�s de streams. Un stream est� formado por una serie ordenada de bytes. Leer o escribir de un fichero implica leer o escribir del stream. Para realizar operaciones se debe asociar un stream con un fichero, mediante la declaraci�n de un puntero a una estructura FILE. En esta estructura se almacena toda la informaci�n para interactuar con el SO. Este puntero es inicializado mediante la llamada a la funci�n fopen(), para abrir un fichero.

Cuando se ejecuta todo programa desarrollado en C hay tres streams abiertos autom�ticamente. Estos son stdin, stdout y stderr. Normalmente estos streams trabajan con el terminal, aunque el sistema operativo permite redireccionarlos. Las funciones printf() y scanf() que hemos visto, utilizan stdout y stdin respectivamente.

Los datos de los ficheros pueden ser accedidos en uno de los dos formatos: texto o binario. Un text stream consiste en una serie de lineas de texto acabadas con un car�cter newline. En modo binario un fichero es una colecci�n de bytes sin ninguna estructura especial.

Respecto a la velocidad de la memoria y de la CPU, los dispositivos de entrada y salida con muy lentos. Puede haber tres, cuatro y hasta cinco �rdenes de magnitud entre la velocidad de la CPU y la de un disco duro. Adem�s una operaci�n de entrada y salida puede consumir una cantidad importante de recursos del sistema. Por ello, conviene reducir en n�mero de lecturas y escrituras a disco. La mejor forma de realizar esto es mediante un buffer. Un buffer es una �rea de memoria en la cual los datos son almacenados temporalmente, antes de ser enviados a su destino. Por ejemplo, las operaciones de escritura de caracteres a un fichero se realizan sobre el buffer del stream. Unicamente cuando se llena el buffer se escriben todos los caracteres sobre el disco de una vez. Esto ahorra un buen n�mero de operaciones sobre el disco. Las funciones del C nos permiten modificar el tama�o y comportamiento del buffer de un stream.

Para utilizar las funciones de ficheros se debe incluir el fichero stdio.h. Este define los prototipos de todas las funciones, la declaraci�n de la estructura FILE y algunas macros. Una macro importante es EOF, que es el valor devuelto por muchas funciones cuando se llega al final de fichero.

Los pasos a seguir para operar con un fichero son: abrir, realizar el tratamiento y cerrar. Para abrir un fichero se utiliza la funci�n fopen. Esta toma dos strings como par�metros. El primero indica el nombre del fichero que deseamos abrir y el segundo indica el modo de acceso.

#include <stdio.h>

FILE *fopen ( const char *filename, const char *mode );

Los modos de acceso para streams de texto son los siguientes:
"r" Abre un fichero que ya existe para lectura. La lectura se realiza al inicio del fichero.
"w" Se crea un nuevo fichero para escribir. Si el fichero existe se inicializa y sobreescribe.
"a" Abre un fichero que ya existe para a�adir informaci�n por el final. S�lo se puede escribir a partir del final.
"r+" Abre un fichero que ya existe para actualizarlo (tanto para lectura como para escritura).
"w+" Crea un nuevo fichero para actualizarlo (lectura y escritura) si existe, lo sobreescribe.
"a+" Abre un fichero para a�adir informaci�n al final. Si no existe lo crea.

Si se desea especificar un fichero binario, se a�ade una b al modo: "wb+". Si el fichero se abre correctamente, la funci�n devuelve un puntero a una estructura FILE. Si no, devuelve NULL. La funci�n fprintf se comporta exactamente a printf, excepto en que toma una argumento m�s que indica el stream por el que se debe realizar la salida. De hecho, la llamada printf("x") es equivalente a fprintf ( stdout, "x").

FILE	*f;



if ((f = fopen( "login.com", "r" )) == NULL )

	printf("ERROR: no puedo abrir el fichero\n");

Para cerrar un fichero se utiliza la funci�n fclose. Esta toma como argumento el puntero que nos proporcion� la funci�n fopen.

#include <stdio.h>

int fclose ( FILE *stream );

Devuelve 0 si el fichero se cierra correctamente, EOF si se produce alg�n error.

Una vez conocemos como abrir y cerrar ficheros, vamos a ver c�mo leer y escribir en ellos. Hay funciones para trabajar con caracteres, lineas y bloques.

Las funciones que trabajan con caracteres son fgetc y fputc. La primera lee un car�cter de un stream y la segunda lo estribe.

#include <stdio.h>

int fgetc ( FILE *stream );

int fputc ( int c, FILE *stream );

La primera lee el siguiente car�cter del stream y los devuelve convertido a entero sin signo. Si no hay car�cter, devuelve EOF. La segunda, fputc, devuelve el propio car�cter si no hay error. Si lo hay, devuelve el car�cter EOF. Hay una tercera funci�n, feof que devuelve cero si no se ha llegado al final del stream. Veamos un ejemplo de c�mo se copia un fichero car�cter a car�cter.

while ( !feof(infile))

	fputc ( fgetc ( infile ), outfile );

Suponemos que infile y outfile son los streams asociados al fichero de entrada y al de salida, que est�n abiertos y luego los cerramos para completar los cambios realizados.

Otras funciones nos permiten realizar operaciones con ficheros de texto trabajando l�nea a l�nea.

#include <stdio.h>

char *fgets ( char *s, int n, FILE *stream );

int fputs ( const char *s, FILE *stream );

La funci�n fgets lee caracteres del stream hasta que encuentra un final de l�nea o se lee el car�cter n-1. Mantiene el car�cter \n en el string y a�ade el car�cter \0. Devuelve la direcci�n del string o NULL si se produce alg�n error. La funci�n fputs copia el string al stream, no a�ade ni elimina caracteres \n y no copia la marca de final de string \0.

Hay dos funciones que nos permiten trabajan en bloques. Podemos considerar un bloque como un array. Debemos especificar el tama�o de cada elemento y el n�mero de elementos.

#include <stdio.h>

size_t fread ( void *p, size_t s, size_t n, FILE *f );

size_t fwrite ( void *p, size_t s, size_t n, FILE *f );

A las anteriores funciones se les pasa un puntero gen�rico p con la direcci�n del �rea de datos que se desea leer o escribir, el tama�o de cada elemento s, y el n�mero de elementos n, adem�s del stream. Ambas devuelven el n�mero de elementos le�dos o escritos, que debe ser el mismo que le hemos indicado, en el caso en que no se haya producido ning�n error.

Hasta ahora hemos visto funciones de tratamiento de ficheros que nos van bien para realizar un tratamiento secuencial de �stos. Debemos tener en cuenta que cuando debemos realizar operaciones de entrada y de salida alternadas, se deben vaciar los buffers de los streams para que las operaciones se realicen correctamente sobre los ficheros. Esto se consigue con las funciones fflush y flushall. La primera es aplicable al stream que deseemos y la segunda vac�a los buffers de todos los streams abiertos.

#include <stdio.h>

int fflush ( FILE *stream );

int flushall ( void );

Si deseamos hacer un acceso aleatorio a un fichero disponemos de las funciones fseek y ftell. La primera mueve el cursor, indicador de posici�n, a un lugar determinado dentro del fichero. Este lugar viene determinado por whence. Este puede ser SEEK_SET, si es desde el inicio del fichero; SEEK_CUR si es desde la posici�n actual y SEEK_END si es desde el final del fichero. Devuelve 0 si la operaci�n se realiza correctamente.

#include <stdio.h>

int fseek ( FILE *stream, long offset, int whence );

long ftell ( FILE *stream );

La segunda funci�n devuelve la posici�n del cursor dentro del fichero. Este valor indica el n�mero de bytes desde el inicio del fichero, si el fichero es binario.

int escribir_planetas ( char *nombre )

{

	PLANETA	*p;

	FILE	*f;



	if ( ( f = fopen ( "w+", nombre )) == NULL )

		return ERROR;



	if (N_PLA != fwrite ( p, sizeof(PLANETA), N_PLA, f ))

		return ERROR;



	if ( fclose(f) != 0 )

		return ERROR;

	else

		return OK;

}

Bibliograf�a y referencias.

[1] El lenguaje de programaci�n C.
Brian W. Kernighan, Dennis M. Ritchie.
Prentice-Hall Hispanoamericana, 1985.
ISBN 968-880-024-4

[2] American National Standard for Information Systems -- Programming Language C.
American National Standards Institute.
1430 Broadway
New York, NY 10018

[3] El lenguaje de programaci�n C, segunda edici�n.
Brian W. Kernighan, Dennis M. Ritchie.
Prentice-Hall Hispanoamericana, 1991.
ISBN 968-880-205-0

[4] C. A software engineering approach.
Peter A Darnell, Philip E. Margolis.
Springer-Verlag, 1991
ISBN 3-540-97389-3



Miquel A Garcies