Mostrando las entradas con la etiqueta gets. Mostrar todas las entradas
Mostrando las entradas con la etiqueta gets. Mostrar todas las entradas

17 de enero de 2017

Ejercicios selectos (archivos).

  1. Investigue para qué sirven los modos de apertura w+ y a+.
  2. Modifique el Ejemplo 9.1 en las líneas 47 y 48. Substituya archivoPtr por stdout y vea lo que sucede ¿Se comprueba lo descrito en el blog?
  3. Para el programa del Ejemplo 9.1, ¿qué sucede si en lugar de abrir el archivo en modo agregar (a) se abre en modo lectura (r) sin cambiar ninguna otra sentencia? Pruebe utilizando el archivo "contactos.txt" generado por el Ejemplo 9.1 y también sin dicho archivo (elimínelo).
  4. Para el programa del Ejemplo 9.1, ¿qué sucede si en lugar de abrir el archivo en modo agregar (a) se abre en modo escribir (w) sin cambiar ninguna otra sentencia? Pruebe utilizando el archivo "contactos.txt" generado por el Ejemplo 9.1 y también sin dicho archivo (elimínelo).
  5. Modifique la función llenaDirectorio del Ejemplo 9.1 para que en lugar de la función gets use la función fgets.
  6. Con base en el Ejemplo 9.1 y Ejemplo 9.2, escriba un programa que integre las dos funcionalidades respectivas. Agregue además la posibilidad, a través de funciones, de que los contactos sean ordenados y almacenados en otro archivo, que se puedan realizar búsquedas (lineal y binaria) con base en el nombre o el teléfono del contacto. No olvide trabajar con los datos en memoria, acceda a archivos de texto sólo para leer información o almacenarla. En este sentido, puede resultar útil guardar, como primera línea del archivo, el número de contactos almacenados, con lo que podría generar un arreglo utilizando asignación dinámica de memoria. La idea de este ejercicio es poner en práctica todo lo aprendido en el blog hasta ahora.
  7. Modifique el programa del Ejemplo 9.3 para que procese, y en consecuencia presente en la salida estándar, todos los archivos que se le proporcionen en la invocación.
  8. Escriba un programa que compare dos archivos y determine si dichos archivos son o no iguales. Los archivos procesados serán proporcionados desde la invocación del programa. Así, si su programa se llama compara, la ejecución: $./compara archivo1 archivo2 implica que se deben comparar archivo1 con archivo2. Si los archivos son iguales, se notifica al usuario de dicha situación; en caso contrario, se indicará la primera línea de cada archivo que no coincidió.
  9. Para el programa del Ejemplo 9.4, pruebe lo siguiente:
    1. Seleccione un archivo de texto que contenga la letra de una canción por ejemplo. Busque de preferencia la letra de una canción en inglés para omitir los detalles referentes a la letra ñ, los acentos, y la diéresis de nuestro lenguaje.
    2. Cambie la vocal a del archivo fuente (archivo1) por el símbolo # en el archivo destino (archivo2).
    3. Use el archivo generado en el paso anterior (archivo2) como archivo fuente para una nueva ejecución, en la que cambiará ahora el símbolo # por la vocal a, generándose un nuevo archivo destino: archivo3.
    4. Si todo está bien y suponiendo que en archivo1 no estaba el símbolo #, archivo1 debe ser idéntico a archivo3. Compruebe que así sea. Puede apoyarse de programas que comparan archivos, como el comando diff de Unix y GNU/Linux, y el programa compara del Ejercicio 7.
    10. Modifique el programa del Ejemplo 9.3 para que presente en la salida estándar, dos veces: una seguida de otra, el archivo procesado. La idea de este ejercicio es que pueda practicar el uso de la función rewind descrita brevemente en el blog.
    11. Escriba un programa que procese un archivo de texto proporcionado en la invocación del programa, y presente ordenadas en la salida estándar cada una de las líneas del archivo. Así, la ejecución de: $./ordena archivo presenta en la salida estándar cada una de las líneas de texto del archivo archivo, ordenadas alfabéticamente. Consulte el funcionamiento del programa sort de Unix y GNU/Linux para tener una mejor idea acerca del funcionamiento de su programa.
    12. Tomando como referencia el Ejemplo 9.4, escriba un programa que cambie una palabra por otra. Genere un nuevo archivo con los cambios, y tome en cuenta que deberá analizar tres casos:
  1. Si la palabra a cambiar tiene la misma longitud, el cambio es simple.
  2. Si la palabra a cambiar es de menor longitud, deberá recorrer los caracteres hacia la izquierda después de la nueva palabra para no dejar "basura" en el arreglo y en consecuencia en el nuevo archivo.
  3. Si la palabra a cambiar es de mayor longitud, deberá introducir "más espacio" en la cadena para no sobre escribir caracteres existentes. Analice detenidamente cómo resolver dicha situación.
        Nota: La idea de este ejercicio es poner en práctica lo aprendido en el
        blog. Tome en cuenta que los cambios se deben realizar directamente
        sobre el arreglo antes de escribirlo en el nuevo archivo.

    13. Pruebe lo que sucede si elimina la función getchar (línea 53) del Ejemplo 9.6.
    14. El programa del Ejemplo 9.6 está incompleto. Modifíquelo para completar las opciones de menú:
  • Eliminar contacto. Tome en consideración que la eliminación de un contacto requiere de la confirmación de la eliminación de dicho contacto antes de proceder.
  • Modificar contacto. La modificación del contacto puede ser en varios sentidos:
    • Conservar los datos del contacto pero moverlo de lugar, es decir, que sea ahora otro número de contacto, lo cual implica verificar que el nuevo número de contacto seleccionado no esté ya ocupado.
    • Modificar sólo el nombre del contacto.
    • Modificar únicamente el teléfono del contacto.
    • Modificar tanto el nombre como el teléfono del contacto.
    • No olvide también confirmar los cambios.
    15. En relación con en el Ejercicio 12 de la sección de ejercicios Abstracción de Datos, complemente la funcionalidad planteada en dicho ejercicio para que su programa sea capaz de leer figuras geométricas desde un archivo de texto especificado por el usuario.
    16. Escriba un programa que en base a lo siguiente: $ ./morse cadena archivo1 archivo2 permita leer el archivo de texto archivo1, de tal forma que si cadena es "a", su programa:
  • Procese únicamente símbolos alfanuméricos y espacios de archivo1 y los convierta a su correspondiente representación en código Morse en archivo2. Los espacios quedan sin cambio alguno. Cualquier otro símbolo es ignorado y descartado.
  • Cada símbolo representado en código Morse irá separado por comas en archivo2. Los espacios quedan sin cambio alguno.
  • Por otro lado, si cadena es "de", se deberá hacer el proceso inverso: convertir de Morse a texto (en efecto, se habrá perdido la redacción original pero la esencia del texto deberá ser la misma). Sugerencia: para el caso de Morse a texto, investigue la función strtok de la biblioteca string.h.
    17. En la entrada referente a Abstracción de Datos se desarrolló una biblioteca de funciones que inició con el Ejemplo 8.5. Posteriormente, en la sección de ejercicios, se fueron desarrollando nuevas funciones que fueron incorporadas a dicha biblioteca. Tomando en cuenta lo anterior, realice lo siguiente:
  • Genere una nueva biblioteca de funciones que incorpore gestión de archivos a todas las operaciones realizadas con matrices:
    • suma.
    • resta.
    • producto.
    • transpuesta.
    • esSimetrica.
    • inversa.
  • Presente un menú de opciones con las operaciones que es posible realizar con matrices. Agregue al menú la opción de Matriz Aleatoria la cual deberá realizar lo siguiente:
    • Solicitar las dimensiones del matriz: m (renglones) y n (columnas).
    • Solicitar un rango para el numerador (num) y un rango para el denominador (den).
    • Generar dinámicamente una matriz de m x n de números racionales aleatorios. El numerador y el denominador deberán estar en sus respectivos rangos.
  • Para cada operación se deberá permitir introducir la o las matrices (según sea el caso), desde la entrada estándar o desde un archivo de texto especificado por el usuario.
  • Para cada operación, excepto esSimetrica, se deberá permitir además de visualizar el resultado en la salida estándar, guardarlo en un archivo de texto especificado por el usuario.
    18. Modifique el Ejemplo 9.7 para que pueda procesar archivos de cualquier tamaño. Sugerencia: defina un buffer de un tamaño fijo, digamos 4 KB, y lea bloques de 4 KB en cada acceso al archivo. Tome en cuenta que el último bloque leído podría tener menos de 4 KB y que si el archivo es muy pequeño dicha situación podría presentarse desde el primer bloque leído, por lo que deberá verificar el número de bytes leídos por la función fread en cada lectura.
