jueves, 10 de abril de 2008

Capítulo 6

.
Tipos de Datos definidos por el Programador

En C no existe la "sección type" de Pascal. Los nuevo tipos de datos se definen con la palabra typedef generalmente en archivos .h o bien en el mismo .c antes de codificar las funciones que los utilicen.

En el siguiente programa definimos con typedef los tipos Entero y Cadena, definimos variables de esos tipos de datos y las utilizamos.

testTypedef.c
   1:
2:#include<stdio.h>
3:
4:// definimos el tipo "Entero"
5:typedef int Entero;
6:
7:// definimos el tipo "Cadena"
8:typedef char Cadena;
9:
10:int main()
11:{
12: // defino una variable de tipo Entero
13: Entero e=10;
14:
15: // definimos una cadena de 20 caracteres
16: Cadena s[20];
17: strcpy(s,"Prueba");
18:
19: printf("%d, %s\n",e,s);
20:}
21:


Estructuras
(o registros)

Los registros (record de Pascal) se definen con la palabra struct. Decimos que son "estructuras".

En el siguiente programa definimos una estructura RAlumno y luego asignamos valores a sus campos leyendo lo que el usuario ingresa por teclado.

testStruct.c
   1:
2:#include<stdio.h>
3:
4:// definimos una estructura
5:struct RAlumno
6:{
7: int legajo;
8: char nombre[20];
9: long fecNac;
10:}typedef RAlumno;
11:
12:int main()
13:{
14: // defino una variable tipo RAlumno
15: RAlumno alumno;
16:
17: // leemos el legajo
18: printf("Ingrese Legajo: ");
19: scanf("%d",&alumno.legajo);
20:
21: // leemos el nombre
22: printf("Ingrese Nombre: ");
23: scanf("%s",alumno.nombre);
24:
25: // leemos el Fecha de Nacimiento.
26: printf("Ingrese Fec. Nac.(aaaammdd): ");
27: scanf("%d",&alumno.fecNac);
28:
29: // mostramos los datos leidos
30: printf("Legajo: %d\n", alumno.legajo);
31: printf("Nombre: %s\n", alumno.nombre);
32: printf("Fecha Nac: %d\n", alumno.fecNac);
33:}
34:

En este ejemplo, en la línea 5, definimos la estructura struct RAlumno y luego, en la línea 10, definimos el tipo de datos RAlumno. Luego de esto quedaron definidos dos nuevos tipos de datos, como vemos a continuación:

// define una variable de tipo struct RAlumno
struct RAlumno alum1;

// define una variable de tipo RAlumno (que es struct RAlumno)
RAlumno alum2;

El programador puede utilizar la definición que le resulte más clara y cómoda.


Estructuras Anidadas

Con el mismo criterio que en Pascal definimos registros anidados, en C podemos crear estructuras anidadas. Esto es: estructuras con campos cuyo tipo de dato es otra estructura.
   1:
2:struct RFecha
3:{
4: int dia;
5: int mes;
6: int anio;
7:}typedef RFecha;
8:
9:struct RAlumno
10:{
11: int legajo;
12: char nombre[20];
13: RFecha fecNac; // un campo tipo RFecha
14:}typedef RAlumno;
15:

En este ejemplo en la estructura RAlumno el campo fecNac es de tipo RFecha.


Archivos


En C los archivos se manejan como punteros del tipo FILE. La forma de manejar archivos es muy similar a la que aprendimos en Pascal.

Las principales funciones de manejo de archivos son:
  • fopen - abrir un archivo
  • fread - leer un registro
  • fwrite - escribir un registro
  • feof - indica si llegó el fin de archivo
  • fseek - posicionar el puntero
  • fclose - cierrar el archivo

Grabar un Archivo

Para abrir el archivo utilizamos la función fopen. Esta función recibe el nombre del archivo que vamos a abrir y el modo de apertura. En este caso el modo será "w+". Esto significa que lo abrimos para escritura. Es análogo al procedimiento rewrite de Pascal.

grabarArchivo.c
   1:
2:#include<stdio.h>
3:
4:int main()
5:{
6: FILE *arch; // variable de archivo
7: int v;
8:
9: // abrimos el archivo para escritura
10: arch = fopen("SALIDA.dat","w+");
11:
12: // el usuario ingresa un valor para grabar
13: printf("Ingrese valor: ");
14: scanf("%d",&v);
15:
16: while( v != 0 ) // ingresa cero para finalizar
17: {
18: // grabamos el valor ingresado por el usuario
19: fwrite(&v,sizeof(int),1,arch);
20:
21: // pedimos el proximo valor para grabar...
22: printf("Ingrese valor: ");
23: scanf("%d",&v);
24: }
25:
26: // cerramos el archivo
27: fclose(arch);
28:}
29:

Para grabar el archivo utilizamos la función fwrite. Esta función recibe un puntero a la variable que contiene el valor que queremos grabar, la longitud del tipo de registro del archivo (como es un archivo de enteros esta longitud será sizeof(int)), la cantidad de registros que va a grabar y (por último) el puntero al archivo.

Para cerrar el archivo utilizamos la función fclose.


Leer un Archivo

El siguiente programa lee el archivo SALIDA.dat (que generamos en el ejemplo anterior) y muestra su contenido por consola (pantalla).

Es importante notar que a diferencia de Pascal, la función feof retorna"true" (o distinto de cero) al intentar leer más allá del último registro, no en el momento de leerlo. Por este motivo, la lectura debe hacerse primero antes de entrar al while y luego antes de cerrarlo.

leerArchivo.c
   1:
2:#include<stdio.h>
3:
4:int main()
5:{
6: FILE *arch;
7: int v;
8:
9: // abrimos el archivo para lectura
10: arch = fopen("SALIDA.dat","r+");
11:
12: // la primer leida la hacemos afuera del while
13: fread(&v,sizeof(int),1,arch);
14:
15: // iteramos mientras no sea eof
16: while( !feof(arch) )
17: {
18: printf("%d\n",v);
19:
20: // leemos el siguiente registro del archivo
21: fread(&v,sizeof(int),1,arch);
22: }
23:
24: // cerramos el archivo
25: fclose(arch);
26:}
27:

Para leer un registro del archivo utilizamos la función fread. Esta función recibe el puntero a una variable en la cual va a asignar el registro que lea del archivo, recibe la longitud del tipo de registro (como es un archivo de enteros la longitud la obtenemos con sizeof(int), la cantidad de registros que debe leer y por último el puntero al archivo.


Acceso directo a Archivos

Al igual que en Pascal podemos posicionar el puntero del archivo directamente. Pero aquí la función fseek posiciona el puntero en el byte que se le indique, por lo tanto si queremos acceder al registro número n tendremos que mover el puntero n*m bytes siendo m el tamaño del registro del archivo.

Veamos el ejemplo:

testAccDirecto.h
   1:
2:struct RPersona
3:{
4: char nombre[20];
5: int edad;
6:}typedef RPersona;
7:

testAccDirecto.c
   1:
2:#include<stdio.h>
3:#include "testAccDirecto.h" // incluimos el .h
4:
5:int main()
6:{
7: int pos;
8: RPersona reg;
9: FILE *arch=fopen("PERSONAS.dat","r+");
10:
11: printf("Nro. Registro: ");
12: scanf("%d",&pos);
13:
14: while( pos >=0 ) // finaliza con edad < 0
15: {
16: // posiciona el puntero en el registro nro pos
17: // para esto multiplicamos pos por el
18: // tamanio del registro que lo obtenemos con
19: // la funcion sizeof
20: fseek(arch,pos*sizeof(RPersona),SEEK_SET);
21:
22: // lee el registro
23: fread(&reg,sizeof(RPersona),1,arch);
24:
25: printf("Nombre=[%s], Edad=[%d]\n",reg.nombre
26: ,reg.edad);
27:
28: printf("Nro. Registro: ");
29: scanf("%d",&pos);
30: }
31:
32: fclose(arch);
33:}
34:

Para mejorar el programa anterior sería bueno poder mostrar la cantidad de registros que tiene el archivo para que el usuario sepa con que rango de números de registro se puede manejar.

Lamentablemente en C (o al menos en lo que se conoce como "C standard" o "ANSI C") no existe una función que nos brinde esta información. En Pascal usamos la función filesize pero en C esa función no existe. La tenemos que programar.

Veremos como programar una función que reciba un puntero a archivo y retorna el tamaño del archivo apuntado por dicho putero. Luego, si queremos conocer la cantidad de registros del archivo tendremos que dividir el tamaño del archivo por el tamaño del registro, o en otras palabras:

cantReg = filesize(arch) / sizeof(TipoRegistro)

Para programar la función filesize utilizaremos la función de librería ftell. Esta función indica en que byte está posicionado el puntero del archivo. Entonces la estrategia para programar la función filesize será:
  1. Guardar la posición actual del puntero
  2. Mover el puntero al último byte
  3. Guardar la posición del último byte
  4. Mover el puntero a la posición original

testAccDirecto2.c
   1:
2:#include<stdio.h>
3:#include "testAccDirecto.h" // incluimos el .h
4:
5:// recibe un puntero a archivo y retorna el tamanio
6:// (en bytes) del archivo apuntado por el parametro
7:long filesize(FILE *fp)
8:{
9: // obtenemos la posicion actual del puntero
10: long curr = ftell(fp);
11:
12: // movemos el puntero al ultimo byte
13: fseek(fp, 0, SEEK_END);
14:
15: // obtenemos la posicion del ultimo byte
16: long size = ftell(fp);
17:
18: // retornamos el puntero a la posicion
19: // en la que estaba posicionado
20: fseek(fp, curr, SEEK_END);
21:
22: return size;
23:}
24:
25:int main()
26:{
27: int pos;
28: int cantRegistros;
29: RPersona reg;
30: FILE *arch=fopen("PERSONAS.dat","r+");
31:
32: // calculamos la cantidad de registros del
33: // archivo dividiendo el tamanio del archivo por
34: // el tamanio del registro
35: cantRegistros=filesize(arch)/sizeof(RPersona);
36:
37: printf("Cantidad de Registros = %d\n\n"
38: ,cantRegistros);
39:
40: printf("Nro. Registro (del 0 al %d): "
41: ,cantRegistros-1);
42: scanf("%d",&pos);
43:
44: while( pos >=0 ) // finaliza con edad < 0
45: {
46: // posiciona el puntero en el registro nro pos
47: // para esto multiplicamos pos por el
48: // tamanio del registro que lo obtenemos con
49: // la funcion sizeof
50: fseek(arch,pos*sizeof(RPersona),SEEK_SET);
51:
52: // lee el registro
53: fread(&reg,sizeof(RPersona),1,arch);
54:
55: printf("Nombre=[%s], Edad=[%d]\n",reg.nombre
56: ,reg.edad);
57:
58: printf("Nro. Registro (del 0 al %d): "
59: ,cantRegistros-1);
60: scanf("%d",&pos);
61: }
62:
63: fclose(arch);
64:}
65:

En el código podemos ver programada la función filesize y luego el programa principal que la utiliza para calcular (línea 35) cuantos registros tiene el archivo.






.

No hay comentarios: