Sunteți pe pagina 1din 68

1

INTRODUCCIÓN A LA PROGRAMACIÓN

1.1 INTRODUCCIÓN
Una breve definición de informática es la siguiente: «Ciencia del tratamiento racional,
mediante máquinas automáticas, de la información». De ella se derivan dos hechos importantes.
Por un lado, el tratamiento de la información es llevado a cabo mediante máquinas automáticas: los
ordenadores. Por otro lado, la racionalidad en dicho tratamiento introduce el componente humano
como elemento indispensable de cualquier proceso informático, ya que será el ser humano quien
determine el modo de tratar la información.
La informática es una disciplina académica relativamente joven. Baste con señalar que el
término Computer Science no fue acuñado hasta los años 60 por el matemático George Forsythe y
que el primer Departamento de Informática en una Universidad se formó en el año 1962. Sin
embargo, la vertiginosa evolución de las tecnologías de la información ha llevado a la informática a
estar presente en la actualidad en gran parte de los campos científico, económico y social.
La principal herramienta que emplea la informática para llevar a cabo las tareas relacionadas
anteriormente es el ordenador o computador. Su definición nos aporta nuevas claves:
«Máquina automática para el tratamiento de la información que ejecuta programas
formados por una sucesión de operaciones aritméticas y lógicas”

Es decir, para realizar el tratamiento de la información, el ordenador acepta una entrada (los
datos), que mediante un programa transforma para proporcionar una salida (el resultado). Es
precisamente en la determinación del programa que el ordenador debe ejecutar donde el ser humano
interviene de forma decisiva. La Ilustración 1 esquematiza este proceso para el tratamiento

programa

ENTRADA ORDENADOR SALIDA


datos resultado
Ilustración 1: Tratamiento automático de la información mediante el ordenador

1
CURSO 2018/19 1. INTRODUCCIÓN A LA PROGRAMACIÓN

paridad Lenguaje de program paridad(input,output);


var n:integer;
begin
BLOCK
programación write('Introduzca un número entero: ');
readln(n);
escribir leer (n) if then else if n mod 2 = 0 then
('Un nº:') writeln('El número es PAR')
else
n mod 2 = 0 escribir escribir writeln('El número es IMPAR')
('Es par') ('Es impar') end.

ALGORITMO PROGRAMA

Ilustración 2: Relación entre algoritmo y programa

automático de la información mediante el ordenador.


En general, la secuencia de operaciones necesarias para resolver un problema se denomina
algoritmo, que puede considerarse como una fórmula o receta para la resolución de un problema.
Cuando se pretende que el problema sea resuelto por un ordenador, dicho algoritmo debe traducirse
a una sintaxis adecuada: el programa. Un programa está formado por una serie de instrucciones que
se corresponden con los distintos pasos del algoritmo que representa. Además, las instrucciones del
programa deben ajustarse a las normas que dicte el lenguaje de programación utilizado.
Finalmente, el conjunto de actividades que llevan al desarrollo de un programa informático se
denomina programación. En la Ilustración 2 se representan todos estos conceptos.

1.2 METODOLOGÍA DE LA PROGRAMACIÓN


Un programador es alguien que resuelve problemas. Para llegar a ser un programador eficaz
es necesario primero aprender a resolver problemas de un modo riguroso y sistemático, lo que se
denomina metodología de la programación. Aunque un problema puede resolverse normalmente de
muchas formas distintas y se requiere cierta dosis de práctica e incluso de habilidad para obtener
programas eficientes y de resolución clara, existen pautas que guían al programador de forma
genérica para alcanzar una buena solución. En esta sección se estudiarán dichas pautas.
La tarea de resolución de problemas se divide en tres fases:
 Análisis del problema: consiste en definir y comprender el problema en toda su
extensión, analizando todo aquello que es relevante para su resolución.
 Diseño del algoritmo: determinar y representar genéricamente el método de resolución
del problema (algoritmo).
 Resolución del problema mediante el ordenador: traducir el algoritmo a un lenguaje
de programación y comprobar que se ha hecho correctamente.

2
CURSO 2018/19 1. INTRODUCCIÓN A LA PROGRAMACIÓN

1.2.1 ANÁLISIS DEL PROBLEMA


La fase de análisis del problema tiene como objetivo conseguir que el programador alcance a
comprender el problema en toda su extensión. Esto requiere que el problema esté bien definido, es
decir, que se disponga de una descripción del problema suficientemente detallada. A menudo, es
tarea del propio programador desarrollar este “enunciado” y completarlo durante el análisis
mediante la celebración de entrevistas con personal experto en el área de aplicación del problema
(directivos de la empresa, personal especializado, etc.).
La correcta definición del problema debe ir acompañada de la identificación y descripción de
los datos de entrada y de salida del problema. Es decir, el programador debe determinar de qué
datos relevantes para la resolución del problema se dispone (entrada) y qué información debe
proporcionarse como resolución del problema (salida).
Un dato de entrada puede definirse como un dato del que depende la salida del problema y
que, a su vez, no depende de ningún otro (no puede calcularse a partir de otros datos de entrada).
Para cada dato de entrada, deberá especificarse lo siguiente:
 Identificador del dato, que permita referenciarlo durante la fase de análisis.
 Descripción del dato: ¿en qué consiste el dato?
 Procedencia: Un dato de entrada puede solicitarse desde un dispositivo de entrada,
generarse aleatoriamente o integrarse directamente en el propio programa.
La primera alternativa deberá emplearse cuando se trate de información de entrada
que varía habitualmente de una resolución a otra del problema y se indicará como
procedencia el dispositivo de entrada receptor (teclado, etc.).
La segunda alternativa se corresponde con programas donde alguno de los datos está
sujeto al azar (por ejemplo, el valor de la tirada de un dado en un juego de azar). En tal
caso, se indicará como procedencia aleatorio.
La tercera alternativa es más apropiada cuando se trate de datos de entrada con
valores que habitualmente no varían, evitando así que el usuario tenga que introducir
dichos valores idénticos en cada resolución del problema. En este caso, se indicará
como procedencia dato fijo.
 Valor (solo para datos fijos): Cuando el dato se integre directamente en el programa
(dato fijo), se indicará el valor asociado.
 Restricciones (solo para datos procedentes de un dispositivo de entrada o aleatorios):
deberán indicarse las restricciones impuestas a los valores que el dato pueda tomar para
que se considere válido. El cumplimiento de estas restricciones deberá posteriormente
ser exigido por el programa mediante el correspondiente mecanismo de control de
entrada (en el caso de datos procedentes de teclado) o durante la generación de los
números aleatorios (en el caso de datos aleatorios).
Un dato de salida es aquel que proporciona toda o parte de la solución del problema y deberá
poder obtenerse a partir de los datos de entrada especificados en el apartado anterior. Para cada dato
de salida se proporcionará:
 Identificador del dato, que permita referenciarlo durante la fase de análisis.
 Descripción del dato: ¿en qué consiste el dato?
 Destino: deberá indicarse el dispositivo de salida hacia el que se dirigirá el dato
(monitor, impresora, etc.).

3
CURSO 2018/19 1. INTRODUCCIÓN A LA PROGRAMACIÓN

Una última sección de la fase de análisis estará reservada a la inclusión de aquellos


comentarios que el programador considere oportunos para clarificar aún más el problema a resolver.

EJEMPLO 1.1. Se desea realizar un programa que determine el precio de una llamada telefónica entre teléfonos fijos.
El precio de la llamada lo constituye el coste por establecimiento de llamada (0.10 euros) y un coste por paso (0.03
euros/paso). La duración de un paso depende del tramo horario en el que se efectúe la llamada, según la siguiente
tabla:

Tramo 1 Tramo 2 Tramo 3


60 seg. 20 seg. 40 seg.
El usuario indicará la duración de la llamada y el tramo horario en el que se efectuó.
ANÁLISIS:
a) Datos de entrada:
 EST_LLAM=0.10. Coste del establecimiento de llamada (en euros). Dato fijo.
 PASO=0.03. Coste de un paso (en euros). Dato fijo.
 DUR1=60. Duración de un paso en el primer tramo horario (en segundos). Dato fijo.
 DUR2=20. Duración de un paso en el segundo tramo horario (en segundos). Dato fijo.
 DUR3=40. Duración de un paso en el tercer tramo horario (en segundos). Dato fijo.
 duracion: Duración de la llamada (valor entero, en segundos). Teclado. (duracion > 0).
 tramo: Tramo en el que se efectuó la llamada (valor entero). Teclado. (tramo  {1,2,3}).
b) Datos de salida:
 precio: Coste de la llamada (en euros). Monitor.
c) Comentarios:
 Las fracciones de pasos se computarán como pasos completos.
 Emplearemos una variable intermedia para calcular el número de pasos.

1.2.2 DISEÑO DEL ALGORITMO


Como ya sabemos, el algoritmo es la representación de los pasos necesarios para resolver un
problema. Estos pasos serán después trasladados a instrucciones que el ordenador podrá ejecutar.
Aprender a diseñar algoritmos correctamente se considera esencial en la práctica de la
programación, más que el conocimiento específico del lenguaje de programación más novedoso. El
algoritmo determinará el método de resolución del problema, y si dicho método se ha desarrollado
adecuadamente su traducción a un lenguaje de programación u otro es una cuestión menor.
Igualmente, el conocimiento de las particularidades internas de un tipo de ordenador u otro tampoco
será crucial para el diseño del algoritmo. Esto se debe a que el algoritmo es independiente tanto del
lenguaje de programación que vaya a emplearse como del ordenador donde finalmente se ejecute el
programa que lo represente: de ahí su gran importancia.
Para la descripción de un algoritmo pueden utilizarse representaciones gráficas (diagramas) o
textuales (pseudocódigo, lenguaje natural).
Además, el diseño de algoritmos requiere conocer de qué herramientas se dispone para
describir los pasos de resolución de un problema y cómo debemos utilizarlas. Esto dependerá de la
metodología de programación que vayamos a emplear. En este curso nos centraremos en la

4
CURSO 2018/19 1. INTRODUCCIÓN A LA PROGRAMACIÓN

metodología de la programación denominada programación estructurada y emplearemos


diagramas estructurados para la representación de los algoritmos, los cuales serán estudiados en el
siguiente capítulo.
La fase de diseño se descompone en dos subfases:
a) Parte declarativa: en ella se incluirá la definición de constantes simbólicas y la declaración
de variables que se estudiarán en el Capítulo 2, en este orden.
b) Representación algorítmica: descripción del algoritmo empleando alguno de los métodos de
representación mencionados.

EJEMPLO 1.2. La fase de diseño del problema planteado en el Ejemplo 1.1, empleando pseudocódigo para la
representación algorítmica, sería la siguiente:
a) Parte declarativa:
CONSTANTES
EST_LLAM=0.10
PASO=0.03
DUR1=60
DUR2=20
DUR3=40
VARIABLES
duracion,tramo,numPasos: entero
precio: real
b) Representación algorítmica:
inicio
leer desde teclado duracion
mientras duracion ≤ 0 hacer
escribir mensaje de error
leer desde teclado duracion
leer desde teclado tramo
mientras tramo < 1 o tramo > 3 hacer
escribir mensaje de error
leer desde teclado tramo
si tramo=1 entonces
almacenar en numPasos el valor techo(duracion/DUR1)
sino (es tramo=1)
si tramo=2 entonces
almacenar en numPasos el valor techo(duracion/DUR2)
sino (es tramo=2)
almacenar en numPasos el valor techo(duracion/DUR3)
almacenar en precio el valor EST_LLAM+numPasos×PASO
escribir precio
fin

1.2.3 RESOLUCIÓN DEL PROBLEMA MEDIANTE EL ORDENADOR


Tras el diseño del algoritmo, este debe traducirse a las instrucciones de un lenguaje de
programación para que pueda ser resuelto por el ordenador. Esta fase se divide en tres subfases:
a) Codificación o implementación del algoritmo: es la conversión de los pasos del algoritmo a
las instrucciones equivalentes en un lenguaje de programación. El algoritmo escrito en un
lenguaje de programación se denomina programa, código fuente o, simplemente, código.
b) Verificación del programa: consiste en la comprobación de que la codificación del algoritmo
se ha realizado correctamente, empleando adecuadamente de las reglas gramaticales y
sintácticas del lenguaje utilizado.

5
CURSO 2018/19 1. INTRODUCCIÓN A LA PROGRAMACIÓN

c) Validación del programa: consiste en la comprobación de que los resultados proporcionados


por el programa se corresponden con los establecidos en el análisis del problema.
Veremos un ejemplo de esta última fase en el siguiente capítulo, una vez que se conozcan las
instrucciones en lenguaje C.

1.3 LOS LENGUAJES DE PROGRAMACIÓN


Para el desarrollo de un programa es necesario conocer al menos un lenguaje de
programación. Esta sección se centra en estos, estableciendo una clasificación atendiendo al grado
de abstracción del lenguaje y describiendo ciertas herramientas de programación necesarias para
que el ordenador pueda realizar la tarea descrita en el lenguaje de programación empleado.

1.3.1 NIVELES DE ABSTRACCIÓN


El nivel de abstracción de un lenguaje se refiere a en qué medida la sintaxis y uso de ese
lenguaje se encuentra cercano al modo de trabajar de la máquina o, por el contrario, al modo de
pensar y hablar del ser humano. La clasificación de los lenguajes atendiendo al nivel de abstracción
está relacionada con la evolución de los lenguajes de programación, ya que el grado de abstracción
de los lenguajes ha ido aumentando con el paso de los años. A grandes rasgos, podemos distinguir
tres grupos: lenguajes máquina, lenguajes de bajo nivel y lenguajes de alto nivel.

LENGUAJES MÁQUINA
El ordenador representa internamente la información que maneja utilizando código binario.
En los primeros tiempos, los programadores tenían que realizar la tediosa tarea de traducir sus
algoritmos directamente a las secuencias binarias que representaban las instrucciones y los datos
contenidos en el programa. Estas instrucciones, conocidas como instrucciones de código máquina,
constituyen el lenguaje máquina de un ordenador.
La ventaja de programar en lenguaje máquina es que el código generado puede ser muy
eficiente, es decir, emplear poca memoria y ser muy rápido, y, por tanto, utilizar la mínima cantidad
de recursos. Sin embargo, la importancia de sus inconvenientes ha acabado con el uso de este tipo
de lenguajes en la actualidad. Uno de estos inconvenientes es la dependencia respecto al hardware
del ordenador, ya que, el lenguaje máquina de una familia de microprocesadores (por ejemplo, Intel
Pentium) no tiene porqué ser compatible con el de otra. Otro inconveniente es la dificultad en la
codificación. Por ejemplo, la operación aritmética 4✶3+5 podría representarse esquemáticamente
en un hipotético lenguaje máquina como
multiplicación 4 3 dirección
0110 0100 0011 0001

suma 5 dirección
0101 0101 0001
donde puede observarse que tanto los códigos de operación como las direcciones de memoria y los
valores se representan mediante código binario. Obsérvese que la equivalencia real en instrucciones
máquina sería exclusivamente la cadena binaria 0110010000110001010101010001, difícilmente
comprensible de forma directa por el programador.

6
CURSO 2018/19 1. INTRODUCCIÓN A LA PROGRAMACIÓN

LENGUAJES DE BAJO NIVEL (ENSAMBLADORES)


Si bien en los primeros tiempos el programador utilizaba lenguaje máquina para describir sus
programas, pronto se extendió la costumbre de emplear, durante el diseño de los algoritmos,
mnemotécnicos para representar sus pasos (es decir, abreviaturas en inglés de las instrucciones de
código máquina a las que equivalen). Esto permitía a los programadores abstraerse de las
particularidades de la representación interna de las instrucciones durante el desarrollo del método
de resolución del problema y propició el siguiente paso en la evolución de los lenguajes de
programación, los lenguajes ensambladores, donde las instrucciones se expresaban utilizando un
lenguaje simbólico (no binario) que aumentó la legibilidad de los programas. Sin embargo, estos
lenguajes mantienen su dependencia de la máquina, siendo por tanto cada lenguaje ensamblador
específico de una familia de microprocesadores. Además, cada instrucción en lenguaje ensamblador
equivale a una única instrucción en código máquina. Por ejemplo, la operación aritmética anterior
tendría en un hipotético lenguaje ensamblador un aspecto parecido al siguiente:
MUL 4,3,A
SUM 5,A
Debido a lo todavía complejo y poco intuitivo de su programación, actualmente los
ensambladores se utilizan en áreas muy reducidas donde las exigencias en velocidad de ejecución o
aprovechamiento de recursos son elevadas.

LENGUAJES DE ALTO NIVEL


Aunque la introducción de los lenguajes ensambladores supuso un importante paso hacia
adelante en la evolución de los lenguajes de programación, aún existían inconvenientes que salvar,
tales como la dependencia respecto al hardware y la obligación de describir los métodos de
resolución como una secuencia de los pequeños pasos que podían realizarse en lenguaje máquina.
Se advirtió la necesidad de proporcionar instrucciones de más alto nivel que permitieran
describir acciones de mayor envergadura, las cuales equivaldrían luego a un conjunto de
instrucciones en código máquina. De esta idea surgieron los lenguajes de alto nivel, más cercanos al
modo de expresarse del ser humano y, por tanto, más fáciles de aprender y de utilizar. Igualmente,
debido a su fácil comprensión, los programas escritos en un lenguaje de alto nivel son más fáciles
de actualizar y corregir.
El lenguaje C, que se trata en este curso, es un lenguaje de alto nivel. El ejemplo anterior se
expresaría en C con una única instrucción:
A = 4✶3+5
fácilmente entendible. Obsérvese que esta única instrucción en un lenguaje de alto nivel equivale a
varias instrucciones en el código máquina y el ensamblador del ejemplo anterior.
Además de su mayor legibilidad, otra de las características esenciales de los lenguajes de alto
nivel es que son transportables. Esto significa que, a diferencia de los lenguajes de más bajo nivel,
el mismo programa puede ser válido con pocos o ningún cambio en distintas plataformas, esto es,
en ordenadores con distintos microprocesadores. Por tanto, los lenguajes de alto nivel son
independientes del hardware.
Sus inconvenientes se corresponden con las ventajas de los lenguajes máquina y
ensambladores, ya que tanto en ocupación de memoria como en tiempo de ejecución los lenguajes
de alto nivel suelen presentar un consumo mayor que aquellos.

