• No se han encontrado resultados

Tema 11. ENTRADA Y SALIDA DE DATOS

N/A
N/A
Protected

Academic year: 2021

Share "Tema 11. ENTRADA Y SALIDA DE DATOS"

Copied!
31
0
0

Texto completo

(1)

1 FLUJOS DE DATOS... 2

1.1 FLUJOS POR DEFECTO... 3

1.2 TIPOS DE FLUJOS... 4

1.2.1 De entrada y/o salida... 4

1.2.2 De texto ... 4

1.2.3 Binarios... 4

2 FICHEROS ... 5

2.1 FICHEROS SECUENCIALES... 5

2.2 FICHEROS DIRECTOS... 6

2.3 FICHEROS INDEXADOS... 6

2.4 FICHEROS EN C ... 7

3 OPERACIONES BÁSICAS CON FLUJOS ... 8

3.1 APERTURA... 8

3.2 CIERRE... 8

3.3 LECTURA-ESCRITURA DE CARACTERES... 8

3.4 LECTURA-ESCRITURA BINARIA... 8

3.5 BORRADO DEL FICHERO ASOCIADO... 8

3.6 MOVIMIENTOS DE LA CABEZA DE LECTURA/ESCRITURA... 8

3.7 PROCESO BÁSICO DE LECTURA DE UN FLUJO... 9

3.8 PROCESO BÁSICO DE ESCRITURA EN UN FLUJO... 10

4 VARIABLES DE TIPO FLUJO... 10

5 FUNCIONES PARA ENTRADA Y SALIDA ... 11

5.1 ESPECIFICAS PARA FLUJOS POR DEFECTO... 11

5.1.1 printf()... 11

5.1.2 putchar() ... 13

5.1.3 puts() ... 13

5.1.4 scanf() ... 14

5.1.5 getchar()... 15

5.1.6 gets() ... 15

5.2 PARA FLUJOS NORMALES... 16

5.2.1 Funciones de Apertura... 17

5.2.2 Funciones de Cierre... 19

5.2.3 Funciones de lectura... 19

5.2.4 Funciones de Escritura ... 23

5.2.5 Funciones de movimiento... 26

5.2.6 Otras funciones ... 27

6 EJERCICIOS ... 30

COMPUTADORES

Curso 2001-2002

Escuela Universitaria de Ingeniería Técnica Industrial de Gijón

(2)

Tema 11. ENTRADA Y SALIDA DE DATOS.

A diferencia de otros lenguajes, C no dispone de sentencias de entrada/salida. En su lugar se utilizan funciones contenidas en la librería estándar y que forman parte integrante del lenguaje.

Las funciones de entrada/salida (Input/Output) son un conjunto de funciones, incluidas con el compilador, que permiten a un programa recibir y enviar datos al exterior. Para su utilización es necesario incluir, al comienzo del programa, el archivo “stdio.h” en el que están definidos sus prototipos:

#include <stdio.h>

Donde stdio proviene de standard-input-output.

1 Flujos de datos

Los datos de entrada que un programa necesita para que sean procesados son obtenidos por este de alguna fuente de datos, hasta ahora se han estado manejando programas cuya fuente de datos es el teclado. Los resultados de la ejecución son enviados a un destino de datos, en los programas vistos hasta ahora, la pantalla. Sin embargo, esta fuente y destino no tienen, ni mucho menos, porqué ser exclusivamente estos dispositivos. Pueden ser cualquier periférico de entrada, salida o que permita ambas operaciones.

Los periféricos destinados al almacenamiento masivo permiten guardar en ellos grandes cantidades de datos de forma organizada. Estos dispositivos permiten agrupar datos y acceder a ellos forma separada. Cada uno de estos conjuntos de datos, agrupados por algún criterio organizativo, es lo que se conoce habitualmente como fichero o archivo de datos. Así pues, un fichero es un conjunto de datos organizados bajo algún criterio, relativos a algún concepto y almacenado en un dispositivo externo.

Los programas son capaces de obtener y mandar datos de y hacia cualquiera de estos dispositivos e incluso de ficheros. Para ello es necesario que se establezca una vía de comunicación entre el programa y el fichero o dispositivo. Esa vía, o canal de comunicación, se suele llamar flujo de datos o stream (la mayor parte de la literatura emplea el término en inglés).

El siguiente gráfico ilustra el concepto:

Programa Dispositivo

Flujo de datos o Stream Datos de entrada

Datos de salida

(3)

Muchos sistemas operativos (UNIX, DOS, ...) ofrecen la facilidad a los usuarios de simular todos los periféricos (pantalla, teclado, impresora, líneas de comunicaciones, ...) con ficheros. Es decir, todas las entradas y salidas se hacen por medio de ficheros dado que todos los periféricos son tratados como ficheros por el sistema. Esto permite usar las mismas funciones para el manejo de todos ellos.

El lenguaje C añade a esto el concepto de stream.

En un programa C, para comunicar con el exterior es necesario crear un flujo (stream) que conecte el programa al dispositivo deseado (fichero o periférico). Una vez establecida la comunicación habrá un trasiego de datos y cuando el programa ya no necesite más comunicación deberá cerrar el flujo.

1.1 Flujos por defecto

Siempre que un programa C se inicia tiene a su disposición tres flujos abiertos automáticamente:

• La entrada estándar (stdin)

• La salida estándar (stdout)

• La salida de errores (stderr)

Habitualmente, la entrada estándar está conectada al teclado, la salida estándar esta conectada a la pantalla y la salida de error también está conectada a la pantalla. No obstante estas conexiones pueden ser alteradas desde el programa o desde el sistema operativo.

Para redireccionar los flujos por defecto desde el sistema operativo se emplean símbolos según la tabla mostrada a continuación:

Símbolo Redirecciona ... Ejemplo

< ... la entrada estandar a un fichero copy < con

> ... la salida estandar a un fichero dir > listado.txt 2> ... la salida de error a un fichero dir 2> errores.txt

Flujo estándar de entrada

Flujo estándar de salida

Flujo estándar de error Flujos por defecto

Programa

Teclado

Pantalla

(4)

1.2 Tipos de flujos

Según la dirección de en la que viaja la información pueden ser:

• De entrada

• De salida

• De entrada/salida

Según el tipo de datos que circulen por ellos pueden ser:

• De texto

• Binarios

1.2.1 De entrada y/o salida

