Apuntes de la asignatura Informática

Anuncio
INFORMÁTICA
ALGORITMIA Y PROGRAMACIÓN EN C
Pablo Carmona del Barco
Dpto. Ingeniería de Sistemas Informáticos y Telemáticos
Universidad de Extremadura
ÍNDICE
Tema 1: Introducción a la programación
1.1. Introducción....................................................................................... 1
1.2. Metodología de la programación........................................................2
1.3. Los lenguajes de programación..........................................................6
Tema 2: Tipos de datos y expresiones
2.1 Tipos de datos, constantes y variables..............................................10
2.1 Expresiones...................................................................................... 15
2.2 Funciones internas............................................................................ 18
2.3 Punteros............................................................................................ 19
2.4 Reglas de prioridad........................................................................... 20
Tema 3: Representación gráfica de los algoritmos y su traducción a C
3.1 Métodos de representación algorítmica............................................23
3.2 Operaciones primitivas.....................................................................24
3.3 Estructura de un programa en C.......................................................29
3.4 Estructuras de control.......................................................................30
Tema 4: Estructuras de datos (I): Arrays. Cadenas de caracteres
4.1 Introducción..................................................................................... 43
4.2 Arrays............................................................................................... 44
4.3 Cadenas de caracteres....................................................................... 49
Tema 5: Modularidad
5.1 Introducción a la modularidad..........................................................55
5.2 Definición de funciones....................................................................57
5.3 Invocación de funciones...................................................................59
5.4 Módulos que no devuelven ningún valor..........................................62
5.5 Módulos que devuelven más de un valor.........................................63
5.6 Arrays como parámetros..................................................................65
Tema 6: Estructuras de datos (II): Registros
6.1 Registros........................................................................................... 70
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
datos
ORDENADOR
SALIDA
resultado
Ilustración 1: Tratamiento automático de la información mediante el ordenador
1
1. INTRODUCCIÓN A LA PROGRAMACIÓN
Lenguaje de
programación
paridad
BLOCK
escribir
('Un nº:')
leer (n)
n mod 2 = 0
if then else
escribir
('Es par')
escribir
('Es impar')
program paridad(input,output);
var n:integer;
begin
write('Introduzca un número entero: ');
readln(n);
if n mod 2 = 0 then
writeln('El número es PAR')
else
writeln('El número 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
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 (sólo para datos fijos): Cuando el dato se integre directamente en el programa
(dato fijo), se indicará el valor asociado.
 Restricciones (sólo 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 aquél 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
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
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 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
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
0110
4
0100
3
0011
dirección
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
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 importantes
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 aquéllos.
7
1. INTRODUCCIÓN A LA PROGRAMACIÓN
1.3.2 TRADUCTORES DE LENGUAJES
El ordenador sólo 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
TRADUCTOR
Programa
Programa
fuente
objeto
Distinguimos tres tipos de traductores: ensambladores, compiladores e intérpretes.
Los ensambladores traducen programas escritos en ensamblador a código máquina, 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
L. ensamblador
Traductor
Ensambladores
Método
L. de alto nivel
Compiladores
Todo el programa
(programa objeto en disco)
Intérpretes
Instrucción a instrucción
Tabla 1: Diferencias entre los tres tipos de traductores
8
1. INTRODUCCIÓN A LA PROGRAMACIÓN
bibliotecas
programa fuente
principal
programas fuentes
secundarios
ENSAMBLADOR
O COMPILADOR
ENSAMBLADOR
O COMPILADOR
programa objeto
ENLAZADOR
programa
ejecutable
otros
módulos
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: son los tipos entero, real y carácter.
10
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 bytes1). Un literal
entero es una secuencia de uno o más dígitos entre el 0 y el 9, debiendo ser el primer dígito distinto
de 0 cuando el literal esté formado por más de un dígito. Al literal puede añadírsele un operador
unario 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
034
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
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
Tabulador horizontal
Nueva línea (inicio de la siguiente línea)
Retorno de carro (inicio de la línea actual)
Carácter nulo
Comillas
Apóstrofo
Barra inclinada
Secuencia de escape
\t
\n
\r
\0
\"
\'
\\
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
IVA
para almacenar la edad de una persona
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 sólo 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á:
letra
subraya
dígito
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
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:
CONSTANTES
LETRA='a'
NUMERO=-3.141592
PI=-NUMERO
En C:
#define LETRA 'a'
#define NUMERO -3.141592
#define 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 sólo 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
2. TIPOS DE DATOS Y EXPRESIONES
identificador como UNO o PRIMER_VALOR no facilitaría la lectura el 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 sólo podrá tomar valores de ese tipo. Por
ejemplo, una variable de tipo entero sólo 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
VARIABLES
identificador
de variable
tipo
:
,
utilizando la palabra entero para hacer referencia al tipo entero 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:
tipo
identificador
de variable
;
,
Declaraciones válidas de variables serían:
En algoritmia:
En C:
VARIABLES
radio,diametro:real
grupo:carácter
edad:entero
float radio,diametro;
char grupo;
int edad;
2.1.3 EL TIPO CADENA DE CARACTERES 3
Un literal de tipo cadena de caracteres es una secuencia de cero o más caracteres incluidos en la
tabla de códigos ASCII y 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í,
3 Aunque el tipo cadena de caracteres es un tipo estructurado y sus características serán estudiadas en profundidad en
el Capítulo 4, se introduce aquí para que se pueda trabajar con información de tipo cadena de caracteres desde un
principio.
14
2. TIPOS DE DATOS Y EXPRESIONES
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
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).
15
2. TIPOS DE DATOS Y EXPRESIONES
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
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.
16
2. TIPOS DE DATOS Y EXPRESIONES
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
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
0
0
distinto de 0
0
distinto de 0
0
1
0
0
0
a
b
a || b
distinto de 0
distinto de 0
0
0
distinto de 0
0
distinto de 0
0
1
1
1
0
Devuelve un resultado
verdadero si y sólo si los
dos operandos son
verdadero
Devuelve un resultado
verdadero si uno
cualquiera de los
operandos es verdadero
17
2. TIPOS DE DATOS Y EXPRESIONES
a
!a
distinto de 0
0
0
1
Devuelve un resultado
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 (||)
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 ESPECIALES
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 parámetros y que
aparecen entre paréntesis detrás del nombre de la función. Estos parámetros 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:
#include
<
archivo de
cabecera
>
Los tipos de datos de los parámetros y del valor devuelto por algunas de las funciones internas
más usuales se muestran en la siguiente tabla. El tipo de los parámetros se representa entre los
paréntesis de las funciones (i=int, d=double, c=char), si bien los parámetros de tipo double
también admiten valores de cualquier otro tipo numérico (que será convertido a double).
18
2. TIPOS DE DATOS Y EXPRESIONES
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
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 es igual a &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:
pv
65520
pv=&v
65522
65522
*pv=v
3.154
19
2. TIPOS DE DATOS Y EXPRESIONES
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:
VARIABLES
*pv:real
En C:
float *pv;
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), /, %
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
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) '\"'
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:

e)
3
2 a b a b
i) ln
2 y 2 − y−10
c)
y2
f)
25×10−4 x
2y
j)
a c2
2
a b 2b
×
d)
2 c b2 c
2a
g)
q3
q−2
k) 2 
h)
 b 2 −4a c
a) 8 x−3 x y
2
b) 20 ' 2 x 24 ' 5 x−40
l)
x 1 x 2
y

3,2
y
2

