Unidad 1 Topicos Selectos de Programacion

25
Unidad I Unidad I Unidad I Unidad I Tipos de Datos Definidos por el usuario Tipos de Datos Definidos por el usuario Tipos de Datos Definidos por el usuario Tipos de Datos Definidos por el usuario 1.1 Agregación de datos ( 1.1 Agregación de datos ( 1.1 Agregación de datos ( 1.1 Agregación de datos (struct struct struct struct). ). ). ). Se trata de la forma más versátil de trabajar con fichas de información. Veamos como se definen y posteriormente comentaremos todos los aspectos relevantes de ellas. struct [Nombre_de_la_estructura] { tipo1 campo1; tipo2 campo2; . . tipoN campoN; } [variable]; La palabra clave struct define una estructura. Por tratarse de un tipo de datos puede utilizarse directamente para definir una variable. La variable aparece entre corchetes puesto que puede ser omitida. Si se especifica una variable, estaremos definiendo una variable cuyo tipo será la estructura que la precede. Si la variable no es indicada definimos un nuevo tipo de datos (struct Nombre_de_la_estructura), que podremos utilizar posteriormente. Si es el nombre de la estructura lo que se omite, tendremos que especificar obligatoriamente una variable que tendrá esa estructura y no podremos definir otras variables con esa estructura sin tener que volver a especificar todos los campos. Lo que se encuentra dentro de las llaves es una definición típica de variables con su tipo y su identificador. Todo esto puede parecer un poco confuso pero lo aclararemos con unos ejemplos. struct punto { float x; float y; int color; } punto_de_fuga; Aquí estamos definiendo una variable llamada punto_de_fuga cuyo tipo es una estructura de datos formada por tres campos y a la que hemos llamado punto. Dos de ellos son de tipo float y representan las coordenadas del punto, el tercer valor es un entero que indica el color de ese punto. En este caso hemos definido una variable y una estructura. Al disponer de un identificador para esta última podemos definir nuevas variables de esta estructura. struct punto origen1; struct punto final1; Donde origen1 y final1 son variables de tipo struct punto que hemos definido anteriormente. Si en la definición de punto_de_fuga no se

Transcript of Unidad 1 Topicos Selectos de Programacion

Page 1: Unidad 1 Topicos Selectos de Programacion

Unidad IUnidad IUnidad IUnidad I Tipos de Datos Definidos por el usuarioTipos de Datos Definidos por el usuarioTipos de Datos Definidos por el usuarioTipos de Datos Definidos por el usuario

1.1 Agregación de datos (1.1 Agregación de datos (1.1 Agregación de datos (1.1 Agregación de datos (structstructstructstruct).).).).

Se trata de la forma más versátil de trabajar con fichas de información. Veamos como se definen y posteriormente comentaremos todos los aspectos relevantes de ellas. struct [Nombre_de_la_estructura] { tipo1 campo1; tipo2 campo2; . . tipoN campoN; } [variable];

La palabra clave struct define una estructura. Por tratarse de un tipo de datos puede utilizarse directamente para definir una variable. La variable aparece entre corchetes puesto que puede ser omitida. Si se especifica una variable, estaremos definiendo una variable cuyo tipo será la estructura que la precede. Si la variable no es indicada definimos un nuevo tipo de datos (struct Nombre_de_la_estructura), que podremos utilizar posteriormente. Si es el nombre de la estructura lo que se omite, tendremos que especificar obligatoriamente una variable que tendrá esa estructura y no podremos definir otras variables con esa estructura sin tener que volver a especificar todos los campos. Lo que se encuentra dentro de las llaves es una definición típica de variables con su tipo y su identificador. Todo esto puede parecer un poco confuso pero lo aclararemos con unos ejemplos. struct punto { float x; float y; int color; } punto_de_fuga;

Aquí estamos definiendo una variable llamada punto_de_fuga cuyo tipo es una estructura de datos formada por tres campos y a la que hemos llamado punto. Dos de ellos son de tipo float y representan las coordenadas del punto, el tercer valor es un entero que indica el color de ese punto. En este caso hemos definido una variable y una estructura. Al disponer de un identificador para esta última podemos definir nuevas variables de esta estructura. struct punto origen1; struct punto final1;

Donde origen1 y final1 son variables de tipo struct punto que hemos definido anteriormente. Si en la definición de punto_de_fuga no se

Page 2: Unidad 1 Topicos Selectos de Programacion

hubiese incluído un identificador para la estructura (en este caso el identificador es punto), no podríamos definir nuevas variables con esa estructura ya que no estaría identificada por ningún nombre.

También podríamos haber excluído el nombre de la variable (punto_de_fuga). En este caso lo que definiríamos sería una estructura llamada punto que pasaría a ser un nuevo tipo disponible por el usuario. Así los tipos de variables de que dispondríamos ahora serían: int float double char struct punto Por tanto podríamos definir cualquier variable con estos tipos o incluso definir matriz de estos tipos. struct punto matriz_de_puntos[30]; Así estaríamos definiendo una matriz de 30 elementos en la que cada elemento es una struct punto con sus tres campos. Lo que ahora nos interesa es saber como referenciar esos campos y acceder o modificar, por tanto la información que contienen. Esto se consigue separando el identificador del campo de la variable mediante un punto.

Struct se puede considerar como una clase ligera. Puede contener

constructores, constantes, campos, métodos, propiedades, indicadores,

operadores y tipos anidados todo lo que una clase puede contener.

En el ejemplo siguiente, se inicializa struct con la palabra clave new, se

llama al constructor predeterminado sin parámetros y, a continuación, se