Los flujos creados como de solo entrada solo permiten leer información del fichero al que están conectados, los de salida solo permiten enviarle información y los creados en el modo entrada/salida permiten hacer ambas operaciones.

1.2.2 De texto

Un flujo de texto es una secuencia de líneas; cada línea tiene cero o más caracteres y está terminada por una marca especial denominada “nueva línea”, en C se representa con el carácter '\n'.

Esta marca varía de unos sistemas operativos a otros, por ejemplo en sistemas operativos como DOS o Windows, es la secuencia de caracteres CR+LF (carry return y line feed), que corresponden a la posición 13 y 10 según la tabla ASCII. En sistemas operativos tipo UNIX, la marca es un único carácter, el LF.

1.2.3 Binarios

Un flujo binario es una secuencia de bytes no procesados que representan datos internos. Los datos que se graben en formato binario deben ser interpretados en la lectura de forma adecuada, cae de parte del programa el saber interpretar la secuencia de bytes. Por ejemplo, si se escriben tres int (supongamos 2 bytes por int) se guardarán 6 bytes. En la lectura posterior esos 6 bytes pueden ser interpretados de formas diversas, como 6 caracteres, como un float, etc. Casi con toda seguridad, los datos que se lean no tendrán ningún sentido a no ser que se

Pantalla Flujo estándar de

entrada redirigido desde un fichero

Flujo estándar de salida

Flujo estándar de error redirigido a un fichero

Flujos por defecto con redirección

datos.dat

Programa

errores.dat

(5)

También hay que tener en cuenta que entre distintos sistemas la representación de un mismo tipo de datos puede ser diferente.

2 Ficheros

Como ya se mencionó son un conjunto de datos almacenados en un dispositivo y con un nombre que permite localizarlo en el espacio de almacenamiento, o si se prefiere, una zona de almacenamiento con un nombre. En la zona destinada al fichero para guardar datos, estos se tendrán que disponer de alguna forma que permita su localización posterior, no pueden ser “tirados” al azar.

El mecanismo de inserción y recuperación de la información debe seguir una

”lógica” que el programa debe conocer y que estará en cierta medida condicionada por las características de los dispositivos.

En el caso de que el contenido del fichero sea una tabla de datos, con columnas de diversa naturaleza, como por ejemplo la siguiente:

DNI Apellidos Nombre Nota Teoría Nota Práctica

99.999.999 Fernández Alba Manuel 8.5 5.7

88.888.888 Cid Rodríguez Fernando 7.6 5.9

77.777.777 Álvarez Gutiérrez Martín 6.8 4.2

A cada fila se le denomina registro, y a cada columna campo del registro.

Según la forma de acceso a la información dentro del fichero estos pueden ser:

• Secuenciales

• Directos

• Indexados

2.1 Ficheros secuenciales

Un fichero de acceso Secuencial es aquel donde los registros están ubicados consecutivamente en un soporte de almacenamiento. De tal forma, que para acceder a un registro determinado, p.e. 'd', debemos pasar obligatoriamente por todos los registros que le preceden. Por ejemplo, en una cinta magnética, para leer el registro 'd' debemos pasar antes por el 'a', 'b', 'c'.

int i = 129;

int j = 1324;

int k = 123421;

Escritura de i en el stream 0000000010000001

Escritura de j en el stream

0000000010000001 0000000100101100 Escritura de k en el stream

0000000010000001 0000000100101100 0101101101111101

Secuencia almacenada:

000000001000000100000001001011000101101101111101

¿Cómo se interpreta?

(6)

... ‘a’ ‘b’ ‘c’ ‘d’ ‘e’ ‘f’ ...

Cabeza

lectora

Esta es la única organización posible en soportes secuenciales por naturaleza, cintas magnéticas, cintas perforadas, etc.

2.2 Ficheros directos

Un fichero de acceso Directo (es también denominado de acceso aleatorio) es aquel que se encuentra almacenado en un dispositivo direccionable; y donde sus registros poseen una numeración que identifica inequívocamente a cada registro. Así, se establece una correspondencia directa entre los números de registro y las direcciones lógicas en el soporte. Los registros se almacenan según el orden de entrada y no quedan ordenados.

De esta forma en un fichero de acceso Directo nos referimos a un registro por medio de su posición en este y se puede acceder directamente a él sin tener que tratar todos los anteriores. Por ejemplo, podremos obtener el reg número 4 sin pasar antes por los demás y tratar solo los datos de Carmen.

Reg Nombre Apellidos Dirección

1 María ... ...

2 Pedro ... ...

3 Luis ... ...

4 Carmen ... ...

5 Ana ... ...

6 Manuel ... ...

7 José ... ...

... ... ... ...

Este tipo de ficheros permite el procesamiento de datos que necesiten ser tratados en orden no secuencial y solo es eficiente si el fichero se almacena en dispositivos que por naturaleza permitan el acceso aleatorio: cd-rom, disquete, disco duro, ...

2.3 Ficheros indexados

Un fichero de acceso Indexado es aquel que se encuentra almacenado en un dispositivo direccionable; y donde sus registros poseen un campo que denominaremos campo clave principal y que identifica inequívocamente a cada registro. La clave principal es un campo del registro que tiene siempre un valor diferente a los ya introducidos, es decir, dentro del fichero Indexado no puede haber dos registros con los campos clave principal iguales.

La gran ventaja de este tipo de ficheros es que usando el campo clave se puede localizar de manera inmediata el registro correspondiente al valor buscado, en el ejemplo a continuación:

Clave principal Clave secundaria Clave secundaria

Reg DNI Nombre Provincia Edad

1 55.366.546 Luis Sevilla 20

2 42.386.225 Teresa Palencia 22

3 32.387.203 Carlos Oviedo 35

(7)

4 46.399.554 Inmaculada Gijón 12

5 60.643.434 Joaquín Salamanca 46

6 22.543.986 José Las Palmas 25

Si se desea localizar el registro perteneciente al DNI 60.643.434 basta con pedir la lectura de ese registro e inmediatamente se recuperará toda la información relativa a Joaquín.

Además del campo clave principal pueden existir otros campos claves secundarios que permitirán la recuperación inmediata por otros criterios de búsqueda.

2.4 Ficheros en C

Los ficheros en C son todos secuenciales. A cada operación de lectura o escritura la cabeza avanza una posición. Estando la cabeza en la posición n la siguiente operación de lectura/escritura dejará la cabeza en la posición n+1.

Estado inicial (se acaba de leer ‘b’):