7
CURSO 2018/19 1. INTRODUCCIÓN A LA PROGRAMACIÓN

1.3.2 TRADUCTORES DE LENGUAJES


El ordenador solo entiende directamente programas escritos en lenguaje máquina. Cuando la
codificación se realiza utilizando un lenguaje simbólico (ensambladores y lenguajes de alto nivel),
será necesario traducir dicho programa al código correspondiente en lenguaje máquina. Como
puede suponerse, el programador no tiene que hacer esto por sí mismo, sino que existen programas
que realizan dicha traducción de forma automática.
Al programa escrito en lenguaje simbólico se le llama programa o código fuente y al
resultado de la traducción programa o código objeto:
Simbólico Máquina
Programa TRADUCTOR Programa
fuente objeto

Distinguimos tres tipos de traductores: ensambladores, compiladores e intérpretes.


Los ensambladores traducen programas escritos en ensamblador, mientras que los
compiladores e intérpretes traducen programas escritos en un lenguaje de alto nivel.
Por otra parte, los intérpretes traducen el programa instrucción a instrucción, de forma que
hasta que no terminan de ejecutar la última instrucción traducida no proceden a traducir la
siguiente, por lo que no generan programa objeto en disco. Por contra, tanto compiladores como
ensambladores traducen primero todo el programa fuente, generando un programa objeto en disco,
y una vez terminada la traducción, el programa podrá ejecutarse.
Las diferencias entre estos tres tipos de traductores están esquematizadas en la Tabla 1.
Ahora bien, cuando se desarrolla un programa, este puede incluir referencias a tareas que no
son instrucciones del lenguaje de programación, sino que están descritas en las llamadas bibliotecas
de funciones que suelen acompañar al traductor del lenguaje para hacerlo más versátil. Dichas
tareas son frecuentes y comunes a muchos programas, tales como operaciones gráficas (dibujar un
círculo, cambiar el color del texto), matemáticas (logarítmicas, trigonométricas), etc.
Por otro lado, un programa puede hacer referencia a otros programas desarrollados por el
propio programador que resuelven tareas no incluidas en la biblioteca de funciones (por ejemplo, C
no incorpora una función para calcular el factorial de un número). Estos programas se denominan
programas fuentes secundarios y requerirán también un proceso de traducción.
Por ello, después de traducir el programa principal mediante un ensamblador o un compilador
(que solo conoce la equivalencia a lenguaje máquina de las instrucciones propias del lenguaje que
traduce), será necesario unir el programa objeto generado con el resto de módulos donde existan
tareas a las que se haga referencia desde dicho programa principal (programas fuentes secundarios y
bibliotecas de funciones). Para conseguir el programa ejecutable resultado de esta operación de
unión se debe utilizar una herramienta denominada montador, enlazador o linkador.

Lenguaje fuente Traductor Método


L. ensamblador Ensambladores
Todo el programa
L. de alto nivel Compiladores (programa objeto en disco)
Intérpretes Instrucción a instrucción

Tabla 1: Diferencias entre los tres tipos de traductores

8
CURSO 2018/19 1. INTRODUCCIÓN A LA PROGRAMACIÓN

bibliotecas

programa fuente programa objeto programa


principal ENSAMBLADOR ejecutable
ENLAZADOR
O COMPILADOR

programas fuentes otros


secundarios ENSAMBLADOR módulos
O COMPILADOR

Ilustración 5: Esquema de funcionamiento de compiladores y ensambladores

De este modo, cuando estamos empleando un compilador o un ensamblador, el esquema de


funcionamiento es el que se muestra en la Ilustración 5.

9
2
TIPOS DE DATOS
Y EXPRESIONES

En el capítulo anterior se definió la informática como la ciencia que estudia el tratamiento


automático de la información. Ahora bien, un ordenador puede manejar dos categorías de
información: instrucciones y datos. Mientras las instrucciones describen el proceso a realizar, los
datos representan la información a procesar. En este capítulo estudiaremos las distintas alternativas
de que dispone el programador para representar los datos (los tipos de datos), así como el modo de
operar con ellos (las expresiones).

2.1 TIPOS DE DATOS, CONSTANTES SIMBÓLICAS Y VARIABLES


Toda información, ya sea numérica, alfabética, gráfica o de cualquier otro tipo, se representa
internamente en el ordenador mediante una secuencia de bits. En los primeros tiempos de la
informática, esto suponía que el programador debía estar familiarizado con dicha representación
interna, debiendo realizar por sí mismo la conversión de los datos al correspondiente código
binario. Sin embargo, con la introducción del concepto de tipo de datos, los lenguajes actuales
permiten que el programador pueda abstraerse de la representación interna de los datos y trabajar
con ellos de una forma más natural.
En la resolución de problemas mediante ordenadores, la estructura seleccionada para representar
los datos con los que trabaja un programa es tan importante como el diseño del algoritmo que
determina su comportamiento, ya que de dicha estructura va a depender en gran parte la claridad,
eficiencia y requisitos de memoria del programa resultante.
Existen dos categorías de datos: simples y estructurados. En este capítulo nos centraremos en los
tipos de datos simples y los tipos de datos estructurados se estudiarán en los Capítulos 4 y 6.

2.1.1 TIPOS DE DATOS SIMPLES


Un dato simple puede definirse como un elemento de información que se trata dentro de un
programa como una unidad (por ejemplo, un número o una letra). El lenguaje C dispone
fundamentalmente de tres tipos de datos simples: entero, real y carácter.

10
CURSO 2018/19 2. TIPOS DE DATOS Y EXPRESIONES

TIPO ENTERO
El tipo entero se representa en C mediante la palabra clave int y consiste en el subconjunto de
valores enteros contenido en el intervalo [-2147483648, 2147483647] (equivalente al intervalo
[-231, 231-1] que permite representar 232=4.294.967.296 valores distintos y ocupa 4 bytes 1). Un
literal entero es una secuencia de uno o más dígitos entre el 0 y el 9. Al literal puede añadírsele un
signo positivo o negativo (+ o −), aunque se asumirá el signo positivo cuando no se especifique
ningún signo. Sin embargo, no admite el uso de separadores de unidades de millar ni el empleo del
separador decimal aunque la parte decimal no sea significativa, debiendo por tanto aparecer
únicamente los dígitos que constituyen la cifra.
Ejemplos de literales válidos de tipo int son:
5 -15 2003
Ejemplos de literales no válidos como tipo int son:
-1050.0 3.456 3,200

TIPO REAL
El tipo real consiste en un subconjunto de los números reales. Un literal real consta de una parte
entera y una parte decimal, separadas por un punto decimal. En C, existen dos tipos básicos para
representar valores reales: simple precisión (float) y doble precisión (double). El tipo float
ocupa 4 bytes y permite representar valores en el rango [1.2×10 -38, 3.4×1038] (positivo y negativo),
además del cero. El tipo double ocupa 8 bytes y permite representar valores en el rango
[2.2×10-308, 1.8×10308] (también positivo y negativo), además del cero.
En aplicaciones científicas, a menudo se emplea una representación especial para manejar
números muy grandes o muy pequeños. Por ello, en C existe una representación denominada
notación exponencial donde cada literal consta de un número entero o real (mantisa) y de un
número entero (exponente), separados por la letra "e" (en minúscula o mayúscula). Por ejemplo, el
literal 1.345e2 es equivalente a 1.345×102 o 134.5, y el literal 2.058e-3 es equivalente a
2.058×10-3 o 0.002058. En el primer valor, el punto decimal se desplaza dos lugares hacia la
derecha porque el exponente es +2. En el segundo, el punto decimal se desplaza tres lugares hacia
la izquierda porque el exponente es -3.
Ejemplos de literales reales válidos en C son:
0.45 .23 1234. 1e10 -2e-3 0.2E4
Ejemplos de literales reales no válidos en C son:
1e0.1 12.0a10 1,345.5 1.345,5 2.3-e5 34

TIPO CARÁCTER
El tipo carácter se representa en C con la palabra clave char y consiste en el conjunto finito y
ordenado de los caracteres representables por el ordenador (en el caso de los PC's, consiste en el
conjunto de caracteres incluido en la tabla de códigos ASCII). Un literal de tipo carácter se
representa en C mediante un solo carácter encerrado entre comillas simples o apóstrofos.

1 Los intervalos de valores válidos y el número de bytes ocupados por los datos de cada tipo pueden variar según el
compilador y plataforma usadas.

11
CURSO 2018/19 2. TIPOS DE DATOS Y EXPRESIONES

Ejemplos válidos de literales de tipo carácter de C serán:


'a' ',' '>' 'A' '3'
Ejemplos de literales de tipo carácter no válidos en C serán:
'ab' 'A q "b"
Existen ciertos caracteres especiales que se representan en C mediante las denominadas
secuencias de escape. Algunas de las más frecuentes son las siguientes:
Carácter Secuencia de escape
Tabulador horizontal \t
Nueva línea (inicio de la siguiente línea) \n
Retorno de carro (inicio de la línea actual) \r
Carácter nulo \0
Comillas \"
Apóstrofo \'
Barra inclinada \\

2.1.2 CONSTANTES SIMBÓLICAS Y VARIABLES


Tanto las constantes simbólicas como las variables permiten almacenar durante la ejecución del
programa valores de los tipos de datos que acabamos de ver.
Cada variable o constante simbólica lleva asociado un nombre o identificador que la designa.
Los nombres elegidos deben ser significativos, es decir, deben hacer referencia al dato que
representan. Por ejemplo:
edad para almacenar la edad de una persona
IVA para almacenar el porcentaje de IVA
Es costumbre habitual por parte de los programadores emplear distinta notación para constantes
simbólicas y variables, por ejemplo, emplear solo letras mayúsculas para las constantes simbólicas
y una combinación de mayúsculas y minúsculas para las variables. Aunque esta práctica no es
obligatoria, facilita la comprensión del código al permitir determinar a simple vista y en cualquier
lugar del programa si un identificador hace referencia a una constante simbólica o a una variable.
Las reglas sintácticas que deben seguir los identificadores en C son:
 El primer carácter debe ser una letra o el carácter de subrayado.
 El resto pueden ser letras, dígitos o el carácter de subrayado.
El diagrama sintáctico2 de un identificador será:
dígito
letra

subraya
letra

subraya

2 Un diagrama sintáctico representa las reglas de construcción sintácticas de un componente de un lenguaje de


programación. Para determinar si una construcción determinada es sintácticamente válida basta con seguir su
diagrama sintáctico en el sentido de las flechas e ir comprobando que la construcción cumple los requisitos
indicados en el diagrama hasta salir del mismo.

12
CURSO 2018/19 2. TIPOS DE DATOS Y EXPRESIONES

Además, un identificador en C no puede contener espacios, ni acentos, ni la letra ñ, ni puede


coincidir con una palabra clave (por ejemplo, no pueden utilizarse int o float como
identificadores).
Por último, debe tenerse en cuenta que el lenguaje C es sensible a mayúsculas/minúsculas. Así,
los identificadores Suma y suma hacen referencia a distintos datos dentro de un mismo programa.
Una constante simbólica denomina un valor que no cambia nunca durante la ejecución del
programa. La definición de una constante simbólica consiste en la asociación de un identificador
con un valor y en C se realiza de la siguiente forma:

#define identificador expresión


Una constante simbólica no ocupa espacio en memoria, ya que el propio compilador sustituye
cada aparición de la constante simbólica en el programa por su valor correspondiente durante el
proceso de traducción.
En algoritmia, la definición de constantes simbólicas utiliza el siguiente diagrama:

CONSTANTES identificador = expresión

A continuación se muestran definiciones válidas de constantes simbólicas construidas utilizando


los diagramas sintácticos anteriores:
En algoritmia: En C:
CONSTANTES #define LETRA 'a'
LETRA='a' #define NUMERO -3.141592
NUMERO=-3.141592 #define PI -NUMERO
PI=-NUMERO

Algunas de las ventajas que puede proporcionar la definición de constantes simbólicas frente al
uso directo de literales son las siguientes:
 Facilita la comprensión del programa: la elección de un identificador alusivo al
significado del valor que representa puede facilitar la comprensión del programa. Por
ejemplo, si en un programa hacemos referencia al porcentaje del IVA, será más
comprensible leer el identificador IVA que el literal 21.
 Facilita la modificación del código del programa: si es necesario modificar un dato
considerado como constante en el programa, la definición de una constante simbólica
permite hacerlo en un solo paso en lugar de tener que explorar todo el código en busca
de literales que hagan referencia a ese dato. Por ejemplo, si el IVA variase, bastaría con
modificar el valor asociado a la constante simbólica en la definición para que dicha
modificación actuara sobre todas las referencias incluidas en el programa.
No obstante, antes de emplear una constante simbólica en lugar de un literal en un programa,
debe considerarse si alguna de las ventajas mencionadas va a ser aprovechada, pues en caso
contrario solo se conseguirá aumentar el tamaño del programa y hacerlo menos claro. Por ejemplo,
si en un programa quisiéramos contabilizar el número de valores que el usuario introduce por
teclado, no sería adecuado definir una constante simbólica para representar al literal 1 que debe
tomar el contador como valor inicial, ya que, por un lado, referirse a dicho valor mediante un

13
CURSO 2018/19 2. TIPOS DE DATOS Y EXPRESIONES

identificador como UNO o PRIMER_VALOR no mejoraría la comprensión del programa y, por


otro, el primer valor que toma un contador no es susceptible de cambiar en un futuro (siempre
comenzaremos a contar desde el valor 1).
Una variable denota a un dato cuyo valor puede cambiar durante la ejecución del programa.
Cabe aclarar que el concepto de variable en el ámbito de la programación es distinto que en el
ámbito de las matemáticas, ya que en programación las variables no representan incógnitas sino
datos almacenados en memoria principal. Cada variable, además de un identificador, lleva asociado
un tipo que determina su uso, de forma que dicha variable solo podrá tomar valores de ese tipo. Por
ejemplo, una variable de tipo entero solo podrá almacenar valores enteros.
Toda variable debe ser declarada antes de su utilización. Este proceso servirá para reservar
espacio en memoria para el valor asociado a dicha variable. En la declaración deberán especificarse
el identificador y el tipo de la variable. En algoritmia, se empleará el diagrama sintáctico siguiente
identificador
VARIABLES : tipo
de variable
,

utilizando la palabra entero para hacer referencia al tipo int de C, la palabra real para los
diferentes tipos de reales y la palabra carácter para el tipo char.
En C, el diagrama sintáctico es el siguiente:
identificador ;
tipo
de variable
,

Declaraciones válidas de variables serían:


En algoritmia: En C:
VARIABLES float radio,diametro;
radio,diametro:real char grupo;
grupo:carácter int edad;
edad:entero

2.1.3 EL TIPO CADENA DE CARACTERES


Un literal de tipo cadena de caracteres es una secuencia de cero o más caracteres incluidos en la
tabla de códigos ASCII encerrados entre comillas dobles. Todo valor de tipo cadena de caracteres
finaliza con el carácter especial '\0' (carácter nulo). Este carácter es insertado automáticamente,
por lo que el programador no tendrá que preocuparse de incluirlo en los literales de tipo cadena.
Así, el literal
"Pulsa una tecla para continuar"
es una cadena de caracteres con 31 caracteres (las 30 letras más el carácter nulo que indica el fin de

14
CURSO 2018/19 2. TIPOS DE DATOS Y EXPRESIONES

cadena).
Las variables de tipo cadena de caracteres permiten almacenar cadenas con una longitud máxima
que se indica en la declaración entre corchetes junto al identificador. Por ejemplo, para declarar una
variable frase de tipo cadena de caracteres con un máximo de 15 caracteres se utiliza la siguiente
sintaxis:
char frase[15];
que permitirá almacenar frases de hasta 14 caracteres significativos, pues el carácter nulo de fin de
cadena ocupará la última posición.

2.2 EXPRESIONES
Una expresión matemática tradicional está formada por una combinación de operandos,
operadores y paréntesis. Por ejemplo:


∑  x − x n 2
n−1

En los lenguajes de programación también es posible representar expresiones como una


combinación de literales, identificadores (de variables o constantes simbólicas), símbolos de
operación básicos, paréntesis y nombres de funciones especiales. Todos estos elementos podrán
indistintamente estar o no separados por uno o más espacios. Los literales e identificadores actúan
como operandos, mientras que los símbolos de operación y las funciones especiales lo hacen como
operadores.
Cada expresión toma el valor resultante de aplicar los operadores a los operandos. Dependiendo
del tipo de este valor resultante, distinguimos dos categorías de expresiones fundamentales:
aritméticas y lógicas.

2.2.1 EXPRESIONES ARITMÉTICAS


Los operandos serán habitualmente de tipo numérico (real o entero). Los símbolos de operación
básicos son:
+ suma y signo positivo
- resta y signo negativo
* multiplicación
/ división
% módulo (resto)
El operador % se debe aplicar siempre a operandos de tipo entero. Cuando la división se aplique
a operandos de tipo entero, el resultado será la división entera (por ejemplo, 5/3 dará como
resultado 1, que es la parte entera de 1.666).
El resultado de una expresión aritmética será de tipo numérico. En concreto,
 Cuando los operandos implicados en la expresión sean del mismo tipo, el resultado será
de ese tipo.
 Por el contrario, cuando en una expresión aritmética se incluyan operandos numéricos

15
CURSO 2018/19 2. TIPOS DE DATOS Y EXPRESIONES

de distinto tipo, el resultado obtenido será del tipo correspondiente al operando de


