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.

 

ESTRUCTURAS DE CONTROL

 

Decíamos, capítulos atrás, que el lenguaje C está definido para trabajar según el paradigma de la programación estructurada. En ese capítulo quedaban recogidas las reglas de la programación estructurada. Conviene ahora volverlas a traer al recuerdo:

1.      Todo programa consiste en una serie de acciones o sentencias que se ejecutan en secuencia, una detrás de otra.

2.      Cualquier acción puede ser sustituida por dos o más acciones en secuencia. Esta regla se conoce como la de apilamiento.

3.      Cualquier acción puede ser sustituida por cualquier estructura de control; y sólo se consideran tres estructuras de control: la secuencia, la selección y la repetición. Esta regla se conoce como regla de anidamiento. Todas las estructuras de control de la programación estructurada tienen un solo punto de entrada y un solo punto de salida.

4.      Las reglas de apilamiento y de anidamiento pueden aplicarse tantas veces como se desee y en cualquier orden.

Ya hemos visto cómo se crea una sentencia: con un punto y coma precedido de una expresión que puede ser una asignación, la llamada a una función, una declaración de una variable, etc. O, si la sentencia es compuesta, agrupando entonces varias sentencias simples en un bloque encerrado por llaves.


Los programas discurren, de instrucción a instrucción, una detrás de otra, en una ordenación secuencial y nunca dos al mismo tiempo, como queda representado en la figura 8.1.

Pero un lenguaje de programación no sólo ha de poder ejecutar las instrucciones en orden secuencial: es necesaria la capacidad para modificar ese orden de ejecución. Para ello están las estructuras de control. Al acabar este capítulo, una vez conocidas las estructuras de control, las posibilidades de resolver diferentes problemas mediante el lenguaje de programación C se habrán multiplicado enormemente.

 

Conceptos previos


La regla 3 de la programación estructurada habla de tres estructuras de control: la secuencia, la selección y la repetición. Nada nuevo hay ahora que decir sobre la secuencia, que vendría esquematizada en la figura 8.1. En la figura 8.2. se esquematizan diferentes posibles estructuras de selección; y en la figura 8.3. las dos estructuras básicas de repetición.

Cuando ejecutamos un programa, en una zona de la memoria tenemos el código y las instrucciones, y en otra los datos que se van a procesar. Durante la ejecución, las instrucciones de la zona de código manipulan los datos de acuerdo con la siguiente secuencia de tareas:

1.     
Poner el contador de programa a cero.

2.      Llevar al registro de instrucción el contenido de la posición de memoria referenciada por el contador de programa. Lo que traemos será una instrucción.

3.      Decodificar el contenido de esa instrucción, para conocer el código de la operación que se debe realizar.

4.      Ejecutar la instrucción según el código.

5.      Incrementar en uno el contador de programa y volver al paso 2 de esa descripción del proceso.

Así las cosas, si en el instante  se ha ejecutado la instrucción , en el instante siguiente  se ejecutará la instrucción , a no ser que la instrucción  especifique un cambio en el flujo de ejecución del programa: en ese caso diremos que la instrucción  es una instrucción de rotura de flujo secuencial. Las dos clases de roturas del flujo se realizan mediante instrucciones de tipo:

1.      Instrucción condicional: Se evalúa una condición y si se cumple se transfiere el control a una nueva dirección indicada por la instrucción.

2.      Instrucción incondicional. Se realiza la transferencia a una nueva dirección sin evaluar ninguna condición (por ejemplo, llamada a una función).

En ambos casos la transferencia del control se puede realizar con o sin retorno: en el caso de que exista retorno, después de ejecutar el bloque de instrucciones de la nueva dirección se retorna a la dirección que sucede a la que ha realizado el cambio de flujo.

Las estructuras de control que se van a ver en este tema son aquellas con trasfieren el control a una nueva dirección, de acuerdo a una condición evaluada.

 

Estructuras de control condicionales

Las estructuras de control condicionales que se van a ver son la bifurcación abierta y la bifurcación cerrada. Un esquema del flujo de ambas estructuras ha quedado recogido en la Figura 8.2.

·                     La bifurcación abierta. La sentencia if.

La sentencia que está precedida por la estructura de control condicionada se ejecutará si la condición de la estructura de control es verdadera; en caso contrario no se ejecuta la instrucción condicionada y continua el programa con la siguiente instrucción. En la figura 8.2. se puede ver un esquema del comportamiento de la bifurcación abierta.

