Support independent publishing: buy this book on Lulu.

 

 
  PRIMERO DE INGENIEROS INDUSTRIALES
UNIVERSIDAD POLITÉCNICA DE CARTAGENA

FUNDAMENTOS DE INFORMÁTICA.

PROGRAMACIÓN EN C.

Pedro María Alcover Garau

PRIMERO DE INGENIEROS TÉCNICOS INDUSTRIALES

   

0. Presentación.
1. Introducción y conceptos generales.
2. Codificación numérica.
3. Codificación interna de la información.
4. Lenguaje C.
5. Modelos de representación.
6. Tipos de datos y variables en C.
7. Funciones de entrada y salida por consola.
8. Estructuras de control.
9. Ámbito y vida de las variables.
10. Arrays numéricos: vectores y matrices.
11. Caracteres y cadenas de caracteres.
12. Punteros.
13. Funciones.
14. Asignación dinámica de memoria.
15. Algunos usos con funciones.
16. Estructuras estáticas de datos y definición de tipos.
17. Gestión de archivos.

 

FUNCIONES DE ENTRADA Y SALIDA POR CONSOLA

 

Hasta el momento hemos visto muchos detalles del comportamiento interno del ordenador. Por ahora nos hemos centrado en las sentencias de creación y declaración de variables. También hemos visto multitud de operaciones que se pueden realizar con las diferentes variables y literales. Pero aún no hemos sido capaces de ver un solo resultado por pantalla. Tampoco hemos introducido un solo valor por teclado. La comunicación entre el programa y el usuario ha sido, por tanto y hasta el momento, nula.

Lograr que el valor de una variable almacenada de un programa sea mostrado por pantalla sería una tarea compleja si no fuese porque ya ANSI C ofrece, en una de sus bibliotecas, funciones que realizan esta tarea. Y lo mismo ocurre cuando el programador quiere que sea el usuario quien teclee una entrada durante la ejecución del programa.

Estas funciones, de entrada y salida estándar de datos por consola, están definidas en una biblioteca llamada stdio.h. Siempre que deseemos usar estas funciones deberemos añadir, al principio del código de nuestro programa, la directriz #include <stdio.h>.

 

Salida de datos. La función printf()

El prototipo de la función es el siguiente:

int printf(const char *cadena_control[, argumento, ...]);

Qué es un prototipo de una función es cuestión que habrá que explicar en otro capítulo. Sucintamente, diremos que el prototipo indica el modo de empleo de la función: qué tipo de dato devuelve y qué valores espera recibir cuando se hace uso de ella. El prototipo nos sirve para ver cómo se emplea esta función.

La función printf devuelve un valor entero. Se dice que es de tipo int. La función printf devuelve un entero que indica el número de bytes que ha impreso en pantalla. Si, por la causa que sea, la función no se ejecuta correctamente, en lugar de ese valor entero lo que devuelve es un valor que significa error (por ejemplo un valor negativo). No descendemos a más detalles.

La función, como toda función, lleva después del nombre un par de paréntesis. Entre ellos va redactado el texto que deseamos que quede impreso en la pantalla. La cadena_control indica el texto que debe ser impreso, con unas especificaciones que indican el formato de esa impresión; es una cadena de caracteres recogidos entre comillas, que indica el texto que se ha de mostrar por pantalla.

Para comenzar a practicar, comenzamos por escribir en el editor de C el siguiente código. Es muy recomendable que a la hora de estudiar cualquier lenguaje de programación, y ahora en concreto el lenguaje C, se trabaje delante de un ordenador que tenga un editor y un compilador de código:

#include <stdio.h>

void main(void)

{

      printf(“Texto a mostrar en pantalla”);

}

Que ofrecerá la siguiente salida por pantalla

Texto a mostrar en pantalla

Y así, cualquier texto que se escriba entre las comillas aparecerá en pantalla.

Si introducimos ahora otra instrucción con la función printf a continuación de la otra, por ejemplo

printf(“Otro texto”);

Entonces lo que tendremos en pantalla será

Texto a mostrar en pantallaOtro texto

Y es que la función printf continua escribiendo donde se quedó la vez anterior.

Muchas veces nos va a interesar introducir, en nuestra cadena de caracteres que queremos imprimir por pantalla, algún carácter de, por ejemplo, salto de línea. Pero si tecleamos la tecla intro en el editor de C lo que hace el cursor en el editor es cambiar de línea y eso que no queda reflejado luego en el texto que muestra el programa en tiempo de ejecución.

Para poder escribir este carácter de salto de línea, y otros que llamamos caracteres de control, se escribe, en el lugar de la cadena donde queremos que se imprima ese carácter especial, una barra invertida (‘\’) seguida de una letra. Cuál letra es la que se debe poner dependerá de qué carácter especial se desea introducir. Esos caracteres de control son caracteres no imprimibles, o caracteres que tienen ya un significado especial en la función printf.

Por ejemplo, el código anterior quedaría mejor de la siguiente forma:

#include <stdio.h>

void main(void)

{

      printf(“Texto a mostrar en pantalla\n”);

      printf(“Otro texto.”)

}

que ofrecerá la siguiente salida por consola:

Texto a mostrar en pantalla

Otro texto

Ya que al final de la cadena del primer printf hemos introducido un carácter de control salto de línea: “\n” significa, dentro de la función printf, salto de línea.

Las demás letras con significado para un carácter de control en esta función vienen recogidas en la tabla 7.1.

 

 

 

 

 

\a

Carácter sonido. Emite un pitido breve.

 

 

\v

Tabulador vertical.

 

 

\0

Carácter nulo.

 

 

\n

Nueva línea.

 

 

\t

Tabulador horizontal.

 

 

\b

Retroceder un carácter.

 

 

\r

Retorno de carro.

 

 

\f

Salto de página.

 

 

\’

Imprime la comilla simple.

 

 

\”

Imprime la comilla doble.

 

 

\\

Imprime la barra invertida ‘\’.

 

 

\xdd

dd es el código ASCII, en hexadecimal, del carácter que se desea imprimir.

 

 

 

 

 

 

Tabla 7.1.: Caracteres de control en la cadena de la función printf.

 

 

 

 

 

Muchas pruebas se pueden hacer ya en el editor de C, para compilar y ver la ejecución que resulta. Gracias a la última opción de la tabla 7.1. es posible imprimir todos los caracteres ASCII y los tres inmediatamente anteriores sirven para imprimir caracteres que tienen un significado preciso dentro de la cadena de la función printf. Gracias a ellos podemos imprimir, por ejemplo, un carácter de comillas dobles evitando que la función printf interprete ese carácter como final de la cadena que se debe imprimir.

El siguiente paso, una vez visto cómo imprimir texto prefijado, es imprimir en consola el valor de una variable de nuestro programa.

Cuando en un texto a imprimir se desea intercalar el valor de una variable, en la posición donde debería ir ese valor se coloca el carácter ‘%’ seguido de algunos caracteres. Según los caracteres que se introduzcan, se imprimirá un valor de un tipo de dato concreto, con un formato de presentación determinado. Ese carácter ‘%’ y esos caracteres que le sigan son los especificadores de formato. Al final de la cadena, después de las comillas de cierre, se coloca una coma y el nombre de la variable que se desea imprimir.

Por ejemplo, el siguiente código

#include <stdio.h>

void main(void)

{

      short int a = 5 , b = 10 , c;

      c = a + b++;

      printf(“Ahora c vale %h \n”,c);

      printf(“y b vale ahora %h ”,b);

}

Que ofrece la siguiente salida por pantalla:

Ahora c vale 15

y b vale ahora 11

Una cadena de texto de la función printf puede tener tantos especificadores de formato como se desee. Tantos como valores de variables queramos imprimir por pantalla. Al final de la cadena, y después de una coma, se incluyen tantas variables, separadas también por comas, como especificadores de formato se hayan incluido en la cadena de texto. Cada grupo de caracteres encabezado por % en el primer argumento de la función (la cadena de control) está asociado con el correspondiente segundo, tercero, etc. argumento de esa función. Debe existir una correspondencia biunívoca entre el número de especificadores de formato y el número de variables que se recogen después de la cadena de control; de lo contrario se obtendrán resultados imprevisibles y sin sentido.