mayor precisión. Por ejemplo, si un operando es double y el otro float, el resultado
será double, si un operando es int y el otro float, el resultado será float, etc.
Para modificar el tipo de un operando o del resultado de una expresión es posible utilizar la
conversión explicita de tipos (casting). Para ello, basta con preceder a la expresión que quiere
convertirse a un nuevo tipo, de dicho tipo encerrado entre paréntesis. Así, aunque f sea una
variable de tipo float, (int) f % 2 será una expresión válida, pues el operando f ha sido
convertido previamente al tipo int antes de aplicar la operación de módulo (adviértase que en esa
conversión se descartaría la parte decimal del valor de f).
Reglas de prioridad:
Es posible que en una expresión aritmética concurran varios operadores. En ese caso, el orden de
aplicación de los mismos puede influir en el resultado final. Por ejemplo, en la expresión aritmética
6+4/2 el resultado será 5 u 8 dependiendo del orden en que se apliquen los operadores + y /.
Por lo tanto, es necesario establecer unas reglas para determinar qué operadores se aplican
primero en estos casos. Estas reglas se denominan reglas de prioridad y en C son las siguientes:
1. Operadores unarios signo positivo (+), signo negativo (-) y casting (tipo)
2. Operadores *, /, %.
3. Operadores binarios suma (+) y resta (-).
Si coincidieran varios operadores de igual prioridad en una expresión, el orden de evaluación es
de izquierda a derecha, excepto para los operadores unarios que será de derecha a izquierda. Por
ejemplo, el resultado de 4+8/-2*6 es -20.
Los paréntesis permiten alterar la prioridad por defecto de los operadores, ya que las expresiones
encerradas entre paréntesis serán las primeras que se evalúen. Si existen paréntesis anidados
(interiores unos a otros), las operaciones internas se evalúan primero. Por ejemplo, el resultado de
(4+8)/(-2*6) es -1.

2.2.2 EXPRESIONES LÓGICAS


Las expresiones lógicas permiten incluir en un programa instrucciones que se ejecutarán
solamente bajo ciertas condiciones. Podrán tomar únicamente dos posibles valores: verdadero (1) o
falso (0). Se forman combinando identificadores (de variables o constantes simbólicas), literales y
subexpresiones con operadores relacionales (de relación o comparación) y operadores lógicos.
Los operadores relacionales permiten realizar comparaciones. Son los siguientes:
< menor que
> mayor que
== igual a
<= menor o igual a
>= mayor o igual a
!= distinto de
El formato general de una expresión relacional es:
expresión1 operador de relación expresión2

16
CURSO 2018/19 2. TIPOS DE DATOS Y EXPRESIONES

La aplicación a valores numéricos es evidente. Si X=4 e Y=3, entonces:


X > Y es 1 (verdadero)
(X - 2) < (Y – 4) es 0 (falso)
Cuando se comparan valores de tipo carácter, el resultado de la comparación será el de la
comparación de los valores ASCII asociados a los caracteres implicados. Por ejemplo, 'A'<='B'
dará como resultado 1 (verdadero), ya que el código ASCII de la A (65) es menor que el de la B
(66), mientras que 'X'=='Z' dará como resultado 0 (falso), ya que el código ASCII de la X (88)
es distinto del de la Z (90).
Los operadores relacionales no pueden aplicarse directamente a valores de tipo cadena de
caracteres.
Las reglas de prioridad entre operadores relacionales son:
1. <, <=, >, >=
2. ==, !=

Los operadores lógicos son: ! (no-lógico), && (y-lógico) y || (o-lógico). Se aplican casi
siempre a operandos que son subexpresiones lógicas, aunque en general pueden aplicarse a
cualquier valor entero. En este sentido, el 0 se interpretará como falso y cualquier entero distinto de
0 (no solo el 1) se interpretará como verdadero. El resultado será 1 o 0 (verdadero o falso,
respectivamente). Los operadores && y || son operadores binarios, mientras que ! es unario.
Los operadores &&, || y ! vienen definidos por sus tablas de verdad:
a b a && b
distinto de 0 distinto de 0 1 Devuelve un resultado
distinto de 0 0 0 verdadero si y solo si los
0 distinto de 0 0 dos operandos son
0 0 0 verdadero

a b a || b
distinto de 0 distinto de 0 1 Devuelve un resultado
distinto de 0 0 1 verdadero si uno
0 distinto de 0 1 cualquiera de los
0 0 0 operandos es verdadero

a !a
distinto de 0 0 Devuelve un resultado
0 1 opuesto al del operando

Las reglas de prioridad entre operadores lógicos son:


1. no-lógico (!)
2. y-lógico (&&)
3. o-lógico (||)

17
CURSO 2018/19 2. TIPOS DE DATOS Y EXPRESIONES

Si aparecen dos o más operadores lógicos iguales en una expresión, los operadores ! se aplicarán
de derecha a izquierda y los operadores && y || de izquierda a derecha. Por ejemplo, si X=4 e
Y=3, la expresión lógica (X > 3) || (Y > 5) && !(X=4) será verdadero (1).

2.3 FUNCIONES INTERNAS


Los operadores básicos que acaban de estudiarse no son suficientes para realizar ciertas
operaciones de cálculo (trigonométricas, logarítmicas, etc.). No obstante, algunas de estas
operaciones se encuentran disponibles en los lenguajes de programación a través de bibliotecas de
funciones y se denominan funciones internas. Otras, como veremos en el Capítulo 5 pueden ser
definidas por el programador y se denominan funciones externas. Cada función, cuando es invocada
desde un programa, provoca la ejecución de un conjunto específico de instrucciones y puede
devolver uno o más valores, que en cada caso serán de un tipo determinado.
La mayoría de las funciones requieren datos de entrada que se denominan argumentos y que
aparecen entre paréntesis detrás del nombre de la función. Estos argumentos también deberán ser
datos de un tipo concreto, que vendrá determinado por la función en cuestión.
Además, para poder utilizar una determinada función interna, es necesario conocer en qué
biblioteca se encuentra e incluir al comienzo del programa una instrucción para hacer referencia al
archivo de cabecera de la biblioteca correspondiente, que será un archivo con extensión “.h” (por
ejemplo, stdio.h para las funciones de entrada/salida estándar o math.h para las funciones
matemáticas). El diagrama sintáctico de esta instrucción es el siguiente:
archivo de
#include < cabecera >

Los tipos de datos de los argumentos y del valor devuelto por algunas de las funciones internas
más usuales se muestran en la siguiente tabla. El tipo de los argumentos se representa entre los
paréntesis de las funciones (i=int, d=double, c=char), si bien los argumentos de tipo
double también admiten valores de cualquier otro tipo numérico (que será convertido a double).
Función Tipo Devuelto Cabecera Propósito
abs(i) int math.h Valor absoluto de i
fabs(d) double math.h Valor absoluto de d
ceil(d) double math.h Redondeo por exceso (el entero más pequeño mayor o igual a d)
floor(d) double math.h Redondeo por defecto (el entero más grande menor o igual a d)
cos(d) double math.h Coseno de d
sin(d) double math.h Seno de d
tan(d) double math.h Tangente de d
exp(d) double math.h Exponencial de d (ed)
log(d) double math.h Logarítmo neperiano de d
pow(d1,d2) double math.h Potencia de d1 elevado a d2
sqrt(d) double math.h Raíz cuadrada de d
tolower(c) char ctype.h Convertir c a minúsculas
toupper(c) char ctype.h Convertir c a mayúsculas

18
CURSO 2018/19 2. TIPOS DE DATOS Y EXPRESIONES

2.4 PUNTEROS
Un puntero es un dato que representa la dirección de memoria donde se almacena otro dato. Por
ello, se dice que el valor de un puntero apunta a otro valor. Los punteros son usados muy
frecuentemente en C y están relacionados con otros conceptos como los de arrays y modularidad
que se estudiarán en capítulos posteriores.
Para trabajar con punteros es necesario conocer los operadores de dirección e indirección. El
operador de dirección (&) permite obtener la dirección de memoria en la que se encuentra
almacenado un dato (es decir, la dirección de memoria asociada a una variable). El resultado de la
operación de dirección será, por tanto, de tipo puntero. Por ejemplo, si v es una variable declarada
de tipo real, en el siguiente esquema
&v 65522
v 3.154
v representa el contenido de la variable (el valor real 3.154) y &v representa la dirección de
memoria donde se almacena dicho valor (la posición 65522). Se dice que &v es un puntero a v.
El operador de indirección (*) permite obtener el valor al que apunta un puntero (es decir, el
valor contenido en la dirección de memoria que representa el puntero). Por ejemplo, si pv es una
variable puntero que apunta a la variable v (es decir, pv vale &v), entonces *pv representa el valor
al que apunta la variable puntero pv (es decir, *pv es igual a v).
En el siguiente esquema se representa la relación entre v y pv mediante los operadores de
dirección e indirección:

65520 pv=&v 65522


pv 65522 *pv=v 3.154

La declaración de una variable de tipo puntero requiere indicar el tipo del valor al que apuntará
el puntero y preceder al identificador del operador de indirección. En el ejemplo anterior, la
declaración de pv tanto en algoritmia como en C sería:
En algoritmia: En C:
VARIABLES float *pv;
*pv:real

2.5 REGLAS DE PRIORIDAD


A continuación se muestran las reglas de prioridad y el orden de evaluación de todos los
operadores vistos en este capítulo:
1. Funciones especiales
2. + (positivo), - (negativo), !, (tipo) (casting), &, * (indirección)
3. * (multiplicación), /, %

19
CURSO 2018/19 2. TIPOS DE DATOS Y EXPRESIONES

4. + (suma), - (resta)
5. <, >, <=, >=
6. ==, !=
7. &&
8. ||
Los operadores de la misma prioridad se aplicarán de izquierda a derecha, exceptos los
operadores unarios (!, +, -, (tipo), & y *) que se aplicarán de derecha a izquierda.

20
CURSO 2018/19 2. TIPOS DE DATOS Y EXPRESIONES

EJERCICIOS

1º) Indicar cuáles de los siguientes identificadores no son válidos:


a) _Id d) Id-1 g) xXx j) años
b) 23 e) D/2 h) D3
c) duración f) 3D i) μm
2º) Indicar cuáles de los siguientes literales son válidos y, de los válidos, de qué tipo son:
a) 432 d) -9.237 g) -87E-5 j) "A"
b) 40,000 e) 5/3 h) 3.5e+1 k) '\"'
45
c) 2.7×10 f) 2,933 i) '9' l) 24e1.2
3º) Escribir las siguientes expresiones algebraicas como expresiones aritméticas en C, empleando
el menor número posible de paréntesis:
a) 8 x−3 x y
b) 20 ' 2 x 224 ' 5 x−40
e)
3
2 a b a b
i) ln  x 1 x 2
y 
2 y 2 − y−10 25×10−4 x 3,2
c) f) j) y
y2 2y 2
q9

d)
a c2
a 2 b 2b
×
g)
q−2
k) 2 
 l
g
2 c b2 c h)  b −4a c
2
l)
2cos 
2a 1−tan 2 
4º) Escribir las siguientes expresiones aritméticas en C como expresiones algebraicas:
a) sqrt(x/y+x/z)
b) x+3/y+2
c) exp(-pow(1-2*x/a,2))
5º) Determinar el valor y tipo del resultado de las siguientes expresiones o decir si no son válidas:
a) sqrt(25)/2
b) pow(sqrt(9),2)
c) 44%(6/3.0)
d) 44%6/3.0
e) 16/5%3
f) 16/5*5
g) 16/5*(double) 5
h) 16/(double)5*5
i) !(floor(6.6)==ceil(5.3)) || *&p==p && ceil(5./3)==5/3
j) 4/2*3/6+fabs(6/2-pow(3,2))*2
k) "A" <= "B"
l) 6>2 && 5<4 || !(toupper(tolower('A'))=='A')

21
CURSO 2018/19 2. TIPOS DE DATOS Y EXPRESIONES

6º) Suponiendo la siguiente información en memoria, responder a las siguientes preguntas:

a) ¿Cuál sería el valor de *v2, si v2 vale &v1?


b) ¿Cuál sería el valor de *v2, si v2 vale v1?
c) ¿Cuál sería el valor de &v2, si v2 vale &v1?
d) ¿Cuál sería el valor de &v2, si v2 vale v1?

7º) Suponiendo la siguiente información en memoria, responder a las siguientes preguntas:

a) ¿Cuál sería el valor de *v3, si v3 vale &v1?


b) ¿Cuál sería el valor de *v3, si v3 vale v1?
c) ¿Cuál sería el valor de *v3, si v3 vale &v2?
d) ¿Cuál sería el valor de *v3, si v3 vale v2?
e) ¿Cuál sería el valor de &v3, si v3 vale &v1?
f) ¿Cuál sería el valor de &v3, si v3 vale &v2?

22
3
REPRESENTACIÓN GRÁFICA
DE LOS ALGORITMOS
Y SU TRADUCCIÓN A C

En el Capítulo 1 estudiamos que un algoritmo nos permite describir paso a paso el proceso
necesario para resolver un problema. La representación de un algoritmo se hace bien mediante texto
(pseudocódigo), bien mediante fórmulas, o bien de forma gráfica utilizando símbolos geométricos
en lo que se denominan diagramas.
La representación de algoritmos mediante diagramas es una forma clara e intuitiva de describir
los distintos pasos del método de resolución, su orden y su estructura. En este capítulo se tratará la
representación de los distintos elementos de un algoritmo mediante diagramas. Paralelamente, se
irán introduciendo las reglas sintácticas para traducir a lenguaje C cada uno de esos elementos.

3.1 MÉTODOS DE REPRESENTACIÓN ALGORÍTMICA


Las técnicas de programación tienen un papel primordial en el desarrollo del software. Una de
estas técnicas es la llamada programación estructurada, desarrollada por Edsger W. Dijkstra1 en
1972 y referida a un conjunto de directrices que aumentan considerablemente la productividad de
los programadores, reduciendo el tiempo requerido para escribir, depurar y mantener los programas.
La programación estructurada se basa en el Teorema de la Estructura, enunciado por Bohm y
Jacopini2 en 1966 y según el cual todo programa puede ser escrito utilizando solamente tres tipos de
estructuras básicas (también llamadas estructuras de control): secuenciales, selectivas y repetitivas.
Este teorema permite minimizar la complejidad de los métodos de resolución al reducir el juego de
estructuras empleado por los programas y facilitar así el seguimiento de la lógica de los mismos.

1 Edsger W. Dijkstra. Notes on structured programming, en Ole-Johan Dahl, Edsger W. Dijkstra y C. A. R. Hoare,
editores, Structured Programming. Academic Press, 1972.
2 Bohm, C. y Jacopini, G, Flow Diagrams, Turing Machines and Languages with Only Two Formation Rules,
Communications of the ACM, No. 5, Mayo 1966, págs. 366-371.

23
CURSO 2018/19 3. LA REPRESENTACIÓN GRÁFICA DE LOS ALGORITMOS Y SU TRADUCCIÓN A C

Nombre del programa


BLOCK

1 2 3 4

3.1 3.2 3.3

3.1.1 3.1.2 3.3.1 3.3.2

Ilustración 1: Un ejemplo de diagrama de Tabourier

Los diagramas estructurados se emplean para representar algoritmos que respetan las reglas de la
programación estructurada. Aunque existen diferentes métodos, aquí nos centraremos en el método
de Tabourier (Ilustración 1), según el cual, todo diagrama estructurado comienza por un rectángulo
dividido horizontalmente, en cuya parte superior aparece el nombre del programa y en la inferior la
palabra BLOCK. De este rectángulo parten, unidos mediante líneas, los pasos que forman el
algoritmo encerrados en rectángulos y rombos. La estructura arborescente resultante se recorre en
preorden (de izquierda a derecha y de arriba a abajo) de forma que la operación situada a la derecha
de la actual se examina solo tras haber examinado todas las descendientes de dicha operación actual.

3.2 ESTRUCTURA DE UN PROGRAMA EN C


El esqueleto de un programa en C simple tiene el siguiente aspecto:

inclusión de archivos de cabecera (#include)


definición de constantes (#define)

int main()
{
declaración de variables

instrucciones del programa


return 0;
}

En este esqueleto encontramos los siguiente elementos:


• En primer lugar se sitúan las instrucciones #include correspondientes a los archivos de
cabecera de las funciones internas que se vayan a utilizar en el programa, seguido de la
definición de las constantes simbólicas (si hubiera alguna), según se estudió en el Capítulo 2.
• La siguiente línea int main() es la cabecera de la función principal, por donde todo
programa comienza a ejecutarse, y el contenido de dicha función se encuentra a continuación
entre llaves. El significado de su sintaxis se explicará en el Capítulo 5.
• La declaración de variables deberá incluir todas las variables utilizadas en el programa,
según la sintaxis estudiada en el Capítulo 2.

24
CURSO 2018/19 3. LA REPRESENTACIÓN GRÁFICA DE LOS ALGORITMOS Y SU TRADUCCIÓN A C

• Finalmente, se incluirán las instrucciones ejecutables correspondientes a la traducción de los


pasos del algoritmo representados en el diagrama de Tabourier, seguidas por la instrucción
return 0 para finalizar la ejecución del programa.

EJEMPLO 3.1. El siguiente programa calcula y muestra la suma de dos números enteros leídos desde teclado.
#include <stdio.h>

int main()
{
int s1,s2,result;

printf("Introduzca dos números enteros: ");


scanf("%d %d",&s1,&s2);
result=s1+s2;
printf("La suma de %d más %d es %d.\n",s1,s2,result);
return 0;
}

3.3 OPERACIONES PRIMITIVAS


Las operaciones primitivas son los elementos básicos de un programa y, junto con las estructuras
de control que se estudiarán a continuación, nos permitirán construir cualquier programa. En C, las
instrucciones primitivas acaban en punto y coma (;), las instrucciones de control, no.

3.3.1 LA OPERACIÓN DE ASIGNACIÓN


La operación de asignación permite almacenar valores en las variables. El operador de asignación
se representa en un algoritmo mediante el símbolo ←. El formato general de una operación de
asignación en un diagrama de Tabourier es:
identificador de variable ← expresión
Por ejemplo, la operación
edad ← 25
asigna a la variable de identificador edad el valor 25. Hay que tener en cuenta que el valor que la
variable pudiera contener antes de la asignación es sustituido por el nuevo.
El operador de asignación tiene menor prioridad que cualquier otro. Esto hace que en primer
lugar se calcule el valor de la expresión al lado derecho del operador, y en segundo lugar este valor
se almacene en la variable cuyo nombre aparece a la izquierda del operador de asignación.
Es posible utilizar el mismo identificador en ambos lados del operador de asignación, dando
lugar a operaciones de acumulación. Por ejemplo,
total ← total + incremento
será una operación de acumulación que incrementa el valor de la variable total en una cantidad igual
al valor almacenado en la variable incremento. Un caso particular de las operaciones de
acumulación es la operación de conteo, donde el valor del incremento es 1.

25
CURSO 2018/19 3. LA REPRESENTACIÓN GRÁFICA DE LOS ALGORITMOS Y SU TRADUCCIÓN A C

Una instrucción de asignación en C utiliza como operador de asignación el carácter igual (=) y
finaliza en punto y coma (;). Por ejemplo, edad=25; correspondería a la operación de asignación
anterior. Adviértase la diferencia entre este operador y el operador relacional “igual que” (==)
estudiado en el capítulo anterior, ya que es habitual confundir ambos: el primero asigna un valor a
una variable y el segundo compara dos valores para determinar si son iguales.
En C, el tipo del valor obtenido al evaluar el lado derecho de una instrucción de asignación se
convertirá al tipo de la variable que se encuentra en el lado izquierdo, lo que puede provocar una
alteración del valor realmente almacenado (por ejemplo, al asignar el valor real 5.45 a una variable
de tipo entero, se almacenará el valor 5). Por ello, para evitar errores, es recomendable en general
que el dato que se asigna pueda almacenarse sin pérdidas en la variable especificada (por ejemplo,
el valor entero 5 podrá almacenarse sin pérdidas en una variable de tipo entero o de tipo real).

3.3.2 OPERACIONES DE ENTRADA


Las operaciones de entrada permiten leer valores desde un dispositivo de entrada (por ejemplo,
el teclado) y asignarlos a variables. Esta operación también se denomina operación de lectura,
indicando que el programa (el ordenador) va a leer cierta información del exterior (usuario).
En un diagrama de Tabourier, una operación de entrada desde teclado se representa:
leer
(id1, id2, ..., idN)
En C, las operaciones de entrada se realizan principalmente con la función scanf, que asigna a
variables valores de tipo simple (carácter, entero o real) introducidos por teclado. Su archivo de
cabecera es stdio.h, que debe incluirse mediante la instrucción #include estudiada en capítulo
anterior. La función scanf emplea el siguiente diagrama sintáctico:

scanf( cadena de , id. variable );


&
control tipo simple

La cadena de control debe contener un número de especificaciones de conversión igual al


número de identificadores de variables que le sucedan. Cuando hay más de una variable, las
especificaciones de conversión deben aparecer separadas entre sí por espacios y quedan asociadas
por orden a las correspondientes variables (la primera especificación de conversión con la primera
variable, la segunda con la segunda, etc.). Cada especificación de conversión es un grupo de
caracteres que indica el tipo de la variable correspondiente según la siguiente tabla:
Especificación
de conversión Tipo
%d int
%f float
%lf double
%c char

26
CURSO 2018/19 3. LA REPRESENTACIÓN GRÁFICA DE LOS ALGORITMOS Y SU TRADUCCIÓN A C

Obsérvese que cada identificador de variable viene precedido por el operador de dirección (&), es
decir, tras la cadena de control se indican las direcciones de memoria asociadas a las variables
donde se almacenarán los valores introducidos desde teclado.
Cuando se utiliza una misma instrucción para leer más de un valor numérico, estos deben
introducirse desde teclado separados por una pulsación de la tecla Intro o espacios. Para finalizar la
entrada se pulsará la tecla Intro.

EJEMPLO 3.2. Dadas las variables grupo, habitantes y masa, declaradas como char, int y double,
respectivamente, podrán recibir valores desde el teclado utilizando la siguiente instrucción de entrada:
scanf("%c %d %lf", &grupo, &habitantes, &masa);

Los valores deberán introducirse desde teclado separados entre sí mediante la tecla Intro o espacios y la introducción
deberá finalizar con la tecla Intro, por ejemplo:
B 146832 1.9891e30

o bien
B
146832
1.9891e30
Obsérvese que en la instrucción scanf las especificaciones de conversión van separadas entre sí por espacios y que
cada identificador va precedido del carácter ampersand (&). Por otro lado, los valores reales podrá introducirlos el
usuario indistintamente en notación clásica o en notación exponencial.

Para leer desde teclado cadenas de caracteres, emplearemos una función específica para este
propósito: la función gets, incluyendo entre paréntesis el identificador de la variable de tipo
cadena a la que se asignará el valor introducido desde teclado. Para evitar ciertos problemas cuando
se emplea en combinación con la función scanf, cada llamada a gets la precederemos de una
llamada a la función fflush(stdin);, que borra el buffer de teclado.

EJEMPLO 3.3. Dada la variable pelicula declarada como cadena de caracteres, podrá recibir una cadena desde
teclado utilizando la siguiente instrucción de entrada:
fflush(stdin); gets(pelicula);

La cadena deberá introducirse seguida de la tecla Intro, por ejemplo:


La Guerra de las Galaxias

Obsérvese que en la función gets el identificador ahora no va precedido del carácter ampersand (&).

3.3.3 OPERACIONES DE SALIDA


Las operaciones de salida (también llamadas de escritura) permiten visualizar el valor de
variables o expresiones a través de un dispositivo de salida (por ejemplo, el monitor).
En un diagrama de Tabourier, las operaciones de escritura sobre pantalla se representan:
escribir
(exp1, exp2, ..., expN)

27
CURSO 2018/19 3. LA REPRESENTACIÓN GRÁFICA DE LOS ALGORITMOS Y SU TRADUCCIÓN A C

En C, utilizaremos la función printf para mostrar por pantalla valores de cualquier tipo
(enteros, reales, caracteres y cadenas), cuyo archivo de cabecera es también stdio.h. Su diagrama
sintáctico es el siguiente:

printf( cadena de );
control

expresión ,
En la instrucción printf, la cadena de control deberá contener una especificación de
conversión por cada expresión y además podrá contener otros caracteres cualesquiera (incluidas las
secuencias de escape vistas en el capítulo anterior) que se mostrarán por pantalla. El valor de cada
expresión se insertará en la cadena de control allí donde aparezca su correspondiente especificación
de conversión.
Las especificaciones de conversión más habituales con la instrucción printf y sus formatos de
salida asociados se muestran en la siguiente tabla:
Especificación
de conversión Tipo
%d int
%f float, double
(notación clásica)
%e float, double
(notación exponencial)
%g float, double
(notación clasica/exponencial dependiendo de la precisión)
%c char
%s char []

EJEMPLO 3.4. Dadas las variables grupo, habitantes y masa declaradas como char, int y double,
respectivamente, y con los valores del Ejemplo 3.1, la siguiente instrucción de salida:
printf("Grupo=%c\nBadajoz=%d hab.\nMasa Sol=%e kg.\n",grupo,habitantes,masa);
generará las siguientes tres líneas en pantalla:
Grupo=B
Badajoz=146832 hab.
Masa Sol=1.989100e+030 kg.
Obsérvese que el valor de las variables se ha insertado en el lugar de las correspondientes especificaciones de
conversión dentro de la cadena de control.

En estas instrucciones los datos se visualizan con un determinado formato por defecto que
depende del tipo del dato. Por ejemplo, en el caso de los reales, las instrucciones
a=15.2;
printf("%f",a);
producirían el resultado
15.200000

28
CURSO 2018/19 3. LA REPRESENTACIÓN GRÁFICA DE LOS ALGORITMOS Y SU TRADUCCIÓN A C

mostrando por tanto el valor de la variable a con 6 dígitos decimales, aunque 5 de ellos no sean
significativos.
Para que la información pueda mostrarse de forma más clara, es posible añadir ciertos
modificadores a las especificaciones de conversión.
Uno de estos modificadores permite indicar el número mínimo de caracteres que ocupará en
pantalla el valor correspondiente. Cuando el valor a mostrar tenga menos caracteres que el número
de caracteres reservado, el valor será precedido de espacios hasta completar dicho número. Cuando
el valor tenga más caracteres que los reservados, el valor no aparecerá truncado, sino que se tomarán
los caracteres necesarios por la derecha. Este modificador se indica mediante un número situado
inmediatamente después del carácter % en la especificación de conversión correspondiente.

EJEMPLO 3.5. Dadas las variables grupo, habitantes y masa del ejemplo anterior, la siguiente instrucción de
salida:
printf("Grupo=%4c\nBadajoz=%5d hab.\nMasa Sol=%14e kg.\n",grupo,habitantes,masa);
generará las siguientes tres líneas en pantalla:
Grupo= B
Badajoz=146832 hab.
Masa Sol= 1.989100e+030 kg.
Obsérvese que el valor de grupo y de masa aparece precedido de 3 y 1 espacios para completar los 4 y 14
caracteres reservados, respectivamente. Sin embargo, aunque para habitantes se han reservado 5 caracteres y el valor
ocupa 6, se visualiza el dato completo.

En el caso de los valores reales, es posible indicar un modificador adicional que especifique el
número de dígitos decimales que se mostrarán por pantalla. El valor a mostrar se redondeará, si es
preciso, al número de decimales indicado por el modificador. Este modificador se incluirá en la
especificación de conversión mediante un punto seguido de un número entero, tras el carácter % (es
decir, %.entero) o, si se especifica el número mínimo de caracteres reservados, tras dicho número
(es decir, %entero.entero).

EJEMPLO 3.6. Dada la variable masa del ejemplo anterior y la variable numeroPi de tipo float y valor
3.14159265, la siguiente instrucción de salida:
printf("Masa Sol=%.2e kg.\nNúmero PI=%8.4f\n",masa,numeroPi);
mostrará en pantalla las siguientes líneas:
Masa Sol=1.99e+030 kg.
Número PI= 3.1416

3.4 ESTRUCTURAS DE CONTROL


A continuación, se estudiará el significado y la representación de las estructuras básicas de la
programación estructurada (secuenciales, selectivas y repetitivas), así como las distintas variantes
que existen.

29
CURSO 2018/19 3. LA REPRESENTACIÓN GRÁFICA DE LOS ALGORITMOS Y SU TRADUCCIÓN A C

3.4.1 ESTRUCTURA SECUENCIAL


Es aquella en la que todas las acciones que la componen se ejecutan exactamente una vez. La
figura siguiente representa una estructura secuencial:

BLOCK

1 2 3 ... n

EJEMPLO 3.7. Realizar un programa que calcule el perímetro y el área de un rectángulo a partir de la base y la
altura dadas por el usuario.
ANÁLISIS:
a) Datos de entrada:
 bas: base del rectángulo. Teclado. (bas > 0)3
 alt: altura del rectángulo. Teclado. (alt > 0)3
b) Datos de salida:
 per: perímetro del rectángulo. Monitor.
 area: área del rectángulo. Monitor.
DISEÑO:
a) Parte declarativa:
VARIABLES
bas,alt,per,area:real
b) Representación algorítmica:

rectangulo
BLOCK

escribir leer per←2bas+2alt area←basalt escribir


("Base y altura:") (bas,alt) (per,area)

CODIFICACIÓN:
/**************************************************/
/** Muestra el perímetro y área de un rectángulo **/
/**************************************************/
#include <stdio.h>

int main()
{
float bas,alt,per,area;

printf("Introduzca la base y la altura del rectángulo: ");


scanf("%f %f",&bas,&alt);
per=2*bas+2*alt; /* Perímetro del rectángulo */
area=bas*alt; /* Area del rectángulo */
printf("Su perímetro es %.2f y su área %.2f\n",per,area);
return 0;
}

3 Dado que aún no se han estudiado las estructuras que permitirán realizar el control de entrada correspondiente a
estas restricciones, dicho control se omitirá excepcionalmente en la representación algorítmica de este ejemplo.

30
CURSO 2018/19 3. LA REPRESENTACIÓN GRÁFICA DE LOS ALGORITMOS Y SU TRADUCCIÓN A C

3.4.2 ESTRUCTURAS SELECTIVAS


La estructura secuencial es típica de los algoritmos que pueden llevarse a cabo con una
calculadora básica, ya que todas las instrucciones introducidas se ejecutan exactamente una vez. El
empleo de ordenadores para la ejecución de algoritmos cobra mayor sentido cuando en ellos se
describe algo más que una mera secuencia de acciones. Este es el caso cuando el siguiente paso a
ejecutar depende del valor de una expresión.
En las estructuras selectivas se evalúa una expresión y en función de su resultado se determina
cuál será el siguiente paso. La acción asociada a cada una de las alternativas consideradas en la
estructura selectiva se denomina cuerpo de dicha alternativa.

ESTRUCTURA SELECTIVA SIMPLE (IF-THEN)


Esta estructura restringe la ejecución de una acción al cumplimiento de una condición. Su
representación en un diagrama de Tabourier es la siguiente:
if then

expresión acción
lógica

En la ejecución de una estructura selectiva simple, en primer lugar se evalúa la condición. Si el


resultado de la expresión lógica es verdadero se ejecuta la acción indicada; en caso contrario, no se
ejecuta.

EJEMPLO 3.8. La siguiente porción de algoritmo expresa que la operación de asignación se ejecutará solo si la
variable x es mayor que 0.
if then

x>0 positivo ← 1

Es posible que la acción a realizar si se cumple la condición sea una acción compuesta, esto es,
que conste de varios pasos más simples. Una acción compuesta en un diagrama de Tabourier se
representará mediante una estructura secuencial:
BLOCK

paso 1 paso 2 ... paso n

31
CURSO 2018/19 3. LA REPRESENTACIÓN GRÁFICA DE LOS ALGORITMOS Y SU TRADUCCIÓN A C

Por tanto, una estructura selectiva simple cuando contiene una acción compuesta se representará
en un diagrama de Tabourier de la siguiente forma:
if then

expresión BLOCK
lógica

paso 1 paso 2... paso n


En C, cuando el cuerpo de una estructura selectiva simple consiste en una acción sencilla (es
decir, una sola instrucción), se utiliza el siguiente diagrama sintáctico:
expresión
if ( ) instrucción
lógica
Las acciones compuestas en C emplean el diagrama sintáctico:

{ instrucción }
Por tanto, la estructura selectiva simple, cuando contiene una acción compuesta, utilizará el
diagrama sintáctico siguiente:

expresión
if ( ) { instrucción }
lógica

EJEMPLO 3.9. A partir de dos números reales introducidos por teclado, mostrar el resultado de la división si el
divisor es distinto de 0.
ANÁLISIS:
a) Datos de entrada:
 ddo: Dividendo. Teclado.
 dsor: Divisor. Teclado.
b) Datos de salida:
 coc (solo si dsor ≠ 0): Cociente de la división. Monitor.
DISEÑO:
a) Parte declarativa:
VARIABLES
coc,ddo,dsor:real
b) Representación algorítmica:
division
BLOCK

escribir leer if then


("Dividendo (ddo,dsor)
y divisor:")
dsor ≠ 0 BLOCK

coc ← ddo/dsor escribir(coc)

32
CURSO 2018/19 3. LA REPRESENTACIÓN GRÁFICA DE LOS ALGORITMOS Y SU TRADUCCIÓN A C

CODIFICACIÓN:
/*****************************/
/** Cálculo de una división **/
/*****************************/
#include <stdio.h>

int main()
{
float ddo,dsor,coc;

printf("Introduzca el dividendo y el divisor: ");


scanf("%f %f",&ddo,&dsor);
if (dsor != 0) /* solo se calcula si el divisor no es cero */
{
coc=ddo/dsor;
printf("El resultado de la división es %.2f\n",coc);
}
return 0;
}

ESTRUCTURA SELECTIVA DOBLE (IF-THEN-ELSE)


La estructura anterior es inadecuada para representar la selección de alternativas cuando se
requiere que el algoritmo seleccione entre dos posibles acciones en función del resultado de una
condición, de forma que si la condición se cumple se seleccione una alternativa y en caso contrario
se seleccione la otra. La estructura selectiva doble nos permite representar más adecuadamente esta
situación.
Por lo tanto, una estructura selectiva doble deberá emplearse cuando se presenten dos alternativas
de actuación mutuamente excluyentes que dependan del resultado de una misma condición.
En un diagrama de Tabourier, esto se representará:

if then else

expresión acción 1 acción 2


lógica

Si la expresión lógica toma valor verdadero, se ejecutará la acción 1; si toma valor falso, se
ejecutará la acción 2. Al igual que en las estructuras selectivas simples, las acciones de una
estructura selectiva doble pueden ser compuestas, en cuyo caso se representarán del modo descrito
anteriormente para la estructura selectiva simple.
En C, el diagrama sintáctico correspondiente a la estructura selectiva doble es el siguiente:
expresión bloque de bloque de
if ( ) else
lógica sentencias sentencias
donde bloque de sentencias se refiere de forma genérica a una única acción simple o a una acción
compuesta, aplicándose en este último caso el diagrama sintáctico para acciones compuestas
estudiado anteriormente.

33
CURSO 2018/19 3. LA REPRESENTACIÓN GRÁFICA DE LOS ALGORITMOS Y SU TRADUCCIÓN A C

EJEMPLO 3.10. Indicar si, dado un número por teclado, este es par o impar.
ANÁLISIS:
a) Datos de entrada:
 n: Número entero. Teclado.
b) Datos de salida:
 paridad: Mensaje indicando la paridad del número n. Monitor
DISEÑO:
a) Parte declarativa:
VARIABLES
n: entero

paridad
BLOCK

escribir leer (n) if then else


("Un nº:")

resto(n,2)=0 escribir escribir


("Es par") ("Es impar")

CODIFICACIÓN:
/*****************************************/
/** Muestra si un número es par o impar **/
/*****************************************/
#include <stdio.h>

int main()
{
int n;

printf("Introduzca un número entero: ");


scanf("%d",&n);
if (n % 2 == 0) /* un número es par si al dividir entre 2 da resto 0 */
printf("El número es PAR\n");
else
printf("El número es IMPAR\n");
return 0;
}