l
g
2cos 
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)
b)
c)
d)
e)
f)
g)
h)
i)
j)
k)
l)
sqrt(25)/2
pow(sqrt(9),2)
44%(6/3.0)
44%6/3.0
16/5%3
16/5*5
16/5*(double) 5
16/(double)5*5
!(floor(6.6)==ceil(5.3)) || *&p==p && ceil(5./3)==5/3
4/2*3/6+fabs(6/2-pow(3,2))*2
"A" <= "B"
6>2 && 5<4 || !(toupper(tolower('A'))=='A')
21
2. TIPOS DE DATOS Y EXPRESIONES
6º) Suponiendo la siguiente información en memoria, responder a las siguientes preguntas:
a)
b)
c)
d)
¿Cuál sería el valor de *v2, si v2 vale &v1?
¿Cuál sería el valor de *v2, si v2 vale v1?
¿Cuál sería el valor de &v2, si v2 vale &v1?
¿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)
b)
c)
d)
e)
f)
¿Cuál sería el valor de *v3, si v3 vale &v1?
¿Cuál sería el valor de *v3, si v3 vale v1?
¿Cuál sería el valor de *v3, si v3 vale &v2?
¿Cuál sería el valor de *v3, si v3 vale v2?
¿Cuál sería el valor de &v3, si v3 vale &v1?
¿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
3. LA REPRESENTACIÓN GRÁFICA DE LOS ALGORITMOS Y SU TRADUCCIÓN
A
C
Nombre del programa
BLOCK
1
2
3
3.1
3.2
3.1.1
3.1.2
4
3.3
3.3.1
3.3.2
Ilustración 1: Un ejemplo de diagrama de Tabourier
Los diagramas estructurados son herramientas que permiten representar adecuadamente
algoritmos que respetan las reglas de la programación estructurada. Aunque existen diferentes
métodos, en este libro nos centraremos en el denominado 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 conforman el algoritmo encerrados en
rectángulos y rombos. La estructura arborescente resultante se recorre en preorden, es decir, de
izquierda a derecha y de arriba a abajo, de forma que la operación situada a la derecha de la actual
se examina sólo después de haber examinado todas las que “cuelgan” de dicha operación actual.
3.2 OPERACIONES PRIMITIVAS
Las operaciones primitivas son los elementos básicos de cualquier algoritmo o programa. Éstas,
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 todas en punto y coma (;), las
instrucciones de control, no.
3.2.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.
24
3. LA REPRESENTACIÓN GRÁFICA DE LOS ALGORITMOS Y SU TRADUCCIÓN
A
C
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 conteo o de acumulación. Por ejemplo,
total ← total + 1
será una operación de conteo que incrementa en 1 el valor de la variable total, mientras que
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.
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. El primero asigna un valor a una variable, mientras que el segundo
compara dos valores para determinar si son iguales. Es habitual entre los programadores noveles
confundir ambos operadores.
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.2.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 fundamentalmente a través de la función scanf que
utilizaremos para asignar 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
control
,
&
id. variable
tipo simple
);
25
3. LA REPRESENTACIÓN GRÁFICA DE LOS ALGORITMOS Y SU TRADUCCIÓN
A
C
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
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 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, éstos 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.1. 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 la introducción desde teclado de cadenas de caracteres, emplearemos una función específica
para este propósito: la función gets. Para utilizarla se incluirá entre paréntesis el identificador de la
variable de tipo cadena a la que se asignará la cadena de caracteres introducida 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.2. Dada la variable pelicula declarada como char[60], podrá recibir una cadena desde teclado
utilizando la siguiente instrucción de entrada:
fflush(stdin); gets(pelicula);
26
3. LA REPRESENTACIÓN GRÁFICA DE LOS ALGORITMOS Y SU TRADUCCIÓN
A
C
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.2.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)
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 las
expresiones 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
%c
float, double
(notación clasica/exponencial dependiendo de la
precisión)
char
%s
char []
27
3. LA REPRESENTACIÓN GRÁFICA DE LOS ALGORITMOS Y SU TRADUCCIÓN
A
C
EJEMPLO 3.3. 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+30 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
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.4. Dadas las variables grupo, habitantes y masa del ejemplo anterior, la siguiente instrucción de
salida:
printf("Grupo=%4c\nBadajoz=%5d hab.\nMasa Sol=%13e kg.\n",grupo,habitantes,masa);
generará las siguientes tres líneas en pantalla:
Grupo=
B
Badajoz=146832 hab.
Masa Sol= 1.989100e+30 kg.
Obsérvese que el valor de grupo y de masa aparece precedido de 3 y 1 espacios para completar los 4 y 13
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).
28
3. LA REPRESENTACIÓN GRÁFICA DE LOS ALGORITMOS Y SU TRADUCCIÓN
A
C
EJEMPLO 3.5. 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+30 kg.
Número PI= 3.1416
3.3 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.
•
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.6. El siguiente programa en C permite calcular y mostrar por pantalla 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;
29
3. LA REPRESENTACIÓN GRÁFICA DE LOS ALGORITMOS Y SU TRADUCCIÓN
A
C
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.
3.4.1 ESTRUCTURA SECUENCIAL
Es aquélla en la que todas las acciones que la componen se ejecutan exactamente una vez. La
figura siguiente representa una estructura secuencial:
BLOCK
1
3
2
...
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)
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
3. LA REPRESENTACIÓN GRÁFICA DE LOS ALGORITMOS Y SU TRADUCCIÓN
A
C
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.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
lógica
acción
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
31
3. LA REPRESENTACIÓN GRÁFICA DE LOS ALGORITMOS Y SU TRADUCCIÓN
A
C
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
...
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
lógica
BLOCK
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
)
lógica
Las acciones compuestas en C emplean el diagrama sintáctico:
if
(
{
instrucción
}
instrucción
Por tanto, la estructura selectiva simple, cuando contiene una acción compuesta, utilizará el
diagrama sintáctico siguiente:
if
(
expresión
lógica
)
{
instrucción
}
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 (sólo si dsor ≠ 0): Cociente de la división. Monitor.
DISEÑO:
a) Parte declarativa:
VARIABLES
coc,ddo,dsor:real
32
3. LA REPRESENTACIÓN GRÁFICA DE LOS ALGORITMOS Y SU TRADUCCIÓN
A
C
division
BLOCK
escribir
("Dividendo
y divisor:")
leer
(ddo,dsor)
if then
dsor ≠ 0
BLOCK
coc ← ddo/dsor
escribir(coc)
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
lógica
acción 1
acción 2
33
3. LA REPRESENTACIÓN GRÁFICA DE LOS ALGORITMOS Y SU TRADUCCIÓN
A
C
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
)
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.
if
(
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
("Un nº:")
leer (n)
if then else
resto(n,2)=0
escribir
("Es par")
escribir
("Es impar")
CODIFICACIÓN:
/***************************************/
/* Muestra si un número es par o impar */
/***************************************/
#include <stdio.h>
int main()
{
int n;
}
printf("Introduzca un
scanf("%d",&n);
if (n % 2 == 0)
/*
printf("El número
else
printf("El número
return 0;
número entero: ");
un número es par si al dividir entre 2 da resto 0 */
es PAR\n");
es IMPAR\n");
34
3. LA REPRESENTACIÓN GRÁFICA DE LOS ALGORITMOS Y SU TRADUCCIÓN
A
C
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.
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
expresión
lista n
lista 2
acción 1
acción 2
default
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
(
expresión
)
{
case
expresión
:
bloque de
sentencias
break;
default
bloque de
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
35
3. LA REPRESENTACIÓN GRÁFICA DE LOS ALGORITMOS Y SU TRADUCCIÓN
A
C
duracionMes
BLOCK
escribir
("Mes:")
leer (mes)
switch
1,3,5,7,8,10,12
mes
escribir
("31 días")
4,6,9,11
default
2
escribir
("30 días")
escribir
("28 o 29 días")
escribir
("Error")
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 aquélla 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.
El conjunto de pasos que constituye la acción que se repite se denomina cuerpo del bucle. Aparte
del cuerpo del bucle, será necesario también establecer una condición de parada que determine el
número de veces que el cuerpo del bucle se ejecutará (el número de iteraciones). Esta condición se
localizará al comienzo o al final de bucle y podrá indicarse de distintos modos, dando lugar a tres
tipos distintos de estructuras repetitivas: while-do, do-while y for-do.
36
3. LA REPRESENTACIÓN GRÁFICA DE LOS ALGORITMOS Y SU TRADUCCIÓN
A
C
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
lógica
acción
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:
while
(
expresión
lógica
)
bloque de
sentencias
EJEMPLO 3.12. Sumar una serie de números no negativos introducidos por teclado hasta que se introduzca uno
negativo y mostrar el total por pantalla.
ANÁLISIS:
a) Datos de entrada:
 n[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
37
3. LA REPRESENTACIÓN GRÁFICA DE LOS ALGORITMOS Y SU TRADUCCIÓN
A
C
b) Representación algorítmica:
contar
BLOCK
tot ← 0
escribir
("Un nº:")
leer(n)
escribir
(tot)
while do
n≥0
BLOCK
tot ← tot+n
escribir
("Otro nº:")
leer(n)
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.
38
3. LA REPRESENTACIÓN GRÁFICA DE LOS ALGORITMOS Y SU TRADUCCIÓN
A
C
En C, la estructura do-while se implementa mediante el siguiente diagrama sintáctico:
expresión
bloque de
while
(
)
;
lógica
sentencias
A diferencia del resto de instrucciones de control, la instrucción do-while acaba en punto y
coma.
do
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[k]: Secuencia de valores creciente, excepto el último valor, que será menor que el anterior. Teclado.
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
("Un nº:")
leer(n)
do while
BLOCK
ant ← n
escribir
("Otro nº:")
n >= ant
leer(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;
39
3. LA REPRESENTACIÓN GRÁFICA DE LOS ALGORITMOS Y SU TRADUCCIÓN
A
C
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
representación en un diagrama de Tabourier es la siguiente:
for do
id ← Vi ,Vf ,inc
acción
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:
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 >=.
for
(
id = Vi; id <= Vf; id = id+inc
)
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
40
3. LA REPRESENTACIÓN GRÁFICA DE LOS ALGORITMOS Y SU TRADUCCIÓN
A
C
suma100
BLOCK
tot ← 0
i ← NUMMIN,
NUMMAX,1
for do
escribir
(tot)
tot ← tot+i
CODIFICACIÓN:
/*********************************************************/
/* Muestra la suma de los 100 primeros números naturales */
/*********************************************************/
#include <stdio.h>
#define NUMMIN 0
#define NUMMAX 99
int main()
{
int tot,i;
}
/* Primer número natural */
/* Centésimo número natural */
tot=0;
for (i=NUMMIN; i <= NUMMAX; i=i+1)
tot=tot+i;
printf("La suma es %d\n",tot);
return 0;
41
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). Diseñar un
algoritmo 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 sólo 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:
ln ( x ) = ( x−1) −
( x−1)2
( x−1)3
( x−1)4
( x−1)5
+
−
+
− ... ,
2
3
4
5
(0< x≤2)
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 sólo podrán emplearse las operaciones de suma y resta.
42
4
ESTRUCTURAS DE DATOS (I):
ARRAYS. CADENAS DE CARACTERES
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án dos de las estructuras de datos más habituales en
programación: los arrays y las cadenas de caracteres.
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
43
4. ESTRUCTURAS
DE
DATOS (I): ARRAYS. CADENAS DE CARACTERES
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:
habitantes
0
30
1
7
2
5
99
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:
VARIABLES
identificador
de variable
valor
entero
[
tipo
:
]
,
El valor entero representa el número de elementos del vector y, por tanto, 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:
tipo
identificador
de variable
[
valor
entero
]
;
,
44
4. ESTRUCTURAS
DE
DATOS (I): ARRAYS. CADENAS DE CARACTERES
Por ejemplo, las declaraciones del vector habitantes en algoritmia y en C emplearían la
siguiente sintaxis:
En algoritmia:
En C:
VARIABLES
habitantes[100]:entero
int habitantes[100];
En C, los conceptos de vector y puntero están relacionados. Concretamente, el identificador de
un vector, sin especificar el índice, representa la dirección de memoria donde comienza el vector (es
decir, la dirección del primer elemento del vector). Así, en el ejemplo anterior, habitantes
representa la dirección de memoria donde se almacena habitantes[0] y, por tanto,
habitantes==&habitantes[0]
o, expresado de otro modo
*habitantes==habitantes[0]==30
Es más, cuando un puntero p apunta a un elemento de un vector, puede utilizarse la sintaxis p+n
(donde n es un entero) para apuntar al elemento situado n posiciones a continuación de la apuntada
por p. Por ejemplo, dado que habitantes es un puntero que apunta al primer elemento del vector
habitantes, habitantes+2 equivale a la dirección del tercer elemento del vector habitantes. Así,
habitantes+2==&habitantes[2]
o, expresado de otro modo
*(habitantes+2)==habitantes[2]==5
EJEMPLO 4.1. A partir de las edades de 10 individuos introducidas por teclado, calcular y mostrar cuántos son
mayores y cuántos menores que la media.
ANÁLISIS:
a) Datos de entrada:
 NUMIND=10. Número de individuos. Dato fijo.
 edad[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[NUMIND]. Monitor.
 men: Número de individuos con edad inferior a la media de edad[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
45
4. ESTRUCTURAS DE DATOS (I): ARRAYS. CADENAS DE CARACTERES
b) Representación algorítmica:
contar
BLOCK
suma←0
for do med←suma/NUMIND men←0 may←0 for do
BLOCK
i ← 0,NUMIND-1,1
escribir
leer
("Edad",i+1) (edad[i])
while do
edad[i] < 0
escribir
("Error")
if then else
i ← 0,NUMIND-1,1
suma←suma+edad[i]
edad[i] < med
BLOCK
escribir
(may,men)
men←men+1
edad[i] > med
if then
may←may+1
escribir
leer
("Edad",i+1) (edad[i])
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=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;
46
4. ESTRUCTURAS DE DATOS (I): ARRAYS. CADENAS DE CARACTERES
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
unidimensional cuyos elementos son vectores. Así, por ejemplo, la representación del censo de
habitantes de 100 poblaciones en las 3 últimas décadas podría efectuarse mediante una matriz de
dimensiones 100×3, como se muestra en la siguiente figura:
Índice de década
0
1
2
31
28
30
10
8
7
...
...
...
60
90
120
habitantes
Índice de
población
0
1
...
99
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, obsérvese que 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:
VARIABLES
identificador
de variable
valor
entero
[
]
:
tipo
,
1 Veremos en el Capítulo 6 que existe una alternativa más adecuada mediante el uso de registros.
47
4. ESTRUCTURAS DE DATOS (I): ARRAYS. CADENAS DE CARACTERES
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:
tipo
identificador
de variable
valor
entero
[
]
;
,
Por ejemplo, la declaración de la matriz habitantes vista anteriormente emplearía la
siguiente sintaxis:
En algoritmia:
En C:
int habitantes[100][3];
VARIABLES
habitantes[100][3]:entero
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[NUMFIL][NUMCOL]: Matriz de números reales. Teclado.
b) Datos de salida:
 s[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 sólo 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
i←0,NUMFIL-1,1
for do
BLOCK
escribir("Fila",i+1)
j←0,NUMCOL-1,1
BLOCK
j←0,NUMCOL-1,1
for do
leer(m[i][j])
s←0
i←0,NUMFIL-1,1
for do
escribir(s)
s←s+m[i][j]
48
4. ESTRUCTURAS DE DATOS (I): ARRAYS. CADENAS DE CARACTERES
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]);
}
}
/* 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;
4.3 CADENAS DE CARACTERES
En el desarrollo de programas, a menudo es necesario tratar información alfabética, por ejemplo,
para la creación y gestión de listas de personal, inventarios, etc. El tipo de datos utilizado para
representar esta información se denomina cadena de caracteres o, simplemente, cadena.
Se llama longitud de una cadena al número de caracteres que contiene. La cadena que no
contiene ningún carácter se denomina cadena vacía o nula y su longitud es cero.
Para declarar una variable nombre de tipo cadena con una longitud máxima de 14 caracteres
significativos utilizaremos la siguiente sintaxis:
En algoritmia:
VARIABLES
nombre[15]:carácter
En C:
char nombre[15];
donde el último elemento de la cadena se reserva para el carácter especial '\0' (carácter nulo) que
representa el fin de la cadena.
49
4. ESTRUCTURAS DE DATOS (I): ARRAYS. CADENAS DE CARACTERES
4.3.1 OPERACIONES CON CADENAS
Una cadena de caracteres no es más que un vector de caracteres delimitado por el carácter nulo.
Por ello, en la mayoría de los casos, podemos operar con las cadenas del mismo modo que con
cualquier otro vector. No obstante, existe una biblioteca de funciones en C (con archivo de cabecera
string.h) que permiten manejar las cadenas de caracteres de un modo más cómodo. En esta
sección estudiaremos algunas de ellas.
ASIGNACIÓN
Al igual que con cualquier otro vector, para almacenar en una variable de tipo cadena una cadena
de caracteres, el uso del operador de asignación requeriría asignar uno a uno los caracteres que
componen la cadena al elemento correspondiente de la variable mediante el uso de una estructura
repetitiva for do. En lugar de ello, puede realizarse la misma asignación más cómodamente
utilizando la función interna strcpy. Esta función emplea dos parámetros: el primero, la variable
cadena que recibirá la asignación, y el segundo, la cadena a asignar.
Por ejemplo, dada la variable nombre declarada en la sección anterior, la instrucción de C
strcpy(nombre,"1234567890"); almacenará en dicha variable el valor "1234567890".
La función strcpy copia todos los caracteres contenidos en el segundo parámetro hasta
encontrar el carácter nulo. Por ello, el programador debe asegurarse de reservar suficiente espacio
durante la declaración de la variable que va a albergar el contenido que se le asigna. Por ejemplo, la
instrucción strcpy(nombre,"123456789012345"); no sería correcta, ya que se están
intentando asignar 16 caracteres (15 significativos más el carácter nulo) a una cadena para la que
solo se han reservado 15.
En algoritmia, nos referiremos a esta operación con el operador de asignación ←.
LECTURA Y ESCRITURA
Aunque una cadena de caracteres es un vector de caracteres y podemos hacer referencia a sus
caracteres individuales utilizando la notación de vectores, a diferencia de otros tipos de vectores,
para leer por teclado o escribir en pantalla una cadena de caracteres basta con incluir el nombre de la
variable entre los paréntesis de una instrucción gets o printf, respectivamente, tal y como se
estudió en el Capítulo 3.
COMPARACIÓN
La comparación de cadenas posee gran importancia en la ordenación de datos alfabéticos,
búsqueda de textos, etc. El criterio de clasificación se basa en el orden numérico de los caracteres
según su código de ASCII.
Dos cadenas serán iguales si contienen exactamente los mismos caracteres situados en el mismo
orden. En otro caso, el resultado de la comparación será el del primer par de caracteres distintos
situados en una misma posición. En este sentido, la presencia de cualquier carácter se considerará
siempre mayor que la ausencia de carácter.
50
4. ESTRUCTURAS DE DATOS (I): ARRAYS. CADENAS DE CARACTERES
En C, para comprobar si dos cadenas son iguales se utilizará la función interna strcmp, que
tiene como parámetros las dos cadenas de caracteres a comparar. El valor devuelto por la función
tiene el siguiente significado:
 Un valor positivo significa que la primera cadena es mayor (sucede alfabéticamente) a la
segunda.
 Un valor igual a cero significa que ambas cadenas son idénticas.
 Un valor negativo significa que la primera cadena es menor (precede alfabéticamente) a la
segunda.
Ejemplos de comparaciones son:
strcmp("norte","norte")
strcmp("ciber","ciudad")
strcmp("DATOS ","DATOS")
strcmp("lima","Lima")
strcmp("casa","casaca")
0
negativo
positivo
positivo
negativo
En algoritmia, representaremos las comparaciones entre cadenas mediante los operadores
relacionales (=, ≠, ≤, ≥, >, <).
CÁLCULO DE LA LONGITUD
Para calcular la longitud de una cadena en lenguaje C se utiliza la función interna strlen.
Como parámetro se especificará un valor de tipo cadena. Devolverá un valor entero igual a la
longitud actual de la cadena (sin contar el carácter nulo). En algoritmia nos referiremos a esta
operación con la palabra longitud.
CONCATENACIÓN
La concatenación consiste en la unión de varias subcadenas en una única cadena. En algoritmia,
utilizaremos el operador de suma (+) para representar la concatenación. Por ejemplo,
"PARA"+"BRISAS" = "PARABRISAS"
Puede observarse que se unen las cadenas sin insertar espacios en blanco entre ellas, por lo que
estos deberán incluirse explícitamente en alguna de las cadenas a concatenar si se desea que
permanezcan separadas.
En lenguaje C, la concatenación se consigue mediante la función interna strcat, que añade una
cadena al final de otra cadena. Utiliza dos parámetros: el primero, la variable cadena destino de la
concatenación, y el segundo, la cadena que se añade.
Por ejemplo, dada la variable nombre de secciones anteriores, tras las siguientes dos
instrucciones:
strcpy(nombre,"JOSE");
strcat(nombre,"MANUEL");
la variable nombre contendrá el valor "JOSEMANUEL".
51
4. ESTRUCTURAS DE DATOS (I): ARRAYS. CADENAS DE CARACTERES
BÚSQUEDA
La operación de búsqueda consiste en localizar una determinada cadena como parte de otra
cadena más grande. En C, la función que realiza este cometido es strstr, que utiliza dos
parámetros de tipo cadena: el primero, la cadena donde se buscará; el segundo, la subcadena a
localizar. El valor devuelto es un puntero a la subcadena buscada en la primera aparición dentro de
la cadena explorada. Si la subcadena no aparece en la cadena, el valor devuelto será NULL. En la
representación algorítmica se utilizará la palabra buscar.
Por ejemplo, dada la cadena quijote="En un lugar de la Mancha de..."
strstr(quijote,"de")
En un lugar de la Mancha de...
^
strstr(quijote,"e")
En un lugar de la Mancha de...
^
strstr(quijote,"arde")
devuelve el valor NULL
EJEMPLO 4.3. Desarrollar un programa que lea dos cadenas de máximo 100 caracteres y muestre el número de
veces que la segunda aparece incluida en la primera, sin contabilizar solapamientos.
ANÁLISIS:
a) Datos de entrada:
 LONMAX=100: Longitud máxima de las cadenas. Dato fijo.
 cad[LONMAX+1]: Cadena a examinar. Teclado
 sub[LONMAX+1]: Subcadena a buscar. Teclado
b) Datos de salida:
 rep: Número de veces que la subcadena sub aparece en la cadena cad (sin solapamientos). Monitor.
c) Comentarios:
 Se empleará una variable para contar el número de ocurrencias de la subcadena en la cadena.
 Debido a que la función strstr sólo busca la primera ocurrencia de una subcadena, habrá que ir
desechando la parte de la cadena ya inspeccionada. Por ello, se utilizará un puntero que apunte a cada
ocurrencia de la subcadena, lo que nos permitirá determinar qué parte de la cadena queda por explorar.
DISEÑO:
a) Parte declarativa:
CONSTANTES
LONMAX=100
VARIABLES
cad[LONMAX+1],sub[LONMAX+1],*p:carácter
rep:entero
b) Representación algorítmica:
repeticiones
BLOCK
escribir
leer
rep←0
("Cadenas:") (cad,sub)
p←buscar(cad,sub)
p ≠ NULL
while do
escribir(rep)
BLOCK
rep←rep+1 cad←p+longitud(sub)
p←buscar(cad,sub)
52
4. ESTRUCTURAS DE DATOS (I): ARRAYS. CADENAS DE CARACTERES
CODIFICACIÓN:
/***************************************/
/***
R E P E T I C I O N E S
***/
/***************************************/
#include <stdio.h>
#include <string.h>
#define LONMAX 100
/* Longitud máxima de las cadenas */
int main()
{
char cad[LONMAX+1],sub[LONMAX+1],*p;
int rep;
}
printf("Introduce la cadena a inspeccionar: ");
fflush(stdin);
gets(cad);
printf("Introduce la subcadena a buscar: ");
fflush(stdin);
gets(sub);
rep=0;
p=strstr(cad,sub);
while (p != NULL)
/* Mientras se encuentre sub en cad */
{
rep=rep+1;
strcpy(cad,p+strlen(sub));
p=strstr(cad,sub);
}
printf("La subcadena aparece %d veces en la cadena\n",rep);
return 0;
53
4. ESTRUCTURAS DE DATOS (I): ARRAYS. CADENAS DE CARACTERES
EJERCICIOS
1º) Desarrollar un programa que almacene desde teclado n números enteros en un vector e indique
por pantalla si este es capicúa, siendo n un número entre 2 y 100 determinado por el usuario. La
comprobación finalizará cuando se detecte una pareja de elementos simétricos distintos.
2º) Desarrollar un programa que almacene desde teclado 10 números enteros en un vector y a
continuación indique 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º) Diseñar un programa que lea desde teclado una matriz de 5×4 valores reales y a continuación
muestre por pantalla su traspuesta.
4º) Desarrollar un programa que lea de teclado dos matrices de valores reales A y B de dimensiones
5×4 y 4×6, respectivamente, y muestre el resultado de A×B por pantalla.
5º) Se desea realizar un programa que, leyendo desde teclado una matriz de 6×6 valores reales
positivos y dos valores a y b, muestre por pantalla la media de los elementos de la matriz que
sean mayores que a y menores que b.
6º) Desarrollar un programa que lea una frase desde teclado (con un máximo de 200 caracteres) y a
continuación muestre por pantalla el número de palabras que contiene la frase.
7º) 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.
8º) Se considera que una frase está encadenada si la letra final de cada palabra coincide con la
inicial de la siguiente. Desarrollar un programa que lea una frase desde teclado (con un máximo
de 200 caracteres) y a continuación indique si está o no encadenada, suponiendo que las
palabras están delimitadas por espacios y que la frase no contiene signos de puntuación.
Además, la frase se considerará incorrecta si comienza o finaliza por espacios, contiene
espacios consecutivos o está formada por una sola palabra.
54
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
datos
Cálculo
resultados
Visualización
resultados
55
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
datos
Datos
personales
Cálculo
resultados
Visualización
resultados
Datos
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
individuo 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 sólo 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 sólo será necesario incluirlas una vez. Por ejemplo, en el
cálculo del IRPF la lectura y validación de una fecha podría considerarse 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, sólo será necesario repetir la llamada a dicho módulo y no
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.
Por lo tanto, la metodología empleada consiste en comenzar, desde una visión general del
problema, determinando los subproblemas que la 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 de diseño descendente. Cada nivel de esta estructura representa un grado de detalle y, por
tanto, de abstracción, distinto.
56
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 esta termina, el control es devuelto al lugar del módulo
llamador desde el que el módulo 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
Llam
ada
leerDatos(...)
datosPersonales(...)
.
.
.
datosPersonales(...)
.
.
.
datosEconomicos(...)
.
.
.
.
.
.
leerFecha(...)
.
.
.
.
leerFecha(...)
.
.
.
da
ma
Lla
o
rn
to
Re
o
rn
to
Re
.
.
.
leerDatos(...)
.
.
calculoResultados(...)
.
.
verResultados(...)
.
.
leerFecha(...)
1
ada
Llam
2
a
ad
m
a
Ll
Re
to
rn
o
1
Re
torn
o2
Instrucciones
del módulo
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 las mismas.
5.2 DEFINICIÓN DE FUNCIONES
Matemáticamente, una función es una operación que toma uno o más valores llamados
argumentos o parámetros 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 cuyos parámetros son 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 los parámetros
formales. Para evaluar la función y obtener un valor concreto es necesario asociar a los parámetros
formales valores específicos que llamaremos parámetros reales. Así, por ejemplo, los parámetros
reales 3 y 4 para 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. Estas funciones se denominan 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 definidas por el programador mediante una definición de función.
La definición de una función consiste en la descripción del modo en que esta realiza su cometido.
Al igual que en el caso de un programa sin modularidad, la definición de una funció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 trate de los parámetros de la función). En ese caso, en la
especificación del dato de entrada se indicarán también las restricciones que se asume que ya
57
5. MODULARIDAD
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 formales, 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
del módulo
(
identificador
de parámetro
:
tipo
)
:
tipo
,
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 formales junto con sus tipos correspondientes (o
únicamente los paréntesis si la función carece 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
La lista de parámetros se refiere a los parámetros formales que utiliza la función. Será, por lo
tanto, 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, en C la definición de una función se sitúa 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:
tipo
identificador
de la función
(
tipo
identificador
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.
58
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:
 n: Valor del que quiere calcularse el factorial. Módulo llamador. (n ≥ 0)
b) Datos de salida:
 factorial: Factorial de n. Módulo llamador.
M. llamador
f
n
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
i ← 2, n, 1
devolver
(f)
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 parámetros reales)...
La lista de parámetros se refiere a los parámetros reales y será una lista de expresiones separadas
por comas. El número de parámetros reales incluidos en esta lista debe coincidir con el de
parámetros formales indicados en la cabecera de la función durante su definición. Además, el tipo
de cada parámetro real debe coincidir con el de su correspondiente parámetro formal o, en caso
contrario, se producirá una conversión de tipo que puede provocar pérdida de información.
59
5. MODULARIDAD
Una llamada a una función implica los siguientes pasos:
1. El control de la ejecución se transfiere del módulo llamador a la función.
2. A cada parámetro formal se le asigna el valor del parámetro real que ocupe su misma
posición en la lista de parámetros.
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 control de la 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 los módulos definidos por
el programador 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
del módulo
(
tipo
)
tipo
:
,
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:
tipo
identificador
de la función
(
tipo
)
;
,
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:
a) Datos de entrada:
 n: número total de elementos. Teclado. (n > 0)
 m: tamaño de cada grupo de elementos tomado. Teclado. (n ≥ m > 0)