El especificador de formato instruye a la función sobre la forma en que deben ir impresos cada uno de los valores de las variables que deseamos que se muestren por pantalla.

Los especificadores tienen la forma:

%[flags][ancho campo][.precisión][F/N/h/l/L] type

Veamos los diferentes componentes del especificador de formato:

type: Es el único argumento necesario. Consiste en una letra que indica el tipo de dato a que corresponde al valor que se desea imprimir en esa posición. En la tabla 7.2. se recogen todos los valores que definen tipos de dato. Esta tabla está accesible en las ayudas de editores y compiladores de C.

·                      [F / N / h / l / L]: Estas cinco letras son modificadores de tipo y preceden a las letras que indican el tipo de dato que se debe mostrar por pantalla.

La letra h es el modificador short para valores enteros.

La letra l tiene dos significados: es el modificador long para valores enteros. Y, precediendo a la letra f indica que allí debe ir un valor de tipo double.

La letra L precediendo a la letra f indica que allí debe ir un valor de tipo long double.

·                     [ancho campo][.precisión]: Estos dos indicadores opcionales deben ir antes de los indicadores del tipo de dato. Con el ancho de campo, el especificador de formato indica a la función printf la longitud mínima que debe ocupar la impresión del valor que allí se debe mostrar.

 

 

 

 

 

%d

Entero con signo, en base decimal.

 

 

%i

Entero con signo, en base decimal.

 

 

%o

Entero sin signo, en base octal.

 

 

%u

Entero sin signo, en base decimal.

 

 

%x

Entero sin signo, en base hexadecimal, usando letras minúsculas.

 

 

%X

Entero sin signo, en base hexadecimal, usando letras mayúsculas.

 

 

%f

Número real con signo.

 

 

%e

Número real con signo en formato científico, con el exponente ‘e’ en minúscula.

 

 

%E

Número real con signo en formato científico, con el exponente ‘e’ en mayúscula.

 

 

%g

Número real con signo, a elegir entre formato e ó f según cuál sea el tamaño más corto.

 

 

%G

Número real con signo, a elegir entre formato E ó f según cuál sea el tamaño más corto.

 

 

%c

Un carácter. El carácter cuyo ASCII corresponda con el valor a imprimir.

 

 

%s

Cadena de caracteres.

 

 

%p

Dirección de memoria.

 

 

%n

No lo explicamos aquí ahora.

 

 

%

Si el carácter % no va seguido de nada, entonces se imprime el carácter sin más.

 

 

 

 

 

 

Tabla 7.2.: Especificadores de tipo de dato en la función printf.

 

 

 

 

 

Para mostrar información por pantalla la función printf emplea un tipo de letra de paso fijo. Esto quiere decir que cada carácter impreso ocasiona el mismo desplazamiento del cursor hacia la derecha. Al decir que el ancho de campo indica la longitud mínima se quiere decir que este parámetro señala cuántos avances de cursor deben realizarse, como mínimo, al imprimir el valor.

Por ejemplo, las instrucciones

long int a = 123, b = 4567, c = 135790;

printf(“La variable a vale ... %6li.\n”,a);

printf(“La variable b vale ... %6li.\n”,b);

printf(“La variable c vale ... %6li.\n”,c);

tiene la siguiente salida:

La variable a vale ...    123.

La variable b vale ...   4567.

La variable c vale ... 135790.

donde vemos que los tres valores impresos en líneas diferentes quedan alineados en sus unidades, decenas, centenas, etc. gracias a que todos esos valores se han impreso con un ancho de campo igual a 6: su impresión ha ocasionado tantos desplazamientos de cursos como indica el ancho de campo.

Si la cadena o número es mayor que el ancho de campo indicado ignorará el formato y se emplean tantos pasos de cursor como sean necesarios para imprimir correctamente el valor.

Si se desea, es posible rellenar con ceros los huecos del avance de cursor. Para ellos se coloca un 0 antes del número que indica el ancho de campo

La instrucción

