tema 1. introducción a la...

51
ESTRUCTURAS DE DATOS INTRODUCCIÓN A LA PROGRAMACIÓN 1 TEMA 1. Introducción a la programación. 1.1. CONCEPTOS BÁSICOS. Un programa es un conjunto ordenado de instrucciones, que se aloja en la memoria del ordenador. Estas instrucciones operan sobre datos, normalmente (aunque no necesariamente) procedentes del mundo exterior a dicha memoria: usuarios, dispositivos de almacenamiento (electrónicos, magnéticos, ópticos, etc), redes de comunicaciones, etc, para producir resultados tanto en forma de acciones como de nuevos datos (información). Figura 1.1. Esquema de funcionamiento de un programa. Adoptamos la definición de Wirth (1985) para programa como “conjunto de algoritmo más estructuras de datos”. La estricta materialización del esquema anterior implicaría que el programa debería estar escrito utilizando un lenguaje binario (de máquina): tanto el algoritmo (haciendo referencia al repertorio de instrucciones -instruction set- del ordenador, concretamente de su CPU) como los datos (adecuadamente codificados). Salvo en una efímera temporada, coincidente con la aparición de los primeros ordenadores (1944), apenas se ha utilizado esta modalidad de programación como Información Acciones Datos Resultados ORDENADOR (memoria) PROGRAMA

Upload: lydien

Post on 05-Oct-2018

218 views

Category:

Documents


0 download

TRANSCRIPT

ESTRUCTURAS DE DATOS INTRODUCCIÓN A LA PROGRAMACIÓN 1

TEMA 1.

Introducción a la programación.

1.1. CONCEPTOS BÁSICOS.

Un programa es un conjunto ordenado de instrucciones, que se aloja en la memoria

del ordenador. Estas instrucciones operan sobre datos, normalmente (aunque no

necesariamente) procedentes del mundo exterior a dicha memoria: usuarios, dispositivos de

almacenamiento (electrónicos, magnéticos, ópticos, etc), redes de comunicaciones, etc, para

producir resultados tanto en forma de acciones como de nuevos datos (información).

Figura 1.1. Esquema de funcionamiento de un programa.

Adoptamos la definición de Wirth (1985) para programa como “conjunto de

algoritmo más estructuras de datos”.

La estricta materialización del esquema anterior implicaría que el programa debería

estar escrito utilizando un lenguaje binario (de máquina): tanto el algoritmo (haciendo

referencia al repertorio de instrucciones -instruction set- del ordenador, concretamente de

su CPU) como los datos (adecuadamente codificados).

Salvo en una efímera temporada, coincidente con la aparición de los primeros

ordenadores (1944), apenas se ha utilizado esta modalidad de programación como

Información

Acciones

Datos Resultados ORDENADOR (memoria)

PROGRAMA

2 INTRODUCCIÓN A LA PROGRAMACIÓN ESTRUCTURAS DE DATOS

consecuencia de su extrema dificultad y escasa productividad para el desarrollo de

software1.

La alternativa por la que se optó fue escribir programas en un lenguaje de más alto

nivel (lenguaje de programación2). Uno de estos lenguajes, de gran popularidad

actualmente, es el Java.

La programación consiste en la actividad de escribir programas utilizando para ello

“lenguajes” de programación adecuados.

Para que un programa escrito (codificado) en un lenguaje de programación

(programa fuente) pueda ejecutarse en un ordenador es necesario traducirlo a su lenguaje

(de máquina). Para ello hay que utilizar los programas adecuados, denominados

genéricamente traductores.

La figura siguiente muestra un esquema de funcionamiento3:

Figura 1.2. Esquema de funcionamiento de un traductor.

La figura anterior se corresponde con una de las dos filosofías4 en la que se basan los

traductores: la compilación. Mediante ella se genera un código denominado objeto, que,

una vez cargado (load) en la memoria del ordenador, se puede ejecutar. La compilación

solo se realiza una vez (siempre que no se hagan cambios en el programa). El resultado

(programa ejecutable) puede guardarse en un dispositivo de almacenamiento externo

(disco, CD, etc.) y desde él cargarse en memoria y ejecutarse en futuras ocasiones.

La otra alternativa, denominada interpretación (que no genera código objeto),

funciona de manera que el programa traductor traduce y ejecuta las líneas del código fuente

de una en una.

El Java es un lenguaje mixto: primero se compila (por medio del compilador Javac,

produciendo ficheros con extensión .class), y a continuación, ese código se interpreta en el

1 Por el contrario, sería la técnica más eficiente (mayor velocidad de ejecución y menor consumo de

memoria). No obstante, y como consecuencia de la incesante mejora y abaratamiento de la tecnología, esta

alternativa no ha sido nunca tomada en consideración. 2 Difícilmente se podrá encontrar otra actividad más “viva” que el desarrollo de lenguajes de programación:

en tan solo cincuenta años de existencia de los ordenadores y de la Informática algunos autores llegan a

identificar en torno a medio millar de ellos. Se puede encontrar una referencia en:

http://www.lenguajesdeprogramacion.com/ 3 Se trata de un modelo muy elemental. La realidad es algo más compleja.

4 Existen otras soluciones intermedias.

Programa fuente

Ordenador

TRADUCTOR

Programa ejecutable

ESTRUCTURAS DE DATOS INTRODUCCIÓN A LA PROGRAMACIÓN 3

ordenador en el que se va a ejecutar el código (por medio de la JVM, Java Virtual

Machine).

1.1.1. Características específicas del lenguaje Java.

1.1.1.1. Clases y objetos.

Una clase es una agrupación de variables miembro (o campos) y métodos miembro

que operan sobre dichos datos y permiten comunicarse con otras clases: una clase puede ser

equivalente a un tipo de datos creado por el usuario junto las operaciones que se pueden

realizar sobre dicho tipo de datos. Una variable de dicho tipo de datos es un objeto o

instancia de la clase.

A los campos de un objeto se les denomina variables miembros de objeto, y a todos

aquellos métodos que se definen dentro de la clase se les denomina métodos miembro.

Para lograr el encapsulamiento en Java, se utiliza un tipo especial de clase: el interfaz

(interface). Una interfaz es un conjunto de declaraciones de funciones. Si una clase

implementa (implements) una interfaz, debe definir todas las funciones especificadas por la

interfaz. Una clase puede implementar más de una interfaz.

1.1.1.2. Métodos.

Dentro de una clase en la que se define una estructura de datos, se pueden definir

métodos de objeto, que se aplican a una variable de la clase poniendo el nombre de la

variable y luego el nombre del método, separados por un punto.

Además de definir métodos de objeto, dentro de la clase se pueden crear métodos con

el modificador static: son métodos de clase, que se pueden utilizar aunque no se haya

creado ningún objeto de la clase (al contrario de lo que ocurre con los métodos de objeto: si

se intentan invocar sin haber creado previamente el objeto se produce un error). Los

métodos static se invocan desde fuera de la clase poniendo el nombre de la clase y luego el

nombre del método, separados por un punto.

1.1.1.3. Modificadores.

Las variables miembro pueden ir precedidas en su declaración por uno de los

modificadores de acceso: public, private, protected y package (este último es el valor por

defecto y se puede omitir). A su vez las clases pueden ser public (accesibles desde

cualquier otra clase, siempre que el paquete esté en un directorio accesible) o package (por

defecto, accesible solo para el resto de las clases del paquete).

El significado de los modificadores de acceso (para variables miembro y métodos

miembro) es el siguiente:

o private: solo son accesibles dentro de la propia clase.

o package: tienen acceso todas las clases del resto del paquete.

4 INTRODUCCIÓN A LA PROGRAMACIÓN ESTRUCTURAS DE DATOS

o protected: tienen acceso las clases del resto del paquete, así como desde

subclases definidas fuera del paquete.

o public: se puede acceder desde cualquier otra clase.

1.1.1.4. Excepciones.

Las excepciones son elementos que almacenan información para detectar hechos

excepcionales, como por ejemplo errores. En Java existen muchos tipos de excepciones

estándar y las más habituales son del tipo IOException (errores de lectura), así como del

tipo NumberFormatException (que se genera cuando se espera leer un número y se recibe

algo diferente).

1.1.2. Entorno de programación (IDE).

Para realizar la actividad de programación se requiere, además del correspondiente

programa traductor (compilador o intérprete), un conjunto de herramientas (software

development tools) que proporcionan funcionalidades tales como escribir / modificar

(editar) el código fuente, facilitar la puesta a punto de programas (debugger), cargar

(load), montar (link) y ejecutar (run) programas, gestionar ficheros con código fuente y

ejecutable, etc. Normalmente estas herramientas se ofrecen en forma de paquete integrado

(Integrated Development Environment –IDE-). Uno de ellos, de libre distribución para

programación en Java es Eclipse (http://www.eclipse.org, donde además de descargar la

aplicación, se pueden encontrar distintos recursos –como por ejemplo tutoriales– para

aprender el manejo del entorno).

Figura 1.3. Entorno de programación Eclipse.

ESTRUCTURAS DE DATOS INTRODUCCIÓN A LA PROGRAMACIÓN 5

1.2. SINTAXIS DEL LENGUAJE JAVA.

Java es un representante de la familia de los lenguajes orientados a objetos.

1.2.1. Estructura de un programa en Java.

A continuación se muestra un incipiente programa en Java sobre el que se indican los

aspectos más importantes.

Código Explicación

//Primer programa en Java Línea de comentario.

public class hola_EUI Se indica el nombre del programa.

{ Delimitador de inicio del programa.

public static void main (String [ ] args) Clase principal

{ Delimitador de inicio de la clase

System.out.println ("Hola EUI");

Ejemplo de acción: sentencia println (escribir en el dispositivo

de salida –system.out– y avanzar al comienzo de la línea

siguiente) que actúa sobre un sujeto (dato): la cadena de

caracteres Hola EUI.

} Delimitador de fin de clase.

} Delimitador de fin de programa.

Tabla 1.1. Estructura básica de un programa en Java.

La estructura general de un programa en Java es la siguiente5:

Sintaxis Semántica

import <nombre>; Llamada a una (o varias) componente(s) de la “biblioteca”.

public class <nombre de la clase general> Nombre de la clase general (obligatorio)

{ Principio de la clase.

<constantes y variables globales>

<métodos6>

public static void main (String [ ] args) Declaración del programa principal

{ Inicio del programa principal

<cuerpo del programa principal>

} Fin del programa principal.

} Fin de la clase.

Tabla 1.2. Estructura general de un programa en Java.

La sintaxis del lenguaje Java es de formato libre7. Para identificar sus partes se

utilizan delimitadores. El delimitador más importante es el punto y coma (;), que se utiliza

como fin de instrucción. Así mismo las llaves ({ }), se utilizan para indicar el inicio y final

5 Este esquema se ampliará en el punto de Programación modular (1.3)

6 Devuelve/n uno o ningún (void) valor/es. En Java no existe la posibilidad de devolver varios valores.

7 La otra alternativa (utilizada por lenguajes tales como COBOL o FORTRAN) se basa en considerar líneas

de código de una longitud determinada estructuradas en campos, cada uno de los cuales tiene un significado

predeterminado.

6 INTRODUCCIÓN A LA PROGRAMACIÓN ESTRUCTURAS DE DATOS

de bloques de instrucciones o del programa. Estos (u otros) delimitadores pueden tener un

significado particular en diferentes contextos.

Generalmente una aplicación se desarrolla en varios ficheros, que se agrupan en

packages, que son librerías de clases. Cada uno de los ficheros que la componen debe tener

la extensión .java, y la aplicación se ejecutará por medio de la clase que contenga la

función main.

1.2.2. Nomenclatura habitual en Java.

Los nombres en Java son sensibles a las mayúsculas y las minúsculas (por ejemplo,

cadena sería una variable distinta de Cadena). Habitualmente en Java se escribe con

minúsculas, con las siguientes excepciones:

o Cuando un nombre consta de varias palabras, se escriben seguidas, comenzando

en minúsculas, excepto la primera letra de cada palabra a partir de la segunda (por

ejemplo presuntoDivisor, datoMayor, etc.).

o Los nombres de las clases e interfaces empiezan con mayúscula (por ejemplo

Pila, Lista, Arbol).

o Los nombres de objetos, variables y métodos empiezan por minúscula (por

ejemplo buscarMayor, maximaDiferencia, etc.).

