Sesión 09: Leer línea
En muchas ocasiones y en muchos ejercicios prácticos y teóricos es necesario leer líneas de un fichero. En general siempre aparece la coletilla “se asume que las líneas no superan (por ejemplo) los 2046 caracteres”. Eso como veremos a continuación simplifica la lectura de cada línea. En este taller veremos como podemos leer líneas de caracteres donde el número de caracteres no está limitado a priori. Este es un problema muy interesante.
Leer líneas con un límite
- Supongamos que tenemos que leer líneas bajo a la suposición anterior: “se asume que las líneas no superan (por ejemplo) los 2046 caracteres”.
- Probablemente la mejor función de la biblioteca estándar para leer
esa línea sea
fgets
. Veamos un extracto del manual:
|
|
- Vamos a simplificar (el tipo
char [restrict .size]
se puede entender comochar *
) y diseccionar la cabecera defgets
:
|
|
- El primer parámetro
s
es el string (puntero a char) en el quefgets
va a dejar los caracteres leídos. Muy importante: tendremos que pasar un array de caracteres lo suficientemente grande como para que quepan los caracteres leídos. - El segundo parámetro
size
(¡menos 1 para que quepa el\0
!) es el número máximo de caracteres a leer. - El tercer parámetro
stream
es un stream, concretamente un puntero aFILE
(dondeFILE
es un tipo que representa un fichero abierto preparado para que se lean datos de él). - En este punto ya surgen unas cuantas preguntas:
- ¿Qué significa “se asume que las líneas no superan los 2046 caracteres”?
- ¿Eso incluye el cambio de línea?
- ¿De qué longitud debería ser el array que pasemos como primer parámetro (
s
)? - ¿Qué valor debería usar como segundo parámetro (
size
)? - ¿De dónde saco un
FILE *
?
- Vamos paso a paso.
Longitud de la línea
Vamos a suponer que esos 2046 caracteres no incluyen el cambio de
línea (\n
). Como fgets
, según el manual, incluiría el cambio de
línea, necesitamos un array de caracteres de 2048 caracteres: los 2046
exigidos, más el cambio de línea, más el caracter de terminación
(\0
). Por lo tanto tendríamos algo como esto:
|
|
Si en vez de usar memoria estática queremos usar memoria dinámica podríamos hacer:
|
|
NOTA: para probar tus ejemplos quizás prefieras usar un tamaño de línea máximo de 10 caracters y así puedes probar fácilmente qué ocurre en los límites.
¿Qué pasaría si le pasas un array de caracters demasiado pequeño?
Piensa en ello, imagina que se hace esta llamada por error y la línea tiene más de 1024 caracteres:
|
|
FILE *
Para poder disponer de un dato de tipo FILE *
tenemos dos opciones:
- Abrimos el fichero del que queremos leer las líneas usando la
función estádnar
fopen
con la siguiente cabecera según el manual:
|
|
- Usamos la entrada estándar
stdin
, una constante definida también en la bibliotecastdio
:
|
|
Implementar y probar la función leer_linea
Tu primera tarea consiste en hacer un programa principal
leer_linea1.c
con un main
que lea una línea usando una función que
tendrás que implementar en el mismo fichero con esta cabecera:
|
|
Tu main
simplemente tiene que imprimir la longitud de la línea tras
haberla leído y cargado en un string, algo como esto:
|
|
Observar free
- Observa que una vez que la memoria dinámica que se ha pedido con un
malloc
no se vuelve a usar, es necesario liberarla. - Pero… ¿dónde hay que liberar esa memoria? Si la liberas en la función entonces no se podrá usar fuera de la función.
¿Y si la longitud de la línea es menor que la memoria reservada?
De alguna forma estamos desperdiciando memoria. Curiosamente hay una
función que te permite reducir la memoria que hayas pedido al sistema
operativo a la que realmente necesitas. ¿Se te ocurre alguna forma de
hacer un malloc
para 2048 y realocar memoria si no necesitas 2048?
Leer líneas sin un límite
Ahora empieza la parte complicada. Tienes que hacer un programa como
el anterior leer_linea2.c
pero esta vez la función leer_linea
no
tiene la precondición de “un máximo de 2046 caracteres”.
Esto complica mucho las cosas como verás. Puedes intentarlo tú misma antes de mirar las siguientes pistas:
Primeras pistas
fgets
exige que le pases unsize
y a priori no conoces ese límite porque no existe.- ¿Qué ocurre si le pasas un límite inferior a la longitud de la línea? Parece que todo funcionaría bien.
- Pero si la línea tiene por ejemplo 5000 caracteres y tú le pasas a
fgets
un string de 1000 (y un límite de 1000, por supuesto) entonces hay 4000 caracteres que se quedan sin leer. - Recuerda que a priori no sabemos cuántos caracteres habrá en la línea.
- Consulta toda la familia de funciones
malloc
(man malloc
).
Segundas pistas
- Presta atención a la función
realloc
. - Tendrás que hacer un algoritmo que vaya haciendo crecer el array y
que vaya llamando a la función
fgets
hasta que en una de esas lecturas se detecte que se ha terminado la línea. - Recuerda que
fgets
recibe como argumento un puntero, ese puntero puede ser la dirección de cualquier elemento del array, por ejemplo, observa con detenimiento:
|
|
- O lo que es lo mismo:
|
|
- ¿Cómo se sabe que ya se ha terminado la línea y por lo tanto ha
terminado el algoritmo mencionado? La clave está en comprobar que se
ha leído el caracter
\n
.