... ‘a’ ‘b’ ‘c’ ‘d’ ‘e’ ‘f’ ...

Cabeza

lectora Lectura de ‘c’:

... ‘a’ ‘b’ ‘c’ ‘d’ ‘e’ ‘f’ ...

Cabeza

lectora Escritura de ‘m’ (machacando ‘d’):

... ‘a’ ‘b’ ‘c’ ‘m’ ‘e’ ‘f’ ...

Cabeza lectora

Cuando se abre un fichero para lectura o escritura la cabeza está situada al inicio de este.

‘1’ ‘2 ‘3 ‘4 ‘5 ‘a ‘b ...

Cabeza lectora

Cuando se llega al final, tras haber recorrido todos los datos, la cabeza no podrá avanzar más indicándose al programa la condición de “fin de fichero”.

... ‘t’ ‘u’ ‘v’ ‘w’ ‘x’ ‘y’ ‘z’

Cabeza lectora

(8)

3 Operaciones básicas con flujos 3.1 Apertura

Consiste en crear y conectar un flujo a un fichero o dispositivo. A la hora de la creación es necesario especificar el tipo de flujo que se crea: texto o binario, entrada y/o salida.

Si el flujo es de salida y se conecta a un fichero que no existe, se creará el fichero vacío. Si es de salida y ya existía un fichero con el mismo nombre será borrado y creado de nuevo vacío.

Si el flujo es de entrada y se conecta a un fichero que no existe se devolverá un error.

3.2 Cierre

Es la desconexión de la comunicación con el dispositivo o fichero. No implica el borrado del fichero.

3.3 Lectura-Escritura de caracteres

En el caso de haber creado un flujo de texto esta es la operación a emplear para recuperar o enviar datos de forma correcta. Los flujos de texto realizan alguna transformación en los datos y detectan condiciones especiales para facilitar el tratamiento de estos.

3.4 Lectura-Escritura binaria

Si el flujo se creó como binario las lecturas y escrituras se realizan sin transformación alguna en los datos, en binario. El programa debe saber tratar los datos que maneja.

3.5 Borrado del fichero asociado

Elimina del almacenamiento todos los datos recuperando el espacio como libre.

3.6 Movimientos de la cabeza de lectura/escritura

A través del flujo se pueden indicar movimientos a la cabeza de lectura del fichero, distintos del movimiento de avance implícito en todas las lecturas y escrituras. Se puede hacer que la cabeza avance a determinada posición del fichero indicada en bytes, o posicionar la cabeza en la posición inicial.

Estos movimientos de la cabeza lectora se indican de forma lógica, los movimientos mecánicos que deban ser realizados influirán en los tiempos de respuesta de las operaciones. Es decir, si se ordena mover la cabeza a determinada posición alejada de la actual y el fichero accedido está en una cinta magnética el tiempo será largo, por contra si el fichero está almacenado en un dispositivo que permite el acceso aleatorio el tiempo de movimiento será mínimo.

Este tipo de movimientos permite “construir” funciones que calculen posiciones de acceso a datos de ficheros en función de claves y obtener así ficheros indexados o directos. Es decir, C no provee de este tipo de ficheros pero si tiene herramientas para construirlos. De hecho hay muchas librerías en el mercado que permiten manejar este tipo de ficheros.

(9)

3.7 Proceso básico de lectura de un flujo

Proceso de lectura

Declarar Variable de tipo FLUJO FLUJO = Crear un flujo para lectura datos = leer del FLUJO

MIENTRAS no FIN de FICHERO . . .

<< procesar datos >>

. . .

datos = leer del FLUJO Fin de MIENTRAS

Cerrar FLUJO . . .

. . . Fin de proceso

NO

SI

¿FIN? Procesar

Crear el flujo

Cerrar el flujo Leer del flujo

(10)

3.8 Proceso básico de escritura en un flujo

Proceso de escritura

Declarar Variable de tipo FLUJO

FLUJO = Crear un flujo para escritura datos = generación de datos

MIENTRAS no FIN de generación de datos . . .

escribir datos en el flujo . . .

Fin de MIENTRAS Cerrar FLUJO . . .

. . . Fin de proceso

4 Variables de tipo flujo

La forma de representar un flujo en C es empleando una variable del tipo adecuado. La siguiente sentencia...

FILE *f;

...declara una variable, llamada f, de tipo puntero a un tipo de dato FILE, declarado y definido en el fichero de inclusión <stdio.h>.

FILE es una estructura dependiente de la implementación y de la plataforma y sobre ella saben trabajar todas las funciones de entrada/salida que empleen flujos.

NO

SI

¿FIN?

Generar datos Crear el flujo

Cerrar el flujo Escribir en flujo

(11)

La definición real del tipo FILE es irrelevante, no obstante puede verse editando el fichero de inclusión citado. Esta definición cambia de unos compiladores a otros, de unos sistemas operativos a otros, etc. pero lo importante es que declarar una variable de ese tipo es declarar una variable de tipo flujo.

Sobre la variable declarada de la forma indicada se realizan entonces todas la operaciones aplicables a los flujos.

Los flujos estándar también son variables de tipo puntero a FILE pero no deben ser declararlas ya que el compilador lo hace implícitamente. O sea, en algún sitio oculto existen declaraciones equivalentes a:

FILE *stdin;

FILE *stdout;

FILE *stderr;

Y sobre ellas el compilador, habrá aplicado las operaciones de apertura pertinentes, de forma que cuando empieza la ejecución de nuestro código estos flujos ya están listos para su uso.

5 Funciones para Entrada y salida

Todas las funciones que se presentan en este apartado están declaradas en el fichero de cabeceras <stdio.h>.

5.1 Especificas para flujos por defecto

Cuando comienza la ejecución de un programa, los flujos stdin, stdout y stderr ya están abiertos. La flexibilidad de la redirección y el hecho de que los ficheros (flujos) estándar estén abiertos por defecto hace cómoda su utilización. Las funciones más sencillas para manejar estos son las siguientes:

• Para escritura o printf() o putchar() o puts()

• Para lectura o scanf() o getchar() o gets()

5.1.1 printf()

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

La función printf() imprime el texto contenido en el string “format” junto con el valor de los otros argumentos, de acuerdo con los especificadores de formato incluidos en “format”. Los puntos suspensivos (...) indican que puede haber un número variable de argumentos. Cada especificador de formato comienza con el carácter (%) y termina con un carácter de conversión.

Esta función tiene un valor de retorno de tipo int, que representa el número de caracteres escritos con éxito en el flujo.