o Los nombres de las constantes (o variables finales) se escriben en mayúsculas

(por ejemplo PI).

1.2.3. Los datos.

Como ya se ha indicado, los datos son los sujetos del tratamiento. Los datos, en un

programa se pueden presentar en tres modalidades:

o En forma literal. Es decir, indicando de forma explícita el propio dato. Por

ejemplo, la cadena de caracteres “Hola EUI” en el ejemplo anterior.

o Mediante un identificador de constante. Si se ha dado un nombre simbólico al

valor de un dato (por ejemplo: PI = 3.14), el programa puede hacer referencia a él

mediante el identificador asociado.

o Mediante un identificador de variable. Se trata de un caso parecido al de

constantes, con la importante diferencia que el valor asociado al identificador de

la variable (en adelante, simplemente, variable) puede cambiar durante la

ejecución del programa.

ESTRUCTURAS DE DATOS INTRODUCCIÓN A LA PROGRAMACIÓN 7

1.2.3.1. Tipos de datos.

El lenguaje Java reconoce de forma predefinida los siguientes tipos8 de datos:

Numéricos:

o Enteros (int). En el rango, aproximadamente, ± 2*109. Además existen otros tipos

de datos enteros: byte, short y long.

o Racionales o fraccionarios (float). Aproximadamente en el rango ± 3.4*1038

. La

parte real y la fraccionaria se separan mediante un punto (.). También se pueden

utilizar números reales con mayor precisión por medio del tipo double

(aproximadamente en el rango ± 1.8*10308)

.

Lógicos (boolean). Se refieren a los valores utilizados en el Álgebra de Boole

(verdadero: true o falso: false).

Carácter (char). Se utiliza para representar todos los caracteres del estándar Unicode

(que incluye el ASCII).

Cadena de caracteres (String). Este tipo de datos consiste en una secuencia de

caracteres (por ejemplo “El cielo está enladrillado”).

1.2.3.2. Tratamiento de datos.

1.2.3.2.1. Operaciones.

Una operación es un proceso que actúa sobre dos datos (operandos) y produce, en

consecuencia, un resultado. Se identifica mediante un símbolo (o palabra reservada)

denominado operador. Excepcionalmente se pueden presentar operaciones (unarias) que

implican un solo operando y/u operaciones con datos de diferentes tipos9. Normalmente

(pero no siempre) se utilizan dos operandos del mismo tipo que, además, es el tipo del

resultado.

Como consecuencia de la naturaleza (tipo) de un dato, tendrá sentido realizar cierto

tipo de operaciones con él. La tabla siguiente muestra las más habituales:

8 El concepto tipo de datos se asocia implícitamente al los datos elementales (simples). Más adelante

(apartado 1.4) se contemplará la posibilidad de agrupar datos simples en unidades de orden superior para dar

lugar a diferentes estructuras de datos. 9 Las “operaciones” con más de dos operandos las consideramos dentro del epígrafe de expresiones.

8 INTRODUCCIÓN A LA PROGRAMACIÓN ESTRUCTURAS DE DATOS

Tipo Operación Operador Ejemplo

Operación Resultado

Entero (int) Suma + 3 + 5 8

Resta - 1 - 3 -2

Multiplicación * 2 * (-5) -10

Cociente / 7 / 3 2

Resto % 7 % 3 1

Real (float) Suma + 1,23 + 8,77 10,0

Resta - 1,23 - 8,77 -7,54

Multiplicación * -3,2 * 4,6 -14,72

División / 3,2 / (-4,6) -0,6956

Lógico (boolean) Negación ! ! true false

Unión (or) || true || false true

Intersección (and) && true && false false

Carácter (char) No existen

Cadena (String) Concatenación + “Hola” + “ Antonio”10

“Hola Antonio”

Tabla 1.3. Operaciones básicas.

Operaciones de relación.

Se trata de un tipo especial de peraciones en donde los operandos son del mismo

tipo11

y el resultado es de tipo lógico (boolean). Su interpretación es ligeramente distinta

en función del tipo de operandos.

Operador Tipos numéricos (int o float) Tipo carácter (char)

Significado Ejemplo Result. Significado Ejemplo Result.

> Mayor que 7 > 3 true Posterior a “a” > “z” false

< Menor que 7 < 3 false Anterior a “P” < “p” true12

== Igual a 7 == 3 false Igual a “a” == “A” false

>= Mayor o igual que 3 >= 3 true Posterior o igual a “z” >= “m” true

<= Menor o igual que (-3) <= 3 true Anterior o igual a “M” <= “N” true

!= Distinto de 3 != 3 false Distinto de “p” !=“P” true

Tabla 1.4. Operaciones de relación.

1.2.3.2.2. Funciones estándar y “complementos”.

En términos generales se define una función como un tratamiento que se realiza

sobre ninguno, uno o varios operandos (argumentos) y produce (devuelve) un resultado.

Los lenguajes de programación incluyen un conjunto más o menos amplio de

funciones13

que se identifican como palabras reservadas (forman parte del lenguaje) y

10 Obsérvese que la cadena (String) “ Antonio” incluye un espacio en blanco a su izquierda.

11 Excepto de tipo lógico (boolean) en donde no tiene sentido.

12 Los códigos Unicode de las letras mayúsculas son más bajos que los de las minúsculas.

13 La diversidad de oferta de funciones “integradas” en el lenguaje es muy dispar, no solo entre diferentes

lenguajes sino, también, para un mismo lenguaje, entre diferentes desarrolladores de traductores. Para su

eficiente utilización se insta encarecidamente a utilizar el manual del fabricante. En este sentido su empleo

debe entenderse como “manejo de catálogo”, dado que la aportación conceptual es escasa.

ESTRUCTURAS DE DATOS INTRODUCCIÓN A LA PROGRAMACIÓN 9

deben emplearse siguiendo la sintaxis adecuada: <nombre de la función> (<arg1>,

<arg2>, … <argN>).

Algunas funciones están disponibles como parte integrante del propio lenguaje en

tanto que otras no forman parte del lenguaje. En el caso de Java, las correspondientes

funciones (métodos) están declaradas y definidas en componentes (en Java, paquetes o

packages) que, en su conjunto, configuran una biblioteca, almacenadas en uno o varios

directorios14

.

Ejemplos:

Algunos de los métodos de la clase MATH son:

o Valor absoluto. (abs). Su sintaxis es abs (<tipo int|float>). Devuelve un

número (integer o real, según proceda, positivo). Ejemplos:

abs(-3.25) devuelve el valor 3.25.

abs(77) devuelve el valor 77.

o Número aleatorio (random). No utiliza argumentos. Devuelve un número

racional (real) en el rango 0 < n < 1. Ejemplo: random puede devolver

0,67635…

o Redondeo (round), Devuelve el entero más cercano al argumento.

La clase String tiene entre otros los siguientes métodos:

o length () devuelve la longitud de una cadena ().

o toLowerCase () devuelve una cadena con todas las letras en minúsculas,

o toUpperCase () devuelve una cadena con todas las letras en mayúsculas

o subString (int, int), devuelve un subString extraído de otro.

La clase Integer (es un wrapper del tipo primitivo int: los wrappers se utilizan para

poder utilizar métodos como conversión de cadenas de caracteres a enteros y

viceversa), tiene entre otros los siguientes métodos:

o parseInt (String) convierte una cadena a un número entero,

o toString () convierte un número entero a una cadena,

o MAX_VALUE, MIN_VALUE, constantes predefinidas que devuelven el

valor mayor y menor de un entero.

Finalmente, usted también puede desarrollar sus propias librerías (packages),

almacenarlas en el directorio que considere oportuno, indicarle dicha ubicación al

compilador y utilizar las correspondientes operaciones (Ver Tema 2 “Tipos

Abstractos de Datos”).

14 Además de las componentes (clases) proporcionadas por el “fabricante” (en este caso Eclipse) el

programador puede incorporar otras de desarrollo propio (ver tema 2: “Tipos Abstractos de Datos”) o

adquiridas a terceros.

10 INTRODUCCIÓN A LA PROGRAMACIÓN ESTRUCTURAS DE DATOS

1.2.3.2.3. Expresiones.

Una expresión es una combinación (coherente) de datos (literales, constantes y/o

variables), operaciones y funciones, enlazados entre sí por medio de operadores.

Como consecuencia de evaluar una expresión, siguiendo las reglas

correspondientes, se obtiene un resultado de tipo acorde con la naturaleza de la expresión.

1.2.4. Sentencias.

Las sentencias son construcciones sintácticas que indican al ordenador qué

operaciones debe realizar y, normalmente, con qué datos. Materializan la “parte operativa”

de un programa que implementa el algoritmo (definición de Wirth, 1985) que se deberá

ejecutar15

. Dicha parte constituye un bloque delimitado por { y }.

Se describe a continuación, la sintaxis de las sentencia de Java, agrupadas en función

de su naturaleza.

1.2.4.1. Asignación.

Permite, como su propio nombre indica, asignar un valor a una variable (ver 1.2.2)

definida en la parte declarativa. Su sintaxis es:

<nombreVariable> = <expresión16

>;

Ejemplo (HolaEui2.Java):

//Ejemplo de asignacion a variables.

public class HolaEui2 {

public static void main (String [ ] args) {

String cadena = "Hola EUI";

System.out.println (cadena);

}

}

1.2.4.2. Entrada y salida17

.

Para realizar la entrada y salida de datos se utiliza la clase Java.io. La entrada

estándar es System.in y la salida estándar es System.out. Para realizar la entrada/salida se

utilizan flujos de datos (streams). Un stream es una conexión entre el programa y la fuente

o destino de los datos. Las sentencias principales son readline (entrada) y print o println

(salida).

La sentencia print, cuya sintaxis básica es:

System.out.print (<expresión>);

15 Con excepción de comentarios y “Directivas de compilación”. Consultar el manual del compilador.

16 Ver epígrafe Expresiones.

17 Hacen referencia a los dispositivos configurados por defecto (el teclado y la pantalla).

ESTRUCTURAS DE DATOS INTRODUCCIÓN A LA PROGRAMACIÓN 11

Hace que se muestre en la pantalla (System.out representa la salida estándar, es decir,

la pantalla) el resultado de evaluar la <expresión> utilizada como argumento. Dicho

argumento debe ser un String, que puede estar formado por la concatenación (con +) de

varias cadenas de caracteres diferentes. Si se incluye un número o valor lógico dentro de la

expresión, se convertirá automáticamente a un String (por medio de los wrappers: las clases

Integer, Float, Boolean etc.).

Si en vez de utilizar print, utilizamos println, se incluirá un salto de línea después de

<expresión>.

La sentencia readline, cuya sintaxis básica es:

BufferedReader linea = new BufferedReader (new InputStreamReader (System.in));

String cadena; ….

cadena = linea.readLine(); ….

implica la realización de la siguiente secuencia de acciones:

En las dos primeras líneas, se declaran los objetos linea del tipo

BufferedReader y cadena de tipo String.

El usuario escribe los caracteres correspondientes y finaliza pulsando la tecla

“Intro”.

Se utiliza el método readLine sobre el objeto linea, y se almacena en cadena.

Se continúa con la ejecución del programa.

Para leer otros datos que no sean de tipo String, habría que utilizar una sintaxis

similar a la siguiente:

BufferedReader linea = new BufferedReader (new InputStreamReader (System.in));

int n; ….

n = Integer.parseInt (linea.readLine()); ….

que implica la realización de la siguiente secuencia de acciones:

En la primera línea, se declara el objeto linea del tipo BufferedReader.

El usuario escribe los datos correspondientes y finaliza pulsando la tecla

“Intro”.

Se utiliza el método readline sobre el objeto linea, y como se espera la

introducción de un dato de tipo int, se convierte la línea leída (de tipo String),

al tipo entero mediante el método parseInt de la clase Integer.

Se continúa con la ejecución del programa.

A continuación se muestra un ejemplo (HolaEui3.Java) que contempla la mayoría de

consideraciones explicadas hasta el momento.

12 INTRODUCCIÓN A LA PROGRAMACIÓN ESTRUCTURAS DE DATOS

import java.io.*;

//Ejemplo de sentencias de entrada/salida.

