martes, 18 de febrero de 2014

Entrada y Salida de caracteres en el Lenguaje C


Introducción: funciones de biblioteca para E/S

En la biblioteca estándar de C se definen las dos principales vías de comunicación de un programa:
  • la entrada estándar (stdin), y
  • la salida estándar (stdout).
Generalmente están ambas asociadas a nuestro terminal, de manera que cuando se imprimen datos en la salida estándar los caracteres aparecen en el terminal, y cuando leemos caracteres de la entrada estándar los leemos del teclado del terminal. La entrada y salida estándar trabajan con caracteres (en modo carácter), con datos o números binarios. Es decir, todos los datos que enviemos a la salida estándar deben ser cadenas de caracteres. Por ello para imprimir cualquier dato en la salida estándar primero deberemos convertirlo en texto, es decir, en cadenas de caracteres. Sin embargo esto lo haremos mediante las funciones de biblioteca, que se encargan de realizar esta tarea eficientemente:
  • putchar y getchar
  • puts y gets
  • printf
  • scanf
  • gotoxy
  • clrscr
putchar y getchar

Comenzaremos con las dos funciones principales de salida y entrada de caracteres: putchar ygetchar.

La función putchar escribe un único carácter en la salida estándar. Su uso en sencillo y generalmente está implementada como una macro en la cabecera de la biblioteca estándar. Por ejemplo:

#include <stdio.h>

main()
{
	putchar('H');
	putchar('o');
	putchar('l');
	putchar('a');

	putchar(32);

	putchar('m');
	putchar('u');
	putchar('n');
	putchar('d');
	putchar('o');

	putchar('\n');
}

El resultado es:

Hola mundo

En el código anterior putchar(32); muestra el espacio entre ambas palabras (32 es el código ASCII del carácter espacio ' ') y putchar('\n'); imprime un salto de línea tras el texto.

La función getchar devuelve el carácter que se halle en la entrada estándar. Esta función tiene dos particularidades. La primera es que aunque se utiliza para obtener caracteres no devuelve un carácter, sino un entero. Esto se hace así ya que con un entero podemos representar tanto el conjunto de caracteres que cabe en el tipo carácter (normalmente el conjunto ASCII de caracteres) como el carácter EOF de fin de fichero. En UNIX es habitual representar los caracteres usando el código ASCII, tanto en su versión de 7 bits como en su versión ampliada a 8 bits. Estos caracteres se suelen representar como un entero que va del 0 al 127 o 256. El carácter EOF entonces es representado con un -1. Además esto también lo aplicaremos cuando leamos los ficheros binarios byte a byte. Un ejemplo:

#include <stdio.h>

main()
{
	int c;

	c = getchar();	/* Nótese que getchar() no devuelve nada 
			hasta que se presiona ENTER */

	putchar(c);
}

Aquí se almacena en el entero c el carácter pulsado en el teclado. Posteriormente se muestra con putchar.

puts y gets

La función puts simplemente imprime una cadena de caracteres en la salida estándar (y produce un salto de línea). Le debemos proporcionar la dirección donde encontrar la cadena de caracteres. El código:


#include <stdio.h>

main()
{
	puts("Bienvenido a la programación");
	puts(" en lenguaje C");
}

produce el resultado:

Bienvenido a la programación
en lenguaje C

La función gets simplemente toma una cadena de caracteres de la entrada estándar (cuya introducción es preciso terminar con un ENTER) y la almacena en una variable string. Supongamos este código:

#include <stdio.h>

main()
{
	char cadena[50];

	puts("Escriba un texto:");
	gets(cadena);
	puts("El texto escrito es:");
	puts(cadena);
}

La declaración char cadena [50]; crea una variable llamada cadena que puede almacenar hasta 50 caracteres. Este código produce, cuando escribimos con el teclado el texto Bienvenido a la programación en lenguaje C, el resultado:

Escriba un texto:
Bienvenido a la programación en lenguaje C
El texto escrito es:
Bienvenido a la programación en lenguaje C

Esta función nos permite introducir frases enteras, incluyendo espacios.
Algunos detalles más; la sintaxis es:

char *gets(char *buffer);

Si se ha almacenado algún carácter en buffer le añade un '\0' al final y devuelve un puntero a su dirección. Si no se ha almacenado ninguno (error o carácter fin-de-fichero, CTRL+Z) devuelve un puntero NULL.

#include <stdio.h>

main()
{
	char cadena[50];
	char *p;

	puts("Escriba un texto:");
	p = gets(cadena);
	if (p) {
		puts("El texto escrito es:"); puts(cadena);
	}
	else
		puts("No se ha guardado nada.");
}

Esta función es un puede ser peligrosa porque no comprueba si nos hemos pasado del espacio reservado (de 50 caracteres en este ejemplo: 49 caracteres + '\0').


printf: impresión en pantalla

Para imprimir datos de un modo más general el C dispone de la función printf, que se ocupa de la impresión con formato en la salida estándar. Cuando se empieza con un nuevo lenguaje suele gustar el ver los resultados, ver que nuestro programa hace "algo". Por eso la mayor parte de programas de principiantes utilizan rápidamente la función printf, que sirve para sacar información por pantalla.


Para utilizar la función printf en nuestros programas debemos incluir la directiva:#include <stdio.h>


al principio de programa, como se ha visto en el primer programa de la sección Estructura de un programa en C. Esto es porque esta función se halla definida en el archivo de cabecera stdio.h de la siguiente manera:

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

Si sólo queremos imprimir una cadena basta con hacer:

	printf( "Cadena" );

Esto dará como resultado en la pantalla:

Cadena

Lo que se ponga entre las comillas es lo que se observa en la pantalla.
Si volvemos a usar otro printf, por ejemplo:

#include <stdio.h>

main()
{
	printf( "Cadena" );
	printf( "Segunda" );
}

obtendremos:

CadenaSegunda

Este ejemplo nos muestra cómo funciona printf. Para escribir en la pantalla se usa un cursor que no vemos. Cuando escribimos algo el cursor va al final del texto. Cuando el texto llega al final de la fila, lo siguiente que pongamos irá a la fila siguiente. Si lo que queremos es sacar cada una en una línea deberemos usar "\n". Es el indicador de retorno de carro. Lo que hace es saltar el cursor de escritura a la línea siguiente:

#include <stdio.h>

main()
{
	printf( "Cadena\n" );
	printf( "Segunda" );
}

y tendremos:

Cadena
Segunda

También podemos poner más de una cadena dentro del printf:

     printf( "Primera cadena" "Segunda cadena" );

Lo que no podemos hacer es inluir cosas entre las cadenas:

     printf( "Primera cadena" texto en medio  "Segunda cadena" );

Esto no es válido. Cuando el compilador intenta interpretar esta sentencia se encuentra "Primera cadena" y luego texto en medio, no sabe qué hacer con ello y da un error.

Pero, ¿qué pasa si queremos imprimir el símbolo " en pantalla? Por ejemplo imaginemos que queremos escribir: Esto es "extraño". Si para ello hacemos:

	printf( "Esto es "extraño"" );

obtendremos unos cuantos errores. El problema es que el símbolo " se usa para indicar al compilador el comienzo o el final de una cadena. Así que en realidad le estaríamos dando la cadena "Esto es", luegoextraño y luego otra cadena vacía "". La función printf no admite esto y de nuevo tenemos errores. La solución es usar \". Veamos:

	printf( "Esto es \"extraño\"" );

Esta vez funcionará. Como vemos, la contrabarra '\' sirve para indicarle al compilador que escriba caracteres que de otra forma no podríamos. Pero, ¿y si lo que queremos es usar '\' como un carácter normal e imprimir, por ejemplo, Hola\Adiós?. La respuesta es volver a usar '\':

	printf( "Hola\\Adiós" );

Esta doble '\' indica a C que queremos es mostrar una contrabarra.

Como hemos visto, la sintaxis de printf acepta un string de formato (const char *format) y cualquier número de argumentos. Estos se aplican a cada uno de los especificadores de formato contenidos en format. Un especificador de formato toma la forma %[flags][width][.prec][h|lL] type.

El tipo (type) puede ser:

d, ientero decimal con signo
oentero octal sin signo
uentero decimal sin signo
xentero hexadecimal sin signo (en minúsculas)
Xentero hexadecimal sin signo (en mayúsculas)
fcoma flotante en la forma: [-]dddd.dddd
ecoma flotante en la forma: [-]d.dddd e[+/-]ddd
gcoma flotante según el valor
Ecomo e pero en mayúsculas
Gcomo g pero en mayúsculas
cun carácter
scadena de caracteres terminada en '\0'
%imprime el carácter %
ppuntero

Los flags pueden ser los caracteres:

+siempre se imprime el signo, tanto + como -
-justifica a la izquierda el resultado, añadiendo espacios al final
blanksi es positivo, imprime un espacio en lugar de un signo +
#especifica que el argumento se convertirá según una forma alternativa

En el campo width se especifica la anchura mínima del valor de salida:

nse imprimen al menos n caracteres.
0nse imprimen al menos n caracteres y, si la salida es menor, se anteponen ceros
*la lista de parámetros proporciona el valor de anchura

Hay tres modificadores de tamaño, para los tipos int y double:

himprime un entero short
limprime un entero long
Limprime un double long

Si se usa el flag # con una conversión de formato de printf, tiene el siguiente efecto sobre el argumento:

c s d i uno tiene efecto
0antepone 0 a un argumento no nulo
x o Xantepone 0x (0X) al argumento
e E fel resultado siempre contiene un punto decimal
g Gigual que e E. Los ceros sobrantes se eliminan

scanf: lectura del teclado

Algo muy usual en un programa es esperar que el usuario introduzca datos por el teclado. Para ello contamos con varias posibilidades: Usar las funciones de la biblioteca estándar, crear nuestras propias interrupciones de teclado (MS-DOS) o usar funciones de alguna biblioteca diferente. Vamos a estudiar la primera, usando las funciones de la biblioteca estándar. 

Para utilizar la función scanf debemos incluir la directiva:

#include <stdio.h>

al principio del programa puesto que esta función se halla definida en el archivo de cabecera stdio.hde la siguiente manera:
	int scanf ( const char *format [, direcciones, ...] );

Como se observa, el uso de scanf es muy similar al de printf con una diferencia fundamental: nos da la posibilidad de que el usuario introduzca datos en vez de mostrarlos. No nos permite mostrar texto en la pantalla; por eso, si queremos mostrar un mensaje previo, usamos un printf delante. Un ejemplo:

#include <stdio.h>
	
main()
{
	int num;

	printf( "Introduce un número: " );
	scanf ( "%i", &num );

	printf( "Has tecleado el número %i\n", num );
}

Obsérvese que en la sentencia scanf la variable num lleva delante el símbolo &. Éste sirve para indicar al compilador cuál es la dirección (o posición en la memoria) de la variable en la que se almacenará el valor introducido. Es imprescindible no olvidar este símbolo.

scanf no mueve el cursor de su posición actual, así que en el ejemplo queda ( _ indica dónde se halla el cursor):

Introduce un número _

Esto es porque en printf no hemos puesto al final el símbolo de salto de línea '\n'. Además, hemos dejado un espacio al final de Introduce un número: para que cuando tecleemos el número no se "pegue" al mensaje.

Veamos cómo funciona scanf. Fijémonos que hay una cadena entre comillas. Ésta es similar a la deprintf; sirve para indicarle al compilador qué tipo de datos estamos introduciendo. Como en este caso se trata de un entero, usamos %i. Después de la coma se halla la variable donde almacenamos el dato, en este caso num.

Podemos preguntar por más de una variable a la vez en un solo scanf, de modo que es preciso poner un%i por cada variable:

#include <stdio.h>
	
main()
{
	int a, b, c;

	printf( "Introduce tres números: " );
	scanf ( "%i %i %i", &a, &b, &c );

	printf( "Has tecleado los números %i %i %i\n", a, b, c );
}

De esta forma, cuando el usuario ejecuta el programa debe introducir los tres datos separados por un espacio.

También podemos pedir en un mismo scanf variables de distinto tipo:

#include <stdio.h>
	
main()
{
	int a;
	float b;

	printf( "Introduce dos números: " );
	scanf( "%i %f", &a, &b );

	printf( "Has tecleado los números %i %f\n", a, b );
}

A cada modificador (%i, %f) le debe corresponder una variable de su mismo tipo. Es decir, al poner %iel compilador espera que su variable correspondiente sea de tipo int. Si ponemos %f espera una variable tipo float.

Veamos ahora el uso de scanf con cadenas (strings). La función scanf almacena en una porción temporal de memoria (un buffer) lo que vamos escribiendo. Cuando pulsamos ENTER (o Intro, oReturn) lo analiza, comprueba si el formato es correcto y, por último, lo almacena en la variable que le indicamos. Veamos el ejemplo:

#include <stdio.h>

main()
{
	char cadena[30];

	printf( "Escribe una palabra: " );
	scanf( "%s", cadena );

	printf( "He guardado: \"%s\" \n", cadena );
}

Ejecutamos el programa e introducimos la palabra "hola". Esto es lo que tenemos:

Escribe una palabra: hola
He guardado: "hola"