Los especificadores de formato tiene una sintaxis muy completa que permite adaptar todos los tipos de datos a múltiples presentaciones. Para comprenderlos se recomienda leer la ayuda del compilador (y sobre todo ejercitarlos).

(12)

Considérese el ejemplo siguiente...

int i;

double tiempo;

float masa;

printf("Resultado nº: %d. En el instante %lf la masa vale %f\n", i, tiempo, masa);

... en el que se imprimen 3 variables (i, tiempo y masa) con los formatos (%d, %lf y %f), correspondientes a los tipos (int, double y float), respectivamente. La cadena de control se imprime con el valor de cada variable intercalado en el lugar del especificador de formato correspondiente. Lo importante es considerar que debe haber correspondencia uno a uno (el 1º con el 1º, el 2º con el 2º, etc.) entre los formatos que aparecen en la cadena de control y los argumentos (constantes, variables o expresiones).

Entre el carácter % y el carácter de conversión (type_char) puede haber, por el siguiente orden, uno o varios de los elementos que a continuación se indican:

% [flags] [width] [.prec] [h|l|L] type_char

[flags]

Pueden ser ninguno, uno o varios de los siguientes caracteres:

o Un signo (-), que indica alineamiento por la izda (el defecto es por la dcha). Se rellena a la derecha con espacios.

o Un signo (+). Si el argumento es un número siempre antepone el signo positivo o negativo a la cifra según corresponda.

o Un espacio en blanco. Si el argumento es una cifra y esta es negativa siempre aparecerá el signo negativo. Si es no negativa la cifra irá precedida de un espacio en blanco, es decir mentiene el alineamiento de las cifras sin necesidad de indicar el signo +.

[width]

Un número entero positivo, que indica la anchura mínima del campo en caracteres.

[.prec]

Un número entero positivo, la precisión, que es el nº máximo de caracteres a imprimir en un string, el nº de decimales de un float o double, o las cifras mínimas de un int o long.

[h|l|L]

Es un modificador del tipo indicado por type_char: una (h) para short, una (l) para long y una (L) para long double.

type_char

Es el carácter de conversión, indica la naturaleza del argumento que va a continuación. Los caracteres de conversión más usuales se muestran en la tabla siguiente:

Carácter Tipo del argumento Carácter Tipo del argumento

d, i int decimal o Octal unsigned

u unsigned int x, X Hexadecimal unsigned

c Char s String

f Float en notación decimal e, g Float notación cientifica

(13)

p puntero void*

A continuación se incluyen algunos ejemplos de uso de la función printf(). El primer ejemplo contiene sólo texto, por lo que basta con considerar la cadena de formato.

printf("Con cien cañones por banda,\nviento en popa a toda vela,\n");

El resultado serán dos líneas con las dos primeras estrofas de la famosa poesía. No es posible partir la cadena de formato en varias líneas con caracteres intro, por lo que en este ejemplo podría haber problemas para añadir más estrofas. Una forma alternativa, muy sencilla, clara y ordenada, de escribir la poesía sería la siguiente:

printf("%s\n%s\n%s\n%s\n"

, "Con cien cañones por banda,"

, "viento en popa a toda vela,"

, "no cruza el mar sino vuela,"

, "un velero bergantín."

);

En este caso se están escribiendo 4 cadenas constantes de caracteres que se introducen como argumentos, con formato %s y con los correspondientes saltos de línea. Un ejemplo que contiene una constante y una variable como argumentos es el siguiente:

printf("En el año %s ganó %ld ptas.\n", "1993", beneficios);

donde el texto 1993 se imprime como cadena de caracteres (%s), mientras que beneficios se imprime con formato de variable long (%ld). Es importante hacer corresponder bien los formatos con el tipo de los argumentos, pues si no los resultados pueden ser muy diferentes de lo esperado.

5.1.2 putchar()

int putchar(int c);

putchar(c) envía el carácter que recibe como argumento al flujo estándar de salida.

En caso de éxito devuelve el carácter mismo carácter que recibió, en caso de error devuelve el carácter “EOF”.

El ejemplo a continuación muestra en pantalla todos los caracteres desde la ‘A’

hasta la ‘Z’:

#include <stdio.h>

void main(void) {

char i, j;

for (I = ’A’; i <= ‘Z’; i++) putchar(i);

}

5.1.3 puts()

int puts(const char *s);

Envía un string al flujo de estándar de salida y al final inserta el carácter ‘\n’ (nueva línea) automáticamente. Si todo va bien devuelve un valor positivo, en caso de fallo devuelve EOF.

(14)

#include <stdio.h>

int main(void) {

char string[] = "This is an example output string";

puts(string);

return 0;

}

5.1.4 scanf()

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

La función scanf() es análoga en muchos aspectos a printf(), y se utiliza para leer datos de la entrada estándar (que por defecto es el teclado).

La cadena “format”, llamada cadena de control, contiene (al igual que printf()) especificadores de formato empleados para indicar a la función a que tipo de dato tiene que convertir lo que lee desde la entrada estándar. Los caracteres de conversión están mostrados en la tabla a continuación:

Carácter Caracteres leídos Tipo del argumento

C Carácter char *

d, i Entero decimal con signo int *

U Entero decimal sin signo unsigned int

O Entero octal unsigned int

x, X Entero hexadecimal unsigned int

e, E, f, g, G Número en punto flotante Float

S String sin espacios Char

h, l Modificador para short, long y double L Modificador para long double

La función scanf() devuelve como valor de retorno el número de conversiones de formato realizadas con éxito.

La cadena de control de scanf() puede contener caracteres además de formatos. En ese caso, se utilizan para tratar de detectar la presencia de caracteres idénticos en la entrada por teclado. Si lo que se desea es leer variables numéricas, esta posibilidad tiene escaso interés. A veces hay que comenzar la cadena de control con un espacio en blanco para que la conversión de formatos se realice correctamente.

En la función scanf() los argumentos que siguen a la cadena de control deben ser pasados por referencia, ya que la función los lee y tiene que trasmitirlos al programa que la ha llamado. Para ello, dichos argumentos deben estar constituidos por las direcciones de las variables en las que hay que depositar los datos, y no por las propias variables. Una excepción son las cadenas de caracteres, cuyo nombre es ya de por sí una dirección (un puntero), y por tanto no debe ir precedido por el operador (&) en la llamada.

Por ejemplo, para leer los valores de dos variables int y double y de una cadena de caracteres, se utilizarían la sentencia:

int n;

double distancia;

char nombre[20];

scanf("%d%lf%s", &n, &distancia, nombre);

(15)

en la que se establece una correspondencia entre n y %d, entre distancia y %lf, y entre nombre y %s. Obsérvese que nombre no va precedido por el operador (&). La lectura de cadenas de caracteres se detiene en cuanto se encuentra un espacio en blanco, por lo que para leer una línea completa con varias palabras hay que utilizar otras técnicas diferentes.

En los formatos de la cadena de control de scanf() pueden introducirse corchetes [...], que se utilizan como sigue. La sentencia,

scanf("%[AB \n\t]", s); // se leen solo los caracteres indicados lee caracteres hasta que encuentra uno diferente de (’A’,’B’,’ ’,’\n’,’\t’). En otras palabras, se leen sólo los caracteres que aparecen en el corchete. Cuando se encuentra un carácter distinto de éstos se detiene la lectura y se devuelve el control al programa que llamó a scanf(). Si los corchetes contienen un carácter (^), se leen todos los caracteres distintos de los caracteres que se encuentran dentro de los corchetes a continuación del (^). Por ejemplo, la sentencia,

scanf(" %[^\n]", s);

lee todos los caracteres que encuentra hasta que llega al carácter nueva línea ’\n’.

Esta sentencia puede utilizarse por tanto para leer líneas completas, con blancos incluidos.

Recuérdese que con el formato %s la lectura se detiene al llegar al primer delimitador (carácter blanco, tabulador o nueva línea).

5.1.5 getchar()

int getchar(void);

Lee un carácter de la entrada estándar. Si no hay problemas devuelve el carácter leído después de convertirlo a entero sin signo. En caso de error retorna EOF.

En el ejemplo a continuación se escriben en la salida estándar todos los caracteres leídos de la entrada hasta que se lee el carácter ‘\n’.

#include <stdio.h>

int main(void) {

int c;

while ((c = getchar()) != '\n') printf("%c", c);

return 0;

}

5.1.6 gets()

char *gets(char *s);

Lee un string de la entrada estándar. Lee de la entrada hasta que se detecta el carácter de nueva línea, entonces devuelve lo leído excepto la marca de nueva línea. La cadena leída puede tener espacios en blanco o tabuladores lo que hace que esta función sea más cómoda para leer strings que scanf() con el especificador

%s.

(16)

Si no hay problemas en la lectura la función retorna el mismo string recibido como argumento pero relleno. En caso de error retorna el puntero NULL.

#include <stdio.h>

int main(void) {

char string[80];

printf("Dame un string:");

gets(string);

printf("El string leído fue: %s\n", string);

return 0;

}

5.2 Para flujos normales

• Apertura o fopen() o freopen()

• Cierre

o fclose() o fcloseall()

• Lectura

o fread() o fscanf() o fgets() o getc()

• Escritura o fwrite() o fprintf() o fputs() o putc()

• Movimientos de la cabeza o fgetpos()

o fseek() o fsetpos() o ftell() o rewind() o feof()

• Otras acciones o fflush() o ferror() o perror()

o strerror() o remove()

o rename() o tmpfile()

(17)

5.2.1 Funciones de Apertura

5.2.1.1 fopen()

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

Esta función crea un flujo de datos sobre el fichero o dispositivo referido por el argumento la cadena de caracteres “filename” y el flujo se crea del tipo especificado por el segundo parámetro, el string “mode”.

La cadena “mode” puede estar formada de esta manera:

Cadena Modo de apertura

“r” Solo lectura en modo texto

“w” Escritura desde el comienzo en modo texto

“a” Escritura en modo texto añadiendo al final del archivo. Si el archivo no existía se crea.

“r+” Abre un flujo conectado a un fichero de texto existente para lectura y escritura

“w+” Abre un flujo conectado a un fichero de texto, que se crea de nuevo aunque exista, para lectura y escritura

Texto

“a+” Abre un flujo conectado a un fichero de texto que ya existe (y sino se crea) para lectura y escritura.

“rb” Solo lectura en modo binario

“wb” Escritura desde el comienzo en modo binario

“ab” Escritura en modo binario añadiendo al final del archivo

“rb+” Abre un flujo conectado a un fichero binario existente para lectura y escritura

“wb+” Abre un flujo conectado a un fichero binario que se crea de nuevo aunque exista para lectura y escritura

Binario

“ab+” Abre un flujo conectado a un fichero binario que ya existe (y sino se crea) para lectura y escritura.

En caso de que todo vaya bien la función devuelve un puntero a FILE que deberemos asignar a la variable declarada de ese tipo. Si hay algún error devuelve un puntero nulo (NULL).

El ejemplo a continuación crea una copia del fichero de texto “autoexec.bat”:

#include <stdio.h>

int main(void) {

FILE *in, *out;

if ((in = fopen("\\AUTOEXEC.BAT", "r")) == NULL) {

fprintf(stderr, "No puede abrir fichero de entrada\n");

return 1;

}

if ((out = fopen("\\AUTOEXEC.BAK", "w")) == NULL) {

fprintf(stderr, "No puede abrir fichero de salida\n");

return 1;

}

(18)

while (!feof(in))

fputc(fgetc(in), out);

fclose(in);

fclose(out);

return 0;

}

Se trabaja sobre dos flujos, por lo que se necesitan dos variables:

FILE *in, *out;

Es necesario crear los dos flujos, se emplea la función vista indicando el modo de apertura adecuado: “r” para el de lectura, “w” para el de escritura. Es necesario siempre comprobar que el fichero ha podido ser abierto con éxito. En el ejemplo las dos operaciones se hacen sobre la marcha:

if ((in = fopen("\\AUTOEXEC.BAT", "r")) == NULL) Pero para mayor claridad se pueden descomponer en dos:

in = fopen("\\AUTOEXEC.BAT", "r");