public class HolaEui3 {

public static void main(String [] args) throws

NumberFormatException,IOException{

String cadena, cadena2;

int edad, año = 2010;

BufferedReader linea=new BufferedReader (new InputStreamReader (System.in));

System.out.println ("Hola, ¿como te llamas? ");

cadena = linea.readLine();

System.out.println ("Encantado de conocerte, " + cadena);

System.out.println ("Cuantos años tienes? ");

edad = Integer.parseInt (linea.readLine());

edad = edad+3;

año = año+3;

cadena2 = cadena+", en algun momento de "+año+" tendrás "+edad+" años";

System.out.println (cadena2);

System.out.println (cadena);

}

}

La figura siguiente (consola de eclipse) muestra un posible resultado de su ejecución.

Figura 1.4. Ejecución del ejemplo HolaEui3.pas.

1.2.4.3. Control de flujo.

En los ejemplos anteriores se ha mostrado una de las tres estructuras consideradas en

el enfoque (paradigma) de programación estructurada: la secuencia. El ordenador ejecuta

(salvo imprevistos) una sentencia tras otra.

Las dos estructuras restantes suponen una ejecución no lineal del conjunto de

sentencias respecto al orden en que aparecen escritas en el programa. Son las estructuras

alternativa y repetitiva (o iteración).

ESTRUCTURAS DE DATOS INTRODUCCIÓN A LA PROGRAMACIÓN 13

1.2.4.3.1. Estructura alternativa.

Lo que se ejecute dependerá del resultado de evaluar una operación de relación

(<condición>).

Alternativa simple (if..else).

En su modalidad más sencilla la estructura alternativa se regula mediante la

sentencia if, cuya sintaxis18

es:

if (<condicion>) {

<grupo de sentencias>;

}

else {

<grupo de sentencias>;

}

Ejemplo:

El siguiente programa informa al usuario de la paridad de un número entero

introducido por el teclado.

import java.io.*;

//Ejemplo de alternativa simple.

public class PruebaIf {

public static void main (String [] args) throws NumberFormatException,

IOException {

int dato;

BufferedReader linea=new BufferedReader (new InputStreamReader (System.in));

System.out.print("Numero: ");

dato = Integer.parseInt (linea.readLine());

if ((dato % 2) == 0)

System.out.println (" es par");

else System.out.println (" es impar");

}

}

Operador condicional

El operador condicional ?: se puede usar como abreviatura de la sentencia if, cuya

sintaxis es:

expresionCondicional ? expresionSI : expresionNO;

Primero se evalúa expresionCondicional. Si es true, se devuelve expresionSI, en

caso contrario se devuelve expresionNO. Por ejemplo, si queremos guardar el valor

menor entre x e y en la variable minimo podríamos hacer:

minimo = x <= y ? x : y;

18 Se consideran los siguientes casos particulares:

<grupo de sentencias> puede ser una única sentencia, y no harían falta las llaves ({ y }).

Se puede omitir else: el ordenador no realizaría ninguna acción y ejecutaría la siguiente sentencia.

14 INTRODUCCIÓN A LA PROGRAMACIÓN ESTRUCTURAS DE DATOS

Alternativa múltiple (switch).

La ejecución del programa continúa por una de entre varias opciones en función

del resultado de evaluar una variable (de tipo char o int). Para utilizar esta estructura

se utiliza la sentencia switch, cuya sintaxis general es:

switch (<variable>) {

case <valor1>:

<grupo de sentencias1;>

break;

……….

case <valorN>:

<grupo de sentenciasN;>;

break;

default

<grupo de sentenciasD;>

break;

}

Ejemplo:

El programa siguiente espera recibir desde el teclado un dígito, entre 1 y 7,

correspondiente al ordinal del día de la semana (lunes a domingo) y mostrará por la

pantalla el literal del día asociado o un mensaje de error si el código no es válido.

import java.io.*;

//Ejemplo de alternativa multiple.

public class PruebaSwitch {

public static void main (String [ ] args) throws NumberFormatException,

IOException {

BufferedReader linea = new BufferedReader (new InputStreamReader

(System.in));

System.out.print ("Opcion: ");

int opc = Integer.parseInt (linea.readLine ());

switch (opc) {

case 1: System.out.println ("lunes");

break;

case 2: System.out.println ("martes");

break;

case 3: System.out.println ("miercoles");

break;

case 4: System.out.println ("jueves");

break;

case 5: System.out.println ("viernes");

break;

case 6: System.out.println ("sabado");

break;

case 7: System.out.println ("domingo");

break;

default: System.out.println ("opcion no valida");

break;

}

}

}

ESTRUCTURAS DE DATOS INTRODUCCIÓN A LA PROGRAMACIÓN 15

1.2.4.3.2. Estructura iterativa.

Consiste en realizar la ejecución de un bloque de código varias veces. Las

modalidades que se explican a continuación dependen de la forma en que se realiza el

control del número de repeticiones.

Estructura iterativa controlada por contador (for).

Se utiliza una variable entera que actúa como contador. La sentencia indica el

valor inicial del contador, hasta cuando se debe realizar el bucle, así como la cuantía

en que se incrementa (o decrementa) el contador. Su sintaxis es:

for (<val._inicicial> ; <comprobacion>;<incremento>) {

<grupo de sentencias;>

}

Ejemplo:

El siguiente programa muestra por la pantalla el valor de los cuadrados de 5

números introducidos por el teclado.

import java.io.*;

//Ejemplo de bucle for.

public class PruebaFor {

public static void main(String[] args) throws NumberFormatException,IOException{

int i, dato, cuadrado;

BufferedReader linea = new BufferedReader (new InputStreamReader (System.in));

for (i = 1; i <= 5; i ++) {

System.out.print ("Numero: ");

dato = Integer.parseInt (linea.readLine ());

cuadrado = dato * dato;

System.out.println ("El cuadrado de " + dato + " es: " + cuadrado);

}

}

}

Estructura iterativa controlada por condición.

En los casos siguientes la repetición, o no, de la ejecución de un bloque de código

es consecuencia del resultado de evaluar una expresión booleana (<condición>).

o Estructura mientras (while).

Se utiliza la sentencia while, cuya sintaxis es:

while (<condicion>) {

<grupo de sentencias;>

}

La evaluación de la <condición> es previa19

a la ejecución del bloque de

código que se repite.

19 En consecuencia, podría ser que el bloque de código pudiera no ejecutarse ninguna vez.

16 INTRODUCCIÓN A LA PROGRAMACIÓN ESTRUCTURAS DE DATOS

Ejemplo:

El siguiente programa ejecuta reiteradamente el ejemplo de los días de la

semana, salvo que el usuario teclee „0‟ como opción20

.

import java.io.*;

//Ejemplo de bucle while.

public class PruebaWhile {

public static void main (String [] args) throws NumberFormatException,

IOException {

int opc;

BufferedReader linea = new BufferedReader (new InputStreamReader

(System.in));

System.out.print ("Opción: ");

opc = Integer.parseInt (linea.readLine());

while (opc != 0) {

switch (opc) {

case 1: System.out.println ("lunes");

break;

case 2: System.out.println ("martes");

break;

case 3: System.out.println ("miercoles");

break;

case 4: System.out.println ("jueves");

break;

case 5: System.out.println ("viernes");

break;

case 6: System.out.println ("sabado");

break;

case 7: System.out.println ("domingo");

break;

default: System.out.println ("opción no valida");

break;

}

System.out.print ("Opción: ");

opc = Integer.parseInt (linea.readLine ());

}

}

}

o Estructura Repetir mientras (do … while).

Se utiliza la sentencia do ... while, cuya sintaxis es:

do

<grupo de sentencias;>

while (<condición>);

La evaluación de la <condición> es posterior21

a la ejecución del bloque de

código que se repite.

20 Pruebe a ejecutarlo introduciendo la primera vez la opción 0.

21 En consecuencia, la ejecución del bloque tiene lugar, al menos, una vez.

ESTRUCTURAS DE DATOS INTRODUCCIÓN A LA PROGRAMACIÓN 17

Ejemplo:

El programa siguiente constituye una variante del ejemplo anterior en el que

el bloque de código se ejecuta una vez y volverá a repetirse reiteradamente, o

no, hasta que el usuario teclee „0‟ como opción22

.

import java.io.*;

//Ejemplo de bucle do ... while.

public class PruebaDoWhile {

public static void main (String [] args) throws NumberFormatException,

IOException {

int opc = 7;

BufferedReader linea=new BufferedReader (new InputStreamReader (System.in));

System.out.println("Empezamos en domingo");

do {

switch (opc) {

case 1: System.out.println ("lunes");

break;

case 2: System.out.println ("martes");

break;

case 3: System.out.println ("miercoles");

break;

case 4: System.out.println ("jueves");

break;

case 5: System.out.println ("viernes");

break;

case 6: System.out.println ("sabado");

break;

case 7: System.out.println ("domingo");

break;

default: System.out.println ("opción no valida");

break;

};

System.out.print ("Opción: ");

opc = Integer.parseInt (linea.readLine ());

} while (opc != 0);

}

}

22 Pruebe a ejecutarlo introduciendo la primera vez la opción 0.

18 INTRODUCCIÓN A LA PROGRAMACIÓN ESTRUCTURAS DE DATOS

1.3. PROGRAMACIÓN MODULAR. SUBPROGRAMAS.

1.3.1. Reflexión previa.

Cualquier producto de Ingeniería (desde un barco hasta una central nuclear, pasando

por una catedral) se concibe como un sistema complejo constituido por un conjunto de

subsistemas más sencillos, y así sucesivamente hasta acabar en piezas indivisibles.

El plantearse las cosas de esta manera permite que un conjunto de profesionales

especializados pueda realizar la obra, con unos costes y tiempos razonables, y que, una vez

finalizada, se puedan acometer con eficiencia las inevitables tareas de actualización y

conservación.

Inexplicablemente, con demasiada frecuencia, nos cuesta transmitir a algunos

alumnos que la filosofía expuesta es (como no podía ser de otra manera), también, aplicable

a las obras producidas mediante la Ingeniería del Software. Sostienen que como, de

momento, las aplicaciones que desarrollan en sus actividades prácticas no son de gran

volumen, se acaba antes “programando de un tirón” y que el día que les toque trabajar en

un proyecto de centenares de millares de líneas de código ya cambiarán de actitud, etc.

Evidentemente, cada cual es libre de equivocarse voluntariamente siempre que así lo

desee.

1.3.2. Tecnología para la programación modular.

Cuando hemos hablado antes de programación modular lo hemos hecho desde una

perspectiva de “disciplina mental”. Por supuesto que la tecnología proporciona un

importante conjunto de facilidades para dar soporte a tales concepciones. Aunque su

explicación pormenorizada queda fuera de los objetivos y alcance de este curso, citaremos,

simplemente, a grandes rasgos las principales variantes:

Módulos externos. La fuente de procedencia de tales módulos es diversa:

construidos previamente por el propio programador (o su empresa), incluidos en el

propio entorno de programación (paquetes o packages en el caso del entorno de

programación de Java utilizado en el curso) o adquiridos a terceros23

. El

programador de aplicaciones, para utilizar dichos módulos deberá únicamente

incorporarlos, indicar al compilador, dónde está/n ubicado/s y saber de que

funcionalidades dispone y que sintaxis hay que emplear.

23 Internet es una fuente casi inagotable donde obtener este tipo de recursos.

ESTRUCTURAS DE DATOS INTRODUCCIÓN A LA PROGRAMACIÓN 19

Módulos desarrollados por el propio programador almacenados como ficheros

independientes de la aplicación principal. A todos los efectos su funcionamiento es

análogo al caso de los módulos externos24

.

Módulos desarrollados por el propio programador y almacenados junto con la

aplicación principal25

en un único fichero.

Existen varias alternativas tecnológicas a la hora de gestionar la memoria del

ordenador cuando ejecuta una aplicación concebida en forma modular. La más sencilla es la

que aparece ilustrada en la figura siguiente.

Figura 1.5. Gestión de la memoria en programas modulares-.

Este tipo de gestión de memoria se denomina estática26

porque durante todo el tiempo

de ejecución tanto el programa principal como la totalidad de módulos están cargados en

memoria, alojados en espacios independientes.

A su vez, cada uno de los espacios se subdivide en otros dos: zona de datos y zona de

algoritmo (la parte de código -previamente traducido a lenguaje de máquina-).