establecen los miembros de la instancia.

Si quieres simular un struct en Java, simplemente lo que tienes que hacer es

definir una clase pública con todas las propiedades públicas:

public class Animal

{

public String especie;

public string genero;

}

Lo que ocurre es que, teniendo la potencia de la programación orientada a

objetos no es recomendable definir clases así, pues las hace más sensibles a los

cambios y el acoplamiento entre clases es mayor, lo cual es algo a evitar. Es mejor

definir métodos set/get, que permiten modificar luego la estructura interna de la

Page 3: Unidad 1 Topicos Selectos de Programacion

clase sin que cambie la interfaz, además de garantizar que las propiedades

toman valores adecuados.

Hay algo gracioso con java por que puedes hacer un struct pero es una

palabra reservada que no se usa en java, por ejemplo, C# admite la palabra clave

struct, que es otro elemento que se origina en C pero no está disponible en Java.

Aquí tambien struct se puede considerar como una clase ligera. Aunque las

structs pueden contener constructores, constantes, campos, métodos,

propiedades, indizadores, operadores y tipos anidados, se utilizan

principalmente para encapsular grupos de campos relacionados. Dado que las

estructuras son tipos de valor, se pueden asignar de un modo ligeramente más

eficaz que las clases.

Diferencia entre Diferencia entre Diferencia entre Diferencia entre structstructstructstruct y clases y clases y clases y clases

Las structs difieren de las clases en que no pueden ser abstractas y no

admiten la herencia de implementaciones.

Ejemplo:

public class Animal