M. P.
n
leerEntPos
b) Datos de salida:
 result: número de combinaciones sin repetición de n elementos tomados de m en m. Monitor.
n
f
factorial
c) Comentarios:
 Se utilizará un módulo para leer un número positivo desde teclado. Devolverá dicho número.
 Se utilizará un módulo para calcular el factorial de un número. Recibirá dicho número y devolverá el
factorial calculado.
60
5. MODULARIDAD
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
n←leerEntPos()
m←leerEntPos()
while do
m>n
result←factorial(n) /
(factorial(m)factorial(n-m))
escribir
(result)
BLOCK
escribir
("Error")
m←leerEntPos()
CODIFICACIÓN:
/***********************************/
/***
C O M B I N A T O R I O
***/
/***********************************/
#include <stdio.h>
/* Prototipos de funciones invocadas desde el programa principal */
int leerEntPos();
double factorial(int);
/* Programa principal */
int main()
{
int n,m;
double result;
}
printf("Introduce un número: ");
n=leerEntPos();
printf("Introduce otro número: ");
m=leerEntPos();
while (m > n)
{
printf("Error, el segundo valor debe ser menor que %d: ",n);
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)
61
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 parámetros)
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
indicado como parámetro.
ANÁLISIS:
a) Datos de entrada:
 mult: valor del que se mostrará la tabla de multiplicar. Módulo llamador.