La ejecución del conjunto tiene lugar a partir de la primera instrucción del programa

principal. Cuando se llega a una <llamada> se suspende y se pasa al contexto del

24 La mayoría de entornos de programación modernos contemplan el concepto de proyecto en donde se indica

el conjunto de módulos que configuran el sistema y sus ubicaciones. En el curso, dado su carácter básico, no

utilizaremos esta posibilidad. 25

Se entiende por aplicación principal a la parte de código (generalmente poco extensa) que utiliza (llama o

invoca) los diferentes módulos funcionales. 26

Las alternativas más evolucionadas son de naturaleza dinámica: el bloque correspondiente al programa

principal está permanentemente en memoria mientras que los subprogramas se cargan cuando se necesitan y

al finalizar su ejecución se libera la memoria ocupada. No obstante esta técnica es la única posible cuando se

utiliza el tratamiento recursivo (ver apartado 1.2.5.5)

Programa principal

Zona de datos Zona de algoritmo

<Llamada>

-----------

-----------

Memoria libre

Zona de datos

Subprograma (módulo)

Zona de algoritmo

<retorno

>

-----------

-----------

20 INTRODUCCIÓN A LA PROGRAMACIÓN ESTRUCTURAS DE DATOS

subprograma, hasta que finalice su ejecución, a partir de entonces se reanuda la ejecución

del programa principal en el punto en que quedó suspendida.

1.3.3. Mecanismos para el paso de información27

.

Cuando un programa llama a un módulo deberá utilizar un nombre que lo identifique

y, además, “pasarle” los datos que necesite. El conjunto de estos datos se denomina

genéricamente parámetros o argumentos y se expresa mediante las correspondientes

variables y su tipo. Existen dos modalidades de paso de argumentos:

Por valor. La variable del subprograma recibe inicialmente una copia del valor

almacenado, en el momento de producirse la llamada, en el módulo principal.

Durante su ejecución (del subprograma) podrá modificarlo pero una vez que

finalice, el programa principal conserva intacto su valor. En Java todos los tipos

primitivos (números enteros, reales, caracteres y booleanos) se pasan siempre por

valor.

Por referencia. En este caso no existe tal variable en el ámbito del subprograma. El

mecanismo consiste en que el módulo principal permite al subprograma actuar

sobre sus propias variables. En consecuencia, cualquier modificación que se

realice en una variable pasada por referencia tendrá efecto en el contexto del

programa (módulo) principal. En Java todos los datos que no sean de un tipo

primitivo se pasan por valor, pero al ser referencias, los datos en sí pueden cambiar

(pero no la posición de memoria en la que están guardados).

Además hay que considerar los siguientes aspectos:

Un subprograma puede declarar sus propias variables locales28

.

Es posible, pero no recomendable29

(salvo contadas excepciones), declarar una

variable como accesible para el programa principal y la totalidad de los

subprogramas30

. Este tipo de variables se denominan globales31

.

27 Lo que se explica en el apartado debe entenderse con carácter general. El lenguaje Java solo

admite el paso de argumentos por valor. Esta afirmación es extensiva al caso de que el argumento sea un

objeto (por ejemplo, un vector) dado que lo que se pasa como argumento por valor es un puntero (referencia)

a dicho objeto que no puede ser modificado por el módulo subordinado (aunque sí el propio objeto). 28

No hay ningún inconveniente en que dos variables locales de módulos diferentes tengan el mismo

nombre. 29

Una modificación de una variable global realizada por un módulo afectará al resto. 30

En este caso si habría ambigüedad en caso de coincidencia de nombres de una variable local y otra

global. Esto no supone ningún error de compilación y la ejecución podría producir resultados no esperados (ni

deseados). 31

No tiene sentido pasar como argumentos variables globales. Ya están accesibles desde todos los

módulos.

ESTRUCTURAS DE DATOS INTRODUCCIÓN A LA PROGRAMACIÓN 21

1.3.4. Aplicación al lenguaje Java.

Lo que en otros lenguajes se llama función o procedimiento, en Java se llama método.

La cabecera de un método consiste en un nombre, una lista de parámetros (que puede ser

vacía), y el tipo de resultado (si no devuelve resultado, el método será de tipo void).

La sintaxis (cabecera o prototipo) en Java es:

<tipo devuelto> <nombre del método> (<lista de argumentos>)

Siendo la sintaxis de <lista de argumentos> la siguiente:

<tipo> <argumento1>, <tipo> <argumento2>, ...

Los nombres de los argumentos son formales. No tienen por qué coincidir con los

nombres reales de las variables utilizadas en la llamada al método32

. El compilador solo

verifica que las listas de argumentos empleadas en la llamada (reales) y en la cabecera

(formales) coinciden en número, tipo y orden.

Las sintaxis de las llamadas es similar a la de las cabeceras, si bien en este caso se

utiliza una versión “reducida” de la <lista de argumentos> en la que solo aparecen sus

nombres (reales), separados por comas.

El método está delimitado por las llaves de inicio y fin de bloque ({ y }), y a

continuación aparece la declaración de variables locales del subprograma y el algoritmo.

Los métodos que no sean de tipo void deben acabar con la siguiente sentencia:

return <variableDeTrabajo>;

(siendo <variableDeTrabajo> el nombre de una variable local en donde se recoge el

resultado generado por el método). De esta forma el método “devuelve” el resultado

generado al módulo de llamada.

Si el método es de tipo void, se puede prescindir de la instrucción return, o utilizarla

sin ninguna variable asociada (return;).

32 El hacerlo así permite que los subprogramas sean reutilizables.

Que el programa “funcione” (correctness) es una condición necesaria, pero no suficiente.

Existen otros criterios: eficiencia, seguridad, fiabilidad, mantenibilidad, legibilidad, productividad,

posibilidad de desarrollo en equipo, etc.

En la Ingeniería del Software, debe prestarse atención especial al uso correcto y eficiente de

los datos (la información) tanto o más que al código (algoritmo). A continuación se indican algunas

recomendaciones en este sentido.

Protección de la información: evitar el uso de variables globales.

Eficiencia: no pasar más argumentos que los estrictamente imprescindibles.

Productividad / mantenibilidad: dotar a los subprogramas de la mayor autonomía posible (no pasar

argumentos que se puedan obtener en el contexto del propio subprograma).

22 INTRODUCCIÓN A LA PROGRAMACIÓN ESTRUCTURAS DE DATOS

Dado que el lenguaje Java solo admite el paso de argumentos por valor no es posible

que el modulo subordinado modifique una variable del módulo de llamada (salvo que se

trate del valor devuelto por el método). En tales situaciones optamos por crear una clase

estática que declare dicha variable como global de la clase (lo que no contraviene la

recomendación general de no utilizar variables globales)33

por lo que carecería de sentido

pasarla como argumento.

Como consecuencia de la utilización de la técnica de subprogramación, el esquema de

estructura general de un programa en Java visto en la sección 1.2.1 queda ampliado tal

como se muestra a continuación.

Sintaxis Semántica

import <nombre> Llamada a una (o varias) componente(s) de la “biblioteca”.

public class <nombre de la clase general> Nombre de la clase general (obligatorio)

{ Principio de la clase.

<constantes y variables de la clase>

<tipo devuelto> <nombre del método1> (<lista

de argumentos>)

Cabecera del método 1

{ Inicio del método 1

<constantes y variables del método>

.... sentencias del método 1

return [<variable de trabajo>];

} fin del método 1

<tipo devuelto> <nombre del método1> (<lista

de argumentos>)

Cabecera del método 2

Inicio del método 2

{

<constantes y variables del método> sentencias del método 2

....

return [<variable de trabajo>]; fin del método 2

......

public static void main (String [ ] args) Declaración del programa principal

{ Inicio del programa principal

<llamadas a los métodos>

sentencias del programa principal

} Fin del programa principal.

} Fin de la clase.

Tabla 1.5. Estructura de un programa en Java.

33 Algunos programadores optan por la alternativa de declarar dicha variable como vector (objeto) y pasar el

puntero (referencia) correspondiente como argumento (por valor).

ESTRUCTURAS DE DATOS INTRODUCCIÓN A LA PROGRAMACIÓN 23

1.3.4.1. Ejemplo.

El siguiente programa pide al usuario una serie de cinco números enteros y muestra

por la pantalla dos de sus estadísticas: valor medio y rango.

import java.io.*;

public class PruebaMetodos {

static float media (int dato1, int dato2, int dato3, int dato4, int dato5) {

float resul;

resul = (dato1 + dato2 + dato3 + dato4 + dato5) / 5;

return resul;

}

static int max (int dato1, int dato2, int dato3, int dato4, int dato5) {

int resul;

resul=dato1;

if (dato2 > resul)

resul = dato2;

if (dato3 > resul)

resul = dato3;

if (dato4 > resul)

resul = dato4;

if (dato5 > resul)

resul = dato5;

return resul;

}

static int min (int dato1, int dato2, int dato3, int dato4, int dato5) {

int resul;

resul = dato1;

if (dato2 < resul)

resul = dato2;

if (dato3 < resul)

resul = dato3;

if (dato4 < resul)

resul = dato4;

if (dato5 < resul)

resul = dato5;

return resul;

}

public static void main(String[] args) throws NumberFormatException,IOException{

int d1, d2, d3, d4, d5, rango;

float media;

BufferedReader linea = new BufferedReader(new InputStreamReader(System.in));

System.out.println ("Introduce cinco números enteros: ");

d1 = Integer.parseInt (linea.readLine ());

d2 = Integer.parseInt (linea.readLine ());

d3 = Integer.parseInt (linea.readLine ());

d4 = Integer.parseInt (linea.readLine ());

d5 = Integer.parseInt (linea.readLine ());

media = media (d1, d2, d3, d4, d5);

System.out.println("El valor medio es: " + media);

rango = max (d1, d2, d3, d4, d5) - min (d1, d2, d3, d4, d5);

System.out.println ("y el rango: " + rango);

}

}

24 INTRODUCCIÓN A LA PROGRAMACIÓN ESTRUCTURAS DE DATOS

1.3.5. Recursividad.

1.3.5.1. Concepto.

Se define objeto recursivo como: “aquel que forma parte de sí mismo o que forma

parte de su propia definición”.

La recursividad es un concepto matemático que permite formular definiciones

como, por ejemplo, la de número natural:

o El 1 es un número natural.

o El siguiente de un número natural es un número natural.

Otro ejemplo sería la definición de factorial de número natural:

o 0! = 1;

o N > 0, N! = N * (N-1)!

1.3.5.2. Aplicación del concepto de recursividad en programación.

Los ejemplos anteriores se pueden implementar mediante programas informáticos que

proporcionan un método sencillo, útil y potente de resolución de infinidad de problemas.

De hecho, hay algunos cuya solución no recursiva sería de elevada complejidad y escasa

eficiencia34

. Frente a las ventajas indicadas (robustez y legibilidad) de la recursividad frente

al tratamiento iterativo se contrapone el inconveniente del mayor consumo de memoria, por

las razones que se explican a continuación.

La recursividad en programación se obtiene mediante subprogramas que se llaman a

sí mismos (es decir, una de las sentencias del cuerpo del subprograma coincide con el

nombre del mismo), no obstante sería más correcto decir que llama a otra copia (instancia)

del mismo, esa copia a otra nueva y así sucesivamente hasta que se alcanza una condición

denominada de terminación o de parada.

Una vez que se alcanza la condición de terminación, en la instancia n-ésima se

retorna (como en todo subprograma) al punto del modulo de llamada de la instancia previa

desde donde ésta se realizó, y así sucesivamente, teniendo lugar una fase de “vuelta” por las

instancias anteriores, hasta llegar al punto del programa principal en que se produjo la

llamada al subprograma recursivo.

La implementación en ordenador de algoritmos recursivos requiere una tecnología

dinámica, diferente a la estática (explicada en el apartado 1.3.2), dado que no se sabe, a

priori, cuantas instancias se van a necesitar. Durante la fase de “ida” se cargan en la

memoria nuevas copias del subprograma35

hasta alcanzar la terminación (fase de

34 Un ejemplo en este sentido sería el clásico algoritmo denominado “Torres de Hanoi”.

35 En caso de un número elevado de llamadas recursivas es posible quedarse sin espacio libre en memoria y se

produce un error en tiempo de ejecución (StackOverflowError).

ESTRUCTURAS DE DATOS INTRODUCCIÓN A LA PROGRAMACIÓN 25

“transición”). Por el contrario, durante la fase de “vuelta” se “libera” la memoria

correspondiente a cada instancia a medida que finaliza su ejecución (ejecuta el código de

retorno).

En el ejemplo siguiente el programa principal llama a un método recursivo que

devuelve el factorial de un número natural introducido por el teclado.

import java.io.*;

public class PruebaRecursividad {

static int factorial (int dato) {

int resul = 1;

if (dato > 0)

resul = dato * factorial (dato - 1);

return resul;

}

public static void main(String[] args) throws NumberFormatException,IOException{

int d, f;

BufferedReader linea = new BufferedReader (new InputStreamReader(System.in));

System.out.print("Introduzca el dato: ");

d = Integer.parseInt (linea.readLine ());

if (d >= 0) {

f = factorial(d);

System.out.println("El factorial de " + d + " es: " + f);

}

else System.out.println("No existe el factorial de un número negativo");

}

}

Explicación:

El programa principal llama al subprograma factorial y le pasa como argumento la

variable d. Cuando éste termine de ejecutarse, el programa de llamada dispondrá de

un resultado que podrá mostrar por la pantalla.

El subprograma factorial es recursivo.

o Su condición de terminación es dato == 0. En la instancia en que se alcance36

(“transición”) devolverá el valor 1.

o Durante la fase de “ida”, la ejecución de cada instancia consiste en multiplicar

el valor del argumento recibido desde la instancia anterior (salvo la primera,

que lo recibe directamente del programa principal) por el factorial del número

anterior (factorial(N-1)). La ejecución de esa instancia queda suspendida hasta

que se le devuelva el valor solicitado.

o En la fase de “vuelta” cada instancia:

Multiplica el valor devuelto por la instancia siguiente por el que, en su

momento de la fase de “ida”, recibió como argumento,

36 Su número de orden será uno más que el dato cuyo factorial se quiere calcular.

26 INTRODUCCIÓN A LA PROGRAMACIÓN ESTRUCTURAS DE DATOS

almacena el resultado en la variable local resul,

devuelve el contenido de resul a la instancia anterior (sentencia factorial =

resul) y

termina su ejecución con „}‟ (Se libera la memoria correspondiente).