printf(“La variable a vale ... %06li.\n”,a);

ofrece como salida la siguiente línea en pantalla:

La variable a vale ... 000123.

El parámetro de precisión se emplea para valores con coma flotante. Indica el número de decimales que se deben mostrar. Indica cuántos dígitos no enteros se deben imprimir: las posiciones decimales. A ese valor le precede un punto. Si el número de decimales del dato almacenado en la variable es menor que la precisión señalada, entonces la función printf completa con ceros ese valor. Si el número de decimales del dato es mayor que el que se indica en el parámetro de precisión, entonces la función printf trunca el número.

Por ejemplo, el código

double raiz_2 = sqrt(2);

printf("A. Raiz de dos vale %lf\n",raiz_2);

printf("B. Raiz de dos vale %12.1lf\n",raiz_2);

printf("C. Raiz de dos vale %12.3lf\n",raiz_2);

printf("D. Raiz de dos vale %12.5lf\n",raiz_2);

printf("E. Raiz de dos vale %12.7lf\n",raiz_2);

printf("F. Raiz de dos vale %12.9lf\n",raiz_2);

printf("G. Raiz de dos vale %12.11lf\n",raiz_2);

printf("H. Raiz de dos vale %5.7lf\n",raiz_2);

printf("I. Raiz de dos vale %012.4lf\n",raiz_2);

que ofrece la siguiente salida por pantalla:

A. Raiz de dos vale 1.414214

B. Raiz de dos vale          1.4

C. Raiz de dos vale        1.414

D. Raiz de dos vale      1.41421

E. Raiz de dos vale    1.4142136

F. Raiz de dos vale  1.414213562

G. Raiz de dos vale 1.41421356237

H. Raiz de dos vale 1.4142136

I. Raiz de dos vale 0000001.4142

La función sqrt está definida en la biblioteca math.h.

Por defecto, se toman seis decimales, sin formato alguno. Se ve en el ejemplo el truncamiento de decimales. En el caso G., la función printf hace caso omiso del ancho de campo pues se exige que muestre un valor que tiene un carácter para la parte entera, otro para el punto decimal y once para los decimales: en total 13 caracteres, y no 12 como señala en ancho de campo. y es que el punto decimal es un carácter más dentro de la impresión por pantalla del valor.

·                     [flags]: Son caracteres que introducen unas últimas modificaciones en el modo en que se presenta el valor. Algunos de sus valores y significados son:

carácter ‘–‘: el valor queda justificado hacia la izquierda.

carácter +: el valor se escribe con signo, sea éste positivo o negativo. En ausencia de esta bandera, la función printf imprime el signo únicamente si es negativo.

carácter en blanco: Si el valor numérico es positivo, deja un espacio en blanco. Si es negativo imprime el signo.

Existen otras muchas funciones que muestran información por pantalla. Muchas de ellas están definidas en la biblioteca stdio.h. Con la ayuda a mano, es sencillo aprender a utilizar muchas de ellas.

 

Entrada de datos. La función scanf()

La función scanf está definida en la biblioteca stdio.h, y permite la entrada de datos desde el teclado. La ejecución del programa queda suspendida en espera de que el usuario introduzca un valor y pulse la tecla de validación (intro).

La ayuda de cualquier editor y compilador de C es suficiente para lograr hacer un buen uso de ella. Presentamos aquí unas nociones básicas para su uso más sencillo, suficientes para su uso más habitual. Para la entrada de datos, al igual que ocurría con la salida, hay otras funciones válidas que también pueden conocerse a través de las ayudas de los distintos editores y compiladores de C.

El prototipo de la función es:

int scanf(const char *cadena_control[,direcciones,…]);

La función scanf puede leer del teclado tantas entradas como se le indiquen. De todas formas, se recomienda usar una función scanf para cada entrada distinta que se requiera.

El valor que devuelve la función es el del número de entradas diferentes que ha recibido. Si la función ha sufrido algún error, entonces devuelve un valor que significa error (por ejemplo, un valor negativo).

En la cadena de control se indica el tipo de dato del valor que se espera recibir por teclado. No hay que escribir texto alguno en la cadena de control de la función scanf: únicamente el especificador de formato.