b) Datos de salida:
 tabla[10]: Valores de la tabla de multiplicar de mult. Monitor
M. llamador
mult
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
(“Tabla”,mult)
i ← 1, 10, 1
for do
escribir
(i,mult,imult)
62
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 enviar valores desde el módulo llamador al
módulo llamado.
Este modo de transferir información entre módulos mediante parámetros se denomina pase de
parámetros por valor y consiste en copiar el valor del parámetro real en el correspondiente
parámetro formal (los parámetros real y formal representan direcciones de memoria distintas). Por lo
tanto, el pase de un parámetro 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 formales dentro del módulo llamado no quedan reflejadas en los
parámetros reales especificados en la invocación. Así, aunque la función factorial del Ejemplo 5.1
se hubiera definido de forma que el parámetro formal 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 formal n dentro de la función no afectan al parámetro real
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 parámetros 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 parámetro real (utilizando el operador de dirección, &), en lugar de transferir una copia de su
63
5. MODULARIDAD
valor (los parámetros real y formal representarán la misma dirección de memoria). Así, a diferencia
de lo que ocurre con el pase de parámetros por valor, las modificaciones realizadas sobre un
parámetro formal pasado por referencia también se efectúan sobre su correspondiente parámetro
real, quedando así reflejadas en el módulo llamador. Un módulo puede pasar por referencia tantos
parámetros como se quiera, ampliando así el número de valores que el módulo puede «devolver» al
módulo llamador.1
Obsérvese que un parámetro pasado por referencia siempre actuará, al menos, como dato de
salida del módulo (a veces, podrá actuar también como dato de entrada). En el caso de que el dato
fuera sólo de entrada al módulo, el parámetro deberá pasarse por valor.
EJEMPLO 5.4. Desarrollar un módulo que intercambie en el módulo llamador el valor de dos variables enteras.
ANÁLISIS:
a) Datos de entrada:
 x: valor a intercambiar. Módulo llamador.
 y: valor a intercambiar. Módulo llamador.