ESTRUCTURA SELECTIVA MÚLTIPLE (SWITCH)


A menudo, en la resolución de un problema se presentan más de dos alternativas que dependen
de una misma circunstancia, por ejemplo, del día de la semana en el que nos encontramos. Esto
podría resolverse usando estructuras selectivas dobles encadenadas, pero generaría un código poco
legible y de difícil escritura si el número de alternativas es grande.

34
CURSO 2018/19 3. LA REPRESENTACIÓN GRÁFICA DE LOS ALGORITMOS Y SU TRADUCCIÓN A C

La estructura selectiva múltiple permite seleccionar una alternativa de entre varias posibles, en
función del resultado de una expresión de tipo entero o de tipo carácter. En un diagrama de
Tabourier, esta estructura se representa de la siguiente forma:
switch

lista 1 lista 2 lista n default

expresión acción 1 acción 2 ... acción n acción d

Las listas lista 1, lista 2, ..., lista n contendrán el o los valores asociados a la alternativa
correspondiente, separados por comas si son varios. La cláusula default es opcional y se usará
cuando una alternativa englobe a los valores de la expresión que no se han indicado explícitamente.
En C, el diagrama sintáctico de la estructura selectiva múltiple es:

switch ( ) { : bloque de
expresión case expresión
sentencias

break;

bloque de }
default
sentencias
Cada valor de una misma lista se representa mediante un par case-expresión acabado en dos
puntos (:). Tras la lista, se sitúa el bloque de sentencias correspondiente a la acción asociada.
Obsérvese que se utiliza una instrucción break; para separar cada par lista-acción de la siguiente
y, si está presente, del par default-acción.

EJEMPLO 3.11. Se desea diseñar un algoritmo que escriba por pantalla la duración en días correspondiente al mes
cuyo número de orden se indique por teclado o un mensaje de error si dicho valor no está en el intervalo [1,12].
ANÁLISIS:
a) Datos de entrada:
 mes: Número de orden del mes. Teclado.
b) Datos de salida:
 dias: Mensaje indicando el número de días que tiene mes o un mensaje de error. Monitor
DISEÑO:
a) Parte declarativa:
VARIABLES
mes:entero

duracionMes
BLOCK

escribir leer (mes) switch


("Mes:")
default
1,3,5,7,8,10,12 4,6,9,11 2
escribir escribir escribir escribir
mes ("31 días") ("30 días") ("28 o 29 días") ("Error")

35
CURSO 2018/19 3. LA REPRESENTACIÓN GRÁFICA DE LOS ALGORITMOS Y SU TRADUCCIÓN A C

CODIFICACIÓN:
/******************************************************************************/
/** Muestra la duración en días correspondiente al número de orden de un mes **/
/******************************************************************************/
#include <stdio.h>

int main()
{
int mes;

printf("Introduzca el número de orden del mes: ");


scanf("%d",&mes);
switch (mes)
{
case 1:
case 3:
case 5:
case 7:
case 8:
case 10:
case 12:
printf("31 días\n");
break;
case 4:
case 6:
case 9:
case 11:
printf("30 días\n");
break;
case 2: /* depende de si el año es o no bisiesto */
printf("28 o 29 días\n");
break;
default:
printf("Error: el valor no corresponde a ningún mes.\n");
}
return 0;
}

3.4.3 ESTRUCTURAS REPETITIVAS


Otro tipo de estructura de control es aquella que describe la repetición de una acción un número
de veces a priori determinado o indeterminado. Estas estructuras se denominan estructuras
repetitivas o bucles y se llama iteración a cada una de las ejecuciones de la acción en el bucle.
La acción que se repite se denomina cuerpo del bucle. Además, será necesario establecer una
condición de parada que determine cuántas de veces se ejecutará el cuerpo del bucle (número de
iteraciones). Esta condición se sitúa al inicio o al final del bucle y puede indicarse de distintos
modos, dando lugar a tres tipos de estructuras repetitivas: while-do, do-while y for-do.

ESTRUCTURA REPETITIVA WHILE-DO


En ella, el cuerpo del bucle se repite mientras se cumpla una determinada condición. Su
representación en un diagrama de Tabourier es la siguiente:
while do

expresión acción
lógica

36
CURSO 2018/19 3. LA REPRESENTACIÓN GRÁFICA DE LOS ALGORITMOS Y SU TRADUCCIÓN A C

Cuando se ejecuta la estructura while-do, primero se evalúa la expresión lógica. Si es falsa,


termina la ejecución del bucle. Si la expresión lógica es verdadera, entonces se ejecuta el cuerpo del
bucle, después de lo cual el flujo de ejecución vuelve hacia atrás para evaluar de nuevo la expresión
lógica. El cuerpo del bucle continuará ejecutándose mientras la expresión lógica sea verdadera. Esta
capacidad de “retroceder” en el flujo de ejecución es exclusiva de las estructuras repetitivas.
Obsérvese que para que un bucle while-do finalice es necesario que la condición de parada se
haga falsa. Por ello, en el cuerpo del bucle debe existir alguna instrucción que altere en cierto
momento alguno de los operandos que intervienen en la condición. En caso contrario, si la
condición se cumpliera en su primera evaluación también se cumpliría en el resto y, por tanto, nos
encontraríamos ante un bucle infinito.
La estructura repetitiva while-do deberá utilizarse cuando se desconozca a priori el número de
iteraciones y no se requiera ejecutar el cuerpo del bucle, al menos, una vez.
En C, se utiliza la instrucción while para implementar la estructura repetitiva while-do. Su
diagrama sintáctico es el siguiente:
expresión bloque de
while ( )
lógica sentencias

EJEMPLO 3.12. Sumar una serie de números enteros no negativos introducidos por teclado hasta que se introduzca
uno negativo y mostrar el total por pantalla.
ANÁLISIS:
a) Datos de entrada:
 n[1..k]: Secuencia de valores no negativos (excepto el último, que será negativo). Teclado.
b) Datos de salida:
 tot: Suma de los valores no negativos de la secuencia. Monitor.
c) Comentarios:
 Para realizar la suma no será necesario recordar todos los valores introducidos, sino que basta con ir
recordando el total acumulado. Por lo tanto, emplearemos una variable acumuladora para almacenar dicho
total y otra para almacenar el número actual.
DISEÑO:
a) Parte declarativa:
VARIABLES
n,tot:entero
b) Representación algorítmica:

contar
BLOCK

tot ← 0 escribir leer(n) while do escribir


("Un nº:") (tot)

n≥0 BLOCK

tot ← tot+n escribir leer(n)


("Otro nº:")

37
CURSO 2018/19 3. LA REPRESENTACIÓN GRÁFICA DE LOS ALGORITMOS Y SU TRADUCCIÓN A C

CODIFICACIÓN:
/**************************************************************/
/** Muestra la suma de una secuencia de valores no negativos **/
/**************************************************************/
#include <stdio.h>

int main()
{
int n,tot;

tot=0;
printf("Introduzca un número: ");
scanf("%d",&n);
while (n >= 0)
{
tot=tot+n; /* Operación de acumulación */
printf("Introduzca otro número: ");
scanf("%d",&n);
}
printf("La suma es: %d\n",tot);
return 0;
}

ESTRUCTURA REPETITIVA DO-WHILE


En la estructura do-while, de nuevo, el cuerpo del bucle se ejecuta mientras se cumpla una
determinada condición. La diferencia respecto a la estructura while-do estriba en que la evaluación
de la condición de parada se hace tras la ejecución del cuerpo del bucle, y no antes. Su
representación en un diagrama de Tabourier es:
do while

acción expresión
lógica
Por tanto, una estructura do-while comienza ejecutando el cuerpo del bucle. A continuación se
evalúa la expresión lógica. Si el resultado es falso, el bucle termina; si es verdadero, el cuerpo del
bucle se ejecuta otra vez y a continuación la condición se evalúa de nuevo. La ejecución del cuerpo
del bucle se repetirá mientras la condición sea verdadera. Obsérvese que, en una estructura
do-while, el cuerpo del bucle se ejecuta al menos una vez.
La estructura repetitiva do-while deberá utilizarse cuando el número de iteraciones sea
desconocida a priori y se requiera ejecutar el cuerpo del bucle, al menos, una vez.
En C, la estructura do-while se implementa mediante el siguiente diagrama sintáctico:

bloque de expresión
do while ( ) ;
sentencias lógica
A diferencia del resto de instrucciones de control, la instrucción do-while acaba en punto y
coma.

EJEMPLO 3.13. Leer desde teclado una secuencia creciente de valores enteros. La secuencia finalizará cuando se
introduzca un valor menor que el anterior.
ANÁLISIS:
a) Datos de entrada:
 n[1..k]: Secuencia de valores creciente, excepto el último valor, que será menor que el anterior. Teclado.

38
CURSO 2018/19 3. LA REPRESENTACIÓN GRÁFICA DE LOS ALGORITMOS Y SU TRADUCCIÓN A C

b) Datos de salida:
c) Comentarios:
 Para comprobar si la secuencia es creciente no será necesario recordar todos los valores introducidos, sino
que basta con recordar el inmediatamente anterior y compararlo con el actual. Por lo tanto, emplearemos una
variable para almacenar el número actual y otra para almacenar el inmediatamente anterior.
DISEÑO:
a) Parte declarativa:
VARIABLES
n,ant:entero

creciente
BLOCK

escribir leer(n) do while


("Un nº:")

BLOCK n >= ant

ant ← n escribir leer(n)


("Otro nº:")

CODIFICACIÓN:
/**********************************************************/
/** Lee desde teclado una secuencia de valores creciente **/
/**********************************************************/
#include <stdio.h>

int main()
{
int n,ant;

printf("Introduzca un número: ");


scanf("%d",&n);
do
{
ant=n; /* el valor actual se almacena como anterior */
printf("Introduzca otro número: ");
scanf("%d",&n);
}
while (n >= ant); /* continúa mientras la secuencia sea creciente */
return 0;
}

ESTRUCTURA REPETITIVA FOR-DO


En ocasiones se conoce a priori el número de iteraciones de un bucle, esto es, justo antes de que
este comience a ejecutarse. En esos casos, debe emplearse la estructura repetitiva for-do, cuya

for do

id ← Vi ,Vf ,inc acción

39
CURSO 2018/19 3. LA REPRESENTACIÓN GRÁFICA DE LOS ALGORITMOS Y SU TRADUCCIÓN A C

En el diagrama, id es una variable que controla de modo automático el número de iteraciones del
bucle y se denomina variable índice. El valor inc podrá ser un entero positivo o negativo y
provocará, respectivamente, un incremento o decremento de la variable índice tras cada iteración.
La ejecución de la estructura for-do comienza asignando el valor inicial Vi a la variable índice. A
continuación comprueba si el valor de la variable índice supera al valor final Vf. Si es así, la
ejecución del bucle finaliza; en otro caso, la ejecución del cuerpo del bucle se repetirá hasta que la
variable índice sobrepase al valor final Vf, la cual se incrementará/decrementará automáticamente en
el valor de inc después de cada iteración.
El valor de la variable índice queda indefinido tras finalizar el bucle, por lo que no se deberá
suponer ningún valor para la misma en acciones posteriores al bucle.
En C, la estructura for-do se implementa con la instrucción for empleando el siguiente
diagrama sintáctico:

for ( id = Vi; id <= Vf; id = id+inc bloque de


)
sentencias
Tanto Vi como Vf serán valores de tipo entero, expresados mediante literales, identificadores o
expresiones aritméticas. La variable índice id también será de tipo entero. Si el incremento es
negativo, en lugar del operador <= se utilizará el operador >=.

EJEMPLO 3.14. Calcular la suma de los 100 primeros números naturales.


ANÁLISIS:
a) Datos de entrada:
 NUMMIN=0. Primer número natural. Dato fijo.
 NUMMAX=99. Centésimo número natural. Dato fijo.
b) Datos de salida:
 tot: suma de los 100 primeros números naturales. Monitor.
c) Comentarios:
 Se utilizará una variable para contar el número de valores sumados.
DISEÑO:
a) Parte declarativa:
CONSTANTES
NUMMIN=0
NUMMAX=99
VARIABLES
tot,i:entero

suma100
BLOCK

tot ← 0 for do escribir


(tot)

i ← NUMMIN, tot ← tot+i


NUMMAX,1

40
CURSO 2018/19 3. LA REPRESENTACIÓN GRÁFICA DE LOS ALGORITMOS Y SU TRADUCCIÓN A C

CODIFICACIÓN:
/***********************************************************/
/** Muestra la suma de los 100 primeros números naturales **/
/***********************************************************/
#include <stdio.h>

#define NUMMIN 0 /* Primer número natural */


#define NUMMAX 99 /* Centésimo número natural */
int main()
{
int tot,i;

tot=0;
for (i=NUMMIN; i <= NUMMAX; i=i+1)
tot=tot+i;
printf("La suma es %d\n",tot);
return 0;
}

41
CURSO 2018/19 3. LA REPRESENTACIÓN GRÁFICA DE LOS ALGORITMOS Y SU TRADUCCIÓN A C

EJERCICIOS

1º) En el supermercado “El 13" las ventas no van muy bien. Por ello, han decidido lanzar una
campaña de captación de clientes consistente en aplicar un descuento de un 5% sobre el
importe de la compra si este supera los 60 euros y un 5% adicional si, además, dicho importe
(despreciando céntimos de euro) es divisible entre 13. Desarrollar un programa que lea por
teclado el importe inicial de una compra y calcule y muestre por pantalla el descuento
aplicado y el importe final.
2º) Calcular y mostrar por pantalla la estatura del individuo más alto y más bajo de una serie de
100 estaturas introducidas por teclado.
3º) Se proporcionan por teclado las calificaciones de un examen (entre 0 y 10). Desarrollar un
programa que muestre por pantalla la media de la clase y el número de aprobados
(calificaciones superiores o iguales a 5). La introducción de calificaciones terminará cuando se
teclee el valor -1.
4º) Calcular el producto de dos valores enteros no negativos introducidos por teclado, teniendo en
cuenta que solo podrá emplearse la operación de suma.
5º) Escribir en pantalla todos los números primos entre 2 y 10000, ambos inclusive.
6º) El desarrollo de la serie de Maclaurin para el logaritmo neperiano es:
( x−1)2 ( x−1)3 ( x−1)4 ( x−1)5
ln ( x ) = ( x−1) − + − + − ... , (0< x≤2)
2 3 4 5
Escribir un programa que evalúe y muestre por pantalla el valor de la serie con n términos,
donde x y n se introducen por teclado.
7º) Calcular la división entera de dos valores enteros no negativos introducidos por teclado
teniendo en cuenta que solo podrán emplearse las operaciones de suma y resta.
8º) El máximo común divisor de dos números es el mayor de sus divisores comunes. Realizar el
análisis, diseño y codificación de un programa C que, dados dos números enteros mayores que
0 introducidos por teclado, muestre en pantalla su máximo común divisor.
9º) Realizar el análisis, diseño y codificación en C de un programa que, a partir de cuatro
números enteros positivos a, b, c y d leídos desde teclado (siendo a<b y c<d), muestre en
pantalla la suma de los números comprendidos en el intervalo [c,d] y, a continuación, todos
los números dentro del intervalo [a,b] que sean mayores que dicha suma o, en el caso de que
no haya ningún número que cumpla la condición, un mensaje indicándolo.
10º) El tamaño de una población formada inicialmente por un número determinado de individuos
Tini evoluciona del siguiente modo:
a) cada día, se añaden dos individuos a la población
b) cada tres días, tras la aplicación del apartado a, hay un incremento extra del 50%
(despreciando decimales) de los individuos existentes en la población
c) cada cinco días, tras la aplicación del apartado a y, en su caso, del apartado b, hay una
pérdida de un cuarto (despreciando decimales) de los individuos existentes en la población
Realizar el análisis, diseño y codificación en C de un programa que, leyendo desde teclado dos
valores Tini y Tfin, muestre por pantalla la evolución del tamaño y el número de días necesarios

42
CURSO 2018/19 3. LA REPRESENTACIÓN GRÁFICA DE LOS ALGORITMOS Y SU TRADUCCIÓN A C

para que una población con un tamaño inicial Tini alcance o supere el tamaño Tfin. Al menos
deberá haber dos individuos en la población inicial y el tamaño de la población final deberá
ser siempre mayor que el de la inicial.
Así por ejemplo, dados los valores leídos desde teclado Tini=5 y Tfin=100, deberá mostrarse la
evolución del tamaño de la población
7 9 16 18 15 25 27 29 46 36 38 60 62 64 75 77 79 121
y el número de días 18, donde la aplicación del apartado b se indica en negrita y del apartado
c mediante subrayado

43
4
ESTRUCTURAS DE DATOS (I):
ARRAYS

En capítulos anteriores se ha estudiado el concepto de datos de tipo simple (entero, real y


carácter). A veces, los datos a tratar en un programa no son elementos individuales de información,
sino que existe cierta relación entre ellos. En esos casos, los lenguajes de programación permiten
trabajar con estructuras de datos, es decir, colecciones de datos más simples con relaciones
establecidas entre ellos. Estas estructuras de datos posibilitan, por un lado, representar la
información de una manera más natural y clara y, por otro, un tratamiento de la información más
cómodo y eficiente. En este capítulo se tratará una de las estructuras de datos más habituales en
programación: los arrays.

4.1 INTRODUCCIÓN
Una estructura de datos es una colección de datos caracterizada por su organización y por las
operaciones definidas sobre ella. Los tipos de datos utilizados para declarar estructuras de datos se
denominan tipos compuestos y se construyen a partir de los tipos simples ya estudiados.
Distinguimos dos categorías de estructuras de datos:
 Estáticas: su tamaño se determina a priori, antes del comienzo de la ejecución del programa,
y este no podrá incrementarse ni disminuirse en tiempo de ejecución. Esto implicará que
cuando se trabaje con una estructura de datos estática cuyo tamaño se desconoce en la fase de
diseño, será necesario establecer un tamaño máximo y reservar espacio en memoria para ese
máximo (con el posible desperdicio de memoria que esto pueda conllevar). Entre las
estructuras de datos estáticas distinguimos arrays, cadenas de caracteres y registros.
 Dinámicas: su tamaño se determina en tiempo de ejecución, reservando y liberando espacio