El formato de este especificador es similar al presentado en la función printf: un carácter % seguido de una o dos letras que indican el tipo de dato que se espera. Luego, a continuación de la cadena de control, y después de una coma, se debe indicar dónde se debe almacenar ese valor: la posición de una variable que debe ser del mismo tipo que el indicado en el especificador. El comportamiento de la función scanf es imprevisible cuando no coinciden el tipo señalado en el especificador y el tipo de la variable; en ese caso, habitualmente aborta la ejecución del programa.

Las letras que indican el tipo de dato a recibir se recogen en la tabla 7.3. Los modificadores de tipo de dato son los mismos que para la función printf.

 

 

 

 

 

%d

Entero con signo, en base decimal.

 

 

%i

Entero con signo, en base decimal.

 

 

%o

Entero sin signo, en base octal.

 

 

%u

Entero sin signo, en base decimal.

 

 

%x

Entero sin signo, en base hexadecimal, usando letras minúsculas.

 

 

%f

Número real con signo.

 

 

%e

Número real con signo en formato científico, con el exponente ‘e’ en minúscula.

 

 

%c

Un carácter. El carácter cuyo ASCII corresponda con el valor a imprimir.

 

 

%s

Cadena de caracteres.

 

 

%p

Dirección de memoria.

 

 

%n

No lo explicamos aquí ahora.

 

 

 

 

 

 

Tabla 7.3.: Especificadores de tipo de dato en la función scanf.

 

 

 

 

 

La cadena de control tiene otras especificaciones, pero no las vamos a ver aquí. Se pueden obtener el la ayuda del compilador.

Además de la cadena de control, la función scanf requiere de otro parámetro: el lugar dónde se debe almacenar el valor introducido. La función scanf espera, como segundo parámetro, el lugar donde se aloja la variable, no el nombre. Espera la dirección de la variable. Así está indicado en su prototipo.

Para poder saber la dirección de una variable, C dispone de un operador unario: &. El operador dirección, prefijo a una variable, devuelve la dirección de memoria de esta variable. El olvido de este operador en la función scanf es frecuente en programadores noveles. Y de consecuencias desastrosas: siempre ocurre que el dato introducido no se almacena en la variable que deseábamos, alguna vez producirá alteraciones de otros valores y las más de las veces llevará a la inestabilidad del sistema y se deberá finalizar la ejecución del programa.

 

Ejemplos

·                     Escriba un programa que solicite un entero y muestre por pantalla su valor al cuadrado y su valor al cubo.

#include <stdio.h>

void main(void)

{    

      short x;

      printf("Introduzca un valor ... ");

      scanf("%hi",&x);

      printf("El cuadrado de %hd es %li\n",x,(long)x * x);

      printf("El cubo de %hd es %li\n",x,(long)x * x * x);

}

Es importante crear una presentación cómoda para el usuario. No tendría sentido comenzar el programa por la función scanf, porque en ese caso el programa comenzaría esperando un dato del usuario, sin aviso previo que le indicase qué es lo que debe hacer.

La variable x es short por lo que el especificador señalado ha de ser %hi. Y al imprimirlo también se le indica a la función printf el mismo especificador. Al imprimir los valores cuadrado y cubo de x forzamos el tipo de dato para que el valor calculado sea long y no se pierda información en la operación. En el cálculo del cubo no queda garantizado que no se llegue a un desbordamiento: cualquier valor de x mayor de 1290 tiene un cubo no codificable con 32 bits. Se puede probar qué ocurre introduciendo valores mayores que éste indicado.

·                     Escriba un programa que solicite dos números y muestre por pantalla su suma, su resta, su producto, y su cociente.

#include <stdio.h>

void main(void)

{

      short x1, x2;

      printf("Introduzca el primer valor ... ");

      scanf("%hi",&x1);

      printf("Introduzca el segundo valor ... ");

      scanf("%hi",&x2);

      printf("%hi + %hi = %li\n",x1,x2,(long)x1 + x2);

      printf("%hi - %hi = %li\n",x1,x2,(long)x1 - x2);