M. llamador
x,y
b) Datos de salida:
 x': valor intercambiado (la y original). Módulo llamador.
 y': valor intercambiado (la x original). Módulo llamador.
intercambio
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:
intercambio(*x:entero, *y:entero)
VARIABLES
aux:entero
b) Representación algorítmica:
intercambio(x,y)
BLOCK
aux←x
x←y
y←aux
CODIFICACIÓN:
/* intercambio(x,y)
/* Intercambia los valores de x e y
void intercambio(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 parámetros 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.
64
5. MODULARIDAD
5.6 ARRAYS COMO PARÁMETROS
Como se estudió en el tema anterior, en lenguaje C, el identificador de un array es un puntero al
primer elemento del array. Por ello, al pasar un array a un módulo como parámetro, realmente
estaremos pasando la dirección del array y no su contenido. En otras palabras, cuando se trate de
arrays, el pase de parámetros 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 formal siempre se
reflejarán en el correspondiente parámetro real del módulo llamador.
En la declaración de parámetros formales de tipo array se indicará, 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, un posible prototipo de función sería:
En algoritmia:
En C:
leerMatriz([][5]:entero)
void leerMatriz(int [][5]);
mientras que en la cabecera de la función sería:
En algoritmia:
En C:
leerMatriz(m[][5]:entero)
void leerMatriz(int m[][5])
65
5. MODULARIDAD
EJERCICIOS
1º) 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
2º) 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.
3º) 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)
4º) El desarrollo de la serie de Maclaurin para la función seno es
sen  x = x −
x3
x5
x7
x9

−

−...
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).
5º) Escribir un programa que lea números enteros de 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.
Nota: La función interna random(x) genera un número aleatorio en el intervalo [0,x-1],
donde el parámetro x y el valor devuelto son de tipo entero. Antes de utilizar esta función en el
programa es necesario invocar a la función randomize() que inicializa el generador de
números aleatorios.
6º) 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
66
5. MODULARIDAD
teclado, determine la siguiente información:
a)
b)
c)
d)
e)
f)
Número de orden de cada cuota mensual.
El importe aplicado cada mes a la reducción del capital pendiente.
Intereses pagados cada mes.
Capital pendiente al final de cada mes.
Intereses acumulados al final de cada mes.
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
7º) 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
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.
8º) ¿Quién tiene menos?" es un juego de estrategia para dos jugadores en el que cada jugador
piensa un número 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 y actualizando a
67
5. MODULARIDAD
continuación el marcador. 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
Marcador: HUMANO 3 - MAQUINA 5
RONDA 2:
Introduce tu jugada ([1,5]): 2
Humano: 2, Máquina: 2. Los números
Marcador: HUMANO 5 - MAQUINA 7
...
RONDA 9:
Introduce tu jugada ([1,5]): 3
Humano: 3, Máquina: 5. Difieren en
Marcador: HUMANO 28 - MAQUINA 24
RONDA 10:
Introduce tu jugada ([1,5]): 3
Humano: 3, Máquina: 4. Difieren en
Marcador: HUMANO 35 - MAQUINA 24
Partida finalizada.
¡Enhorabuena!, has ganado.
más de una unidad.
coinciden.
más de una unidad.
una unidad.
Nota: Las jugadas del ordenador se determinarán de forma aleatoria empleando la función
interna random(x) explicada en el Ejercicio 5.
9º) 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

=
4
n
1
∑ −1i1 2 i−1
i=1
y la de Wallis por

=
4
donde
n
u
∏ vi
i=1
i
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
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.
10º) Desarrollar un módulo que, recibiendo del módulo llamador un vector de enteros y su tamaño,
devuelva si el vector está o no ordenado crecientemente.
11º) Desarrollar un módulo que ordene ascendentemente y devuelva un vector de enteros recibido
del módulo llamador.
12º) “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.
68
5. MODULARIDAD
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).
13º) 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 entero v; las
diagonales adyacentes a la diagonal principal, un valor superior en una unidad a v; las
diagonales aún sin rellenar adyacentes a las dos anteriores, un valor dos unidades mayor que 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á 25×25.
14º) 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.
69
6
ESTRUCTURAS DE DATOS (II):
REGISTROS
En el Capítulo 4 conocimos el concepto de array, una de las estructuras de datos estáticas más
habituales. Sin embargo, esta estructura tienen la limitación de permitir agrupar solamente datos de
un mismo tipo. En este capítulo trataremos otra estructura de datos estática, los registros, que nos
permitirán representar colecciones formadas por datos de distintos tipos.
6.1 REGISTROS
Los arrays nos permiten agrupar datos con la condición de que sean todos de un mismo tipo.
Existe un tipo de datos compuesto denominado registro que se puede utilizar para agrupar
información relacionada entre sí pero formada por datos de tipos distintos. Un tipo registro está
definido por un conjunto de componentes, denominados campos, que establecen su estructura. Cada
dato de tipo registro podrá tener ciertos valores asignados a sus campos.
Para trabajar con registros, es necesario en primer lugar definir la estructura de campos del tipo
registro. Para ello, definiremos un nuevo tipo con el que posteriormente podremos declarar variables
de dicho tipo.
En la fase de diseño, la definición de tipos se situará en una sección específica de la parte
declarativa, que titularemos TIPOS e irá situada inmediatamente después de la sección
CONSTANTES. En concreto, la definición de un tipo registro utilizará el siguiente diagrama
sintáctico:
identificador
de tipo registro
=
identificador
de campo
:
tipo
,
70
6. ESTRUCTURAS DE DATOS (II): REGISTROS
En lenguaje C, el tipo registro se representa con el tipo estructura, cuya definición se sitúa
inmediatamente después de la definición de constantes y obedece al siguiente diagrama sintáctico:
typedef struct
{
identificador
de campo
tipo
;
}
,
identificador
de tipo registro
;
Por ejemplo, para representar la siguiente información de un alumno:
alumno
id
5
nombre Antonio González
grupo
B
nota
6.125
puede definirse un tipo registro de la siguiente forma:
En algoritmia:
En C:
TIPOS
tipoAlumno = id:entero
nombre[35]:carácter
grupo:carácter
nota:real
typedef struct
{
int id;
char nombre[35];
char grupo;
double nota;
} tipoAlumno;
Para utilizar un tipo registro, será necesario declarar variables de dicho tipo:
En algoritmia:
En C:
VARIABLES
alumno: tipoAlumno
tipoAlumno alumno;
Ahora bien, un registro contiene varios campos de información, por lo que para acceder a ellos
de forma independiente será necesario especificar el campo que nos interesa. En concreto, deberá
indicarse el identificador de la variable registro que lo contiene seguido de un punto (operador
denominado descriptor de campo) y del identificador de campo (por ejemplo, alumno.id,
alumno.nombre, alumno.grupo o alumno.nota). Un campo así especificado puede
tratarse como si fuera una variable del mismo tipo que el campo y, por tanto, se le podrán aplicar las
operaciones permitidas sobre variables de ese mismo tipo (por ejemplo, podrán realizarse
operaciones aritméticas sobre alumno.nota).
En C, el descriptor de campo es un operador con mayor prioridad que ningún otro (incluidos
todos los operadores unarios estudiados hasta el momento).
A diferencia de lo que ocurre con arrays, en C todos los valores de una variable registro pueden
asignarse a otra variable registro en una sola instrucción mediante el operador de asignación:
tipoAlumno alumno1,alumno2;
(...)
alumno2=alumno1;
71
6. ESTRUCTURAS DE DATOS (II): REGISTROS
También, a diferencia de los arrays, en C un registro puede pasarse por valor así como devolverse
asociado al nombre de la función. Por ejemplo, los siguientes prototipos de funciones son válidos:
tipoAlumno leerAlumno();
void mostrarAlumno(tipoAlumno alumno);
Por otro lado, al igual que en el tipo registro tipoAlumno uno de los campos era de tipo cadena,
es posible que otros campos sean de cualquier otro tipo de datos compuesto. Por ejemplo, podrían
representarse en el registro las calificaciones del alumno en 10 asignaturas distintas
alumno
id
nombre
grupo
notas
0123456789
utilizando un campo de tipo vector para las calificaciones, del siguiente modo:
typedef struct
{
int id;
char nombre[35];
char grupo;
double notas[10];
} tipoAlumno;
(...)
tipoAlumno alumno;
Para referirnos a la calificación del alumno en la tercera asignatura utilizaremos la sintaxis
alumno.notas[2].
Del mismo modo, a menudo es necesario representar en un programa, no un dato de tipo registro,
sino una colección de datos de tipo registro. Esto se puede implementar utilizando arrays cuyos
elementos son registros. Por ejemplo, lo más probable es que un programa requiera procesar la
información de un conjunto de alumnos y no de un solo alumno. Así, la información de los alumnos
matriculados en 3 titulaciones distintas, con 200 alumnos por titulación, puede representarse con la
siguiente matriz de registros:
0
1
id
nombre
grupo
notas
id
nombre
grupo
notas
id
nombre
grupo
notas
01234567890123456789
199
...
0
...
1
...
2
0123456789
72
6. ESTRUCTURAS DE DATOS (II): REGISTROS
En lenguaje C, la definición de esta estructura de datos sería:
typedef struct
{
int id;
char nombre[35];
char grupo;
double notas[10];
} tipoAlumno;
(...)
tipoAlumno alumnos[3][200];
Para referirnos al grupo del vigésimo tercer alumno de la segunda titulación emplearemos la
sintaxis alumnos[1][22].grupo, mientras que la sintaxis alumnos[0][33].notas[5]
haría referencia a la sexta nota de trigésimo cuarto alumno de la primera titulación.
EJEMPLO 6.1. Desarrollar un módulo que, recibiendo del módulo llamador un vector de registros de 'np'
poblaciones, con el nombre de cada población y su censo (número de habitantes en unidades de millar) durante 'nd'
décadas, muestre por pantalla, para cada población, su nombre y el número de orden de la década con mayor número
de habitantes (si hay varias décadas con el mayor número de habitantes, deberá mostrar el número de orden de la
primera de ellas). El número máximo de décadas es 10.
M. llamador
np, nd,
lPob[np].nom,
lPob[np].hab[nd]
mayorCenso
MÓDULO mayorCenso:
1º) ANÁLISIS:
a) Datos de entrada:
 np: Número de poblaciones. Módulo llamador (np > 0)
 nd: Número de décadas. Módulo llamador (nd > 0)
 lPob[np].nom: Nombre de cada población. Módulo llamador.
 lPob[np].hab[nd]: Miles de habitantes de cada población en cada década. Módulo llamador.
b) Datos de salida:
 lPob[np].nom: Monitor.
 decada[np]: Número de orden de la década con mayor censo de cada población. Monitor.
c) Comentarios:
 Se supone que en algún módulo ascendiente se ha definido lo siguiente:
CONSTANTES
MAXDEC=10
TIPOS
tipoPob= nom[40]:carácter
hab[MAXDEC]:reales
 Se utilizará una variable índice para recorrer la lista de poblaciones y otra para recorrer los censos de
cada población en busca del mayor censo.
 Se utilizará una variable para almacenar el número de orden de la década a la que pertenece el censo
mayor de cada población durante cada búsqueda.
2º) DISEÑO:
a) Parte declarativa:
mayorCenso(lPob[]:tipoPob, np:entero, nd:entero)
VARIABLES
ip,id,iMax:entero
73
6. ESTRUCTURAS DE DATOS (II): REGISTROS
b) Representación algorítmica:
mayorCenso(lPob,np,nd)
BLOCK
for do
ip ← 0, np-1, 1
iMax←0
id ← 1, nd-1, 1
lPob[ip].hab[id] >
lPob[ip].hab[iMax]
BLOCK
for do
escribir
(lPob[ip].nom,iMax+1)
if then
iMax←id
3º) CODIFICACIÓN:
#define MAXDEC 10
/* Número máximo de décadas */
typedef struct
{
char nom[40];
double hab[MAXDEC];
} tipoPob;
(...)
/* mayorCenso(listaPob,np,nd)
*/
/* Muestra, para cada población, su nombre y la década */
/* con mayor censo.
*/
void mayorCenso(tipoPob listaPob[], int np, int nd)
{
int ip,id,iMax;
}
for (ip=0; ip<=np-1; ip=ip+1)
{
iMax=0;
for (id=1; id<=nd-1; id=id+1)
if (listaPob[ip].hab[id] > listaPob[ip].hab[iMax])
iMax=id;
printf("La población %s tiene su mayor censo en la década %d\n",
listaPob[ip].nom,iMax+1);
}
(...)
74
6. ESTRUCTURAS DE DATOS (II): REGISTROS
EJERCICIOS
1º) El juego “Cricket simplificado” es un juego de dardos en el que sólo se consideran los sectores
de la diana del 15 al 20. Un sector se considera cerrado cuando el mismo jugador acierta en ese
sector tres veces. Una vez que un jugador ha cerrado un sector, cada vez que vuelva a acertar en
ese sector, se le sumará al resto de jugadores que no lo hayan cerrado una puntuación igual a su
valor (por ejemplo, si un jugador ha cerrado el sector 15, cada vez que acierte sobre el 15 se le
sumarán 15 puntos al resto de jugadores que no lo hayan cerrado).
Desarrollar un módulo que procese y devuelva el efecto sobre el marcador de una tirada en
cualquier momento de la partida. Para ello, recibiendo la información referente al estado del
marcador antes de esa tirada (numero de jugadores y puntuación y estado de los sectores para
cada jugador) y la tirada a procesar (jugador que ha lanzado y sector acertado), deberá devolver
el nuevo contenido del marcador. Una tirada sólo alterará el marcador cuando se acierte sobre
alguno de los números del 15 al 20.
2º) En el juego de los barcos dos jugadores sitúan barcos de distintas longitudes (2, 3 ó 4 casillas)
en sendos tableros de 9×9 casillas. A continuación los jugadores realizan alternativamente
jugadas, consistente cada una en indicar una casilla (fila y columna) del tablero oponente y
cuyo resultado puede ser agua, tocado o hundido dependiendo de si la casilla no contiene
ninguna porción de barco, contiene una porción de un barco que dispone de otras porciones aún
sin destruir o contiene la última porción aún no destruida de un barco, respectivamente.
Para desarrollar este juego con un programa informático se empleará una representación de
tablero de tal forma que cada casilla represente la siguiente información (con el tipo de datos
más adecuado en cada caso): indicador de si la casilla está o no ocupada por una porción de
barco, tamaño del barco del que forma parte la casilla, dirección en la que está situado el barco
(horizontal o vertical), posición que ocupa la porción respecto al barco completo (enumeradas
las posiciones de izquierda a derecha para la dirección horizontal y de arriba a abajo para la
vertical) e indicador de si la porción está destruida o no. Si la casilla no está ocupada, el resto
de información se considerará indeterminada (podrá contener cualquier valor pues el programa
ignorará dicha información).
Desarrollar el módulo más adecuado que recibiendo del módulo llamador un tablero y una
jugada, devuelva el tablero actualizado y el resultado de la jugada (agua, tocado o hundido).
3º) Se desea desarrollar un programa de cálculo de impuestos con las siguiente opciones de menú:
1. Añadir la información de cada unidad familiar
2. Listar la información de cada unidad familiar
3. Eliminar un miembro de una de las unidades familiares
4. Calcular y mostrar los impuestos de cada unidad familiar.
5. Salir del programa.
La información de cada unidad familiar consistirá en el apellido (10 caracteres), nombre (10
caracteres) e ingresos de su miembros, donde se supondrá que dos personas consecutivas con el
mismo apellido pertenecen a la misma unidad familiar.
El impuesto se calcula como sigue:
impuestos = porcentaje * ingresoAjustado
75
6. ESTRUCTURAS DE DATOS (II): REGISTROS
donde
{
ingresoAjustado
si ingresoAjustado  30.000
porcentaje =
120.000
0.25
en otro caso
ingresoAjustado = ingresosTotales – (1200 * númeroDeducciones)
y el númeroDeducciones es igual al número de personas de la unidad familiar sin ingresos.
Cuando ingresoAjustado sea negativo, los impuestos serán 0 euros.
El proceso de entrada de datos finalizará cuando se indique como apellido XXXX. El listado
de impuestos consistirá en un listado en pantalla donde, en cada línea, deberá mostrarse el
apellido de una familia y su impuesto calculado. Como máximo, podrán tratarse 100 personas
en cada ejecución del programa. Por ejemplo, dada la entrada siguiente,
Apellidos: Castilla
Nombre: Rafael
Ingresos: 10150
Apellidos: Castilla
Nombre: María
Ingresos: 13820
Apellidos: Castilla
Nombre: Francisco
Ingresos: 0
Apellidos: Pérez
Nombre: Roberto
Ingresos: 39350
Apellidos: Pérez
Nombre: María
Ingresos: 7210
Apellidos: XXXX
el listado de impuestos sería:
FAMILIA
Castilla
Pérez
IMPUESTOS
4320.61
11640.00
4º) El sistema de mensajes cortos (SMS) permite enviar desde un teléfono móvil mensajes de texto
con un máximo de 160 caracteres. Debido a su limitada extensión, es habitual el uso de
abreviaturas que reducen el texto considerablemente. Desarrollar un programa que, empleando
la tabla de sustituciones que se indica a continuación, abrevie un texto introducido desde
teclado por el usuario y lo muestre en pantalla.
TABLA DE SUSTITUCIONES
UNO
UN
UNA
DOS
TRES
CUATRO
CINCO
SEIS
SIETE
OCHO
NUEVE
BE
1
1
1
2
3
4
5
6
7
8
9
B
VE
CE
DE
EFE
GE
KA
CA
ELE
EME
ENE
EÑE
PE
B
C
D
F
G
K
K
L
M
N
Ñ
P
CU
ERE
ESE
TE
UVE
UBE
ZETA
CETA
POR
MAS
MENOS
Q
R
S
T
V
B
Z
Z
X
+
-
76
Descargar