La sintaxis de la estructura de control condicionada abierta es la siguiente:

if(condición) sentencia;

Si la condición es verdadera (distinto de cero en el lenguaje C), se ejecuta la sentencia. Si la condición es falsa (igual a cero en el lenguaje C), no se ejecuta la sentencia.

Si en lugar de una sentencia, se desean condicionar varias de ellas, entonces se crea una sentencia compuesta mediante llaves.

Ejemplo:

Programa que solicite dos valores enteros y muestre el cociente:

#include <stdio.h>

void main(void)

{

      short D, d;

      printf("Programa para dividir dos enteros...\n");

      printf("Introduzca el dividendo ... ");

      scanf("%hd",&D);

      printf("Introduzca el divisor ... ");

      scanf("%hd",&d);

      if(d != 0) printf("%hu / %hu = %hu", D, d, D / d);

}

Se efectuará la división únicamente en el caso en que se verifique la condición de que d != 0.

·                     La bifurcación cerrada. La sentencia if – else.

En una bifurcación cerrada, la sentencia que está precedida por una estructura de control condicionada se ejecutará si la condición de la estructura de control es verdadera; en caso contrario se ejecuta una instrucción alternativa. Después de la ejecución de una de las dos sentencias (nunca las dos), el programa continúa la normal ejecución de las restantes sentencias que vengan a continuación.

La sintaxis de la estructura de control condicionada cerrada es la siguiente:

if(condición) sentencia1;

else sentencia2;

Si la condición es verdadera (distinto de cero en el lenguaje C), se ejecuta la sentencia llamada sentencia1. Si la condición es falsa (igual a cero en el lenguaje C), se ejecuta la sentencia llamada sentencia2.

Si en lugar de una sentencia, se desean condicionar varias de ellas, entonces se crea una sentencia compuesta mediante llaves.

Ejemplo:

El mismo programa anteriormente visto. Quedará mejor si se escribe de la siguiente forma:

#include <stdio.h>

void main(void)

{

      short D, d;

      printf("Programa para dividir dos enteros...\n");

      printf("Introduzca el dividendo ... ");

      scanf("%hd",&D);

      printf("Introduzca el divisor ... ");

      scanf("%hd",&d);

      if(d != 0) printf("%hu / %hu = %hu", D, d, D / d);

      else printf(“No se puede realizar division por cero”);

}

Se efectuará la división únicamente en el caso en que se verifique la condición de que d != 0. Si el divisor introducido es igual a cero, entonces imprime en pantalla un mensaje de advertencia.

·                     Anidamiento de estructuras condicionales.

Decimos que se produce anidamiento de estructuras de control cuando una estructura de control aparece dentro de otra estructura de control del mismo tipo.

Tanto en la parte if como en la parte else, los anidamientos pueden llegar a cualquier nivel. De esa forma podemos elegir entre numerosas sentencias estableciendo las condiciones necesarias.

Una estructura de anidamiento tiene, por ejemplo, la forma:

if(expresión_1)              /* primer if */

{

      if(expresión_2)         /* segundo if */

      {

            if(expresión_3)   /* tercer if */

                  sentencia_1;

            else              /* alternativa al tercer if */

                  sentencia_2;

      }

      else                    /* alternativa al 2º if */

            sentencia_3;

}

else                         /* alternativa al primer if */

      sentencia_4;

Cada else se asocia al if más próximo en el bloque en el que se encuentre y que no tenga asociado un else. No está permitido utilizar un else sin un if previo. Y la estructura else debe ir inmediatamente después de la sentencia condicionada con su if.

Un ejemplo de estructura anidada sería, siguiendo con los ejemplos anteriores, el caso de que, si el divisor introducido ha sido el cero, el programa preguntase si se desea introducir un divisor distinto.

#include <stdio.h>

void main(void)

{

      short D, d;

      char opcion;

      printf("Programa para dividir dos enteros...\n");

      printf("Introduzca el dividendo ... ");

      scanf("%hd",&D);

      printf("Introduzca el divisor ... ");

      scanf("%hd",&d);

      if(d != 0)

            printf("%hu / %hu = %hu", D, d, D / d);

      else

      {

            printf("No se puede dividir por cero.\n");

            printf("¿Introducir otro denominador (s/n)?");

            opcion = getchar();

            if(opcion == 's')

            {

                  printf("\nNuevo denominador ... ");

                  scanf("%hd",&d);

                  if(d != 0)

                        printf("%hu / %hu = %hu", D, d, D/d);

                  else

                        printf("De nuevo ha introducido 0.");

            }

      }

}