{

private String especie;

private String altura;

public Animal(String especie,String genero)

{

setespecie(especie);

setgenero(genero);

}

public void setespecie(String especie)

{

If (especie==null)

{ throw new IllegalArgumentException("la especie no debe ser

nulo"); }

else

Page 4: Unidad 1 Topicos Selectos de Programacion

this.especie=especie; }

public String getespecie()

{ return especie: }

public void setaltura(String altura)

{ If (altura>=0)

throw new IllegalArgumentException("La altura debe ser positiva: "+altura);

else

this.altura=altura;

public int getAltura()

{ return altura; }

}

}

Estructuras Estructuras Estructuras Estructuras

Ejemplo:

// IDL

struct Date

{ short year;

short month; // 1-12

short day; // 1-31

};

// Java

final public class Date

implements org.omg.CORBA.portable.IDLEntity

{ public short year;

public short month;

public short day;

public Date() {}

public Date(short year, short month, short day)

Page 5: Unidad 1 Topicos Selectos de Programacion

{ ... }

}

Tipos de Datos Definidos por el usuarioTipos de Datos Definidos por el usuarioTipos de Datos Definidos por el usuarioTipos de Datos Definidos por el usuario

Tipos de datos fundamentales Tipos de datos fundamentales Tipos de datos fundamentales Tipos de datos fundamentales

Char Representa un carácter en código ASCII, también se puede interpretar como un entero.

Short int Indica un entero de tamaño corto. int Entero igual que integer en Pascal. long int Entero largo. unsigned short int Como short int pero sin signo. unsigned int Como int pero sin signo. unsigned long int Como long int pero sin signo. float Flotante corto. Análogo al single de Pascal. double Flotante largo. Análogo al double de Pascal. void No indica ningún tipo. Es el tipo de las funciones que no devuelven nada. Los tipos short int, long int, unsigned int y long float se pueden escribir como: short, long, unsigned y double. Con respecto al tamaño que ocupan en memoria variables de estos tipos, todo lo que garantiza C es: sizeof(char) = 1 sizeof(short) <= sizeof(int) <= sizeof(long) sizeof(unsigned) = sizeof(int) sizeof(float) =< sizeof(double) Donde sizeof es un operador que está incorporado en C y devuelve el número de bytes que tiene un objeto.

Page 6: Unidad 1 Topicos Selectos de Programacion

Hay un grupo de modificadores que indican la forma que se almacena una determinada variable de un determinado tipo. Se indica antes del tipo de la variable. Static Cuando se invoca a una función por segunda vez se pierden los valores que las variables locales de la función tenían al acabar la anterior llamada. Declarando una variable de este tipo cuando se llama por segunda vez a la subrutina la variable static (estática) contiene el mismo valor que al acabar la llamada anterior. Auto

Es lo mismo que si no se usara ningún modificador Volatile El compilador debe asumir que la variable está relacionada con un dispositivo y que puede cambiar de valor en cualquier momento. Register El compilador procurará almacenar la variable cualificada de este modo en un registro de la CPU. Extern

La variable se considera declarada en otro fichero. No se le asignará dirección ni espacio de memoria.

La palabra clave struct es usada para definir una estructura, es una forma versátil de trabajar con fichas de información. Este tipo de dato se suele utilizar en la encapsulación de pequeños grupos de variables relacionadas.

Page 7: Unidad 1 Topicos Selectos de Programacion

En sí mismo, es muy parecido a una clase en la programación orientada a objetos, solo que los datos struct no almacenan métodos, solo sirven como manera de agrupación.

Una declaración de estructura define un tipo, y así, es posible también definir variables de este nuevo tipo. Por ejemplo:

public struct Alumno { public String Nombre; public int Edad; public char Sexo; public DateTime Fecha; }; /*Una vez que hemos declarado el tipo de dato Struct, podemos crear

variables de este nuevo tipo de dato.*/ Alumno alumnos; alumnos.Nombre=Andres; alumnos.Edad=19; alumnos.Sexo=’M’; alumnos.Fecha=new DateTime(13/05/89);

1.2 Unión de datos1.2 Unión de datos1.2 Unión de datos1.2 Unión de datos

La herencia en javaLa herencia en javaLa herencia en javaLa herencia en java

Java permite el empleo de la herencia, característica muy potente que permite definir una clase tomando como base a otra clase ya existente. Esto es una de las bases de la reutilización de código, en lugar de copiar y pegar.

En java, como ya vimos la herencia se especifica agregando la claúsula extends después del nombre de la clase. En la claúsula extends indicaremos el nombre de la clase base de la cuál queremos heredar.

Al heredar de una clase base, heredaremos tanto los atributos como los métodos, mientras que los constructores son utilizados, pero no heredados.

Practicas: Construyamos la clase Taxista.java con el siguiente código: public class Taxista extends Persona

{ private int nLicencia; public void setNLicencia(int num)

{ nLicencia = num;

} public int getLicencia() {

return nLicencia; } }

Y construyamos ArranqueTaxista.java:

Page 8: Unidad 1 Topicos Selectos de Programacion

public class ArranqueTaxista {

public static void main (String arg[])

{ Taxista tax1 = new Taxista();

tax1.setNombre("Luis"); tax1.setEdad(50); System.out.println( tax1.getNombre()); System.out.println(tax1.getEdad()); }

} Ahora intentemos usar el constructor que existía en la clase Persona que recibia el nombre de la persona y vamos a usarlo para la clase Taxista. Para ello construyamos la clase ArranqueTaxista2.java: public class ArranqueTaxista2

{ public static void main (String arg[])

{ Taxista tax1 = new Taxista("Jose"); tax1.setEdad(50); System.out.println( tax1.getNombre()); System.out.println(tax1.getEdad()); System.out.println(tax1.getNLicencia()); }

} Se genera un error de compilación, debido a que los constructores no se heredan, sino que hay que definir nuestros propios constructores. Agreguemos en la clase Taxista los siguientes constructores: public Taxista(int licencia) { super(); nLicencia = licencia; } public Taxista(String nombre,int licencia) { super(nombre); nLicencia = licencia; } Ahora si podremos compilar y ejecutar la clase ArranqueTaxista2. La llamada al método super indica que estamos llamando a un constructor de la clase base (pensemos que un Taxista antes que Taxista es Persona y por tanto tiene sentido llamar al constructor de Persona antes que al de Taxista). Además gracias al número de parámetros de la llamada a super podemos especificar cuál de los constructores de la clase base queremos llamar.

Page 9: Unidad 1 Topicos Selectos de Programacion

En java se pueden emplear dos palabras clave: this y super . Como vimos en la introducción a la programación orientada a objetos, this hace alusión a todo el objeto y super hace alusión a la parte heredada, por ello empleamos super para referenciar al constructor de la clase base.

Este tipo de dato (unión) es similar a la estructura, pero no idéntico; una

de sus diferencias más remarcables es que la unión almacena las variables en

un solo campo solapándose unos a otros, mientras que en la estructura, cada

variable cuenta con su campo almacenándose unos detrás de otros. Así pues,

las uniones sirven para ahorrar espacio en memoria, ya que el espacio que

utilizan es igual al del dato más largo.

Como todos los miembros son almacenados en el mismo espacio de

memoria, existe el riesgo de la perdida de información, o el cambio de valor

indeseado de algunas variables.

Union ejemplo

{

public char chval;

public int intval;

}

ejemplo valores=new ejemplo();

valores.intval=65;

console.writeline(“chval:{0}”,valores.chval);

Al darle valor al entero de 65, como el valor del carácter está en la misma

localidad de memoria, a la hora de imprimir el valor del carácter se imprime

el carácter correspondiente al número según el código ASCII que en este caso

sería una A.

JAVA la unión de datos se da cuando una clase a se extiende mediante la

herencia por otra clase. Un ejemplo podría ser el siguiente:

public class alumno

{ Int calificaciones;

Int horario; }

Y la otra clase hereda la clase anterior por lo tanto asi es como se maneja las

uniones de datos.

Public class datos_alumno extends alumno

{ String direccion;

String nombre;

Int edad; }

Recordando la herencia es la capacidad de tener las propiedades de una clase de

otra por medio de una palabra reservada “extends”

Page 10: Unidad 1 Topicos Selectos de Programacion

La HerenciaLa HerenciaLa HerenciaLa Herencia

La herencia es el mecanismo fundamental de relación entre clases en la orientación a objetos. Relaciona las clases de manera jerárquica; una clase padre o superclase sobre otras clases hijas o subclases.

Imagen 4: Ejemplo de otro árbol de herencia

Los descendientes de una clase heredan todas las variables y métodos que sus ascendientes hayan especificado como heredables, además de crear los suyos propios.

La característica de herencia, nos permite definir nuevas clases derivadas de otra ya existente, que la especializan de alguna manera. Así logramos definir una jerarquía de clases, que se puede mostrar mediante un árbol de herencia.

En todo lenguaje orientado a objetos existe una jerarquía, mediante la que las clases se relacionan en términos de herencia. En Java, el punto más alto de la jerarquía es la clase Object de la cual derivan todas las demás clases.

C. Herencia múltiple En la orientación a objetos, se consideran dos tipos de herencia, simple y

múltiple. En el caso de la primera, una clase sólo puede derivar de una única superclase. Para el segundo tipo, una clase puede descender de varias superclases.

En Java sólo se dispone de herencia simple, para una mayor sencillez del lenguaje, si bien se compensa de cierta manera la inexistencia de herencia múltiple con un concepto denominado interface, que estudiaremos más adelante.

. Declaración Para indicar que una clase deriva de otra, heredando sus propiedades

(métodos y atributos), se usa el término extends, como en el siguiente ejemplo: public class SubClase extends SuperClase { // Contenido de la clase } Por ejemplo, creamos una clase MiPunto3D, hija de la clase ya mostrada

MiPunto: class MiPunto3D extends MiPunto { int z; MiPunto3D( ) { x = 0; // Heredado de MiPunto y = 0; // Heredado de MiPunto z = 0; // Nuevo atributo }}

Page 11: Unidad 1 Topicos Selectos de Programacion

La palabra clave extends se utiliza para decir que deseamos crear una

subclase de la clase que es nombrada a continuación, en nuestro caso MiPunto3D es hija de MiPunto.

E. Limitaciones en la herencia Todos los campos y métodos de una clase son siempre accesibles para el

código de la misma clase. Para controlar el acceso desde otras clases, y para controlar la herencia por

las subclase, los miembros (atributos y métodos) de las clases tienen tres modificadores posibles de control de acceso:

public: Los miembros declarados public son accesibles en cualquier lugar en que sea accesible la clase, y son heredados por las subclases.

private: Los miembros declarados private son accesibles sólo en la propia clase.

protected: Los miembros declarados protected son accesibles sólo para sus subclases

Por ejemplo: class Padre { // Hereda de Object // Atributos private int numeroFavorito, nacidoHace, dineroDisponible; // Métodos public int getApuesta() { return numeroFavorito; } protected int getEdad() { return nacidoHace; } private int getSaldo() { return dineroDisponible; } } class Hija extends Padre { // Definición } class Visita { // Definición } En este ejemplo, un objeto de la clase Hija, hereda los tres atributos

(numeroFavorito, nacidoHace y dineroDisponible) y los tres métodos ( getApuesta(), getEdad() y getSaldo() ) de la clase Padre, y podrá invocarlos. Cuando se llame al método getEdad() de un objeto de la clase Hija, se devolverá

Page 12: Unidad 1 Topicos Selectos de Programacion

el valor de la variable de instancia nacidoHace de ese objeto, y no de uno de la clase Padre.

Sin embargo, un objeto de la clase Hija, no podrá invocar al método getSaldo() de un objeto de la clase Padre, con lo que se evita que el Hijo conozca el estado de la cuenta corriente de un Padre.

La clase Visita, solo podrá acceder al método getApuesta(), para averiguar el número favorito de un Padre, pero de ninguna manera podrá conocer ni su saldo, ni su edad (sería una indiscreción, ¿no?).

F. La clase Object La clase Object es la superclase de todas las clases da Java. Todas las clases

derivan, directa o indirectamente de ella. Si al definir una nueva clase, no aparece la cláusula extends, Java considera que dicha clase desciende directamente de Object.

La clase Object aporta una serie de funciones básicas comunes a todas las

clases: public boolean equals( Object obj ): Se utiliza para comparar, en valor, dos

objetos. Devuelve true si el objeto que recibe por parámetro es igual, en valor, que el objeto desde el que se llama al método. Si se desean comparar dos referencias a objeto se pueden utilizar los operadores de comparación == y !=.

public int hashCode(): Devuelve un código hash para ese objeto, para poder almacenarlo en una Hashtable.

protected Object clone() throws CloneNotSupportedException: Devuelve una copia de ese objeto.

public final Class getClass(): Devuelve el objeto concreto, de tipo Class, que representa la clase de ese objeto.

protected void finalize() throws Trowable: Realiza acciones durante la recogida de basura.

1.3 Registros variantes1.3 Registros variantes1.3 Registros variantes1.3 Registros variantes

Un registro o record es una estructura de datos compuesta de un número fijo de componentes de diferente tipo. Cada componente o campo de un registro se accede por el nombre del registro y el nombre de la componente. Puede que un registro contenga partes de su estructura que puedan variar en función de otras. Para ello se definen los REGISTROS VARIANTES que son un tipo especial de registros discriminados en los que la existencia de algunos campos depende del valor del discriminante.

Page 13: Unidad 1 Topicos Selectos de Programacion

Si el discriminante lleva un valor por defecto entonces el registro es variante durante su ciclo de vida (es mutable), si no, el valor del discriminante se fija en su declaración y no varía durante la vida del objeto.

Los registros variantes en cierto modo están sobrepasados por la extensión de los tipos etiquetados, sin embargo aún son útiles en casos simples como aquellos en los que se conocen todas las posibilidades y no se desea que alguien añada una derivación a posteriori.

Los registros variantes son una especie de sentencia “case” dentro de un registro y son muy útiles cuando tenemos ciertos atributos que sabemos de antemano que sólo usaremos en ciertas situaciones.

Los registros variantes ahorran memoria, porque como sólo una de las estructuras definidas dentro de ellos puede usarse a la vez, el compilador sólo necesita reservar memoria para la mayor de ellas (en vez de tener que hacerlo para todos los componentes del registro).

Ejemplo: Registros variantes struct elemento { char nombre[2]; int numero_atomico; double peso_atomico; int es_metalico; int es_natural; union { Struct { char *donde; double prevalencia; } natural; Double tiempo_de_vida; } extra; } cobre;

Los registros variantes son una especie de sentencia “case” dentro de un registro y son muy útiles cuando tenemos ciertos atributos que sabemos de antemano que sólo usaremos en ciertas situaciones. Los registros variantes ahorran memoria, porque como sólo una de las estructuras debidas dentro de ellos puede usarse a la vez, el compilador sólo necesita reservar memoria para la mayor de ellas (en vez de tener que hacerlo para todos los componentes del registro). Registros Variantes Esta propiedad de compartición de espacio en memoria es similar a la de los bloques disjuntos en ALGOL. De hecho, también en el caso de

Page 14: Unidad 1 Topicos Selectos de Programacion

los registros variantes se permite el anidamiento. Por tanto, los registros variantes son muy eficientes y seguros, porque sólo permiten al programador realizar operaciones que tengan algún significado dentro del contexto dado y que sean permisibles dentro del registro. Sin embargo, el problema con esta estructura de datos es que no requiere ser inicializada. Esto significa que podríamos tener cualquier valor dentro de un registro variante después de modificar el valor de un campo identificador. El acceso a variables no inicializadas puede dar lugar a que se usen valores que fueron dejados previamente en el bloque y que compartían la misma zona de memoria. En el caso de los registros variantes, este valor puede incluso ser de un tipo diferente al esperado. La raíz del problema es que los registros variantes permiten una forma de “aliasing”, ya que los campos de las diferentes variantes son todos “alias” de las mismas posiciones de memoria.

Ejemplo:Registros variantes struct elemento { char nombre[2]; int numero_atomico; double peso_atomico; int es_metalico; int es_natural; union { Struct { char *donde; double prevalencia;} natural; Double tiempo_de_vida;} extra;} cobre;

1.4 Tipos de datos enumerados1.4 Tipos de datos enumerados1.4 Tipos de datos enumerados1.4 Tipos de datos enumerados

Page 15: Unidad 1 Topicos Selectos de Programacion

Una enumeración o tipo enumerado es un tipo especial de estructura en la que los literales de los valores que pueden tomar sus objetos se indican explícitamente al definirla. Por ejemplo, una enumeración de nombre Tamaño cuyos objetos pudiesen tomar los valores literales Pequeño, Mediano o Grande se definiría así:

enum Tamaño

{ Pequeño, Mediano, Grande

} Tipos enumerados. Un tipo enumerado o enumeración está construido por una serie de constantes simbólicas enteras. En JAVA una enumeración es simplemente un conjunto de constantes que representan diferentes valores. Un ejemplo antiguo de cómo se utilizaba en JAVA anteriormente podria ser el siguiente: public final int SPRING = 0; public final int SUMMER = 1; public final int FALL = 2; public final int WINTER = 3; Eso a lo largo de la evolución de JAVA a través del tiempo se puede optimizar utilizando el siguiente formato: enum Season { WINTER, SPRING, SUMMER,FALL } Un enum es un nuevo tipo de clase, Puedes declarar variables de tipo enumerado y tener comprobación de tipos en tiempo de compilación. Cada valor declarado es una instancia de la clase enum, Los Enums son implícitamente public, static y final y los puedes comparar mediante: equals o == Los tipos enums heredan de java.lang.Enum e implementan java.lang.Comparable EJEMPLO: import java.util.*; class java15

{ static public enum prioridad{ alta, media, baja}; public static void main(String [] params)

{ prioridad actual = prioridad.alta; System.out.println("La prioridad es: " + actual ); }

} Tipos definidos. Enumerado Para definirlo se enumeran los valores de sus elementos; ejemplos de tipos enumerados:

Page 16: Unidad 1 Topicos Selectos de Programacion

*días de la semana, *estaciones del año, *palos de una baraja, *sexo de una persona, Los ejemplos anteriores se definirían tipos dias_semana={lunes,martes,miercoles,jueves,viernes,sabado,domingo} estaciones_año={primavera,verano,otoño,invierno} palos_baraja={oros,copas,espadas,bastos} sexo={mujer,hombre}

En cualquier clase de colección, debe haber una forma de meter cosas y otra de sacarlas; después de todo, la principal finalidad de una colección es almacenar cosas. En un Vector, el método addElement() es la manera en que se colocan objetos dentro de la colección y llamando al método elementAt() es cómo se sacan. Vector es muy flexible, se puede seleccionar cualquier cosa en cualquier momento y seleccionar múltiples elementos utilizando diferentes índices.

Si se quiere empezar a pensar desde un nivel más alto, se presenta un inconveniente: la necesidad de saber el tipo exacto de la colección para utilizarla. Esto no parece que sea malo en principio, pero si se empieza implementando un Vector a la hora de desarrollar el programa, y posteriormente se decide cambiarlo a List, por eficiencia, entonces sí es problemático.

El concepto de enumerador, o iterador, que es su nombre más común en C++ y OOP, puede utilizarse para alcanzar el nivel de abstracción que se necesita en este caso. Es un objeto cuya misión consiste en moverse a través de una secuencia de objetos y seleccionar aquellos objetos adecuados sin que el programador cliente tenga que conocer la estructura de la secuencia. Además, un iterador es normalmente un objeto ligero, lightweight, es decir, que consumen muy pocos recursos, por lo que hay ocasiones en que presentan ciertas restricciones; por ejemplo, algunos iteradores solamente se puede mover en una dirección.

La Enumeration en Java es un ejemplo de un iterador con esas características, y las cosas que se pueden hacer son:

• Crear una colección para manejar una Enumeration utilizando el método elements(). Esta Enumeration estará lista para devolver el primer elemento en la secuencia cuando se llame por primera vez al método nextElement().

• Obtener el siguiente elemento en la secuencia a través del método nextElement().

• Ver si hay más elementos en la secuencia con el método hasMoreElements().

Y esto es todo. No obstante, a pesar de su simplicidad, alberga bastante poder. Para ver cómo funciona, el ejemplo java412.java, es la modificación de anterior, en que se utilizaba el método elementAt() para seleccionar cada uno de

Page 17: Unidad 1 Topicos Selectos de Programacion

los elementos. Ahora se utiliza una enumeración para el mismo propósito, y el único código interesante de este nuevo ejemplo es el cambio de las líneas del ejemplo original

for( int i=0; i < coches.size(); i++ ) (( Coche )coches.elementAt( i ) ).print();

por estas otras en que se utiliza la enumeración para recorrer la secuencia de objetos

while( e.hasMoreElements() ) (( Coche )e.nextElement()).print();

Con la Enumeration no hay que preocuparse del número de elementos que contenga la colección, ya que del control sobre ellos se encargan los métodos hasMoreElements() y nextElement().

1.5 Manejo de bits1.5 Manejo de bits1.5 Manejo de bits1.5 Manejo de bits

El método más sencillo de representación son los números naturales. Por ejemplo, si tengo el número 85 en decimal, solo tengo que llevarlo a binario y obtengo una serie de unos y ceros. :

1010101 = 85 en binario

Cada dígito (un cero o un uno) de este número se llama bit. Java tiene una serie de operadores capaces de manipular estos dígitos, son los operadores de bits.

Operadores de bits

Operador Utilización Resultado

<< A << B Desplazamiento de A a la izquierda en B posiciones

>> A >> B Desplazamiento de A a la derecha en B posiciones, tiene en

cuenta el signo.

>>> A >>> B Desplazamiento de A a la derecha en B posiciones, no tiene en

cuenta el signo.

& A & B Operación AND a nivel de bits

| A | B Operación OR a nivel de bits

^ A ^ B Operación XOR a nivel de bits

~ ~A Complemento de A a nivel de bits

Para operar a nivel de bit es necesario tomar toda la longitud predefinida para el tipo de dato. Estamos acostumbrados a desechar los ceros a la izquierda en nuestra representación de números. Pero aquí es importante. Si trabajamos una variable de tipo short con un valor de 3, está representada de la siguiente manera:

0000000000000011 Aquí los 16 bits de un short se tienen en cuenta.

Page 18: Unidad 1 Topicos Selectos de Programacion

DesplazamientosDesplazamientosDesplazamientosDesplazamientos

Los operadores de desplazamiento, mueven los bits a la izquierda o a la derecha. El primer operando será la victima a sacudir. El segundo indicará cuantas posiciones. Desplazamiento a la izquierda con signo (cíclico)Desplazamiento a la izquierda con signo (cíclico)Desplazamiento a la izquierda con signo (cíclico)Desplazamiento a la izquierda con signo (cíclico)

Deseamos correr el número 33 dos posiciones a la izquierda. Entonces realizamos :

int j = 33; int k = j << 2; Este es el resultado: 00000000000000000000000000100001 : j = 33 00000000000000000000000010000100 : k = 33 << 2 ; k = 132 Cada "hueco" que queda a la derecha tras correr este número se rellena

con los dígitos que van saliendo por la izquierda (ceros en este caso). Si prestamos atención, observaremos que esta operación multiplicó a j por 2 tantas veces como posiciones se ha desplazado. En este caso se multiplicó por 4 ( 2 x 2 ). Como el signo del número puede cambiar tras la operación, se denomina desplazamiento con signo. Desplazamiento a la derecha con signo (cíclico)Desplazamiento a la derecha con signo (cíclico)Desplazamiento a la derecha con signo (cíclico)Desplazamiento a la derecha con signo (cíclico)

Volvamos a colocar como estaban los bits del caso anterior. Queremos obtener nuevamente el número 33. Para esto desplazamos el número 132 dos posiciones a la derecha.

int k = 132; int m = k >> 2; Como resultado obtenemos el número original. 00000000000000000000000010000100 : k = 132 00000000000000000000000000100001 : m = 132 >> 2 ; m = 33 Podemos ver que el corrimiento a la derecha realiza una división de

enteros. Divide por 2, tantas veces como posiciones desplazadas. Los huecos que quedan por la izquierda se cubren con los bits que van saliendo por la derecha (es cíclico).

Veamos que ocurre si pretendemos realizar un desplazamiento a la derecha con un número negativo. Tengan en cuenta que la representación de números es de complemento a 2. Si tengo una variable de tipo int con el valor –1 , internamente está almacenada de la siguiente forma :

11111111111111111111111111111111 : -1 complemento a 2 Ahora realicemos un programa para ver que ocurre con el

desplazamiento. public class CorreNeg { public static void main(String args[]){ int x = -1; int y = x >> 2; System.out.println("El resultado es: " + String.valueOf(y)); } } La salida del programa nos indica que:

Page 19: Unidad 1 Topicos Selectos de Programacion

El resultado es: -1 Quedó exactamente igual. Prueben de correr el número tantas posiciones

como tengan ganas y obtendrán el mismo resultado. Esto ocurre porque en el desplazamiento, los "huecos" que quedan a la izquierda se rellenan con el bit uno (1), quedando inalterable.

Este operador desplaza el conjunto de bit a la derecha y agrega a la izquierda los bits que faltan según el signo. Si se encuentra con un número positivo, agrega ceros, en cambio si son negativos agrega unos. Se lo conoce como desplazamiento con signo. Desplazamiento a la derecha sin signoDesplazamiento a la derecha sin signoDesplazamiento a la derecha sin signoDesplazamiento a la derecha sin signo

Modifiquemos ligeramente el programa anterior agregándole al operador un símbolo >. Nos queda de esta manera :

int x = -1; int y = x >>> 2; Si ejecutamos el programa nos dice lo siguiente : El resultado es: 1073741823 Veamos de donde salió este número raro. Si lo llevamos a binario

tenemos : 00111111111111111111111111111111 : 1073741823 en binario Ahora nos damos cuenta que se han agregado dos ceros a la izquierda.

Este operador desplaza a la derecha, pero no tiene en cuenta el signo. Siempre agrega bit con el valor cero, por lo que se llama desplazamiento sin signo. Operadores lógicos de bitsOperadores lógicos de bitsOperadores lógicos de bitsOperadores lógicos de bits

Estos operadores extienden las operaciones booleanas a los enteros. Para comprender como trabajan debemos descomponer los enteros en un conjunto de bits. El operador aplicará una operación lógica bit por bit, tomando el valor de uno como verdadero y el valor de cero como falso. De un operando toma un bit y aplica la operación al bit que tiene la misma posición del segundo operando. Como resultado obtenemos otro entero. Operador AND de BitsOperador AND de BitsOperador AND de BitsOperador AND de Bits

Si ambos bits comparados son 1, establece el resultado en 1. De lo contrario da como resultado 0.

int k = 132; // k: 00000000000000000000000010000100 int l = 144; // l: 00000000000000000000000010010000 int m = k & l; // m: 00000000000000000000000010000000 El resultado da 128

Operador XOR de BitsOperador XOR de BitsOperador XOR de BitsOperador XOR de Bits

Si uno de los bits comparados es 0 y el otro 1, el resultado es 1. Si ambos bits comparados son iguales, el resultado es 0.

int k = 132; // k: 00000000000000000000000010000100 int l = 144; // l: 00000000000000000000000010010000 int m = k ^ l; // m: 00000000000000000000000000010100 El resultado da 20

Operador NOT de BitsOperador NOT de BitsOperador NOT de BitsOperador NOT de Bits

Sólo invierte los bits, es decir, convierte los ceros en unos y viceversa. Observemos que es el único de esta familia que tiene un solo operando.

Page 20: Unidad 1 Topicos Selectos de Programacion

int k = 132; // k: 00000000000000000000000010000100 int m = ~k; // m: 11111111111111111111111101111011 El resultado da -133 Como los enteros negativos en Java se representan con el método del

complemento a dos, podemos realizar un sencillo experimento de cambiarle el signo a un número. Para realizarlo debemos aplicar a un entero el operador NOT y sumarle uno. Operadores de manejo de BitsOperadores de manejo de BitsOperadores de manejo de BitsOperadores de manejo de Bits

Se dispone de 6 operadores para manejo de bits que realizan dos tipos de operaciones. Son los siguientes:

~ Complemento a uno

<< Desplazamiento a izquierda

>> Desplazamiento a derecha

& AND; compara dos bits

^ XOR (OR exclusivo); compara dos bits

| OR inclusivo; compara dos bits

El primero es un operador unario, los restantes son binarios. Los tres primeros realizan manipulaciones en los bits del operando. Los restantes realizan comparaciones lógicas entre los bits de ambos operandos, similares a las que realizan los operadores lógicos entre objetos booleanos

2 ~ Complemento a uno (palabra clave compl)

Este operador unitario invierte cada bit del operando; 0 es convertido en 1 y viceversa.

Sintaxis ~cast-expresion Ejemplo signed int s1 = ~2; // equivale a: signed int s1 = compl 2;

signed int s2 = ~s1 + 2;

En la primera línea, el complemento a uno de 2 es asignado al entero con signo s1. Tenga en cuenta que el resultado de este operador cambia el signo del operando, de ahí el "signed".

La representación binaria de los los complementos a uno de los decimales 0, 1 y 2 son los que se expresan (para simplificar los representamos como un octeto):

0 == 0000 0000 ~ 0 == 1111 1111

1 == 0000 0001 ~ 1 == 1111 1110

Page 21: Unidad 1 Topicos Selectos de Programacion

2 == 0000 0010 ~ 2 == 1111 1101 Desplazamiento de BDesplazamiento de BDesplazamiento de BDesplazamiento de Bitsitsitsits

1.6 Campos de B1.6 Campos de B1.6 Campos de B1.6 Campos de Bitsitsitsits

int x = 123; int y = ~x; int z = y + 1; El resultado da -123,

1.6. Campos de B1.6. Campos de B1.6. Campos de B1.6. Campos de Bitsitsitsits

Los campos de bits, o simplemente campos, son grupos de un número determinado de bits, que pueden o no tener un identificador asociado. Representan un artificio que permite utilizar miembros de tamaño arbitrario en estructuras, uniones y clases; independiente de la posibilidad que proporcionan los tipos básicos cuyo tamaño está predeterminado por el lenguaje.

Los campos de bits solo pueden existir en estructuras, uniones y clases, y son accedidos utilizando los mismos operadores de acceso que al resto de los miembros.

El uso de campos de bits requiere algunas consideraciones a ser tenidas en cuenta:

Los campos de bits se han utilizado históricamente para empaquetar variables en un espacio más pequeño, pero obligan al compilador a generar código adicional para manejarlos, lo que resulta costoso en términos de tamaño y

Page 22: Unidad 1 Topicos Selectos de Programacion

velocidad del ejecutable. El resultado es que frecuentemente, el código resulta mayor y más lento si se usan estos tipos, por lo que generalmente se desaconseja su uso excepto para aplicaciones muy específicas de bajo nivel, en las que la alineación exacta de los patrones de bits a utilizar es un aspecto primordial. Por ejemplo, transmisiones de datos

Otra cuestión distinta, a veces decisiva para su utilización, es la significativa reducción de espacio de almacenamiento externo (disco por ejemplo) que puede conseguirse cuando en determinados casos, se almacena gran número de registros que utilizan campos de bits en sustitución de tipos básicos.

Limitaciones de uso El uso de campos de bits requiere algunas consideraciones a ser tenidas en

cuenta: · El código puede no resultar portable, dado que la organización de bits

dentro de bytes, y de estos dentro de palabras, depende de la plataforma utilizada en cada caso. Esta organización puede variar incluso dentro de las sucesivas versiones de un mismo compilador.

· Los campos de bits no son direccionables, es decir, no se les puede aplicar el operador de

eferencia & . Otra cuestión distinta, a veces decisiva para su utilización, es la

significativa reducción de espacio de almacenamiento externo (disco por ejemplo) que puede conseguirse cuando en determinados casos, se almacena gran número de registros que utilizan campos de bits en sustitución de tipos básicos.

Declaración La sintaxis para declaración de campos es la siguiente: especificador-de-

tipo <identificador> : ancho. Los operadores binarios a nivel de bits se pueden usar sólo con datos

enteros o booleanos: & Y | O inclusivo ^ O exclusiva (XOR) ~ Complemento << Desplaza los bits a la izquierda, rellenando por la derecha con ceros. >> Desplaza los bits a la derecha, rellenando por la izquierda con el bit

más alto (signo). >>> Desplaza los bits a la derecha, rellenando por la izquierda con ceros. Ejemplos de sentencias que utilizan estos operandos son: public class binario { public static void main (String args[]) { int a=1; int b; b=a<<1; /*desplaza el 1 a la izquierda, llenado de 1 cero a la derecha:

10 en binario; por lo que en decimal valdría 2.*/ System.out.println("a << 1 = " + b);

Page 23: Unidad 1 Topicos Selectos de Programacion

b=a<<2; /*desplaza el 1 a la izquierda, llenado de 2 ceros a la derecha: 100 en binario; por lo que en decimal valdría 4.*/

System.out.println("a << 2 = " + b); b=a & 0x00000000; /*Como a es diferente de 0x00000000, y como en

and debe cumplir las dos condiciones el valor que generará será: 0;*/ System.out.println("a & 0x00000000 = " + b); b=a | 0x00000000; /*Como a es diferente de 0x00000000, pero en or

con una condición que cumpla regresará el valor de: 1;*/ System.out.println("a | 0x00000000 = " + b); } } cuyos resultados serán; a << 1 = 2 a << 2 = 4 a & 0x00000000 = 0 a | 0x00000000 = 1 Los operadores booleanos a nivel de bits, & y | se pueden usar también

sobre valores boolean, devolviendo el mismo valor que sus homólogos lógicos && y ||, aunque con una diferencia: los operadores a nivel de bit evalúan siempre los dos operandos.

1.7 Operaciones con bits1.7 Operaciones con bits1.7 Operaciones con bits1.7 Operaciones con bits

Existen seis operadores para manejo de bits: 1. & AND de bits 2. | OR inclusivo de bits 3. ^ OR exclusivo de bits 4. << corrimiento a la izquierda 5. >> corrimineto a la derecha 6. ~ complemento a uno (unario)

Page 24: Unidad 1 Topicos Selectos de Programacion

ANDANDANDAND El operador AND, &, combina los bits de manera que se obtiene un 1 si

ambos operandos son 1, obteniendo 0 en cualquier otro caso. 00101010 42 & 00001111 15 = 00001010 10 El operador & realiza la operación AND de BIT. Aplica la función AND

sobre cada par de bits de igual peso de cada operando. La función AND es evaluada a cierto si ambos operandos son ciertos. Por ejemplo vamos a aplicar la operación AND a los valores 12 y 13: 12 & 13

El resultado de esta operación es 12. ¿Por qué?. La representación en binario de 12 es 1100, y de 13 es

1101. La función AND pone el BIT de resultado a uno si los dos bits de los operandos son 1, sino, el BIT

de resultado es 0: 1101 & 1100 ------------- 1100

OROROROR

El operador OR, |, combina los bits de manera que se obtiene un 1 si cualquiera de los operandos es un 1.

00101010 42 | 00001111 15 = 00101111 47 El operador | realiza la operación OR de BIT. Aplica la función OR sobre

cada par de bits de igual peso de cada operando. La función OR es evaluada a cierto si alguno de los operandos es cierto. XORXORXORXOR

El operador XOR, ^, combina los bits de manera que se obtiene un 1 si cualquiera de los operandos es un 1, pero no ambos, y cero en caso contrario.

00101010 42 ^ 00001111 15 = 00100101 37 El operador ^ realiza la operación OR exclusivo de bit (XOR). Aplica la

función XOR sobre cada par de bits de igual peso de cada operando. La función XOR es evaluada a cierto si los operandos el mismo valor. 15 NOTNOTNOTNOT

El operador NOT unario, ~, invierte todos los bits de su operando. Por ejemplo, en número 42, que tiene el siguiente patrón de bits 00101010 se convierte en 11010101 después de aplicar el operador NOT.

Tanto el operador de corrimiento a la izquierda («) como el operador de corrimiento a la derecha (»), solo desplazan los bits del operando de la izquierda el numero de posiciones indicadas por el operador de la derecha. Los desplazamientos ocurren en la dirección indicada por el propio operador. Por ejemplo

13 >> 1 desplaza los bits del entero 13 una posición a la derecha así: 13 = 1101 1101 >> 1 = 0110 0110 = 6

Page 25: Unidad 1 Topicos Selectos de Programacion

Complemento a uno El operador unario “~” da el complemento a uno de un entero; esto es,

convierte cada bit 1 en un bit 0 y viceversa.