en memoria en el momento que interese. Esto permitirá, por tanto, optimizar al máximo el
espacio de ocupación en memoria, aunque requiere una gestión de memoria más complicada.
Se distinguen listas, árboles y grafos.
En esta asignatura nos centraremos en las estructuras de datos estáticas, adecuadas como una
primera aproximación al manejo de estructuras de datos por ser más sencillas de gestionar.
Una característica común a todas las estructuras de datos es la existencia de un único
identificador que hace referencia a la misma en su conjunto. Además, cada tipo de estructura de

44
CURSO 2018/19 4. ESTRUCTURAS DE DATOS (I): ARRAYS

datos dispone de su propio mecanismo para hacer referencia de forma independiente a los elementos
que la integran.

4.2 ARRAYS
Un array es una colección de elementos de un mismo tipo, donde cada elemento puede
identificarse por su posición dentro de la estructura. Todo array posee un identificador que lo
designa y una serie de índices que toman valores enteros y permiten diferenciar por su posición a los
distintos elementos que lo constituyen.

4.2.1 ARRAYS UNIDIMENSIONALES


Un array unidimensional puede considerarse como una lista ordenada de valores. Lleva asociado
un único índice que designa la posición de los valores dentro de la lista, comenzando en lenguaje C
desde 0. Debido a su similitud con el concepto matemático de vector, los arrays unidimensionales
también se conocen con el nombre de vectores.
En la siguiente figura se representa gráficamente un vector que representa el número de
habitantes (en unidades de millar) de 100 poblaciones:
0 1 2 99
habitantes 30 7 5 ... 120

Para hacer referencia a cada elemento del vector, tanto en algoritmia como en C, se utiliza la
siguiente sintaxis:
identificador[índice]
Por ejemplo, el número de habitantes de la tercera población se designa habitantes[2] y su
contenido es igual a 5.
En algoritmia, la declaración de estructuras de datos de tipo vector utiliza el siguiente diagrama
sintáctico:
identificador valor
VARIABLES [ ] : tipo
de variable entero
,
El valor entero representa el número de elementos del vector y, por tanto, determina el rango de
valores que puede tomar el índice (en C, desde 0 a valor entero – 1, ambos inclusive).

La traducción a C de esta declaración se realiza según el siguiente diagrama sintáctico:


identificador valor
tipo [ ] ;
de variable entero
,

45
CURSO 2018/19 4. ESTRUCTURAS DE DATOS (I): ARRAYS

Por ejemplo, las declaraciones del vector habitantes en algoritmia y en C emplearían la


siguiente sintaxis:
En algoritmia: En C:
VARIABLES int habitantes[100];
habitantes[100]:entero

EJEMPLO 4.1. A partir de las edades de 10 individuos introducidas por teclado, calcular y mostrar cuántos
individuos son mayores y cuántos menores que la media.
ANÁLISIS:
a) Datos de entrada:
 NUMIND=10. Número de individuos. Dato fijo.
 edad[1..NUMIND]: Edades de los individuos. Teclado. (edad[i] ≥ 0, ∀i)
b) Datos de salida:
 may: Número de individuos con edad superior a la media de edad[1..NUMIND]. Monitor.
 men: Número de individuos con edad inferior a la media de edad[1..NUMIND]. Monitor.
c) Comentarios:
 Dado que, una vez calculada la media de las edades, será necesario recorrer de nuevo la lista de edades, se
utilizará un vector para representar dicha lista.
 Se utilizará una variable índice para recorrer el vector, en primer lugar, para almacenar los valores en él
durante la lectura desde teclado y el cálculo de la media y, en segundo lugar, para determinar cuántos de
esos valores son mayores y cuántos menores que la media.
 Se utilizará una variable para acumular los valores del vector durante el cálculo de la media.
 Se utilizará una variable para almacenar el resultado de la media.
DISEÑO:
a) Parte declarativa:
CONSTANTES
NUMIND=10
VARIABLES
edad[NUMIND]:entero
med:real
i,suma,may,men:entero
b) Representación algorítmica:
contar
BLOCK

suma←0 for do med←suma/NUMIND men←0 may←0 for do escribir


(may,men)

i ← 0,NUMIND-1,1 BLOCK i ← 0,NUMIND-1,1 if then else

escribir leer while do suma←suma+edad[i] edad[i] < med men←men+1 if then


("Edad",i+1) (edad[i])

edad[i] < 0 BLOCK edad[i] > med may←may+1

escribir escribir leer


("Error") ("Edad",i+1) (edad[i])

46
CURSO 2018/19 4. ESTRUCTURAS DE DATOS (I): ARRAYS

CODIFICACIÓN:
/*************************/
/*** E D A D E S ***/
/*************************/
#include <stdio.h>

#define NUMIND 10 /* Número de individuos (edades) */


int main()
{
int edad[NUMIND];
double med;
int i,suma,may,men;

/* Lectura desde teclado del vector y cálculo de la media */


suma=0;
for (i=0; i <= NUMIND-1; i=i+1)
{
printf("Edad %d: ",i+1);
scanf("%d",&edad[i]);
while (edad[i] < 0)
{
printf("Error, debe ser mayor o igual que cero.\n");
printf("Edad %d: ",i+1);
scanf("%d",&edad[i]);
}
suma=suma+edad[i];
}
med=(double)suma / NUMIND;

/* Cálculo de número de valores superiores e inferiores a la media */


men=0;
may=0;
for (i=0; i <= NUMIND-1; i=i+1)
if (edad[i] < med)
men=men+1;
else
if (edad[i] > med)
may=may+1;

/* Visualización de resultados */
printf("Número de individuos mayores que la media: %d.\n",may);
printf("Número de individuos menores que la media: %d.\n",men);
return 0;
}

4.2.2 ARRAYS MULTIDIMENSIONALES


A veces, existen datos cuya representación es más adecuada en forma de tabla con dos o más
índices, por ejemplo, para tratar la disposición de las fichas en un tablero de ajedrez (8×8 casillas) o
valores diarios durante los meses de una década (31×12×10 valores). Para ello, se pueden emplear
arrays multidimensionales.
Un array bidimensional (también denominado matriz) puede considerarse como un array
organizado en filas y columnas. Así, por ejemplo, el censo de 100 poblaciones durante 3 décadas
podría representarse mediante una matriz de dimensiones 100×3, como se muestra a continuación:
habitantes Índice de década
0 1 2
0 31 28 30
Índice de 1 10 8 7
población ... ... ... ...
99 60 90 120

47
CURSO 2018/19 4. ESTRUCTURAS DE DATOS (I): ARRAYS

Para referenciar a cada elemento de un array bidimensional se utilizan dos índices. El primero se
refiere a la fila y el segundo a la columna que ocupa dicho elemento. Se utiliza la siguiente sintaxis:
identificador[fila][columna]
Por ejemplo, el número de habitantes de la segunda población en el censo de la primera década
se designa habitantes[1][0] y su contenido es igual a 10.
Análogamente, pueden declararse arrays de tantas dimensiones como se quiera, teniendo como
limitación el tamaño de la memoria del ordenador. El número total de elementos del array es el
producto del número de elementos de cada dimensión. Por ejemplo, un array de dimensión 3×10×2
tendrá 60 elementos. No obstante, a mayor número de dimensiones, menor será la legibilidad de la
solución y mayor la dificultad de su manejo, pues cada índice hará referencia a una característica de
los datos y debemos saber en qué orden debe situarse cada uno de los índices (por ejemplo, primero
el código de población, a continuación la década, etc.).
En general, un elemento de un array n-dimensional se referencia con la siguiente sintaxis:
identificador[índice1][índice2]...[índicen]
Al igual que en el caso de los vectores, todos los elementos de un array multidimensional deben
ser de igual tipo. Si junto con el número de habitantes del ejemplo anterior quisiéramos almacenar
el nombre de la entidad que realizó el censo (representado por un dato de tipo cadena de caracteres),
sería preciso declarar una nueva matriz de tamaño 100×3 en vez de añadir una nueva dimensión al
array original, ya que los datos a almacenar son de distinto tipo1.
En algoritmia, la declaración de una estructura de datos array multidimensional utiliza el
siguiente diagrama sintáctico:

identificador valor
VARIABLES [ ] : tipo
de variable entero
,
Ahora, cada valor entero representa el número de elementos de la dimensión correspondiente y,
por tanto, el rango de valores que puede tomar su índice (en C, desde 0 a valor entero – 1).
La traducción a C de esta declaración se realiza según el siguiente diagrama sintáctico:

identificador valor
tipo [ ] ;
de variable entero
,
Por ejemplo, la declaración de la matriz habitantes vista anteriormente emplearía la
siguiente sintaxis:
En algoritmia: En C:
VARIABLES int habitantes[100][3];
habitantes[100][3]:entero

1 Veremos en el Capítulo 6 que existe una alternativa más adecuada mediante el uso de registros.

48
CURSO 2018/19 4. ESTRUCTURAS DE DATOS (I): ARRAYS

EJEMPLO 4.2. Almacenar desde teclado valores reales en una matriz de dimensiones 20x20 por filas y mostrar por
pantalla la suma de sus columnas.
ANÁLISIS:
a) Datos de entrada:
 NUMFIL=5. Número de filas. Dato fijo.
 NUMCOL=5. Número de columnas. Dato fijo.
 m[1..NUMFIL][1..NUMCOL]: Matriz de números reales. Teclado.
b) Datos de salida:
 s[1..NUMCOL]: Sumas de las columnas de la matriz. Monitor.
c) Comentarios:
 Se utilizarán dos variables índice para recorrer la matriz, en primer lugar, durante su lectura desde teclado y,
en segundo, durante el cálculo de la suma de cada columna.
 Dado que se mostrará la suma de cada columna tras su cálculo, no es necesario recordar simultáneamente
cada una de ellas, por lo que solo se utilizará una variable para almacenarlas.
DISEÑO:
a) Parte declarativa:
CONSTANTES
NUMFIL=5
NUMCOL=5
VARIABLES
m[NUMFIL][NUMCOL]:real
i,j:entero
s:real
b) Representación algorítmica:
sumaColumnas
BLOCK

for do for do

i←0,NUMFIL-1,1 BLOCK j←0,NUMCOL-1,1 BLOCK

escribir("Fila",i+1) for do s←0 for do escribir(s)

j←0,NUMCOL-1,1 leer(m[i][j]) i←0,NUMFIL-1,1 s←s+m[i][j]

CODIFICACIÓN:
/***************************************/
/*** S U M A C O L U M N A S ***/
/***************************************/
#include <stdio.h>

#define NUMFIL 5 /* Número de filas de la matriz */


#define NUMCOL 5 /* Número de columnas de la matriz */
int main()
{
double m[NUMFIL][NUMCOL],s;
int i,j;

/* Lectura desde teclado de la matriz por filas */


for (i=0; i <= NUMFIL-1; i=i+1)
{
printf("Fila %d: ",i+1);
for (j=0; j <= NUMCOL-1; j=j+1)
scanf("%lf",&m[i][j]);

49
CURSO 2018/19 4. ESTRUCTURAS DE DATOS (I): ARRAYS

/* Cálculo de la suma de cada columna y visualización de resultados */


for (j=0; j <= NUMCOL-1; j=j+1)
{
s=0;
for (i=0; i <= NUMFIL-1; i=i+1)
s=s+m[i][j];
printf("La suma de la columna %d es %.2f.\n",j+1,s);
}
return 0;
}

50
CURSO 2018/19 4. ESTRUCTURAS DE DATOS (I): ARRAYS

EJERCICIOS

1º) Leer desde teclado un vector con n números enteros y a continuación indicar por pantalla si este
es capicúa, siendo n un número entre 2 y 100, también leído desde teclado.
2º) Leer desde teclado un vector con 10 números enteros y a continuación indicar por pantalla si el
valor de algún elemento coincide con la suma de todos los que están a su izquierda en el vector.
3º) Leer desde teclado una matriz con 5×4 valores reales y a continuación mostrar su traspuesta.
4º) Leer desde teclado dos matrices A y B de dimensiones 5×4 y 4×6, respectivamente, con valores
reales y mostrar el resultado de A×B.
5º) Leer desde teclado una matriz con 6×6 valores reales positivos y dos valores a y b, y mostrar la
media de los elementos de la matriz que sean mayores que a y menores que b.
6º) Leer desde teclado un vector de 10 valores reales y mostrar la lista de índices de los elementos
del vector menores que la media del vector y el tamaño de dicha lista.
7º) Generar aleatoriamente un vector de m dígitos y leer desde teclado una lista con n índices del
vector, donde m y n se introducen por teclado y donde 10 ≤ m ≤ 30. A continuación, mostrar el
vector generado y el valor del vector que más se aproxima a la media de los elementos
apuntados por la lista de índices.
Nota: Para generar números aleatorios debe utilizarse la función interna rand(), que genera
un número aleatorio de tipo entero en el intervalo [0, 32767]. Antes de utilizar esta función en
el programa será necesario invocar una vez a la función interna srand(time(NULL)); que
inicializa el generador de números aleatorios a partir de la hora actual del sistema, para evitar
así que siempre se genere la misma secuencia de números aleatorios. Para el uso de ambas
funciones es necesario incluir los archivos de cabecera stdlib.h y time.h.
8º) Leer desde teclado y almacenar en una matriz una serie de temperaturas tomadas cada hora
durante una semana. A continuación, mostrar por pantalla la máxima de las temperaturas
mínimas diarias.
9º) Generar aleatoriamente un array tridimensional de 5×10×3 con valores entre el 1 y el 40 y
mostrar por pantalla el array generado, el valor del array que más veces se repite y el número de
repeticiones del mismo (si hay varios valores que se repiten el número máximo de veces,
devolver uno de ellos cualquiera).
10º) La distancia entre dos valores x e y se calcula como el valor absoluto de su diferencia |x-y|.
Realizar el análisis, diseño y codificación de un programa en C que lea desde teclado n valores
reales dentro del intervalo [1,n2], donde 5 ≤ n ≤ 30, y a continuación muestre por pantalla la
distancia mínima entre dos valores de posiciones distintas de entre todos los pares posibles.
11º) Realizar el análisis, diseño y codificación en C de un programa que muestre en pantalla 100
números enteros aleatorios que deberán estar entre dos números (el primero será un número
introducido por teclado que deberá estar entre el 5 y el 100, y el segundo será el 1000). Además
los números que aparezcan en pantalla no deberán estar repetidos.
12º) Realizar el análisis, diseño y codificación en C de un programa que genere aleatoriamente una
matriz de 5 filas y 10 columnas con valores enteros entre el 1 y el 5, lea un valor n desde
teclado y muestre por pantalla la matriz y un mensaje que indique si existe en ella alguna

51
CURSO 2018/19 4. ESTRUCTURAS DE DATOS (I): ARRAYS

columna que sume n y, en caso positivo, el índice de la primera columna que cumpla la
condición.
13º) Realizar el análisis, diseño y codificación en C de un programa que rellene un vector de 20
reales a partir de una secuencia de números organizados en pares (a,b). El primer número a de
cada par indica el valor que debe introducirse en el vector y será un número real mayor o igual
que cero introducido por teclado. El segundo número b de cada par especifica la cantidad de
valores que deben introducirse en el vector y será generado de manera aleatoria en el intervalo
[1,10]. Finalmente, una vez que el vector esté completamente relleno, su contenido deberá
mostrarse por pantalla.
14º) Realizar el análisis, diseño y codificación en C de un programa que:
a) en primer lugar, lea desde teclado un máximo de 30 valores reales entre 1 y 40,
almacenando los valores de nº de orden par (segundo, cuarto, sexto, etc.) en un vector y los
de orden impar (primero, tercero, quinto, etc.) en otro vector. La lectura de valores finalizará
cuando se llegue a 30 valores o se introduzcan dos valores iguales en la misma posición de
sendos vectores (sean iguales el primero y el segundo, o el tercero y el cuarto, o el quinto y
el sexto, etc).
b) en segundo lugar, una vez finalizada la lectura desde teclado, calcular y mostrar por pantalla
la distancia mayor entre pares de valores situados en la misma posición en sendos vectores.
Importante: el cálculo de la distancia descrito en el apartado b no deberá comenzar antes de
que haya finalizado la lectura descrita en el apartado a.

52
5
MODULARIDAD

Una vez conocidas las estructuras de control disponibles en la programación estructurada, es


posible desarrollar cualquier programa. Sin embargo, a medida que los problemas se hacen más
complejos, resulta más difícil su resolución como un todo, al tener que considerar simultáneamente
todos los aspectos que afectan al problema.
En este capítulo se introduce la modularidad, una técnica basada en la separación de problemas
complejos en tareas más simples, simplificando su resolución y contribuyendo a mejorar la claridad
de las soluciones alcanzadas. Además, se estudiarán los mecanismos disponibles en C para
implementar esta técnica.

5.1 INTRODUCCIÓN A LA MODULARIDAD


La resolución de un problema complejo puede simplificarse dividiendo a este en subproblemas
más sencillos y a estos, a su vez, en otros más simples hasta que los más pequeños sean fáciles de
resolver. Esta técnica, basada en la estrategia “divide y vencerás”, es empleada habitualmente en
programación y se conoce con el nombre de modularidad. A cada uno de los subproblemas
considerados se le denomina módulo.
Para ilustrar esta idea, supóngase que se desea desarrollar un programa para el cálculo del IRPF.
Acometer como un todo la resolución de este problema, de considerable envergadura, sería una
tarea complicada y el programa resultante contendría probablemente una gran cantidad de código
difícil de depurar y mantener. Sin embargo, este problema podría desglosarse en la solución
sucesiva de tareas más simples, por ejemplo: lectura de los datos del contribuyente, cálculo de los
resultados y visualización de los resultados.

Cálculo IRPF

Lectura Cálculo Visualización


datos resultados resultados

53
CURSO 2018/19 5. MODULARIDAD

Además, la lectura de datos del contribuyente podría aún desglosarse, por ejemplo, en lectura de
datos personales y lectura de datos económicos:

Cálculo IRPF

Lectura Cálculo Visualización


datos resultados resultados

Datos Datos
personales económicos

Las ventajas de esta forma de resolver los problemas son numerosas:


 Facilita la depuración y el mantenimiento de programas. Así, en el ejemplo del cálculo
del IRPF, un error en la lectura de la fecha de nacimiento del contribuyente podría buscarse
directamente en el módulo Datos personales, mientras que una modificación de las escalas
de gravamen afectaría únicamente al módulo Cálculo resultados.
 Facilita el trabajo en equipo, ya que la asignación de tareas puede hacerse de forma sencilla
y, con una buena división modular, la independencia entre dichas tareas permitirá a cada
miembro del equipo concentrarse en su trabajo sin preocuparse del método de resolución
empleado para realizar las restantes tareas. A este respecto, debe señalarse que el diseño
modular ha de buscar siempre la encapsulación de sus módulos, es decir, la ocultación de los
aspectos internos de implementación del módulo (el cómo) al resto de módulos que lo
utilizan, los cuales solo requerirán conocer qué hace (el qué).
 Posibilita la reducción del tamaño de los programas, ya que un módulo puede ser
utilizado en varias ocasiones dentro de un mismo programa, mientras que las instrucciones
que describen la tarea del módulo solo será necesario incluirlas una vez. Por ejemplo, en el
cálculo del IRPF la lectura y validación de una fecha podría separarse en un módulo
independiente. Si durante la lectura de los datos personales es necesario leer la fecha del
contribuyente y la de su cónyuge, solo será necesario repetir la llamada a dicho módulo en
lugar de repetir la descripción de cómo se realiza la lectura y validación de una fecha.
 Favorece la reutilización del código, es decir, el aprovechamiento de resoluciones de
subproblemas llevadas a cabo con anterioridad. El desarrollo de módulos que resuelven
problemas frecuentes permitirá al programador recopilar en una biblioteca de funciones
propia una batería de soluciones genéricas que incrementen su rendimiento futuro. Por
ejemplo, un módulo que lea una fecha y la valide podría reutilizarse fácilmente en la
resolución de otros problemas distintos al del cálculo del IRPF mencionado.
Por lo tanto, la metodología empleada consiste en comenzar, desde una visión general del
problema, determinando los subproblemas que lo componen. Cada uno de estos se divide a
continuación, si se estima necesario, en otros más simples. Como se ha visto, esto puede
representarse como una estructura jerárquica en cuya parte superior se encuentra el problema
principal y en la inferior los subproblemas más simples. Por esto, a esta metodología también se la
denomina diseño descendente, donde cada nivel de esta estructura representa un grado de detalle y,
por tanto, de abstracción, distinto.

54
CURSO 2018/19 5. MODULARIDAD

Las soluciones de un diseño descendente pueden implementarse en C con el concepto de módulo.


El módulo correspondiente al problema principal se denomina programa principal y el resto de
módulos en los que se divide subprogramas. En lenguaje algorítmico utilizaremos los términos
algoritmo principal y subalgoritmos.
Normalmente, un módulo es llamado desde otro módulo situado en un nivel jerárquico superior
al suyo. Tras la llamada, el flujo de ejecución se traslada a la primera instrucción del módulo
llamado y comienza su ejecución. Cuando este termina, el control es devuelto al lugar del módulo
llamador desde el que fue invocado. Como se ha comentado, esto puede ocurrir en diferentes
lugares del módulo llamador. A continuación se ilustra esa transferencia del flujo de ejecución en el
ejemplo anterior.
Calculo IRPF leerDatos(...) datosPersonales(...) leerFecha(...)
. .
ada ada
1
. Llam .
da .
Llam 2
. . ma . ad
a
leerDatos(...) . Lla leerFecha(...) m
datosPersonales(...) Lla
. .
. . . Instrucciones
calculoResultados(...) . . del módulo
. . . Re
Re

datosEconomicos(...)
Re

. leerFecha(...) to
to

rn
to

verResultados(...) . . o
rn
rn

Re 1
o
o

. . . torn
. . . o2

En C, el concepto de módulo se corresponde con el de función. A continuación, se detallan los


aspectos de definición y uso de funciones.

5.2 DEFINICIÓN DE FUNCIONES


Matemáticamente, una función es una operación que toma uno o más valores y genera un
resultado basándose en estos. Así, por ejemplo:
f  x , y =  x 2  y 2
es una función cuyo nombre es f y cuyo resultado depende de los valores asignados a x e y.
Obsérvese que en la definición de la función no se asocia ningún valor específico ni a x ni a y:
diremos que x e y son parámetros. Para evaluar la función y obtener un valor concreto es necesario
asociar a los parámetros valores específicos que llamaremos argumentos. Así, por ejemplo, los
argumentos 3 y 4 para los parámetros x e y, respectivamente, permiten obtener el valor 5 de la
función f.
En el Capítulo 2 se presentaron algunas funciones que C lleva incorporadas, tales como
funciones trigonométricas, de redondeo, etc., que denominamos funciones internas. Cuando el tipo
de cálculo deseado no lo cubren las funciones internas, es necesario recurrir a las funciones
externas, que serán aquellas definidas por el programador.
La definición de una función consiste en la descripción del modo en que esta realiza su cometido.
Al igual que el desarrollo de los programas vistos hasta ahora, la definición de una función también
se divide en tres fases: análisis, diseño y resolución en el ordenador.
En la fase de análisis, la especificación de datos de entrada y datos de salida es semejante a la
vista en capítulos anteriores. Sin embargo, ahora, los datos de entrada podrán proceder también del
módulo llamador (en concreto, cuando se correspondan con los parámetros de la función). En ese

55
CURSO 2018/19 5. MODULARIDAD

caso, en la especificación del dato de entrada se indicarán también las restricciones que se asume
que ya cumple dicho dato cuando llega a la función. Igualmente, los datos de salida pueden tener
como destino, no solamente el monitor, sino también el módulo llamador si son el resultado de la
función.
En la fase de diseño, en la parte declarativa y antes de la definición de constantes simbólicas y la
declaración de variables de que haga uso la función, deberá indicarse la cabecera de la función, que
contendrá los identificadores de los parámetros, el tipo asociado a cada uno de ellos y el tipo
asociado al valor devuelto por la función, utilizando el siguiente diagrama sintáctico:

identificador identificador
( : tipo ) : tipo
del módulo de parámetro
,
El identificador del módulo se refiere al nombre de la función. A continuación, entre paréntesis,
se incluirán los identificadores de los parámetros junto con sus tipos correspondientes (o
únicamente los paréntesis si la función careciera de parámetros). Por último, se indicará el tipo
devuelto por la función.
La representación algorítmica será muy similar a la vista en el Capítulo 3 para un algoritmo
completo. La única diferencia es que en un subalgoritmo, el primer símbolo del que parten el resto
de instrucciones será el que se indica a continuación:
identificador_módulo(lista de parámetros)
BLOCK
donde la lista de parámetros se refiere a los parámetros que utiliza la función y consistirá en una
lista de identificadores separados por comas (sin indicar tipos). En cuanto a identificador_módulo
será el nombre de la función.
Además, para expresar en el subalgoritmo el valor que finalmente devolverá la función como
resultado al módulo llamador, se empleará la siguiente representación:
devolver(expresión)

En la fase de resolución en el ordenador, la definición de una función se sitúa en C después del


programa principal (función main()). Una función tiene una constitución similar al programa
principal, es decir, posee una sección de definición de constantes simbólicas, una cabecera de
función y un cuerpo que contiene la declaración de variables y las instrucciones correspondientes al
diagrama de Tabourier. La cabecera, sigue el siguiente diagrama sintáctico:

identificador identificador
tipo ( tipo )
de la función de parámetro
,
Las secciones de definición de constantes simbólicas y declaración de variables contendrán,
respectivamente, las constantes simbólicas y variables que la función utilice. El resto del cuerpo
estará formado por el conjunto de instrucciones que llevan a la obtención del valor que la función
debe devolver. La última instrucción del cuerpo de la función deberá ser de la forma:
return expresión;
indicando así el valor que esta devuelve como resultado al módulo llamador.

56
CURSO 2018/19 5. MODULARIDAD

EJEMPLO 5.1. Desarrollar un módulo que calcule y devuelva el factorial de un número recibido como parámetro.
ANÁLISIS:
a) Datos de entrada: M. llamador
 n: Valor del que quiere calcularse el factorial. Módulo llamador. (n ≥ 0)
f n
b) Datos de salida:
 factorial: Factorial de n. Módulo llamador. factorial
c) Comentarios:
 Emplearemos una variable para contar el número de multiplicaciones efectuadas.
DISEÑO:
a) Parte declarativa:
factorial(n:entero):real
VARIABLES
i:entero
f:real
b) Representación algorítmica:
factorial(n)
BLOCK

f←1 for do devolver


(f)

i ← 2, n, 1 f ← fi

CODIFICACIÓN:
/** factorial(n) **/
/** Devuelve el factorial de un número n **/
double factorial(int n)
{
int i;
double f;

f=1;
for (i=2; i<=n; i=i+1)
f=f*i;
return f;
}

5.3 INVOCACIÓN DE FUNCIONES


Para emplear una función es necesario invocarla desde otro módulo. Dado que la invocación se
sustituirá por el resultado devuelto, esta deberá formar parte de una expresión para que su valor no
se pierda en el módulo llamador. La sintaxis que se utiliza es la siguiente:
...identificador_función(lista de argumentos)...
La lista de argumentos se refiere a los valores empleados en la invocación y será una lista de
expresiones separadas por comas. El número de argumentos incluidos en esta lista debe coincidir
con el de parámetros indicados en la cabecera de la función durante su definición. Además, el tipo
de cada argumento debe coincidir con el de su correspondiente parámetro o, en caso contrario, se
producirá una conversión de tipo que puede provocar pérdida de información.

57
CURSO 2018/19 5. MODULARIDAD

Una llamada a una función implica los siguientes pasos:


1. El flujo de ejecución se transfiere del módulo llamador a la función.
2. A cada parámetro se le asigna el valor del argumento que ocupe su misma posición en la
lista.
3. Se ejecutan las instrucciones de la función.
4. El valor de la función es devuelto al módulo llamador junto con el flujo de ejecución.
5. Se evalúa la expresión del módulo llamador que contiene a la invocación.
El uso de módulos definidos por el programador también afecta a las distintas fases de resolución
del módulo llamador.
En la fase de análisis, deberá incluirse un comentario por cada uno de las funciones externas que
vayan a utilizarse, indicando su cometido y una descripción de la información que se transferirá
desde y hacia el módulo llamador.
En la fase de diseño, en la parte declarativa y tras la definición de constantes simbólicas, deberá
indicarse la lista de módulos a los que se invoca, empleando para cada uno la siguiente sintaxis:

identificador
( tipo ) : tipo
del módulo
,
y precediendo a toda la lista del epígrafe MÓDULOS LLAMADOS. En la representación
algorítmica, los pasos que contengan una invocación a un módulo definido por el programador
estarán encerrados en el siguiente símbolo:

Por último, en la fase de resolución en el ordenador deberá indicarse, inmediatamente después


de la definición de constantes de los módulos que invoquen a funciones externas, los prototipos de
dichas funciones, que deben seguir el siguiente diagrama sintáctico:

identificador
tipo ( tipo ) ;
de la función
,

EJEMPLO 5.2. Calcular y mostrar por pantalla el número de combinaciones sin repetición de n elementos tomados de
m en m, solicitando n y m por teclado.
ANÁLISIS: M. P.
a) Datos de entrada: n
 n: número total de elementos. Teclado. (n > 0) n
f
 m: tamaño de cada grupo de elementos tomado. Teclado. (n ≥ m > 0)
leerEntPos factorial
b) Datos de salida:
 result: número de combinaciones sin repetición de n elementos tomados de m en m. Monitor.
c) Comentarios:
 Se utilizará un módulo para leer un número positivo desde teclado (sin mostrar ningún mensaje solicitando el
dato). Devolverá dicho número.

58
CURSO 2018/19 5. MODULARIDAD

 Se utilizará un módulo para calcular el factorial de un número. Recibirá dicho número y devolverá el
factorial calculado.
DISEÑO:
a) Parte declarativa:
MÓDULOS LLAMADOS
leerEntPos():entero
factorial(entero):real
VARIABLES
n,m:entero
result:real
b) Representación algorítmica:
combinatorio
BLOCK

escribir escribir result←factorial(n)/ escribir


("Total") n←leerEntPos() ("Grupo") m←leerEntPos() while do
(factorial(m)factorial(n-m)) (result)

m>n BLOCK

escribir escribir escribir


("Error") ("Total") n←leerEntPos() ("Grupo") m←leerEntPos()

CODIFICACIÓN:
/***********************************/
/*** C O M B I N A T O R I O ***/
/***********************************/
#include <stdio.h>

/* Prototipos de funciones */
int leerEntPos();
double factorial(int);

/** Programa principal **/


int main()
{
int n,m;
double result;

printf("Número total de elementos: ");


n=leerEntPos();
printf("Tamaño de cada grupo: ");
m=leerEntPos();
while (m > n)
{
printf("Error: el tamaño del grupo no puede ser mayor que el total.");
printf("Número total de elementos: ");
n=leerEntPos();
printf("Tamaño de cada grupo: ");
m=leerEntPos();
}
result=factorial(n)/(factorial(m)*factorial(n-m));
printf("Combinaciones de %d elementos tomados de %d en %d (s.r.)= %.0f",
n,m,m,result);
return 0;
}

/** leerEntPos() **/


/** Devuelve un número positivo leído desde teclado **/
int leerEntPos()
... (aquí se situará la definición de la función leerEntPos)

/** factorial(n) **/


/** Devuelve el factorial de un número n **/
... (aquí se situará la definición de la función factorial)

59
CURSO 2018/19 5. MODULARIDAD

5.4 MÓDULOS QUE NO DEVUELVEN NINGÚN VALOR


Como se indicaba en la introducción, el objetivo de la modularidad es dividir un problema en
subproblemas más simples. Por ello, a veces puede convenir separar en un módulo un conjunto de
instrucciones que representen una subtarea independiente dentro del programa, incluso aunque
dicho módulo no proporcione ningún resultado al módulo llamador. Tal es el caso, por ejemplo, de
un módulo que muestre por pantalla las instrucciones de uso de un programa.
En la fase de diseño, la definición de un módulo que no devuelve ningún valor al módulo
llamador se indicará omitiendo la parte :tipo del final de la cabecera del módulo. Además, en el
diagrama de Tabourier, no se incluirá al final la operación devolver que se estudió en la sección
anterior.
En la fase de resolución en el ordenador, la cabecera de la función comenzará situando la
palabra clave void en el lugar del tipo devuelto por la función y se omitirá la instrucción return
al final del cuerpo de la función.
La invocación de un módulo que no devuelve ningún valor se efectuará de forma idéntica a como
se hace con otra función, es decir,
identificador_función(lista de argumentos)
teniendo en cuenta que ahora debe aparecer independientemente y no como parte de una expresión,
ya que no hay un resultado asociado a la invocación.

EJEMPLO 5.3. Desarrollar un módulo que muestre por pantalla la tabla de multiplicar de un número entero
recibido como parámetro.
ANÁLISIS:
a) Datos de entrada: M. llamador
 mult: valor del que se mostrará la tabla de multiplicar. Módulo llamador.
mult
b) Datos de salida:
 tabla[1..10]: Valores de la tabla de multiplicar de mult. Monitor
tablaMultiplicar
c) Comentarios:
 Utilizaremos una variable para contabilizar el número de multiplicaciones mostradas durante la visualización
de la tabla de multiplicar.
DISEÑO:
a) Parte declarativa:
tablaMultiplicar(mult:entero)
VARIABLES
i:entero
b) Representación algorítmica:
tablaMultiplicar(mult)
BLOCK

escribir for do
("Tabla",mult)

i ← 1, 10, 1 escribir
(i,mult,imult)

60
CURSO 2018/19 5. MODULARIDAD

CODIFICACIÓN:
/** tablaMultiplicar(mult) **/
/** Calcula y muestra la tabla de multiplicar de 'mult' **/
void tablaMultiplicar(int mult)
{
int i;

printf(" TABLA DE MULTIPLICAR DEL %2d\n",mult);


printf("=============================\n");
for (i=1; i<=10; i=i+1)
printf("%10d X %2d = %2d\n",i,mult,i*mult);
}

5.5 MÓDULOS QUE DEVUELVEN MÁS DE UN VALOR


Las funciones devuelven, como máximo, un resultado asociado al nombre de la función. En los
ejemplos anteriores, los parámetros se han utilizado para recibir valores desde el módulo llamador.
Este modo de transferir información entre módulos mediante argumentos/parámetros se
denomina pase de argumentos por valor y consiste en copiar el valor del argumento en el
correspondiente parámetro. Por lo tanto, el pase de un argumento por valor constituye un canal de
comunicación unidireccional desde el módulo llamador hacia el módulo llamado: a través de ellos
solo puede enviarse información desde el módulo llamador hacia el módulo llamado, ya que las
modificaciones que pudieran producirse en los parámetros dentro del módulo llamado no quedan
reflejadas en los argumentos especificados en la invocación desde el módulo llamador. Así, aunque
la función factorial del Ejemplo 5.1 se hubiera definido de forma que el parámetro n modificara su
valor dentro de la función:
double factorial(int n)
{
double f;

if (n < 2)
f=1;
else
{
f=n;
while (n > 2)
{
n=n-1;
f=f*n;
}
}
return f;
}
el resultado del cálculo combinatorio del Ejemplo 5.2 seguiría siendo correcto, ya que las
modificaciones que sufre el parámetro n dentro de la función no afectan al argumento con el que se
realiza cada llamada.
Cuando necesitemos definir un módulo que devuelva más de un valor al módulo llamador,
deberemos utilizar otro modo de transferir información entre módulos: el pase de argumentos por
referencia. Este modo permite establecer un canal de comunicación bidireccional entre el módulo
llamador y el módulo llamado. En concreto, consistirá en transferir al módulo llamado la dirección
del argumento (utilizando el operador de dirección, &), en lugar de transferir una copia de su valor.
Así, a diferencia de lo que ocurre con el pase de argumentos por valor, la información contenida en