Tome como referencia el Ejemplo 9.7 para hacer una implementación del comando cp (copy) de GNU/Linux.
 

16 de enero de 2017

Archivos de texto.

   En C los archivos están representados por flujos (streams) de bytes. Por esta razón, cuando un archivo se abre, se dice que le es asociado un flujo.

   C no impone o establece algún tipo de estructura específica para los archivos, la estructura asociada a cada archivo es responsabilidad del programador.

Archivos de acceso secuencial.
Un archivo de acceso secuencial es habitualmente un archivo de texto en el que los datos se van almacenando uno detrás de otro sin tomar en consideración su tipo de dato. El acceso a los datos en este tipo de archivos se realiza normalmente de manera secuencial, es decir, no se accede de manera directa a algún dato específico dentro del archivo sin antes haber pasado por todos los datos anteriores a él.

   Los Ejemplos 9.1 y 9.2 muestran el uso de archivos de acceso secuencial para almacenamiento y lectura de datos respectivamente, y se describen en las secciones siguientes.

Almacenamiento de datos.
   En la entrada referente a Arreglos de Estructuras se trabajó con un programa que administra un directorio de contactos, en esta entrada se retomará el planteamiento utilizado en el Ejemplo 8.4 para almacenar los datos del directorio de contactos en un archivo.

   El Ejemplo 9.1 muestra la forma de utilizar un archivo de acceso secuencial para el almacenamiento de datos. La explicación del ejemplo se centrará en la función guardaDirectorio (líneas 38 - 53).

   La línea 39 declara una variable de tipo apuntador a FILE de nombre archivoPtr. FILE es una estructura (struct) que contiene información indispensable para el control del archivo o flujo incluyendo, entre otras cosas, un apuntador al buffer donde se almacenarán los datos que serán enviados o leídos a o del archivo respectivamente, el indicador de posición del archivo, indicadores de estado del archivo, etc.

   La función fopen (línea 43) abre el archivo especificado en la cadena de su primer argumento (la cadena puede incluir la ruta completa de acceso al archivo; si no se especifica una ruta, se busca el archivo en el directorio de trabajo actual), lo asocia con un flujo, y regresa un apuntador a la estructura FILE que representa dicho flujo, mismo que será utilizado en accesos posteriores a través de la variable archivoPtr.

   Las operaciones que se habilitarán sobre el flujo así como la forma en que éstas se llevan a cabo, son especificadas en la cadena del segundo argumento: el modo de apertura. La siguiente lista  muestra los modos básicos de apertura utilizados por la función fopen.
  • "r"      leer (read) : abre el archivo para lectura de datos. El archivo debe existir. El indicador de posición del archivo se establece al inicio.
  • "w"     escribir (write) : crea un archivo vacío para el almacenamiento de datos. Si el archivo existe, su contenido se descarta y el archivo es tratado como si fuera un nuevo archivo. El indicador de posición del archivo se establece al inicio.
  • "a"     agregar (append) : abre un archivo para el almacenamiento de datos al final del mismo respetando los que ya existen (si hubiera). Si el archivo no existe, se crea. El indicador de posición del archivo se establece al final para poder agregar los datos.
  • "r+"    leer/actualizar (read/update) : abre el archivo para actualización (lectura y escritura) de datos. El archivo debe existir. El indicador de posición del archivo se establece al inicio.
   Con los modos de apertura especificados en la lista anterior, el archivo es abierto y tratado como un archivo de texto. Para que el archivo pueda ser tratado como un archivo binario, se debe agregar el símbolo b (binary) a la cadena de modo de apertura, tal y como se muestra en la línea 15 del Ejemplo 9.7, la línea 20 del Ejemplo 9.5, y la línea 25 del Ejemplo 9.6, de los cuales se hablará más adelante.

   Continuando con el Ejemplo 9.1, note que la apertura del archivo ha sido solicitada en modo agregar y no en modo escribir; éste modo de apertura debe utilizarse con mucha precaución, ya que se pueden perder archivos completos debido a que se eliminan todos los datos de un archivo existente, aunque estos no hayan sido creados por un programa en C. Si la función fopen pudo abrir el archivo regresará, como ya se mencionó con anterioridad, una referencia al flujo que representa dicho archivo; en caso contrario, regresará un apuntador nulo, y ésta situación debe ser siempre verificada, como se muestra en la sentencia if de la línea 43.

   Una vez que el archivo ha sido abierto, almacenar datos en él es tan simple como mandar datos a la salida estándar. La función fprintf (líneas 47 y 48) es análoga a la función printf por lo que su uso debe ser más que natural en este momento; la única diferencia radica en su primer argumento, el cual es un apuntador al flujo que se utilizará para enviar los datos (si el primer argumento de la función fprintf es stdout, fprintf se comporta exactamente igual que printf. Note que el flujo al que se están enviando los datos es precisamente el apuntador a FILE archivoPtr, el cual es el medio de acceso para el archivo "contactos.txt" (vea nuevamente la línea 43).

   Por último, la función fclose (línea 50) cierra el archivo asociado al flujo representado por archivoPtr y elimina también dicha asociación. Una posible salida para el programa del Ejemplo 9.1 se muestra en la siguiente figura.

Una posible salida para el Ejemplo 9.1.

Lectura de datos.
   En la sección anterior se utilizó un archivo de acceso secuencial para almacenar datos en él, en esta sección se hará uso de dicho archivo para leer los datos almacenados en el mismo y mostrarlos en la salida estándar. En este sentido, el Ejemplo 9.2 es la parte complementaria del Ejemplo 9.1.

   Al igual que antes, la explicación estará limitada a la función leeDirectorio (líneas 28 - 40), la cual gestiona los aspectos referentes al manejo del archivo para su lectura.

   La función fopen abre el archivo "contactos.txt" (línea 32) en modo de lectura debido a que no se requiere modificar los datos del mismo, sino sólo procesarlos para su almacenamiento en un arreglo.

   Por otro lado, la función leeDirectorio muestra el uso de la función fgets en las líneas 35 y 36. La función fgets trabaja de manera análoga a la función gets, pero a diferencia de ésta última, fgets recibe tres parámetros:
  1. La cadena donde se almacenarán los datos que se procesen (representada en el Ejemplo 9.2 por el argumento contactos[i].nombre y contactos[i].telefono respectivamente).
  2. El número máximo de caracteres a almacenar en la cadena (representada en el Ejemplo 9.2 por el argumento TAM_NOM y TAM_TEL respectivamente). Esta característica resulta sumamente conveniente debido a que la función fgets protege de desbordamientos: aunque la cantidad de datos que se procesen sea mayor que el tamaño de la cadena, dichos datos son descartados y sólo se almacena como máximo, el número de datos especificado en este argumento menos uno, dado que se preserva el espacio para almacenar el fin de cadena ('\0').
  3. El flujo del cual se procesarán los datos (representado en el Ejemplo 9.2 por el argumento archivoPtr).
   La función fgets lee caracteres de un flujo y los almacena en la cadena correspondiente hasta que se hayan leído a lo más el número de caracteres indicado menos uno (para almacenar el fin de cadena '\0'), se encuentre el símbolo de fin de archivo (el símbolo de fin de archivo está representado por la constante simbólica EOF), o se encuentre el símbolo de avance de línea y retorno de carro ('\n').

   Tome en cuenta que aunque el símbolo '\n' hace que la función fgets termine la lectura, éste es considerado un carácter válido y en consecuencia, es también almacenado en la cadena.

   Ahora bien, si la función fgets encuentra el símbolo EOF antes que cualquier otro carácter, la función regresa un apuntador NULL, situación que es verificada en la línea 35 del Ejemplo 9.2. Se recomienda revisar un manual de referencia para tener una mejor y más amplia comprensión de la función fgets.

   Por último, note que la función leeDirectorio regresa el número (i) de contactos leídos del archivo (línea 39). Una posible salida para el Ejemplo 9.2 se muestra en la siguiente figura:

Una posible salida del Ejemplo 9.2.

30 de septiembre de 2016

Arreglos de estructuras.

   Las estructuras, al igual que los tipos de datos básicos de C, también pueden ser agrupadas en arreglos y el Ejemplo 8.4 es muestra de ello, el cual modela, por medio de un arreglo (línea 16) y una estructura (líneas 10 - 13), un directorio de contactos con los datos elementales.

   El ciclo for de las líneas 19-25 recorre el arreglo contactos para su lectura. Note que cada elemento del arreglo contactos es una entidad de tipo CONTACTO, y que por lo tanto, se leen de la entrada estándar sus dos elementos miembro: nombre (línea 21), y telefono (línea 23), a través de la función gets.

   Por otro lado, el ciclo for de las líneas 27-31 realiza un recorrido del arreglo contactos para su impresión en la salida estándar de manera análoga a lo descrito en el párrafo anterior. La siguiente figura muestra una posible salida del Ejemplo 8.4:

Una posible salida del Ejemplo 8.4.
 
    El nivel de detalle para el directorio de contactos puede ser tan elaborado como se requiera, el Ejemplo 8.4 es sólo una muestra del uso de un arreglo de estructuras, mismo que puede ser generalizado y extendido hacia otros tipos de datos.

   Los arreglos de estructuras siguen las misma reglas que se analizaron y presentaron para los arreglos en las entradas correspondientes del blog.

   Finalmente, vale la pena mencionar que en la entrada referente a la gestión de archivos se retomará el Ejemplo 8.4, y se relacionará su uso con el almacenamiento y recuperación de los datos procesados en, y de un archivo de texto, respectivamente.

30 de agosto de 2016

Arreglos de caracteres (cadenas).

   Todo lo que hasta este momento se ha comentado para los arreglos respecto a su declaración, inicialización, recorrido, y uso con funciones, es aplicable a arreglos de cualquiera de los tipos de datos de C incluyendo char; sin embargo, este tipo de arreglos poseen características particulares, empezando porque es común denominar a un arreglo de caracteres (char) como cadena, de ahí que su tratamiento y discusión se hayan separado en una entrada especial.

   Si se han revisado las entradas anteriores, en este momento el lector cuenta con algo de experiencia en la declaración, inicialización, recorrido, y uso de arreglos con funciones, por lo que en el Ejemplo 6.6 se presentan estos conceptos en el contexto de cadenas, es decir: arreglos de caracteres.

   Las líneas 12, 13 y 14 muestran la declaración de tres cadenas: cadena1, cadena2 y cadena3. Note que las dos primeras no tienen un tamaño explícito y que han sido inicializadas de forma distinta.

   La cadena1 se ha inicializado con una frase contenida entre comillas dobles. En el lenguaje de programación C, cualquier sucesión de elementos contenida entre comillas dobles es una cadena, y dicha cadena está compuesta por todos los caracteres entre comillas (incluyendo los espacios en blanco), más un carácter no visible, pero que delimita el fin de la cadena. Este carácter recibe el nombre de fin de cadena y es '\0', que aunque es un carácter compuesto por dos símbolos (\ y 0), se considera como uno solo y se denota entre comillas simples: '\0'. Por lo tanto la cadena1 tiene un tamaño implícito o longitud no de 18 caracteres como podría pensarse en un principio, sino de 19.

   Por otro lado, la cadena2 (línea 13) tiene una inicialización más parecida a lo que se vio en su momento para arreglos de tipo int y float. Note que cada unos de los elementos de la cadena, incluyendo el fin de cadena, se encuentran entre comillas simples. Para este tipo de inicialización el terminador de cadena '\0' debe proporcionarse de manera explícita, ya que de otra forma, la cadena estaría mal formada, es decir, sin terminar, y esto podría llevar a efectos o resultados desde inesperados (impresión de caracteres extraños más allá de lo que en principio conforma la cadena) hasta catastróficos (intentos de acceso a memoria no reservada y la consecuente anulación del programa). Esta cadena tiene una longitud implícita de seis caracteres.

   La cadena3 no ha sido inicializada y se utilizará para leer una cadena desde la entrada estándar a través de la función gets (la cual recibe una cadena almacena en ella lo que se procesa de la entrada estándar, hasta que se introduzca un ENTER, (\n) (línea 18).

   El ciclo for de la línea 21 muestra el recorrido clásico de una cadena. Observe que la expresión condicional depende del carácter de fin de cadena ('\0'), y que el cuerpo del ciclo (línea 22) imprime la cadena cadena1 carácter por carácter con un espacio entre ellos.

   La línea 23 muestra el uso de la función putchar para imprimir el carácter de avance de línea y un retorno de carro. Dicha función es equivalente a la sentencia:

printf("\n");

   Note que la secuencia de escape '\n' ha sido escrita entre comillas simples en la línea 23 debido a que la función putchar escribe en la salida estándar el carácter que recibe como argumento.

   El Ejemplo 6.6 también dos funciones que se describen a continuación:
  • longitud (línea 38): recibe una cadena, la cual se identifica como c dentro de la función, y realiza un recorrido sobre c para llegar al final de la misma, de tal forma que la variable de control i contiene la longitud de c cuando la expresión condicional del for se evalúa como 0; el cuerpo del ciclo está vacío porque no hay nada más que hacer y esto se enfatiza con el ";" de la línea 42. Finalmente la función regresa la longitud de la cadena: i (línea 43).
  • imprimeAlReves (línea 30):  recibe una cadena, la cual se identifica como c dentro de la función y realiza un recorrido inverso sobre c, es decir, del último carácter de la cadena (longitud - 1) al primero (0). Dentro del recorrido, la cadena se imprime en la salida estándar carácter por carácter.
   Note que la cadena original (como argumento la cadena original es cadena2 (línea 25), como parámetro es c (línea 30)) no se invierte, sino que únicamente se imprime al revés, por lo tanto la cadena original permanece inalterada; asegúrese de comprender esto antes de continuar.

   Una posible salida del Ejemplo 6.6 se  muestra en la siguiente figura:

Una posible salida del Ejemplo 6.6.
 
    En la entrada correspondiente a los apuntadores se retomará el tema de cadenas.