o Observe que la palabra dato tiene dos significados en el código del

subprograma recursivo:

En la llamada factorial (dato-1) se trata de una variable real. Lo que se pasa

como argumento es el valor actual decrementado en una unidad.

En la declaración del subprograma static int factorial (int dato) es una

variable formal, debe interpretarse como: “lo que se reciba”.

1.3.5.3. Consideraciones complementarias.

Terminación anticipada.

La condición de terminación la entendemos como “pesimista” en el sentido de se

alcanza cuando se han invocado todas las instancias posibles. No obstante en

determinadas circunstancias se puede producir37

una terminación anticipada. En tal

caso lo que se debe “hacer” es no realizar nuevas llamadas recursivas.

Utilización de módulos de llamada

En determinadas ocasiones, el subprograma recursivo requiere el uso de argumentos

adicionales, difíciles de imaginar desde el programa principal. En estos casos la solución

recomendada es utilizar un subprograma no recursivo (que es el que se llama desde el

módulo principal) encargado de “preparar” los argumentos adicionales necesarios y, a

continuación, llamar al subprograma recursivo.

Momentos de realización del proceso e implicación en la modalidad del paso de

argumentos.

La ejecución de la lógica del algoritmo puede realizarse en cualquiera de las fases del

tratamiento recursivo (“ida”, “transición” o vuelta”) o distribuido en varias de ellas.

Recursividad indirecta.

Si tenemos dos subprogramas A y B, se utiliza la recursividad indirecta cuando el

subprograma A contiene una referencia al subprograma B, y a su vez el subprograma B

contiene una referencia al A, como se puede ver en el siguiente esquema:

static void A () {

<sentencia 1>;

<sentencia 2>;

B ();

...

<sentencia n>;

}

static void B () {

<sentencia 1>;

...

A ();

......

<sentencia m>;

}

37 En algunos casos la terminación anticipada es necesaria para el correcto funcionamiento del programa

mientras que en otros, aunque el “programa funcione” se debe usar por consideraciones de eficiencia.

ESTRUCTURAS DE DATOS INTRODUCCIÓN A LA PROGRAMACIÓN 27

1.3.5.4. Ejemplos.

Ejemplo 1. (Utilización de un módulo de llamada).

El programa que se muestra a continuación utiliza un método que obtiene la suma de

los divisores de un número natural. Por ejemplo: la suma de los divisores de 40 (2, 4, 5,

8, 10, 20) es 49.

import java.io.*;

public class Recursividad1 {

static int sumaDivisores (int dato, int presuntoDivisor) {

int resul = 0;

if ((presuntoDivisor*2) <= dato) {

resul = sumaDivisores (dato, presuntoDivisor+1);

if ((dato % presuntoDivisor) == 0)

resul = resul + presuntoDivisor;

}

return resul;

}

static int sumaDivisores (int dato) {

return sumaDivisores (dato, 2);

}

public static void main(String[] args) throws NumberFormatException,IOException{

int d, suma;

BufferedReader linea = new BufferedReader (new InputStreamReader(System.in));

do {

System.out.print ("Introduzca un número mayor que cero: ");

d = Integer.parseInt (linea.readLine ());

} while (d <= 0);

suma = sumaDivisores (d);

System.out.print ("La suma de los divisores de " + d + " es " + suma);

}

}

El diseñador del programa principal no tiene por qué saber que se necesitan otros

argumentos adicionales al dato (d), sin embargo, el subprograma recursivo realmente

necesita otro argumento (presuntoDivisor, de tipo entero), para probar recursivamente

desde 2 hasta d/2 y controlar la terminación (“pesimista”).

Ejemplo 2. (Terminación anticipada).

El siguiente ejemplo consiste en una función booleana (esPrim) que indica si un

número (d) que se introduce desde el teclado es primo (true) o no (false).

La condición de terminación “pesimista” es d > dato .

En cada instancia, (fase de “ida”) se prueba si presuntoDivisor es divisor de dato. En

caso afirmativo dato no es primo y se produce terminación anticipada con resultado

false, en caso contrario se realiza una llamada recursiva incrementando una unidad el

argumento presuntoDivisor. Si se llega a alcanzar la condición de terminación

“pesimista” significa que el número (d) es primo y la función devuelve true.

28 INTRODUCCIÓN A LA PROGRAMACIÓN ESTRUCTURAS DE DATOS

A continuación se muestra una posible implementación.

import java.io.*;

public class Recursividad2 {

static boolean esPrim (int dato, int presuntoDivisor) {

boolean resul;

if ((presuntoDivisor*presuntoDivisor) <= dato)

if ((dato % presuntoDivisor) != 0)

resul = esPrim (dato, presuntoDivisor+1);

else resul = false;

else resul = true;

return resul;

}

static boolean esPrimo (int dato) {

boolean resul;

resul = esPrim (dato, 2);

return resul;

}

public static void main(String[] args) throws NumberFormatException,IOException{

int d;

BufferedReader linea = new BufferedReader (new InputStreamReader(System.in));

do {

System.out.print("Introduzca un número mayor que cero: ");

d = Integer.parseInt (linea.readLine());

} while (d <= 0);

if (esPrimo(d))

System.out.println(d + " es primo");

else System.out.println (d + " no es primo");

}

}

Ejemplo 3. (Proceso en la fase de “vuelta”).

En el siguiente ejemplo se desarrolla un método que devuelve:

o La suma de la primera mitad (los más pequeños) de los factores primos de un

número (d). si el número total de factores primos es par. Por ejemplo, el número

420 tiene un número par (4) de factores primos (2, 3, 5, 7). El método devolverá

5 (2+3).

o La suma de la segunda mitad (los más grandes) de los factores primos de un

número (d), si el número total de factores primos es impar. Por ejemplo, el

número 2310 tiene un número impar (5) de factores primos (2. 3, 5, 7, 11). La

función devolverá 18 (7+11).

Para poder comprobar a la vuelta el número de factores primos, sería necesario pasar

la variable que represente el número de factores por referencia. Como el valor es entero,

y en Java no es posible pasar tipos primitivos por referencia, crearemos una clase

(Suma), dentro de la cual se utiliza la variable miembro numeroFactores (global dentro

de dicha clase, pero no accesible al resto de métodos). En la llamada al método

sumaPrimosCondicionada desde el programa principal, es necesario indicar que

pertenece a la clase Suma (haciendo la llamada: Suma.sumaPrimosCondicionada (d)).

A continuación se muestra el código.

ESTRUCTURAS DE DATOS INTRODUCCIÓN A LA PROGRAMACIÓN 29

import java.io.*;

public class Recursividad3 {

static boolean esPrim (int dato, int presuntoDivisor) {

boolean resul;

if ((presuntoDivisor*presuntoDivisor) <= dato)

if ((dato % presuntoDivisor) != 0)

resul = esPrim (dato, presuntoDivisor+1);

else resul = false;

else resul = true;

return resul;

}

public static class Suma {

static int numeroFactores = 0; //variable global de la clase Suma

static int sumaPrimos (int dato, int presuntoPrimo) {

int resul, orden;

if ((presuntoPrimo * 2) <= dato)

if ((dato % presuntoPrimo) == 0)

if (esPrim (presuntoPrimo, 2)) {

numeroFactores= numeroFactores + 1;

orden = numeroFactores;

resul = sumaPrimos(dato, presuntoPrimo + 1);

if ((numeroFactores% 2) == 0) {

if (orden <= (numeroFactores / 2))

resul = resul + presuntoPrimo;

}

else {

if (orden > ((numeroFactores / 2) + 1))

resul = resul + presuntoPrimo;

}

}

else resul = sumaPrimos (dato, presuntoPrimo + 1);

else resul = sumaPrimos (dato, presuntoPrimo + 1);

else resul = 0;

return resul;

}

static int sumaPrimosCondicionada (int dato) {

int resul;

resul = sumaPrimos (dato, 2);

return resul;

}

}

public static void main(String[] args) throws NumberFormatException,IOException{

int d, suma;

BufferedReader linea = new BufferedReader (new InputStreamReader(System.in));

do {

System.out.print("Introduzca un número mayor que cero: ");

d = Integer.parseInt (linea.readLine());

} while (d <= 0);

suma = Suma.sumaPrimosCondicionada (d);

System.out.println ("El resultado es: " + suma);

}

}

En este caso, el proceso no puede realizarse completamente durante la fase de

“ida”, dado que hasta la “transición” no se conocerá el número total de factores

30 INTRODUCCIÓN A LA PROGRAMACIÓN ESTRUCTURAS DE DATOS

primos, por lo que no se sabe qué hay que sumar. La parte de proceso que se realiza

en ella consiste en:

o Identificar si un número (argumento presuntoPrimo, pasado por valor) es

primo y factor de d.

En caso negativo se realiza una nueva llamada recursiva con

presuntoPrimo+1.

En caso afirmativo:

Se contabiliza en la variable numeroFactores global dentro de la clase

Suma, inicializado a 0 (pues se utilizará su valor final en la fase de

“vuelta”).

Se “toma nota” del número de orden (orden) del factor primo

encontrado (variable local que recoge el valor actual de

numeroFactores)38

.

El proceso de la fase de “vuelta” está condicionado por el lugar desde el que se

hizo la llamada recursiva:

o Si se hizo en una instancia correspondiente a un valor de presuntoPrimo que

no era factor primo, simplemente se retorna el valor recibido desde la

instancia siguiente hacia la anterior39

.

o En caso contrario:

Se recibe el valor devuelto por el método desde la instancia siguiente

(resul).

Si el valor de orden se encuentra dentro del rango adecuado (primeros o

últimos, según proceda) se suma al valor de resul el de presuntoPrimo.

Se devuelve el resultado a la instancia anterior.

38 En las instancias correspondientes a valores de presuntoPrimo que no son factores primos el valor de la

variable orden es aleatorio 39

Vamos “marcha atrás”.

NOTA IMPORTANTE: Evitar el error de “intentar” pasar a la siguiente instancia el valor

de una variable local.

ESTRUCTURAS DE DATOS INTRODUCCIÓN A LA PROGRAMACIÓN 31