61
CURSO 2018/19 5. MODULARIDAD

el parámetro, que será la dirección de memoria de una variable del módulo llamador, permite afectar
al valor del argumento en el módulo llamador. Un módulo puede pasar por referencia tantos
argumentos como se quiera, ampliando así el número de valores que el módulo puede «devolver» al
módulo llamador.1
Obsérvese que el parámetro correspondiente a un argumento pasado por referencia siempre
deberá actuar, al menos, como dato de salida del módulo (a veces, si interesa, podrá también actuar
como dato de entrada). En el caso de que el dato sea solo de entrada al módulo, el argumento
deberá pasarse siempre por valor.

EJEMPLO 5.4. Desarrollar un módulo que intercambie en el módulo llamador el valor de dos variables de tipo
entero.
ANÁLISIS:
a) Datos de entrada:
 x: valor a intercambiar. Módulo llamador. M. llamador
 y: valor a intercambiar. Módulo llamador.
x,y
b) Datos de salida:
 x': valor intercambiado (la y original). Módulo llamador. intercambiar
 y': valor intercambiado (la x original). Módulo llamador.
c) Comentarios:
 Será necesario utilizar una variable auxiliar que mantenga uno de los valores iniciales temporalmente, con el
objetivo de que este no se pierda tras la primera asignación.
DISEÑO:
a) Parte declarativa:
intercambiar(*x:entero, *y:entero)
VARIABLES
aux:entero
b) Representación algorítmica:
intercambiar(x,y)
BLOCK

aux←x x←y y←aux

CODIFICACIÓN:
/** intercambiar(x,y) **/
/** Intercambia los valores de x e y **/
void intercambiar(int *x, int *y)
{
int aux;

aux=*x;
*x=*y;
*y=aux;
}

1 Si bien el lenguaje C permite que un mismo módulo devuelva un valor asociado al nombre de la función y otros
valores asociados a argumentos pasados por referencia, el uso simultáneo de ambos métodos no se considera una
buena práctica de programación. Por ello, emplearemos el primer método cuando el módulo devuelva un único valor
y el segundo cuando devuelva más de uno.

62
CURSO 2018/19 5. MODULARIDAD

5.6 ARRAYS COMO PARÁMETROS


En lenguaje C, los conceptos de array y puntero están relacionados. Concretamente, el
identificador de un array, sin especificar el índice, equivale a la dirección de memoria donde
comienza el array (es decir, la dirección del primer elemento del array). Por ello, al pasar un array a
un módulo como argumento, realmente estaremos pasando la dirección del array y no su contenido.
En otras palabras, cuando se trate de arrays, el pase de argumentos en C se hace siempre por
referencia.
Por lo tanto, habrá que prestar especial cuidado cuando se trabaje dentro de un módulo con
parámetros de tipo array, pues las modificaciones que se hagan en el parámetro siempre se reflejarán
en el correspondiente argumento en el módulo llamador.
En la cabecera de las funciones, los parámetros de tipo array incluirán, entre corchetes, la
longitud de cada dimensión, excepto la primera, que podrá omitirse (aunque no los corchetes).
Por ejemplo, si queremos definir un módulo que lea desde teclado una matriz de enteros de 10×5
y la devuelva al módulo llamador, una posible cabecera de función sería:
En algoritmia: En C:
leerMatriz(m[][5]:entero) void leerMatriz(int m[][5])

siendo por tanto su prototipo:


En algoritmia: En C:
leerMatriz([][5]:entero); void leerMatriz(int [][5]);

63
CURSO 2018/19 5. MODULARIDAD

EJERCICIOS

1º) Calcular y mostrar por pantalla la edad de una persona a partir de la fecha actual y la de su
nacimiento con formato dd mm aaaa, introducidas ambas por teclado.
2º) El desarrollo de la serie de Maclaurin para la función seno es:
x3 x5 x7 x9
sen  x = x −  −  −...
3! 5! 7! 9!
Evaluar y mostrar por pantalla el seno de x empleando n términos en el desarrollo, donde x y n
se introducen por teclado (x vendrá expresado en radianes).
3º) "¿Quién tiene menos?" es un juego de estrategia para dos jugadores en el que cada jugador
piensa un número entero entre el 1 y el 5 y a continuación los comparan. Si coinciden o se
diferencian en más de una unidad, cada jugador recibe un número de puntos igual al número
que pensó. Si por el contrario, los números se diferencian en una unidad, el jugador que pensó
el número menor recibe una cantidad de puntos igual a la suma de los dos números pensados.
El juego consta de 10 rondas y después de cada una de ellas se acumulan los puntos. Gana el
que alcance mayor número de puntos. Realizar un programa que permita jugar una partida entre
el usuario y el ordenador, solicitando para cada ronda la jugada al usuario desde teclado y
generándola aleatoriamente para el ordenador. Una vez finalizada la partida, se mostrará un
mensaje indicando quién ha sido el vencedor. Un ejemplo de ejecución sería:

RONDA 1:
Introduce tu jugada ([1,5]): 3
Humano: 3, Máquina: 5. Difieren en más de una unidad.
Marcador: HUMANO 3 - MAQUINA 5
RONDA 2:
Introduce tu jugada ([1,5]): 2
Humano: 2, Máquina: 2. Los números coinciden.
Marcador: HUMANO 5 - MAQUINA 7
...
RONDA 9:
Introduce tu jugada ([1,5]): 3
Humano: 3, Máquina: 5. Difieren en más de una unidad.
Marcador: HUMANO 28 - MAQUINA 24
RONDA 10:
Introduce tu jugada ([1,5]): 3
Humano: 3, Máquina: 4. Difieren en una unidad.
Marcador: HUMANO 35 - MAQUINA 24
Partida finalizada.
¡Enhorabuena!, has ganado.

4º) Desarrollar un módulo que reciba una lista de valores enteros y su tamaño y devuelva si está o
no ordenada crecientemente.
5º) Desarrollar un módulo que reciba una lista de valores enteros y su tamaño y la devuelva
ordenada ascendentemente empleando el método de inserción.
6º) Desarrollar un módulo que, recibiendo una matriz cuadrada de valores enteros y su dimensión
(10×10 como máximo), devuelva la suma de los valores situados por encima de la diagonal
principal, la suma de los valores de la diagonal principal y la suma de los valores por debajo de
la diagonal principal.

64
CURSO 2018/19 5. MODULARIDAD

7º) Desarrollar un módulo que inicialice y devuelva una matriz cuadrada de dimensiones n×n de la
siguiente forma: la diagonal principal deberá contener un determinado valor real v; las
diagonales adyacentes a la diagonal principal, un valor igual al doble de v; las diagonales aún
sin rellenar adyacentes a las dos anteriores, un valor igual al triple de v, y así sucesivamente
hasta rellenar completamente la matriz, resultando, por tanto, una matriz simétrica. El tamaño
máximo de la matriz será 15×15.
8º) Desarrollar un módulo que realice el cálculo de la diferencia entre dos conjuntos. Para ello,
recibiendo dos conjuntos de valores enteros y sus respectivos tamaños, deberá devolver el
conjunto diferencia (elementos del primer conjunto que no están en el segundo) y su tamaño.
Considérese que el tamaño máximo de cada conjunto recibido es igual a 100 elementos.
9º) Un punto en un espacio bidimensional se representa mediante el par de coordenadas (x,y). La
distancia entre dos puntos P=(x,y) y Q=(x’,y’) se calcula de la siguiente manera:

Desarrollar un módulo que recibiendo dos listas de puntos del mismo tamaño y dicho
tamaño, evalúe la distancia entre pares de puntos situados en la misma posición en sendos
vectores y devuelva el número de orden del par de puntos más alejados entre sí. En el caso de
que varios pares de puntos se encuentren a la máxima distancia, se devolverá el número de
orden del primer par encontrado.
10º) Desarrollar un módulo que evalúe si dos listas de valores enteros con el mismo tamaño superan
determinado grado de coincidencia. El grado de coincidencia se calculará como el porcentaje
de elementos que coinciden en posición y valor en sendos vectores. Para ello, el módulo
recibirá del módulo llamador las listas de valores, su tamaño y el grado de coincidencia, que
será un valor en el intervalo (0,1].
11º) Desarrollar un módulo que, recibiendo del módulo llamador una lista de valores enteros con al
menos 2 valores, su tamaño y una longitud l mayor que 0, devuelva la longitud (número de
elementos) de la primera secuencia de valores consecutivos iguales de longitud mayor o igual
que l, así como el valor que se repite en dicha secuencia. Si no existe ninguna secuencia de
valores consecutivos iguales de longitud mayor o igual que l, devolverá el valor 0.
12º) Desarrollar un módulo que, a partir de una lista de longitudes mayores o iguales que 1 y su
tamaño, devuelva una lista de secuencias binarias formadas alternativamente por ceros y por
unos y su tamaño. La lista de secuencias binarias deberá construirse según la lista de longitudes
recibida y comenzando por una secuencia de ceros.
13º) Con objeto de anticipar la intención de voto, una empresa de demoscopia selecciona muestras
de individuos del mismo tamaño en distintas poblaciones a los que les pregunta por su
intención de voto. Cada encuestado puede indicar el partido al que tiene intención de votar o no
indicar nada.
Desarrollar un módulo que, recibiendo el número de muestras consideradas, el número de
partidos políticos que se presentan a las elecciones y una matriz donde cada fila representa la
distribución de intención de voto de una muestra (porcentaje de individuos de la muestra que
votarán a cada partido), devuelva la distribución de intención de voto global y el número de
muestras erróneas (muestras donde la suma de porcentajes de los distintos partidos supere a
100). La distribución de intención de voto global será el porcentaje de individuos totales que
votarán a cada partido, calculado como será la media de los porcentajes de cada muestra (sin
considerar las muestras erróneas). El número máximo de partidos es 10.

65
CURSO 2018/19 5. MODULARIDAD

14º) Desarrollar un módulo que, recibiendo del módulo llamador la temperatura media diaria
durante una semana para p poblaciones (matriz de p filas y 7 columnas), el número p de
poblaciones y un valor real d, devuelva el resultado de comprobar si existe alguna población
para la cual la diferencia entre la máxima y mínima de sus temperaturas es mayor que d.
15º) Las localidades de un cine están dispuestas en f filas de c asientos cada una. Desarrollar el
análisis, diseño y codificación en C de un módulo para obtener la fila más lejana a la pantalla
en la que puedan sentarse n personas unas al lado de las otras en la misma fila. El módulo
recibirá tabla con información de la ocupación actual de las localidades (representada por los
caracteres L-libre, O-ocupado), el número de filas y columnas totales del cine y el número n de
asientos a ocupar consecutivamente, y deberá devolver el número de la fila más lejana a la
pantalla con un hueco de longitud mayor o igual a n. Si no existe tal hueco, devolverá el valor
-1. El número máximo de asientos en una fila será 50.
16º) Un número complejo es de la forma a+bi, donde a y b son números reales e i= −1 .
Desarrollar un programa dirigido por opciones de menú que lea dos números complejos (donde
a+bi se introduce como un par de números reales a y b), permita al usuario seleccionar la
ejecución de una operación (suma, resta, multiplicación o división) y muestre el resultado de la
forma a+bi. Las cuatro operaciones se definen de la siguiente manera:
(a+bi) + (c+di) = (a+c) + (b+d)i
(a+bi) - (c+di) = (a-c) + (b-d)i
(a+bi) · (c+di) = (ac-bd) + (ad+bc)i
(a+bi) / (c+di) = [(ac+bd)/(c2+d2) ] + [(bc-ad)/(c2+d2)]i
17º) Escribir los n primeros números de la serie de Fibonacci a partir de un número natural n
introducido por teclado.
Nota: La serie de Fibonacci es 0,1,1,2,3,5,8,13,... de acuerdo con la ley siguiente:
fibonacci(1) = 0
fibonacci(2) = 1
fibonacci(3) = 1 = fibonacci(2) + fibonacci(1)
fibonacci(4) = 2 = fibonacci(3) + fibonacci(2)
...
fibonacci(n) = fibonacci(n-1) + fibonacci(n-2)
18º) Escribir un programa que lea números enteros desde teclado hasta que el usuario acierte un
número secreto generado aleatoriamente entre 1 y 100. Cada vez que se introduzca un número
se debe indicar al usuario si es mayor o menor que el número secreto. Una vez acertado, se
mostrará el número de intentos que se han empleado.
19º) La distancia del punto de caída de un proyectil lanzado con un ángulo a (en grados) y una
velocidad inicial v (en metros por segundo), ignorando la resistencia del aire, viene dada por la
fórmula
v 2 × sen [  ×a/90 ]
distancia =
9,81
Suponiendo que la diana se encuentra a 100 metros de distancia, simular un juego en el que
el usuario introduce el ángulo y la velocidad de lanzamiento de un proyectil. Si el proyectil cae
a menos de un 1 metro de la distancia a la diana se considera que ha dado en el blanco y el
programa finaliza; en caso contrario, se le indica al usuario cuánto se ha alejado el proyectil del
punto de lanzamiento y se le permite intentar un nuevo lanzamiento. El usuario seguirá
lanzando hasta dar en el blanco, después de lo cual se mostrará el número de intentos

66
CURSO 2018/19 5. MODULARIDAD

empleados acompañado de uno de los mensajes EXCELENTE, BIEN, REGULAR, MAL o


PÉSIMO, dependiendo de si el número de intentos es 1, es 2 o 3, está entre 4 y 6, es 7 u 8, o es
mayor que 8, respectivamente.
20º) Para amortizar un préstamo de P euros en un banco, el cliente deberá devolver una cuota fija de
C euros al mes hasta que haya completado la cantidad total prestada. Parte de la cuota mensual
serán intereses, calculados como el I por ciento de la cantidad aún no pagada (capital
pendiente). El resto del pago servirá para reducir dicho capital pendiente.
Se desea realizar un programa para que, a partir de valores para P, C e I introducidos por
teclado, determine la siguiente información:
a) Número de orden de cada cuota mensual.
b) El importe aplicado cada mes a la reducción del capital pendiente.
c) Intereses pagados cada mes.
d) Capital pendiente al final de cada mes.
e) Intereses acumulados al final de cada mes.
f) La cuantía del último pago (ya que puede ser menor que C).
Un ejemplo de ejecución para un préstamo de 6.000 euros, con una cuota mensual de 500
euros y un interés del 1% podría ser:
Introduce la cantidad total del préstamo: 6000
Introduce el importe de la cuota mensual: 500
Introduce el interés aplicado: 1

MES Capital Intereses Pendiente T.Intereses


===========================================================
1 440.00 60.00 5560.00 60.00
2 444.40 55.60 5115.60 115.60
3 448.84 51.16 4666.76 166.76
4 453.33 46.67 4213.42 213.42
5 457.87 42.13 3755.56 255.56
6 462.44 37.56 3293.11 293.11
7 467.07 32.93 2826.04 326.04
8 471.74 28.26 2354.30 354.30
9 476.46 23.54 1877.85 377.85
10 481.22 18.78 1396.63 396.63
11 486.03 13.97 910.59 410.59
12 490.89 9.11 419.70 419.70
13 419.70 4.20 0.00 423.90

Cuota del último mes: 423.90

21º) Existen muchas series infinitas que convergen a π o a fracciones de π. Dos de estas series son la
de Leibniz y la de Wallis. La serie de Leibniz viene dada por
n
 1
4
= ∑ −1i1 2i−1
i=1

y la de Wallis por
n
 u
4
= ∏ vi
i=1 i

donde ui = 2 + 2 × (i div 2)
vi = 1 + 2 × ((i+1) div 2)
Mostrar por pantalla una tabla con las aproximaciones a π mediante las series de Leibniz y

67
CURSO 2018/19 5. MODULARIDAD

de Wallis desde 10 hasta n términos, donde n es un número introducido por el usuario entre 20
y 999, ambos inclusive. En cada línea deberá aparecer, además de las dos aproximaciones
calculadas, el número de términos empleado, el error sobre el valor exacto de π y una
indicación (L, W o =) de cuál de las aproximaciones es la mejor.
22º) “Las 4 en raya” es un juego de mesa para 2 jugadores que emplea un tablero en disposición
vertical de f filas por c columnas. Los jugadores, alternativamente, van insertando fichas de su
color (blancas o negras) en las columnas del tablero, que caen situándose unas encima de las
otras. Gana el jugador que consigue formar una línea horizontal, vertical o diagonal de, al
menos, 4 fichas de su color. Los valores de f y c deben ser especificados por los jugadores antes
de comenzar cada partida y no deberán exceder de 20×20.
Desarrollar un módulo que procese y devuelva el efecto sobre el tablero de una jugada en
cualquier momento de la partida. En concreto, recibiendo la información referente al estado del
tablero antes de esa jugada (contenido y dimensiones) y la jugada a procesar (la columna donde
se inserta la ficha y el color de la ficha), deberá devolver el nuevo contenido del tablero y el
resultado de comprobar si la jugada fue válida o no (si la columna donde se intentaba insertar la
ficha estaba o no llena).
23º) En una versión electrónica del juego “Master Mind” el objetivo es averiguar una combinación
de 5 dígitos (entre el 0 y el 9) sin repetición. Para ello, el usuario va proporcionando
combinaciones y el ordenador responde informando sobre el número de JAQUES y de MATES
entre la combinación secreta y la especificada por el usuario. El número de MATES equivale al
número de dígitos entre ambas combinaciones que coinciden en valor y posición. El número de
JAQUES equivale al número de dígitos entre ambas combinaciones que coinciden solamente
en valor, pero no en posición.
Desarrollar un programa que simule el juego del “Master Mind”. Para ello, el ordenador
leerá en primer lugar la combinación secreta desde teclado y a continuación, tras borrar la
pantalla, el usuario deberá introducir combinaciones hasta averiguar la combinación secreta,
después de lo cual se mostrará el número de intentos empleados.

68

S-ar putea să vă placă și