Si ahora introducimos "hola amigos" esto es lo que tenemos:

Escribe una palabra: hola amigos
He guardado: "hola"

Sólo ha recogido la palabra "hola" y se ha olvidado de amigos. ¿Por qué?. Porque scanf toma una palabra como cadena y usa los espacios para separar variables.

Es importante siempre asegurarse de que no vamos a almacenar en cadena más caracteres que los que caben. Para ello debemos limitar el número de los mismos que va a introducir scanf. Si, por ejemplo, queremos un máximo de 5 caracteres, usaremos %5s:

#include <stdio.h>

main()
{
	char cadena[6];

	printf( "Escribe una palabra: " );
	scanf( "%5s", cadena );

	printf( "He guardado: \"%s\" \n", cadena );
}

Si introducimos una palabra de 5 caracteres (no se cuenta '\0') o menos, la recoge sin problemas y la guarda en cadena:

Escribe una palabra: Frodo
He guardado: "Frodo"

Si introducimos más de 5 caracteres cortará la palabra y dejará sólo 5.

Escribe una palabra: Gandalf
He guardado: "Ganda"


scanf permite controlar qué caracteres introducimos. Supongamos que sólo queremos recoger las letras mayúsculas:

#include <stdio.h>

main()
{
	char cadena[30];

	printf( "Escribe una palabra: " );
	scanf( "%[A-Z]s", cadena );

	printf( "He guardado: \"%s\" \n", cadena );
}

Guarda las letras mayúsculas en la variable hasta que encuentra una minúscula:

Escribe una palabra: Aragorn
He guardado: "A"

Escribe una palabra: GOLLUM
He guardado: "GOLLUM"


Cómo funcionan los buffer

Veamos qué sucede cuando scanf no recoge todas las pulsaciones efectuadas. Tomemos el código:

#include <stdio.h>

main()
{
	char c;
	char nombre[20], apellido[20];

	printf( "Escribe tu nombre: " );
	scanf( "%[A-Z]s", nombre );

	printf( "Lo que recogemos de scanf es: %s\n", nombre );
	printf( "Lo que había quedado en el buffer: " );
	while( (c = getchar())!= '\n' )
		putchar( c );
}

Imaginemos que introducimos un nombre con mayúsculas y minúsculas:

Escribe tu nombre: GANdalf
Lo que recogemos de scanf es: GAN
Lo que había quedado en el buffer: dalf

Veamos otro ejemplo en el que hay que obrar con precaución respecto a scanf:

#include <stdio.h>

main()
{
	char nombre[20], apellido[20];

	printf( "Escribe tu nombre: " );
	scanf( "%s", nombre );

	printf( "Escribe tu apellido: " );
	gets( apellido );
}

Cuando se teclea un nombre se obtiene:

Escribe tu nombre: Gandalf
Escribe tu apellido: 

No hay opción a introducir el apellido puesto que en el buffer queda un ENTER (el de finalizar la introducción del nombre) que es recogido directamente por gets.

Posicionado del cursor y borrado de pantalla
  • gotoxy: posicionado del cursor
  • clrscr: borrado de la pantalla
  • gotoxy: posicionado del cursor (DOS)
Esta función sólo está disponible en compiladores de C que dispongan de la biblioteca <conio.h>.

Hemos visto que cuando usamos printf se escribe en la posición actual del cursor y se mueve éste al final de la cadena que hemos escrito. Pero ¿qué sucede cuando queremos escribir en una posición determinada de la pantalla?. La solución está en la función gotoxy. Supongamos que queremos escribir 'Hola' en la fila 10, columna 20 de la pantalla:

#include <stdio.h>
#include <conio.h>

main()
{
	gotoxy( 20, 10 );
	printf( "Hola" );
}

Obsérvese que primero se indica la columna (x) y luego la fila (y). La esquina superior izquierda es la posición (1, 1).
  • clrscr: borrado de la pantalla (DOS)
Ahora ya sólo resta saber cómo se limpia la pantalla. Ello es tan fácil como usar

	clrscr()

(clear screen, borrar pantalla).

Esta función no sólo borra la pantalla, sino que sitúa el cursor en la posición (1, 1), en la esquina superior izquierda.

#include <stdio.h>
#include <conio.h>

main()
{
	clrscr();
	printf( "Hola" );
}

Este método sólo sirve para compiladores que incluyan el fichero conio.h.

0 comentarios:

Publicar un comentario

Aprende a Programar tus propias aplicaciones