Ejemplo 4. (Recursividad indirecta).

En el siguiente ejemplo, se desarrollan los métodos esPar y esImpar que utilizan la

recursividad indirecta para indicar si el número que se recoge del teclado es par o

impar.

import java.io.*;

public class RecursividadIndirecta {

static boolean esPar (int n) {

boolean resul = true;

if (n != 0)

resul = esImpar (n - 1);

return resul;

}

static boolean esImpar (int n) {

boolean resul = false;

if (n != 0)

resul = esPar (n - 1);

return resul;

}

public static void main (String [ ] args) throws IOException {

int n;

boolean esPar;

BufferedReader linea = new BufferedReader (new InputStreamReader (System.in));

System.out.print ("Escriba un número para saber si es par o impar: ");

n = Integer.parseInt (linea.readLine ());

esPar = esPar (n);

if (esPar)

System.out.println ("El número " + n + " es par");

else System.out.println ("El número " + n + " es impar");

}

32 INTRODUCCIÓN A LA PROGRAMACIÓN ESTRUCTURAS DE DATOS

1.4. ESTRUCTURAS DE DATOS.

Una estructura de datos es una agrupación de éstos que se trata como una unidad en

su conjunto. Se construyen a partir de los tipos de datos simples, vistos en la sección

1.2.3.1.

Las estructuras de datos pueden ser homogéneas (todos los datos son del mismo tipo)

o heterogéneas (constituidas por datos de tipos diferentes).

Las estructuras de datos homogéneas más representativas son los vectores, las tablas

y, en general, las matrices n-dimensionales. El ejemplo más representativo de estructuras

de datos heterogéneas son los registros

Desde otro punto de vista puede hablarse de estructuras de datos estáticas y

dinámicas.

Una estructura estática se caracteriza porque su tamaño es conocido a priori (antes de

la ejecución del programa). En consecuencia, en el código del programa se declaran

variables de tipo estático y en la compilación se reserva en memoria el espacio necesario

para ellas.

Por el contrario, las estructuras de datos dinámicas no tienen un tamaño predefinido

y la memoria utilizada para almacenarlas se reserva o libera, en tiempo de ejecución,

según se requiera.

1.4.1. Estructuras de datos dinámicas

Se dice que una estructura de datos es dinámica cuando inicialmente (en el momento

de la compilación) no tiene espacio asignado para almacenar información. Durante la

ejecución del programa el sistema (en tiempo de ejecución, run time) asigna y libera

espacio en memoria, en función de las necesidades.

Los temas 3 (Listas), 4 (Árboles) y 5 (Grafos) describen distintos tipos de Estructuras

de Datos Dinámicas.

En algunos lenguajes de programación, para permitir la implementación de

estructuras de datos dinámicas es necesario un tipo de datos especial, denominado puntero

(pointer).

El concepto de puntero (pointer) hace referencia a una variable cuyo contenido es la

dirección de otra variable (nodo) que realmente contiene el propio dato que se emplea en el

programa40

.

40 Este concepto toma su suporte físico en el mecanismo de direccionamiento indirecto tal como se maneja en

los lenguajes de bajo nivel.

ESTRUCTURAS DE DATOS INTRODUCCIÓN A LA PROGRAMACIÓN 33

La figura 1.6. muestra un modelo de funcionamiento.

Figura 1.6. Punteros y variables de trabajo.

El interés del uso de punteros en lenguajes de programación de alto nivel reside en

que constituyen la base para la generación de estructuras de datos dinámicas. Esto es, que a

lo largo de la ejecución de un programa, y como consecuencia de las sucesivas operaciones

de inserción y eliminación, el sistema en tiempo de ejecución (run time) reserva y libera

respectivamente unidades (nodos) de una región de memoria destinada a uso dinámico (a

diferencia de la que se reserva en el momento de la compilación que contiene el código

máquina y el espacio requerido por las variables estáticas). Este espacio (estático) se

mantiene inalterado durante toda la ejecución del programa.

La utilización de esta posibilidad requiere que el lenguaje de programación disponga

de operaciones que soliciten (de forma transparente al programador) espacio en la memoria

dinámica para crear nuevos nodos, así como la contraria, para liberar espacio

correspondiente a nodos que ya no se necesitan y poder disponer, en consecuencia, de dicho

espacio para uso posterior. En Java se utiliza la sintaxis:

<tipo> <variable> = new <tipo>; para crear un nodo

La variable puntero (en Java se denominan referencias), se almacena en la memoria

estática y su tamaño es fijo. No así los nodos que pueden ser de diferente naturaleza. Es

decir que en la declaración de un nodo debe hacerse referencia al tipo de dato al que apunta

para que cuando se cree un nodo se reserve con el tamaño necesario. Por ejemplo, al

ejecutar el código en Java:

char [ ] puntVector = new char [100];

se declara una variable (puntVector) de tal forma que, al realizar la operación new se

reserve el espacio necesario para almacenar un vector de 100 caracteres.

Además de la operación ya indicada de reserva (new), las referencias admiten las

siguientes operaciones:

puntero

Variable de trabajo (apuntada por puntero)

Memoria estática

Memoria dinámica

34 INTRODUCCIÓN A LA PROGRAMACIÓN ESTRUCTURAS DE DATOS

○ Asignación: <puntero1> = <puntero2>. La referencia <puntero2> se copia en

<puntero1>, o dicho en otros términos: <puntero1> deja de apuntar al nodo a que

apuntaba previamente para pasar a apuntar al nodo apuntado por <puntero2>. Una

vez ejecutada la operación ambos punteros apuntan al mismo nodo41

.

○ Comparación: <puntero1> == <puntero2>. Devuelve un valor booleano en función

de que ambas referencias sean iguales o no, o dicho en otros términos: que apunten

o no al mismo nodo.

Aunque, como se ha indicado, una variable de tipo puntero apunta a una zona de

memoria (nodo) existe una situación excepcional consistente en no apuntar a ninguno. En

Java se utiliza para esto la constante null.

Sobre las variables referencia, se realizan las operaciones propias del tipo de datos a

que pertenezcan.

41 Este tipo de operaciones deberá hacerse con especial cuidado pues, si no se han tomado previamente las

precauciones oportunas, podría perderse la información almacenada en el nodo inicialmente apuntado por

<puntero1> (se perderá si no tenemos otra referencia a esa información)

ESTRUCTURAS DE DATOS INTRODUCCIÓN A LA PROGRAMACIÓN 35

1.4.2. Estructuras de datos estáticas.

Una estructura estática se caracteriza porque su tamaño es conocido a priori (antes de

la ejecución del programa). En consecuencia, en el código del programa se declaran

variables de tipo estático y en la compilación se reserva en memoria el espacio necesario

para ellas.

1.4.2.1. Estructuras de datos homogéneas.

En las estructuras de datos homogéneas todos los datos son del mismo tipo. Como

ejemplo veremos los vectores, las tablas y, en general, las matrices n-dimensionales.

1.4.2.1.1. Unidimensionales (vectores).

El caso más sencillo es el vector, que se define como un conjunto de elementos, cada

uno de los cuales puede identificarse mediante su posición relativa (índice42

). La figura

muestra un ejemplo de vector de 5 números enteros.

0 1 2 3 4

20 40 60 80 100

Tabla 1.6. Vector de números enteros.

El lenguaje Java permite utilizar este tipo de estructuras utilizando la sintaxis43

:

Declaración de variables de tipo vector:

<tipo de datos de los elementos del vector> [] <nombre de la variable>;

Por ejemplo, la línea: int [] vector1;

Declara una variable llamada vector1 que es un vector de enteros inicialmente vacío.

Para indicar el tamaño del vector, se puede hacer directamente en la propia línea de

declaración:

Int [] vector1 = new int [5]; //declara un vector de 5 elementos de tipo entero.

Acceso:

o Al conjunto. Por ejemplo asignar un vector a otro del mismo tipo:

vector2 = vector1.

En realidad esta instrucción no copia el contenido de vector1 en vector2, sino que

hace que vector2 apunte a la misma posición de memoria en la que está el vector1.

o A un elemento del vector:

<variable_tipo_vector> [<índice>];

42 En Java el índice es numérico y siempre comienza en 0.

43 Se describen solamente las operaciones básicas. El manejo de este tipo de estructuras admite una gran

flexibilidad. Consultar el manual del lenguaje para mayor información.

36 INTRODUCCIÓN A LA PROGRAMACIÓN ESTRUCTURAS DE DATOS

Con lo que se podrá realizar cualquier operación acorde con el tipo del elemento

correspondiente. Por ejemplo, si datos es un vector de números enteros podríamos hacer:

datos [3] = datos [3] * 2;

Ejemplo.

A continuación se retoma el ejemplo expuesto en el apartado 1.3.4.1 (valores medio y

rango de un conjunto de 5 números enteros) utilizando un vector.

import java.io.*;

public class PruebaVectores {

static float media (int [ ] dato) {

float resul = 0;

int i;

for (i = 0; i < 5; i ++)

resul = resul + dato [i];

resul = resul / i;

return resul;

}

static int max (int [] dato) {

int resul,i;

resul = dato[0];

for (i = 1; i<5; i++)

if (dato[i] > resul)

resul = dato[i];

return resul;

}

static int min (int [] dato) {

int resul,i;

resul = dato[0];

for (i = 1; i < 5; i ++)

if (dato [i] < resul)

resul = dato [i];

return resul;

}

public static void main(String [] args)throws NumberFormatException,IOException{

int [ ] d = new int [5];

int i, rango;

float media;

BufferedReader linea = new BufferedReader(new InputStreamReader(System.in));

System.out.println ("Introduce cinco números enteros: ");

for (i = 0; i < 5; i ++)

d[i] = Integer.parseInt (linea.readLine ());

media = media (d);

System.out.println ("El valor medio es: " + media);

rango = max (d) - min (d);

System.out.println ("y el rango: " + rango);

}

}

ESTRUCTURAS DE DATOS INTRODUCCIÓN A LA PROGRAMACIÓN 37

1.4.2.1.2. Bidimensionales (matrices).

Una matriz es una estructura de dos dimensiones: horizontal (filas) y vertical

(columnas), que contiene elementos del mismo tipo. Se puede hacer referencia a cada uno

de los elementos (celdas) mediante un índice de fila y otro de columna.

Por ejemplo, la tabla siguiente (temperaturas) muestra las temperaturas máxima y

mínima a lo largo de una semana (las filas indican el día de la semana y las columnas las

temperaturas mínima y máxima, respectivamente. Por ejemplo, la temperatura mínima del

4º día es: temperaturas (3, 0) = 5 grados).

temperaturas 0 1

0 7 15

1 8 17

2 6 13

3 5 14

4 7 14

5 6 16

6 5 13

Tabla 1.7. Temperaturas a lo largo de una semana

Los lenguajes de programación permiten utilizar este tipo de estructuras. Para ésto en

Java se emplea la siguiente sintaxis:

Declaración de variables de tipo matriz:

< tipo de datos de los elementos > [ ] [ ] <nombre de la variable>;

Ejemplo: int [ ] [ ] temperaturas;

Acceso:

o Al conjunto. Por ejemplo asignar una matriz a otra del mismo tipo:

matriz2 = matriz1.

Como en el caso de los vectores, esta instrucción no copia el contenido de matriz1 en

matriz2, sino que hace que matriz2 apunte a la misma posición de memoria que matriz1.

o A un elemento de la matriz:

<variable_tipo_matriz>[<índice1>] [<índice2>];

(Con lo que se podrá realizar cualquier operación acorde con el tipo del elemento

correspondiente). Por ejemplo: temperaturas [3] [1] = temperaturas [3] [1] + 2;

Ejemplo.

El siguiente código permite encontrar la temperatura mínima de la semana y el día en

que se produjo la máxima diferencia (en caso de coincidencia de varias se muestra el

primero de ellos). En el ejemplo anterior los resultados serían:

Temperatura mínima: 5 grados.

Día de máxima diferencia de temperaturas: 5 (10 grados).

38 INTRODUCCIÓN A LA PROGRAMACIÓN ESTRUCTURAS DE DATOS

import java.io.*;

public class PruebaMatrices {

static BufferedReader linea=new BufferedReader(new

InputStreamReader(System.in));

public static void leerMatriz (int [][] temperaturas) throws

NumberFormatException, IOException{

int i,j;

for (i = 0;i < 7; i ++)

for (j = 0; j < 2; j ++) {

System.out.println ("Valor dia: " + i + " extremo: " + j + ": ");

temperaturas [i] [j] = Integer.parseInt (linea.readLine ());

}

}

public static int min (int [][] temperaturas) {

int resul, i;

resul = temperaturas [0] [0];

for (i = 1; i < 7; i++)

if (temperaturas [i] [0] < resul)

resul = temperaturas [i] [0];

return resul;

}

public static int maxDif (int [] [] temperaturas) {

int resul, i, dif;

resul = 0;

dif = temperaturas [0][1]- temperaturas [0][0];

for (i = 1; i < 7; i++) {

if (temperaturas [i][1] - temperaturas [i][0] > dif) {

dif = temperaturas [i][1] - temperaturas [i][0];

resul = i;

}

}

return resul;

}

public static void main(String[] args) throws NumberFormatException,

IOException{

int [][] temperaturas = new int [7][2];

int minimaTemperatura, diferenciaTemperaturas;

leerMatriz (temperaturas);

minimaTemperatura = min (temperaturas);

diferenciaTemperaturas = maxDif (temperaturas);

System.out.println ("Resultados:");

System.out.println ("Temperatura minima: " + minimaTemperatura);

System.out.println ("Dia extremo: " + diferenciaTemperaturas);

}

}

ESTRUCTURAS DE DATOS INTRODUCCIÓN A LA PROGRAMACIÓN 39

1.4.2.1.3. N-dimensionales.

Por extensión, lo explicado para una y dos dimensiones se puede aplicar al caso de

matrices N-dimensionales.

1.4.2.2. Estructuras de datos heterogéneas.

Como ya se ha indicado, están constituidas por un conjunto de tipos de datos (ya sean

datos simples u otras estructuras de datos) de diferente naturaleza. La forma básica de

estructura de datos heterogénea es el registro cuyos elementos se llaman campos (o

atributos)44

.

Por ejemplo, la figura siguiente muestra un registro (alumno) cuyos campos son: el

número de matrícula (numeroMatricula), apellidos (apellidos), nombre (nombre), dirección

de correo electrónico (eMail), año de nacimiento (anio) y calificación (calificacion).

numeroMatricula apellidos nombre eMail año calificacion

alumno bc2658 Sánchez Arellano Estela [email protected] 1987 6.75

Tabla 1.8. Registro alumno

Para manejar este tipo de estructuras en Java45

, se construye una clase dentro de la

cual se definen los datos que van a formar parte de la estructura, así como uno o varios

constructores, como se puede ver en el siguiente ejemplo (correspondiente a la figura):

class RegistroAlumno {

public String numeroMatricula;

public String apellidos;

public String nombre;

public String eMail;

public int año;

public float calificacion;

public RegistroAlumno (){

numeroMatricula= null;

apellidos = null;

nombre = null;

eMail= null;

año = 1980;

calificacion = 0;

}

44 Con frecuencia uno de los campos (o combinación de ellos) se utiliza como identificativo del registro. A

dicho campo (o conjunto) se le denomina clave (key). 45

Existen otras operaciones. Consultar el manual del lenguaje.

40 INTRODUCCIÓN A LA PROGRAMACIÓN ESTRUCTURAS DE DATOS

Acceso:

o Al conjunto. Por ejemplo apuntar con una variable de tipo registro mismo sitio

que apunta otra del mismo tipo:

variable2_RegistroAlumno = variable1_RegistroAlumno;

o A un campo del registro:

<variable>.<campo>;

Con lo que se podrá realizar cualquier operación acorde con el tipo del

elemento correspondiente. Por ejemplo: alumno.eMail = “[email protected]”;

Normalmente, el trabajo con un registro (o con un conjunto pequeño de variables

independientes de tipo registro) ofrece “poco juego”. Lo habitual es utilizar una “colección”

de registros estructurados en un vector, o mucho mejor, almacenarlos, como un “fichero”

en un dispositivo de almacenamiento externo (típicamente disco).

La utilización de ficheros en dispositivos de almacenamiento externos se explica en el

apartado 1.5. A continuación se muestra un ejemplo que utiliza un vector de registros del

tipo anterior cuyo modelo se ilustra gráficamente en la figura siguiente:

numeroMatricula apellidos nombre eMail año calificacion

0 aa1253 Arias González Felipe [email protected] 1988 3.50

1 ax0074 García Sacedón Manuel [email protected] 1985 8.35

2 mj7726 López Medina Margarita [email protected] 1990 7,70

3 lp1523 Ramírez Heredia Jorge [email protected] 1998 4,50

4 bc2658 Sánchez Arellano Estela [email protected] 1989 6.75

5 gb1305 Yuste Peláez Juan [email protected] 1990 5,50

Tabla 1.9. Vector de registros.

El programa que se muestra a continuación se ha incluido en dos ficheros (clases) que

forman parte del mismo paquete (package):

RegistroAlumno, que contiene el constructor del registro (construye un registro vacío),

así como los métodos aCadena, que devuelve el contenido del registro en un String, y

cargarRegistro, que introduce los datos recogidos por teclado en un registro.

PruebaRegistro, en el que aparece el programa principal e incluye dos métodos,

cargarTabla, que permite cargar la estructura en la memoria central y mediaCalif, que

utiliza la estructura anterior para calcular la calificación media.

Primero se muestra la clase RegistroAlumno:

ESTRUCTURAS DE DATOS INTRODUCCIÓN A LA PROGRAMACIÓN 41

Import java.io.*;

class RegistroAlumno {

public RegistroAlumno () {

numeroMatricula= null;

apellidos = null;

nombre = null;

eMail= null;

año = 1980;

calificacion = 0;

}

public String aCadena () {

return numeroMatricula + " " + apellidos + " " + nombre + " " + eMail +

" " + año + " " + calificacion;

}

public String numeroMatricula;

public String apellidos;

public String nombre;

public String eMail;

public int año;

public float calificacion;

public void cargarRegistro () throws IOException {

BufferedReader linea = new BufferedReader (new InputStreamReader (System.in));

System.out.println ("Numero de matricula: ");

numeroMatricula = new String (linea.readLine ());

System.out.println ("Apellidos: ");

apellidos = new String (linea.readLine ());

System.out.println ("Nombre: ");

nombre = new String (linea.readLine ());

System.out.println ("Correo electronico: ");

eMail = new String (linea.readLine ());

System.out.println ("Año de nacimiento: ");

año = Integer.parseInt (linea.readLine());

System.out.println ("Calificación: ");

calificacion = Float.parseFloat (linea.readLine());

System.out.println (this.aCadena ());

}

}

Como se ha visto en el apartado 1.2.4., los métodos aCadena y cargarRegistro son

métodos de objeto (sin modificador static), y para utilizarlos desde fuera de la clase tendrá

que hacerse de la forma <nombreVariable>.<nombreMétodo>. Para poder utilizar los

métodos de objeto, es necesario que previamente hayamos utilizado el constructor del

objeto sobre la variable correspondiente, como hacemos con las instrucciones:

for (i = 0; i < 6;i++)

alumnos [i]= new RegistroAlumno ();

A continuación aparece la clase PruebaRegistro, donde se incluye el programa

principal:

42 INTRODUCCIÓN A LA PROGRAMACIÓN ESTRUCTURAS DE DATOS

import java.io.*;

public class PruebaRegistro {

static void cargarTabla (RegistroAlumno [ ] alumnos) throws IOException {

int i;

for (i = 0; i < 6; i++) {

System.out.println ("Datos del alumno N: "+ i);

alumnos [i].cargarRegistro ();

}

}

static float mediaCalif (RegistroAlumno [ ] alumnos) {

float resul = 0;

int i;

for (i = 0; i < 6; i++) {

System.out.println(alumnos [i].aCadena ());

resul = resul + alumnos [i].calificacion;

}

return resul/6;

}

public static void main (String [ ] args) throws IOException {

RegistroAlumno [ ] alumnos = new RegistroAlumno [6];

float media;

int i;

for (i = 0; i < 6; i++)

alumnos [i]= new RegistroAlumno ();

cargarTabla (alumnos);

media = mediaCalif (alumnos);

System.out.println ("La media de las calificaciones es: " + media);

}

}

ESTRUCTURAS DE DATOS INTRODUCCIÓN A LA PROGRAMACIÓN 43

1.5. UTILIZACIÓN DE DISPOSITIVOS DE ALMACENAMIENTO

EXTERNO.

Con frecuencia las aplicaciones informáticas trabajan sobre datos almacenados de

forma estable en dispositivos de almacenamiento (típicamente discos magnéticos).

Una primera vez el usuario crea un “fichero” (file) y lo “guarda” en un disco.

Posteriormente podrá utilizar dicho fichero, consultar y modificar los datos, así como

añadir nuevos o eliminar alguno/s de los actuales.

La utilización de ficheros en disco es muy amplia y va más allá de los objetivos de

este curso. Simplemente indicar que los ficheros están constituidos por un conjunto de

registros que definen las características de cada uno de los elementos (“fichas”) del

fichero46

.

Otro aspecto a considerar es cómo organizar las “fichas” dentro del dispositivo

(fichero). Con soportes tradicionales la forma de organización es única (por ejemplo,

alfabética) y secuencial (una ficha tras otra, en el orden predefinido). La Informática ha

heredado esta idea aunque permite otras modalidades de organización y modos de acceso

adicionales a los puramente únicos y secuenciales.

1.5.1. Ficheros de texto.

En un fichero de texto, cada una de sus “fichas” consiste en una cadena de caracteres

de longitud variable (líneas de texto). Las “fichas” se almacenan una tras otra sin mantener

ningún criterio de orden entre sí. Por ejemplo (marsell.txt):

Allons enfants de la Patrie,

Le jour de gloire est arrivé!

Contre nous de la tyrannie,

L'étendard sanglant est levé,(bis)

Entendez-vous dans le campagnes,

Mugir ces féroces soldats?

Ils viennent jusque dans nos bras,

Égorger nos fils, nos compagnes!

Entre cada una de las líneas existen “códigos invisibles” cuyo efecto es hacer saltar al

inicio de la línea siguiente. Estos códigos se generan automáticamente al pulsar la tecla

“Intro”. Así mismo, para indicar el final del fichero de texto, el usuario deberá haber

pulsado la tecla de función “F6”.

La figura siguiente muestra el código ASCII correspondiente al ejemplo.

46 Este concepto está heredado de los antiguos “ficheros” (o archivadores) que contenían “fichas” (de papel)

cada una de las cuales representaba una información unitaria: ficheros de películas, personas, asignaturas….

44 INTRODUCCIÓN A LA PROGRAMACIÓN ESTRUCTURAS DE DATOS

Figura 1.7. Códigos de fin de línea y retorno de carro en un fichero de texto.

La figura siguiente muestra, a título de ejemplo, un programa que crea un fichero

(archivo.txt)47

e introduce en él dos líneas de texto (procedentes del teclado y recogidas,

sucesivamente, en la variable, de tipo String, lineaLeida). Una vez finalizado el proceso el

fichero deberá “cerrarse” para permitir posteriores usos del mismo.

Figura 1.8. Funcionamiento del programa

47 La ruta, nombre y extensión del fichero se eligen libremente (con las restricciones propias del sistema

operativo). En el ejemplo se han utilizado:

Ruta: (por omisión) la misma que el programa.

Nombre: archivo.

Extensión txt (para poder visualizarlo mediante el bloc de notas –notepad- de Windows).

Pareja de códigos (ocultos):

Salto de línea (LF): „X‟=0A; dec=10

Retorno de “carro” (CR): „X‟=0D; dec=13

Marse.txt

lineaLeida

import java.io.*; public class prueba_fichero_texto {

public static void main(String[] args) throws IOException {

FileWriter fich_s = new FileWriter ("archivo.txt"); BufferedWriter bw = new BufferedWriter (fich_s); PrintWriter salida = new PrintWriter (bw); BufferedReader linea = new BufferedReader (new InputStreamReader(System.in)); String lineaLeida;

System.out.println ("Escriba la primera linea: "); lineaLeida = new String (linea.readLine()); salida.println (lineaLeida); System.out.println ("Escriba la segunda linea: "); lineaLeida = new String (linea.readLine()); salida.println (lineaLeida); salida.close(); } }

ESTRUCTURAS DE DATOS INTRODUCCIÓN A LA PROGRAMACIÓN 45

import java.io.*;

public class PruebaFicheroSalida {

public static void main (String [ ] args) throws IOException {

FileWriter fich_s = new FileWriter ("archivo.txt");

BufferedWriter bw = new BufferedWriter (fich_s);

PrintWriter salida = new PrintWriter (bw);

BufferedReader linea = new BufferedReader (new InputStreamReader(System.in));

String lineaLeida;

System.out.println ("Escriba la primera linea: ");

lineaLeida = new String (linea.readLine ());

salida.println (lineaLeida);

System.out.println ("Escriba la segunda linea: ");

lineaLeida = new String (linea.readLine ());

salida.println (lineaLeida);

salida.close ();

}

}

La figura siguiente ilustra un ejemplo de posible resultado de la ejecución del

programa anterior.

Figura 1.9. Resultado de la ejecución del programa

46 INTRODUCCIÓN A LA PROGRAMACIÓN ESTRUCTURAS DE DATOS

1.5.1.1. Principales consideraciones semánticas.

El programador no es consciente del fichero físico con que está trabajando. Se crea

un nombre simbólico (por ejemplo fich_s), y sobre él se realizan todas las

operaciones48

. Sólo existe una excepción, la sentencia:

FileWriter fich_s = new FileWriter ("archivo.txt");

establece una vinculación entre el nombre físico del fichero y el nombre lógico.

Se utiliza una variable de tipo String (en el ejemplo: lineaLeida) como “puente”

entre la memoria del ordenador y el dispositivo de almacenamiento (disco). Según

sea el sentido de la transferencia se puede hablar de lectura: disco → memoria

(readline) o de escritura memoria → disco (println).

Hay que preparar el fichero en disco para utilizarlo (apertura) y dejarlo disponible

para posteriores usos (cierre).

1.5.1.2. Sintaxis de las principales operaciones relacionadas con los ficheros de texto49

.

Acceso (Asignación y apertura) y declaración de variables:

o Crear un nuevo fichero y destruir, en su caso, un fichero previo.

Posteriormente se podrá escribir en él:

FileWriter <nombre lógico> = new FileWriter (<nombre físico>);

BufferedWriter <buffer escritura> = new BufferedWriter (<nombre lógico>);

PrintWriter <variable> = new PrintWriter (<buffer escritura>);

o Preparar un fichero (que debe existir previamente) para poder leer

posteriormente su contenido:

FileReader <nombre lógico> = new FileReader (<nombre físico>);

BufferedReader <buffer lectura> = new BufferedReader (<nombre lógico>);

o Escribir en un fichero (previamente creado) nuevas líneas de texto a partir de

la última:

FileWriter <nombre lógico> = new FileWriter (<nombre físico>,true);

Proceso:

o Leer (transferir a la variable correspondiente) el contenido de una línea del

fichero y prepararse para la siguiente.

<variable tipo String> = <buffer lectura>.readline();

o Escribir (transferir) el contenido de una línea (<expresión tipo String>) al

fichero y prepararse para la siguiente:

<variable>.println = <expresión tipo String>;

48 Dicho fichero estará gestionado por el sistema operativo. Con frecuencia se cambia de ubicación los

ficheros (físicos) y de no seguir esta filosofía, esto implicaría re-escribir parte del código cada vez que un

fichero cambiase de ubicación. 49

Para mayor información consultar el manual de programación.

ESTRUCTURAS DE DATOS INTRODUCCIÓN A LA PROGRAMACIÓN 47

Terminación:

o Dejar el fichero preparado para usos posteriores:

<variable>.close ();

Por ejemplo, si queremos leer un fichero hasta llegar al final lo podemos hacer

utilizando lo siguiente:

import java.io.*;

public class PruebaFicheroEntrada {

public static void main (String [ ] args) throws IOException {

String lineaLeida;

FileReader fichLeido = new FileReader ("archivo.txt");

BufferedReader entrada = new BufferedReader (fichLeido);

System.out.println ("Contenido del fichero: ");

while ((lineaLeida = entrada.readLine ()) != null)

System.out.println (lineaLeida);

entrada.close ();

}

}

1.5.2. Ficheros binarios.

En un fichero binario, las “fichas” son elementos de tipo registro50

. En consecuencia

(entre otras) no se puede visualizar su contenido con un visor de texto del sistema operativo

(por ejemplo, el bloc de notas de Windows).

Para poder guardar objetos (por ejemplo, del tipo RegistroAlumno visto en apartados

anteriores) en un fichero hay que hacerlos “serializables”. Mediante la serialización, un

objeto se convierte en una secuencia de bytes con la que se puede reconstruir

posteriormente manteniendo el valor de sus variables. Esto permite guardar un objeto en un

archivo o mandarlo por red. Una clase se serializa añadiendo en su definición:

implements Serializable

Para poder leer y escribir objetos que se han declarado como serializables se utilizan

las clases ObjectInputStream y ObjectOutputStream, que cuentan con los métodos

writeObject() y readObject().

Para escribir un objeto en un fichero se utilizará:

ObjectOutputStream <objEscrito> = new ObjectOutputStream (new FileOutputStream("<nombre fichero>"));

<objEscrito>.writeObject (<variable>);

50 Ver apartado 1.4.2 “Estructuras de datos heterogéneas”.

48 INTRODUCCIÓN A LA PROGRAMACIÓN ESTRUCTURAS DE DATOS

Mientras que las instrucciones utilizadas para leerlos después serían:

ObjectInputStream <objLeido> = new ObjectInputStream (new FileInputStream ("<nombre fichero>"));

<tipo> <variable> = (<tipo>) <objLeido>.readObject ();

Cuando se ha terminado de leer o escribir el fichero correspondiente es necesario

cerrar el fichero con:

<nombre fichero>.close()

El siguiente ejemplo es una variante del programa de gestión de alumnos visto en el

apartado 1.4.2.2. Sus características más importantes son51

:

Concepción modular: un programa principal y un conjunto de subprogramas.

No se utilizan variables globales.

El programa principal:

o Prepara el fichero en disco:

String nombre;

BufferedReader linea = new BufferedReader (new InputStreamReader(System.in));

System.out.println ("Introducir nombre del fichero: ");

nombre = new String (linea.readLine());

o Su lógica se basa en un planteamiento de “tipo menú”:

Llama al módulo que realiza la interfaz de usuario: menu (sin

argumentos)

Recibe (variable op) la opción seleccionada, la analiza (mediante una

estructura case) e invoca al módulo correspondiente:

crearFichero (fichero).

cargarTabla (alumnos, fichero)

mediaCalif (alumnos)

o Utiliza el menor conjunto posible de información (variables). Obsérvese que

el programa principal no necesita para nada utilizar registros

correspondientes a alumnos individuales (tipo RegistroAlumno).

El procedimiento menu. Es autónomo (no necesita recibir ni devolver argumentos).

La función mediaCalif (alumnos). El único argumento que necesita es una tabla de

registros de alumnos (RegistroAlumno []).

El procedimiento cargarTabla (alumnos, fichero). Utiliza como información de

entrada un fichero y genera en memoria (alumnos) una estructura RegistroAlumno

51 Por simplicidad no se han considerado situaciones excepcionales (“por excepción”). Por ejemplo:

No tiene sentido utilizar la opción [2]: “Cargar tabla de registros”, si no existe el fichero en disco.

Tampoco lo tiene utilizar la opción [3]: “Calcular calificación media”, si no se ha ejecutado (con éxito)

previamente la opción [2].

ESTRUCTURAS DE DATOS INTRODUCCIÓN A LA PROGRAMACIÓN 49

[] que quedará a disposición del programa principal. Se encarga de abrir, en modo

lectura, el fichero y cerrarlo al finalizar.

El procedimiento crearFichero (fichero). Genera un fichero binario en disco a partir

de los datos introducidos por el usuario desde el teclado. Abre el fichero en modo

escritura y lo cierra al terminar.

El siguiente esquema muestra gráficamente la arquitectura de la aplicación.

Figura 1.10. Arquitectura del programa ejemplo.

El código del ejemplo sería:

import java.io.*;

public class PruebaFichES {

static void crearFichero (RegistroAlumno [ ] alumnos, String nombref) throws

IOException {

int i;

FileOutputStream fich = new FileOutputStream (nombref);

ObjectOutputStream objEscrito = new ObjectOutputStream (fich);

for (i = 0; i < 6; i++) {

alumnos [i] = new RegistroAlumno();

System.out.println ("Datos del alumno N: "+ i);

alumnos [i].cargarRegistro ();

objEscrito.writeObject (alumnos [i]);

}

fich.close ();

}

static void cargarTabla (RegistroAlumno [ ] alumnos, String nombref) throws

IOException, ClassNotFoundException {

int i;

FileInputStream fich =new FileInputStream (nombref);

ObjectInputStream objLeido = new ObjectInputStream (fich);

for (i = 0; i < 6; i++) {

alumnos [i] = (RegistroAlumno) objLeido.readObject ();

System.out.println ("Datos del alumno N: " + i);

System.out.println(alumnos [i].aCadena ());

}

fich.close ();

}

Inicio menu

op

crearFichero cargarTabla mediaCalif

Módulo principal

Fin

50 INTRODUCCIÓN A LA PROGRAMACIÓN ESTRUCTURAS DE DATOS

static float mediaCalif (RegistroAlumno [ ] alumnos) {

float resul;

int i;

resul = 0;

for (i = 0; i < 6; i++) {

System.out.println (alumnos [i].aCadena ());

resul = resul + alumnos [i].calificacion;}

return resul/6;

}

static void menu () {

System.out.println ("OPCIONES:");

System.out.println ("Opcion 1: Crear fichero.");

System.out.println ("Opcion 2: Cargar tabla de registros.");

System.out.println ("Opcion 3: Calcular calificacion media.");

System.out.println ("Opcion 0: Salir.");

System.out.println ("\n Introduzca opcion: ");

}

public static void main (String[] args) throws IOException,

ClassNotFoundException {

RegistroAlumno [ ] alumnos = new RegistroAlumno [6];

float media;

int op;

String nombre;

BufferedReader linea = new BufferedReader (new InputStreamReader(System.in));

System.out.println ("Introducir nombre del fichero: ");

nombre = new String (linea.readLine ());

menu();

op = Integer.parseInt (linea.readLine ());

while (op != 0) {

switch (op) {

case 1: crearFichero (alumnos,nombre);

break;

case 2: cargarTabla (alumnos, nombre);

break;

case 3: media = mediaCalif (alumnos);

System.out.println ("La media de las calificaciones es: "+media);

break;

default: System.out.println("opción no valida");

break;

}

menu ();

op = Integer.parseInt (linea.readLine ());

}

System.out.println ("Adios");

}

}

ESTRUCTURAS DE DATOS INTRODUCCIÓN A LA PROGRAMACIÓN 51

TEMA 1. ....................................................................................................................... 1

1.1. Conceptos básicos. ......................................................................................... 1

1.1.1. Características específicas del lenguaje Java. ........................................ 3

1.1.2. Entorno de programación (IDE). ............................................................ 4

1.2. Sintaxis del lenguaje Java. .............................................................................. 5

1.2.1. Estructura de un programa en Java. ........................................................ 5

1.2.2. Nomenclatura habitual en Java. .............................................................. 6

1.2.3. Los datos. ................................................................................................ 6

1.2.4. Sentencias. ............................................................................................ 10

1.3. Programación modular. Subprogramas. ....................................................... 18

1.3.1. Reflexión previa. .................................................................................. 18

1.3.2. Tecnología para la programación modular. .......................................... 18

1.3.3. Mecanismos para el paso de información. ........................................... 20

1.3.4. Aplicación al lenguaje Java. ................................................................. 21

1.3.5. Recursividad. ........................................................................................ 24

1.4. Estructuras de datos. ..................................................................................... 32

1.4.1. Estructuras de datos dinámicas ............................................................. 32

1.4.2. Estructuras de datos estáticas. .............................................................. 35

1.5. Utilización de dispositivos de almacenamiento externo. ............................. 43

1.5.1. Ficheros de texto. ................................................................................. 43

1.5.2. Ficheros binarios. ................................................................................. 47