if (in == NULL) {

...

5.2.1.2 freopen()

FILE *freopen(const char *filename, const char *mode, FILE *stream);

Asocia el stream ya existente que se le pasa como último parámetro a un fichero distinto cuyo nombre se le pasa en el parámetro “filename” y lo abre en el nuevo modo indicado por “mode”.

El stream existente antes de ejecutar la operación siempre cierra primero la conexión con el fichero original.

Si la función se ejecuta sin problemas retorna el mismo puntero a stream que recibe. En caso de fallo devuelve el puntero NULL.

Esta función es útil para redirigir los flujos de estándar por programa. El siguiente fragmento de código es un ejemplo de ello:

#include <stdio.h>

int main(void) {

if (freopen("OUTPUT.FIL", "w", stdout) == NULL)

fprintf(stderr, "error al redireccionar stdout\n");

/* Esto saldra en el fichero y no en la pantalla */

printf("This will go into a file.");

fclose(stdout);

return 0;

}

(19)

5.2.2 Funciones de Cierre

5.2.2.1 fclose()

int fclose(FILE *stream);

Cierra el flujo de datos. La variable “stream” ya no puede volver a ser usada a no ser que vuelva a emplear otra vez la función open() o reopen().

En caso de error devuelve EOF, en caso de que todo vaya bien devuelve 0.

5.2.2.2 fcloseall() int fcloseall(void);

Cierra todos los flujos abiertos por el programa hasta ese momento excepto los flujos por defecto.

5.2.3 Funciones de lectura

5.2.3.1 fscanf()

int fscanf(FILE *stream, const char *format[, address, ...]);

Tiene el mismo funcionamiento que scanf() con el matiz de que lee del flujo que se la pasa como primer parámetro y no del flujo estándar de entrada. El segundo y siguientes parámetros tienen la misma consideración que en la función ya vista.

Retorna el numero de campos leídos con éxito.

El ejemplo a continuación emplea la función para leer del flujo de entrada:

#include <stdlib.h>

#include <stdio.h>

int main(void) {

int i;

printf("Input an integer: ");

if (fscanf(stdin, "%d", &i))

printf("The integer read was: %i\n", i);

else {

fprintf(stderr, "Error reading an integer from stdin.\n");

exit(1);

}

return 0;

}

Si se aplica sobre cualquier otro flujo este debe ser abierto previamente.

5.2.3.2 fgets()

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

Lee caracteres del flujo representado por el ultimo parámetro y los deja en el string pasado como primer parámetro. Esta función deja de leer del flujo cuando lleva leídos n-1 caracteres (segundo parámetro) o cuando se encuentra la marca de fin

(20)

de línea entre los caracteres leídos. La marca de fin de línea también se copia en el string y le añade además la marca de fin de string.

En caso de éxito devuelve un puntero al string recién leído. Devuelve NULL como indicación de fin de fichero.

El ejemplo a continuación emplea la función para contar el numero de líneas de un fichero de texto.

#include <stdio.h>

#include <stdlib.h>

#define FICHERO "texto.txt"

void main(void) {

FILE *f;

int nl;

char string[256];

f = fopen(FICHERO, "r");

if (f == NULL) {

perror("Abriendo");

exit(-1);

}

nl = 0;

while(fgets(string, sizeof(string), f) != NULL) {

nl++;

}

fclose(f);

printf("El fichero tiene %d lineas\n", nl);

getchar();

}

5.2.3.3 fread()

size_t fread(void *ptr, size_t size, size_t n, FILE *stream);

Lee bloques de datos del flujo representado por el último parámetro. Trata de leer tantos bloques de bytes como indique “n” del tamaño de bloque indicado por el parámetro “size” y el resultado de la lectura lo deja en el buffer apuntado por el puntero “ptr”.

Devuelve el numero de bloques leídos con éxito (en un tipo size_t definido en stdio.h y equivalente a un long int). Cuando se llega al final del fichero puede que el último bloque no se haya leído completo por lo que es necesario tener precaución para controlar esta situación.

El siguiente ejemplo lee un fichero binario y muestra en pantalla detalles de las operaciones de lectura y en la salida de error escribe una copia del fichero leído.

#include <stdio.h>

#include <stdlib.h>

#define BUFF_SIZE 32

(21)

void main(void) {

FILE *f;

char buffer[BUFF_SIZE];

int n, bytesLeidos, lecturas;

f = fopen("datos.dat", "rb");

if (f == NULL) {

printf("Error abriendo el fichero\n\a");

exit(-1);

}

lecturas = 0;

do {

n = fread(buffer, BUFF_SIZE, 1, f);

bytesLeidos = n * BUFF_SIZE;

lecturas++;

printf("leidos %d, lectura %d\n", leidos, lecturas);

if (leidos > 0) {

fwrite(buffer, BUFF_SIZE, 1, stderr);

}

}while(leidos == BUFF_SIZE);

fclose(f);

}

Este código no es correcto ya que la última lectura del fichero puede no leer un buffer completo, pero la operación de escritura si escribe el buffer entero que estará relleno al final con caracteres falsos.

Como conclusión: esta función de lectura debe solo emplearse en fichero que tienen un formato controlado o múltiplo del tamaño del buffer empleado en la lectura. El ejemplo siguiente emplea correctamente la función ya que lee un fichero de registros de tamaño conocido:

#include <stdio.h>

typedef struct {

int dia;

int mes;

int anio;

char refCuenta[20];

float debe;

float haber;

}ApunteContable;

void main(void) {

FILE *f;

ApunteContable registro;

float totalDebe = 0;

float totalHaber = 0;

(22)

f = fopen("apuntesContables.dat", "rb");

if (f == NULL) {

printf("Error abriendo el fichero\n\a");

exit(-1);

}

fread(&registro, sizeof(ApunteContable), 1, f);

while(! feof(f)) {

totalDebe += registro.debe;

totalHaber += registro.haber;

fread(&registro, sizeof(ApunteContable), 1, f);

};

fclose(f);

printf("Debe %d, Haber %d\n", totalDebe, totalHaber);

getchar();

} Es de notar:

• La apertura en modo binario del flujo:

f = fopen("apuntesContables.dat", "rb");

• La lectura de cada registro:

fread(&registro, sizeof(ApunteContable), 1, f);

Donde a la función fread() se le pasa el puntero a la variable que va a acoger el registro (uso de &) y se le indica que lea 1 bloque de tantos bytes como tamaño tiene un registro, sizeof(ApunteContable).

• Primero se hace una lectura para poder detectar la condición de fin de fichero y no se ha alcanzado entonces se da por buena la lectura y tratan los datos.

5.2.3.4 getc()

int getc(FILE *stream);

Devuelve el carácter apuntado por la cabeza de lectura y avanza esta una posición.

El carácter es convertido a un int. Si no se ha podido leer nada devuelve EOF.

El ejemplo a continuación muestra en pantalla el contenido de un fichero:

#include <stdio.h>

void main(void) {

FILE *f;

int c;

f = fopen("c:\autoexec.bat", "r");

if (f == NULL) {

printf("Error abriendo el fichero\n\a");

exit(-1);

}

(23)

while((c = getc(f)) != EOF) {

putchar(c);

}

fclose(f);

}

Obsérvese el uso del bucle:

while((c = fgetc(f)) != EOF)

La función fgetc(f) lee del flujo f un carácter que asigna a la variable c y posteriormente el valor de la variable es comparado con la indicación EOF de fin de fichero. Esta condición determina el fin del bucle de lectura y escritura.

5.2.4 Funciones de Escritura

5.2.4.1 fprintf()

int fprintf(FILE *stream, const char *format[, argument, ...]);

Tiene el mismo funcionamiento que printf() con el matiz de que escribe en el flujo que se la pasa como primer parámetro y no del flujo estándar de salida. El segundo y siguientes parámetros tienen la misma consideración que en la función ya vista.

Retorna el numero de caracteres enviados con éxito.

#include <stdio.h>

int main(void) {

FILE *stream;

int i = 100;

char c = 'C';

float f = 1.234;

stream = fopen("DUMMY.FIL", "w+");

fprintf(stream, "%d %c %f", i, c, f);

fclose(stream);

return 0;

}

5.2.4.2 fputs()

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

Manda al flujo indicado como último parámetro la cadena de caracteres pasada como primero. No añade al flujo de salida las marcas de fin de línea ni de fin de string.

Si todo funciona correctamente devuelve un valor positivo, en caso de error devuelve la constante EOF.

El ejemplo a continuación copia un fichero en otro.

(24)

#include <stdlib.h>

#define FICHERO "eula.txt"

#define FICHERO_SALIDA "copia.txt"

void main(void) {

FILE *fe;

FILE *fs;

char string[256];

fe = fopen(FICHERO, "r");

fs = fopen(FICHERO_SALIDA, "w");

if (fe == NULL || fs == NULL) {

perror("Abriendo");

exit(-1);

}

while(fgets(string, sizeof(string), fe) != NULL) {

fputs(string, fs);

}

fcloseall();

getchar();

}

5.2.4.3 fwrite()

size_t fwrite(const void *ptr, size_t size, size_t n, FILE *stream);

Es la función recíproca de fread(). Recibe los mismos parámetros: un puntero al buffer de memoria donde están los datos que se desean escribir, el numero de bloques que hay en el buffer, el tamaño del bloque y el flujo de escritura al que se van a enviar los datos.

Devuelve el numero de bloques escritos con éxito.

El ejemplo a continuación crea el fichero de apuntes contables leídos en el ejemplo de la función fread().

#include <stdio.h>

#include <stdlib.h>

typedef struct {

int dia;

int mes;

int anio;

char refCuenta[20];

float debe;

float haber;

}ApunteContable;

void main(void) {

FILE *f;

ApunteContable registro;

extern int errno;

char c;

(25)

f = fopen("apuntesContables.dat", "ab");

if (f == NULL) {

printf("Error abriendo el fichero\n\a");

puts(strerror(errno));

getc(stdin);

exit(-1);

} do {

printf("Anio: "); scanf("%d", &registro.anio);

printf("Mes: "); scanf("%d", &registro.mes);

printf("Dia: "); scanf("%d", &registro.dia);

printf("Cuenta: "); scanf("%s", registro.refCuenta);

printf("Debe: "); scanf("%f", &registro.debe);

printf("Haber: "); scanf("%f", &registro.haber);

fwrite(&registro, sizeof(ApunteContable), 1, f);

printf("Mas registros? (s/n): ");

c = (char)getchar();

}while(c != 's');

fclose(f);

}

5.2.4.4 putc()

int putc(int c, FILE *stream);

Manda al flujo “stream” el carácter que le llega como primer parámetro. Devuelve el carácter enviado en caso de éxito y si no devuelve el carácter especial EOF.

#include <stdio.h>

void main(void) {

FILE *fe, *fs;

int c;

fe = fopen("c:\autoexec.bat", "r");

if (fe == NULL) {

printf("Error abriendo el fichero\n\a");

exit(-1);

}

while((c = getc(fe)) != EOF) {

putc(c, fs);

}

fcloseall();

}

(26)

5.2.5 Funciones de movimiento

5.2.5.1 fseek()

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

Mueve lacabeza de lectura/escritura del flujo “stream” a “offset” bytes de distancia desde el punto de referencia indicado por “whence”. Donde “whence” puede tomar los siguientes valores:

SEEK_SET 0 Desde el principio del fichero

SEEK_CUR 1 Desde la posición actual de la cabeza SEEK_END 2 Desde el final del fichero

#include <stdio.h>

long filesize(FILE *stream);

int main(void) {

FILE *stream;

stream = fopen("MYFILE.TXT", "w+");

fprintf(stream, "This is a test");

printf("Filesize of MYFILE.TXT is %ld bytes\n"

, filesize(stream) );

fclose(stream);

return 0;

}

long filesize(FILE *stream) {

long curpos, length;

curpos = ftell(stream);

fseek(stream, 0L, SEEK_END);

length = ftell(stream);

fseek(stream, curpos, SEEK_SET);

return length;

}

La función construida “filesize()” calcula el tamaño en bytes del fichero empleando las funciones ftell() y fseek(). Obtiene la posición actual de la cabeza, mueve la cabeza al final y obtiene la posición en bytes. Ese valor es el tamaño total.

Nota: Hay otras formas de obtener este valor, aquí se resuelve así para ejemplificar el uso de fseek() y ftell().

5.2.5.2 ftell()

long int ftell(FILE *stream);

Devuelve la posición en bytes desde el inicio del fichero de la cabeza. Solo es fiable si el flujo es binario.

Véase el ejemplo anterior.

5.2.5.3 rewind()

void rewind(FILE *stream);

(27)

Reposiciona la cabeza al principio de fichero. El mismo efecto se podría haber conseguido con la función fseek().

#include <stdio.h>

#include <dir.h>

int main(void) {

FILE *fp;

char first;

fp = fopen(“prueba.txt”,"w+");

fprintf(fp, "abcdefghijklmnopqrstuvwxyz");

rewind(fp);

fscanf(fp,"%c",&first);

printf("El primer caracter es: %c\n", first);

fclose(fp);

return 0;

}

5.2.5.4 feof()

int feof(FILE *stream);

Detecta la condición de fin de fichero, es decir que la cabeza está al final y no es posible hacer más lecturas.

El ejemplo muestra en pantalla el contenido de un fichero de texto.

#include <stdio.h>

int main(void) {

FILE *stream;

stream = fopen("DUMMY.FIL", "r");

while(!feof(stream)) {

putchar(fgetc(stream));

}

fclose(stream);

return 0;

}

5.2.6 Otras funciones

5.2.6.1 fflush()

int fflush(FILE *stream);

Elimina del flujo todos los caracteres que aún no han sido procesados. Si el stream es de salida son enviados el fichero y el flujo se limpia, si es de lectura se borran todos los datos pendientes de procesar. Es útil al hacer lecturas de teclado para eliminar los posibles caracteres extraños antes de hacer una lectura.

Retorna 0 en caso de éxito o EOF en caso de problemas.

(28)

...

fflush(stdin);

c = getchar();

...

5.2.6.2 ferror()

int ferror(FILE *stream);

Detecta errores de lectura o escritura en el stream que se le pasa como argumento.

En caso de que existan devuelve TRUE y sino FALSE.

#include <stdio.h>

int main(void) {

FILE *stream;

// Se abre un flujo para escritura stream = fopen("DUMMY.FIL", "w");

// Se fuerza un error al intentar leer (void) getc(stream);

if (ferror(stream)) {

printf("Error leyendo de DUMMY.FIL\n");

}

fclose(stream);

return 0;

}

5.2.6.3 perror()

void perror(const char *mensaje);

Escribe en el flujo estándar de error un mensaje descriptivo del ultimo error de entrada/salida ocurrido en cualquier flujo. El mensaje está compuesto por la cadena

“mensaje” más un mensaje generado por el sistema separado por el carácter “:”

del aportado como parámetro.

#include <stdio.h>

int main(void) {

FILE *fp;

fp = fopen("perror.dat", "r");

if (!fp)

perror("No se puede abrir");

return 0;

}

En el ejemplo se obtendría el mensaje:

“No se puede abrir : No such file or directory”

Compuesto por el mensaje que se le pasa a la función más una descripción que da el sistema.

(29)

5.2.6.4 remove()

int remove(const char *filename);

Borra el fichero de nombre el indicado por el parámetro “filename”. No debe haber flujos conectados al fichero para poder ejecutar esta función. Si tras la ejecución se pudo borrar el fichero devuelve un 0, en caso de error devuelve –1.

#include <stdio.h>

int main(void) {

char file[80];

printf("File to delete: ");

gets(file);

if (remove(file) == 0)

printf("Removed %s.\n",file);

else

perror("remove");

return 0;

}

5.2.6.5 tmpfile() FILE *tmpfile(void);

Crea un flujo conectado a un fichero temporal creado con el modo “wb+”. O sea, el fichero se crea vacío, en modo binario y para lectura y escritura. El fichero se borra automáticamente al cerrar el flujo asociado a él o al terminar el programa.

#include <stdio.h>

#include <process.h>

int main(void) {

FILE *tmpf;

int i, j;

tmpf = tmpfile();

if (tmpf)

printf("Fichero temporal creado\n");

else {

printf("No ha creado el fichero temporal\n");

exit(1);

}

for(i = 0; i < 15; i++) {

fwrite(&i, sizeof(i), 1, tmpf);

}

rewind(tmpf);

for(i = 0; i < 15; i++) {

fread(&j, sizeof(j), 1, tmpf);

printf("Leído: %d\n", j);

}

fclose(tmpf);

(30)

getchar();

return 0;

}

6 Ejercicios

1 Desarrollar un programa C para contar las líneas de un archivo de texto.

2 Desarrollar programa C para contar las palabras en un archivo de texto.

3 Desarrollar programa C que transforme a mayúsculas todas las letras contenidas en un archivo de texto.

4 Desarrollar un programa C que dado un archivo de texto muestre por pantalla la cantidad de veces que se repita cada letra en el texto. No se debe distinguir entre mayúsculas y minúsculas.

5 Desarrollar la función con prototipo:

void delete(FILE *f, int l);

De forma que elimine del flujo f, el texto desde la línea número l, inclusive, hasta el final.

6 Un problema común en el procesamiento de datos es la mezcla de dos conjuntos de datos, cada uno de los cuales se encuentra ordenado en forma ascendente. Considérese una situación en la cual existen dos archivos de datos de tipo entero, ordenados ascendentemente, cuyos nombres son “datos1.dat” y

“datos2.dat”. Es posible que algunos datos estén presentes en ambos ficherios.

Se necesita desarrollar un programa C que genere un tercer archivo de nombre

“mezcla.dat” que contenga el total de los datos de los dos archivos ordenados en forma ascendente.

7 Se dispone de un archivo maestro de registros con el nombre “maestro.dat”

que contiene el inventario de una bodega y que consta de un registro para cada artículo, con la información del código del artículo y la cantidad actual.

Cada cierto tiempo este archivo debe ser actualizado, para lo cual existe un segundo archivo con el nombre “transacciones.dat” con las transacciones realizadas durante un período, con un registro por artículo con la información del código del artículo, el tipo de operación (entrada o salida) y la cantidad de operaciones.

Se debe desarrollar un programa C que realice la actualización del archivo maestro generando un nuevo archivo actualizado con el nombre “nuevo.dat”.

Observaciones:

• En el archivo maestro y de transacciones los registros están ordenados por código del artículo en forma ascendente.

• Cada artículo tiene como máximo una transacción, habiendo artículos que no tienen transacción.

8 Se desea manejar información sobre los alumnos de un curso considerando el nombre y las notas de tres exámenes. Desarrollar un programa C que permita:

• Generar un archivo “alumnos.dat” que contenga el nombre de cada alumno y sus notas.

• A partir del archivo “alumnos.dat”, generar un archivo que contenga

(31)

9 Dados dos archivo “a.dat” y “b.dat”, generar un tercer archivo “c.dat”, con todos los registros comunes entre “a.dat” y “b.dat” (intersección).

Nota: Se puede suponer que los registros almacenados en los archivos se encuentran ordenados según una clave.

10 Desarrollar un programa C que permita hacer las siguientes operaciones sobre un archivo conteniendo el nombre, cantidad y precio de venta de un artículo:

• Consultar un artículo.

• Agregar un artículo.

• Vender un artículo (disminuir su cantidad; si la cantidad es igual a cero, eliminarlo del archivo).

• Listar los artículos que están bajo el stock mínimo (stock mínimo = 10).

Referencias