SlideShare una empresa de Scribd logo
1 de 51
Descargar para leer sin conexión
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” true
12
== 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ón
16
>;
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 esanchez@servidor.es 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 = “esanchez@servidor.es”;
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 farias@servidor.es 1988 3.50
1 ax0074 García Sacedón Manuel mgarcia@servidor.es 1985 8.35
2 mj7726 López Medina Margarita mlopez@servidor.es 1990 7,70
3 lp1523 Ramírez Heredia Jorge jramirez@servidor.es 1998 4,50
4 bc2658 Sánchez Arellano Estela esanchez@servidor.es 1989 6.75
5 gb1305 Yuste Peláez Juan jyuste@servidor.es 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

Más contenido relacionado

La actualidad más candente

Manual NeuroSolutions
Manual NeuroSolutionsManual NeuroSolutions
Manual NeuroSolutionsESCOM
 
Arquitectura de una computadora
Arquitectura de una computadoraArquitectura de una computadora
Arquitectura de una computadorajessiicasaldana
 
Conceptos básicos de programación
Conceptos básicos de programaciónConceptos básicos de programación
Conceptos básicos de programaciónUPEL
 
Trabajo lenguajes de programacion
Trabajo lenguajes de programacionTrabajo lenguajes de programacion
Trabajo lenguajes de programacionJulian Suna Paez
 
Comparticion de recursos
Comparticion de recursosComparticion de recursos
Comparticion de recursosGabs Dempsey
 
Fundamentos de programacion
Fundamentos de programacionFundamentos de programacion
Fundamentos de programacionMaritere Cruz
 
Fundamentos de programacion
Fundamentos de programacionFundamentos de programacion
Fundamentos de programacionJesus Chaux
 
Presentación trabajo programación
Presentación trabajo programaciónPresentación trabajo programación
Presentación trabajo programaciónionurrutia
 
Resumen introduccion a la programacion
Resumen introduccion a la programacionResumen introduccion a la programacion
Resumen introduccion a la programacionVictor Galicia
 
Glosario De Trabajo De Examen
Glosario De Trabajo De ExamenGlosario De Trabajo De Examen
Glosario De Trabajo De ExamenJoel Soto
 

La actualidad más candente (19)

Practica1
Practica1Practica1
Practica1
 
Programa informático
Programa informáticoPrograma informático
Programa informático
 
Manual NeuroSolutions
Manual NeuroSolutionsManual NeuroSolutions
Manual NeuroSolutions
 
Unidad 1. Introducción. Conceptos fundamentales de la POO
Unidad 1. Introducción. Conceptos fundamentales de la POOUnidad 1. Introducción. Conceptos fundamentales de la POO
Unidad 1. Introducción. Conceptos fundamentales de la POO
 
Arquitectura de una computadora
Arquitectura de una computadoraArquitectura de una computadora
Arquitectura de una computadora
 
Conceptos básicos de programación
Conceptos básicos de programaciónConceptos básicos de programación
Conceptos básicos de programación
 
Compiladores
CompiladoresCompiladores
Compiladores
 
Unidad 2
Unidad 2Unidad 2
Unidad 2
 
Modelo Dinamico
Modelo DinamicoModelo Dinamico
Modelo Dinamico
 
Unidad 2 margie
Unidad 2 margieUnidad 2 margie
Unidad 2 margie
 
Tema3 procesos
Tema3 procesos Tema3 procesos
Tema3 procesos
 
Tarea
TareaTarea
Tarea
 
Trabajo lenguajes de programacion
Trabajo lenguajes de programacionTrabajo lenguajes de programacion
Trabajo lenguajes de programacion
 
Comparticion de recursos
Comparticion de recursosComparticion de recursos
Comparticion de recursos
 
Fundamentos de programacion
Fundamentos de programacionFundamentos de programacion
Fundamentos de programacion
 
Fundamentos de programacion
Fundamentos de programacionFundamentos de programacion
Fundamentos de programacion
 
Presentación trabajo programación
Presentación trabajo programaciónPresentación trabajo programación
Presentación trabajo programación
 
Resumen introduccion a la programacion
Resumen introduccion a la programacionResumen introduccion a la programacion
Resumen introduccion a la programacion
 
Glosario De Trabajo De Examen
Glosario De Trabajo De ExamenGlosario De Trabajo De Examen
Glosario De Trabajo De Examen
 

Similar a Conceptos estructuras de datos

[ES] Fundamentos esenciales de la plataforma java
[ES] Fundamentos esenciales de la plataforma java[ES] Fundamentos esenciales de la plataforma java
[ES] Fundamentos esenciales de la plataforma javaEudris Cabrera
 
Lenguajes de programación orientados a objetos
Lenguajes de programación orientados a objetosLenguajes de programación orientados a objetos
Lenguajes de programación orientados a objetosDoris Aguagallo
 
Lec11 metodos
Lec11 metodosLec11 metodos
Lec11 metodoshtmrk
 
Nuevo presentación de microsoft office power point
Nuevo presentación de microsoft office power pointNuevo presentación de microsoft office power point
Nuevo presentación de microsoft office power pointJ_cordero
 
Presentacion aplicaciones en java
Presentacion aplicaciones en javaPresentacion aplicaciones en java
Presentacion aplicaciones en javarsalazar16988
 