La función getchar() está definida en la biblioteca stdio.h. Esta función espera a que el usuario pulse una tecla del teclado y, una vez pulsada, devuelve el código ASCII de la tecla pulsada.

En este ejemplo hemos llegado hasta un tercer nivel de anidación.

·                     Escala if - else – if

Cuando se debe elegir entre una lista de opciones, y únicamente una de ellas ha de ser válida, se llega a producir una concatenación de condiciones de la siguiente forma:

if(condición1) setencia1;

else  

{

         if(condición2) sentencia2;

         else

         {

                   if(condición3) sentencia3;

                   else sentencia4;

         }

}

Este tipo de anidamiento se resuelve con la estructura else if, que permite una concatenación de las condicionales. Un código como el antes escrito quedaría:

if(condición1) sentencia1;

else if (condición2) sentencia2;

else if(condición3) sentencia3;

else sentencia4;

Como se ve, una estructura así anidada se escribe con mayor facilidad y expresa también más claramente las distintas alternativas.

Un ejemplo de concatenación podría ser el siguiente programa, que solicita al usuario la nota de un examen y muestra por pantalla la calificación académica obtenida:

#include <stdio.h>

void main(void)

{

      float nota;

      printf("Introduzca la nota del examen ... ");

      scanf("%f",&nota);

      if(nota < 0 || nota > 10) printf("Nota incorrecta.");

      else if(nota < 5) printf("Suspenso.");

      else if(nota < 7) printf("Aprobado.");

      else if(nota < 9) printf("Notable.");

      else if(nota < 10) printf("Sobresaliente.");

      else printf("Matrícula de honor.");

}

Únicamente se evaluará un else if cuando no haya sido cierta la condición de ninguno de los anteriores ni del if inicial. Si todas las condiciones resultan ser falsas, entonces se ejecutará el último else.

·                     La estructura condicional y el operador condicional

Existe un operador que selecciona entre dos opciones, y que realiza, de forma muy sencilla y bajo ciertas limitaciones la misma operación que la estructura de bifurcación cerrada. Es el operador interrogante, dos puntos (?:).

La sintaxis del operador es la siguiente:

expresión_1 ? expresión_2 : expresión3;

Se evalúa expresión_1; si resulta ser verdadera, entonces se ejecutará la sentencia recogida en expresión_2; y si es falsa, entonces se ejecutará la sentencia recogida en expresión_3. Tanto expresión_2 como expresión_3 pueden ser funciones,  o expresiones muy complejas, pero siempre deben ser sentencias simples.

Ejemplo de este operador serían cualquiera de las siguientes sentencias:

x >= 0 ? printf(“Positivo\n”) : printf(“Negativo\n”);

d != 0 ? printf(“cociente = %hd\n”,D / d) : printf(“División por cero”);

También podría expresarse dentro de la función printf, por ejemplo así:

printf(“cociente = %hd\n”, d != 0 ? D / d : 0);

 

Estructura de selección múltiple: Sentencia switch

La sentencia switch permite transferir el control de ejecución del programa a un punto de entrada etiquetado en un bloque de sentencias. La decisión sobre a qué instrucción del bloque se trasfiere la ejecución se realiza mediante una expresión entera.

La forma general de la estructura switch es:

switch(variable_del_switch)

{

         case expresionConstante1:

                   [sentencias;]

                    [break;]

         case expresionConstante2:

                   [sentencias;]

                    [break;]

         […]

         case expresionConstanteN:

                   [sentencias;]

                    [break;]

         [default

                    sentencias;]

}

El cuerpo de la sentencia switch se conoce como bloque switch y permite tener sentencias prefijadas con las etiquetas case. Una etiqueta case es una constante entera (variables de tipo char ó short ó long, con o sin signo). Si el valor de la expresión de switch coincide con el valor de una etiqueta case, el control se transfiere a la primera sentencia que sigue a la etiqueta. No puede hacer dos case con el mismo valor de constante. Si no se encuentra ninguna etiqueta case que coincida, el control se transfiere a la primera sentencia que sigue a la etiqueta default. Si no existe esa etiqueta default, entonces se salta el cuerpo switch entero.

Por ejemplo, para el siguiente código:

switch(a)

{

         case 1:  printf(“UNO\t”);

         case 2:  printf(“DOS\t”);

         case 3:  printf(“TRES\t”);

         default: printf(“NINGUNO\n”);

}

Si el valor de a es, por ejemplo, 2, entonces comienza a ejecutar el código del bloque a partir de la línea que da entrada el case 2:. Producirá la siguiente salida por pantalla:

DOS   TRES  NINGUNO.

Una vez que el control se ha trasferido a la sentencia que sigue a una etiqueta concreta, ya se ejecutan todas las demás sentencias del bloque switch, de acuerdo con la semántica de dichas sentencias. El que aparezca una nueva etiqueta case no obliga a que se dejen de ejecutar las sentencias del bloque. Si se desea detener la ejecución de sentencias en el bloque switch, debemos transferir explícitamente el control al exterior del bloque. Y eso se realiza utilizando la sentencia break. Dentro de un bloque switch, la sentencia break transfiere el control a la primera sentencia posterior al switch. Ese es el motivo por el que en la sintaxis de la estructura switch se escriba (en forma opcional) las sentencias break en las instrucciones inmediatamente anteriores a cada una de las etiquetas.

En el ejemplo anterior, si colocamos la sentencia break en cada case,

switch(a)

{

      case 1: printf(“UNO”);

         break;

         case 2: printf(“DOS”);

         break;

         case 3: printf(“TRES”);

         break;

         default: printf(“NINGUNO”);

}

Entonces la salida por pantalla, si la variable a tiene el valor 2 será únicamente:

DOS

La ejecución de las instrucciones que siguen más allá de la siguiente etiqueta case puede ser útil en algunas circunstancias. Pero lo habitual será que aparezca una sentencia break al final del código de cada etiqueta case.

Una sola sentencia puede tener más de una etiqueta case. Queda claro en el siguiente ejemplo:

short int nota;

printf("Introduzca la nota del examen ... ");

scanf("%hd",&nota);

switch(nota)

{

         case 1:

         case 2:

         case 3:

         case 4:     printf(“SUSPENSO”);

                            break;

         case 5:

         case 6:        printf(“APROBADO”);

                            break;

         case 7:

         case 8:        printf(“NOTABLE”);

                            break;

         case 9:        printf(“SOBRESALIENTE”);

                            break;

         case 10:      printf(“MATRÍCULA DE HONOR”);

                            break;

         default:      printf(“Nota introducida errónea.”);

}

No se puede poner una etiqueta case fuera de un bloque switch. Y tampoco tiene sentido colocar instrucciones dentro del bloque switch antes de aparecer el primer case: eso supondría un código que jamás podría llegar a ejecutarse. Por eso, la primera sentencia de un bloque switch debe estar ya etiquetada.

Se pueden anidar distintas estructuras switch.

El ejemplo de las notas, que ya se mostró al ejemplificar una anidación de sentencias if–else–if puede servir para comentar una característica importante de la estructura switch. Esta estructura no admite, en sus distintas entradas case, ni expresiones lógicas o relacionales, ni expresiones aritméticas, sino literales. La única relación aceptada es, pues, la de igualdad. Y además, el término de la igualdad es siempre entre una variable o una expresión (la del switch) y valores literales: no se puede indicar el nombre de una variable. El programa de las notas, si la variable nota hubiese sido de tipo float, como de hecho quedo definida cuando se resolvió el problema con los condicionales if–else–if no tiene solución posible mediante la estructura switch.

Y una última observación: las sentencias de un case no forman un bloque y no tiene porqué ir entre llaves. La estructura switch entera, con todos sus case’s, sí es un bloque.

 

Estructuras de repetición

Una estructura de repetición o de iteración es aquella que nos permite repetir un conjunto de sentencias mientras que se cumpla una determinada condición.

Las estructuras de iteración o de control de repetición, en C, se implementan con las estructuras do–while, while y for. Todas ellas permiten la anidación de unas dentro de otras a cualquier nivel. Puede verse un esquema de su comportamiento en la figura 8.3., en páginas anteriores.

·                     Estructura while.

La estructura while, también llamada condicional, o centinela, se emplea en aquellos casos en que no se conoce por adelantado el número de veces que se ha de repetir la ejecución de una determinada sentencia o bloque: ninguna, una o varias.

La sintaxis de la estructura while es la que sigue:

while(condición) sentencia;