Tarea3 Ezamora
Tarea3 EzamoraTarea3 Ezamora
Tarea3 Ezamoraedzamo13
 
Programacion no numerica ii
Programacion no numerica iiProgramacion no numerica ii
Programacion no numerica iirolmanpaul
 
investigacion unidad tres componentes y librerias
investigacion unidad tres componentes y libreriasinvestigacion unidad tres componentes y librerias
investigacion unidad tres componentes y libreriasAnel Sosa
 
Apendice general 4 terminos de programador
Apendice general 4 terminos de programadorApendice general 4 terminos de programador
Apendice general 4 terminos de programadorWilson Delgado Ramos
 
Entorno de Desarrollo Orientado a Objetos Java.pptx
Entorno de Desarrollo Orientado a Objetos Java.pptxEntorno de Desarrollo Orientado a Objetos Java.pptx
Entorno de Desarrollo Orientado a Objetos Java.pptxssuserdfcf9e
 
Arquitectura del JDK.pptx
Arquitectura del JDK.pptxArquitectura del JDK.pptx
Arquitectura del JDK.pptxAnthonySacha
 

Similar a Conceptos estructuras de datos (20)

Ap01 java
Ap01 javaAp01 java
Ap01 java
 
Guia3 java
Guia3 javaGuia3 java
Guia3 java
 
Semana9 Vbr
Semana9 VbrSemana9 Vbr
Semana9 Vbr
 
[ES] Fundamentos esenciales de la plataforma java
[ES] Fundamentos esenciales de la plataforma java[ES] Fundamentos esenciales de la plataforma java
[ES] Fundamentos esenciales de la plataforma java
 
Lenguajes de programación orientados a objetos
Lenguajes de programación orientados a objetosLenguajes de programación orientados a objetos
Lenguajes de programación orientados a objetos
 
Lec11 metodos
Lec11 metodosLec11 metodos
Lec11 metodos
 
UNIDAD 3 MODULARIZACIÓN
UNIDAD 3 MODULARIZACIÓNUNIDAD 3 MODULARIZACIÓN
UNIDAD 3 MODULARIZACIÓN
 
Nuevo presentación de microsoft office power point
Nuevo presentación de microsoft office power pointNuevo presentación de microsoft office power point
Nuevo presentación de microsoft office power point
 
Framework
FrameworkFramework
Framework
 
Presentacion aplicaciones en java
Presentacion aplicaciones en javaPresentacion aplicaciones en java
Presentacion aplicaciones en java
 
Tarea3 Ezamora
Tarea3 EzamoraTarea3 Ezamora
Tarea3 Ezamora
 
Programacion no numerica ii
Programacion no numerica iiProgramacion no numerica ii
Programacion no numerica ii
 
investigacion unidad tres componentes y librerias
investigacion unidad tres componentes y libreriasinvestigacion unidad tres componentes y librerias
investigacion unidad tres componentes y librerias
 
Lp pract2006
Lp pract2006Lp pract2006
Lp pract2006
 
Apendice general 4 terminos de programador
Apendice general 4 terminos de programadorApendice general 4 terminos de programador
Apendice general 4 terminos de programador
 
Java
JavaJava
Java
 
Java basico
Java basicoJava basico
Java basico
 
Entorno de Desarrollo Orientado a Objetos Java.pptx
Entorno de Desarrollo Orientado a Objetos Java.pptxEntorno de Desarrollo Orientado a Objetos Java.pptx
Entorno de Desarrollo Orientado a Objetos Java.pptx
 
Apendice general 4
Apendice general 4Apendice general 4
Apendice general 4
 
Arquitectura del JDK.pptx
Arquitectura del JDK.pptxArquitectura del JDK.pptx
Arquitectura del JDK.pptx
 

Conceptos estructuras de datos

  • 1. 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. 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
  • 3. 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. 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.
  • 5. 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. 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.
  • 7. 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. 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” true 12 == 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.
  • 9. 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. 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ón 16 >; 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).
  • 11. 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. 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).
  • 13. 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. 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; } } }
  • 15. 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. 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.
  • 17. 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. 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.
  • 19. 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. 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.
  • 21. 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. 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).
  • 23. 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. 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).
  • 25. 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. 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.
  • 27. 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. 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.
  • 29. 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. 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.
  • 31. 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. 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.
  • 33. 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. 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)
  • 35. 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. 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); } }
  • 37. 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. 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); } }
  • 39. 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 esanchez@servidor.es 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. 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 = “esanchez@servidor.es”; 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 farias@servidor.es 1988 3.50 1 ax0074 García Sacedón Manuel mgarcia@servidor.es 1985 8.35 2 mj7726 López Medina Margarita mlopez@servidor.es 1990 7,70 3 lp1523 Ramírez Heredia Jorge jramirez@servidor.es 1998 4,50 4 bc2658 Sánchez Arellano Estela esanchez@servidor.es 1989 6.75 5 gb1305 Yuste Peláez Juan jyuste@servidor.es 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:
  • 41. 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. 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); } }
  • 43. 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. 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(); } }
  • 45. 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. 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.
  • 47. 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. 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].
  • 49. 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. 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"); } }
  • 51. 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