Programación orientada a objetos - Servidor de Apoyo al Sistema

Anuncio
Programación orientada a objetos
Unidad
Temas
1
Arreglos
unidimensionales y
multidimensionales.
1.1
1.2
1.3
2
Métodos y mensajes.
2.1
2.2
2.3
2.4
2.5
2.6
2.7
2.8
2.9
Subtemas
Arreglo Unidimensionales listas
(vectores).
1.1.1 Conceptos básicos.
1.1.2 Operaciones.
1.1.3 Aplicaciones.
Arreglo bidimensional.
1.2.1 Conceptos básicos.
1.2.2 Operaciones.
1.2.3 Aplicaciones.
Arreglo Multidimensional.
1.3.1 Conceptos básicos.
1.3.2 Operaciones.
1.3.3 Aplicaciones.
Atributos const y static.
Concepto de método.
Declaración de métodos.
Llamadas a métodos
(mensajes).
Tipos de métodos.
2.5.1 Métodos const, static.
2.5.2 Métodos normales y
volátiles.
Referencia this.
Forma de pasar argumentos.
Devolver un valor desde un
método.
Estructura del código.
3
Constructor, destructor.
3.1 Conceptos de métodos
constructor y destructor.
3.2 Declaración de métodos
constructor y destructor.
3.3 Aplicaciones de constructores y
destructores.
3.4 Tipos de constructores y
destructores.
4
Sobrecarga.
4.1 Conversión de tipos.
4.2 Sobrecarga de métodos.
4.3 Sobrecarga de operadores.
5
Herencia.
5.1 Introducción a la herencia.
5.2 Herencia simple.
5.3 Herencia múltiple.
5.4 Clase base y clase derivada.
5.4.1 Definición.
5.4.2 Declaración.
5.5 Parte protegida.
5.5.1 Propósito de la parte
protegida.
5.6 Redefinición de los miembros de
las clases derivadas.
5.7 Clases virtuales y visibilidad.
5.8 Constructores y destructores en
clases derivadas.
5.9 Aplicaciones.
6
Polimorfismo y
reutilización
6.1 Concepto del polimorfismo.
6.2 Clases abstractas.
6.2.1 Definición.
6.2.2 Redefinición.
6.3 Definición de una interfaz.
6.4 Implementación de la definición
de una interfaz.
6.5 Reutilización de la definición de
una interfaz.
6.6 Definición y creación de
paquetes / librería.
6.7 Reutilización de las clases de un
paquete / librería.
6.8 Clases genéricas (Plantillas).
7
Excepciones.
7.1 Definición.
7.1.1 Que son las excepciones.
7.1.2 Clases de excepciones,
excepciones predefinidas por
el lenguaje.
7.1.3 Propagación.
7.2 Gestión de excepciones.
7.2.1 Manejo de excepciones.
7.2.2 Lanzamiento de
excepciones.
7.3 Excepciones definidas por el
usuarios.
7.3.1 Clase base de las
excepciones.
7.3.2 Creación de un clase
derivada del tipo excepción.
7.3.3 Manejo de una excepción
definida por el usuario.
8
Flujos y archivos.
8.1 Definición de Archivos de texto
y archivos binarios.
8.2 Operaciones básicas en
archivos texto y binario.
8.2.1 Crear.
8.2.2 Abrir.
8.2.3 Cerrar.
8.2.4 Lectura y escritura.
8.2.5 Recorrer.
8.3 Aplicaciones.
Unidad 1. Arreglos unidimensionales
y multidimensionales.
1.1 Arreglo Unidimensionales listas (vectores).
1.1.1 Conceptos básicos.
Un arreglo unidimensional es un tipo de datos estructurado que está formado de una colección
finita y ordenada de datos del mismo tipo. Es la estructura natural para modelar listas de
elementos iguales.
El tipo de acceso a los arreglos unidimensionales es el acceso directo, es decir, podemos acceder
a cualquier elemento del arreglo sin tener que consultar a elementos anteriores o posteriores, esto
mediante el uso de un índice para cada elemento del arreglo que nos da su posición relativa.
Para implementar arreglos unidimensionales se debe reservar espacio en memoria, y se debe
proporcionar la dirección base del arreglo, la cota superior y la inferior.
REPRESENTACION EN MEMORIA
Los arreglos se representan en memoria de la forma siguiente:
x : array[1..5] of integer
Para establecer el rango del arreglo (número total de elementos) que componen el arreglo se
utiliza la siguiente formula:
RANGO = Ls - (Li+1)
donde:
ls = Límite superior del arreglo
li = Límite inferior del arreglo
Para calcular la dirección de memoria de un elemento dentro de un arreglo se usa la siguiente
formula:
A[i] = base(A) + [(i-li) * w]
donde :
A = Identificador único del arreglo
i = Indice del elemento
li = Límite inferior
w = Número de bytes tipo componente
Si el arreglo en el cual estamos trabajando tiene un índice numerativo utilizaremos las siguientes
fórmulas:
RANGO = ord (ls) - (ord (li)+1)
A[i] = base (A) + [ord (i) - ord (li) * w]
Arreglos Unidimensionales
La declaración del arreglo unidimensional es un tipo seguido de un identificador con una
expresión constante INT entre corchetes. El valor de la expresión, que debe ser positivo,es el
tamaño del arreglo y especifica el numero de elementos que contiene.
La forma de declarar un arreglo es la siguiente:
< tipo > < variable > [N]
Declara un arreglo de nombre < variable > con N elementos de tipo < tipo >, en donde N es una
constante)
Los corchetes [ ], sirven para encerrar los subíndices.
Por ejemplo, para declarar un arreglo de enteros llamado arreglo con 5 elementos se hace de la
siguiente forma:
int arreglo[5];
Para accesar a algún elemento del arreglo, puede hacerse de las siguientes formas:
a) int arreglo[] = {0,1,2,3,4,5} /*se valida cuando se inicializan todos los elementos del array
*/
b) int arreglo[4] = {4, 2, 3, 6} /*se declara y asigna valores iniciales al arreglo */
c) arreglo[3] = 10;
num = arreglo[2];
/* Asigna 10 al 4er elemento del arreglo lista*/
/* Asigna el contenido del 3er elemento a la variable num */
Una Estructura de Datos, es una colección de datos que se caracterizan por su organización las
operaciones que se definen en ella. Las Estructuras de Datos pueden ser de dos tipos: Estáticas y
Dinámicas.
Las Estructuras de Datos estáticas son aquellas en las que el espacio ocupado en la memoria se
define en tiempo de compilación no puede ser modificado durante la ejecución del programa; por
el contrario, en las Estructuras de Datos Dinámicas el espacio ocupado en memoria puede ser
modificado en tiempo de ejecución>
Un ARRAY (arreglo), es una colección de datos del mismo tipo, que se almacena en posiciones
consecutivas de memoria y recibe un nombre com?os componentes individuales de un arreglo se
llaman Elementos y se distinguen entre ellos por el nombre del arreglo seguido uno o varios �ices.
El �ice es un n? que indica la posici�ue ocupa el elemento dentro del arreglo.
Los elementos del arreglo se almacenan en la memoria de la computadora en posiciones
adyacentes (un elemento por posici� Los arreglos se dividen en:
- UNIDIMENSIONALES (Vectores o Listas)
- BIDIMENSIONALES (Tablas o matrices)
- MULTIDIMENSIONALES
Arreglos Unidimensionales o Vectores.
Un Vector es una secuencia de elementos del mismo tipo y en los que el orden es significativo. El
orden estᠤado por el �ice del vector; por ejemplo:
Vector[10]
0
21 -1
9
8
5
-9 15
7
6
-4 33
Declaraci�e Vectores:
Es la reservaci�e un espacio en memoria, para almacenar un conjunto de datos.
Formato:
NOMBRE: ARRAY [N] DE TIPODEDATO
NOMBRE: Identificador del arreglo.
N: N? de elementos del arreglo.
TIPODEDATO: Entero, Real, Cadena, L�o.
El �ice del primer elemento del vector es 0 (cero) y el ?o es N-1.
Ejemplo: Declarar un Vector llamado TEMP que almacene las tempraturas de cada hora durante un
d�
TEMP : ARRAY [24] DE REAL
Arreglos
A diferencia de los elementos estudiados hasta ahora, los arreglos pertenecen al dominio de los
tipos de datos y no al de las instrucciones de Pascal. A grandes rasgos, son conjuntos de variables
que comparten un mismo nombre, pudiendo ser referenciadas de manera individual las variables
del conjunto con ayuda de uno o más índices. Los arreglos guardan estrecha similitud con
elementos de datos de la vida cotidiana, como los vectores y las tablas, y en cierto modo trazan
una línea que separa la programación básica de la avanzada. Como la sintaxis formal de los
arreglos es muy general, se comenzará definiendo los arreglos unidimensionales o vectores.
Arreglos unidimensionales: vectores
Supóngase que se tienen cinco variables: a, b, c, d y e. Si se desea hacer referencia a las cinco,
deben usarse sus nombres, y eso implicaría, a nivel de programación, poner sus nombres en cada
sentencia que se requiera (ReadLn, fórmulas, etc.) y repetir las mismas cinco veces. Un enfoque
más general es poner a las variables un mismo nombre, y distinguirlas por un número, parecido a
como hacen los libros de matemáticas para referirse de manera general a las componentes de los
vectores o de las tuplas. Entonces, se hablaría de a(1), a(2), a(3), a(4) y a(5), en vez de las letras
usadas anteriormente; como ya se sabe cómo generar los índices (los números entre paréntesis
que permiten distinguir las variables), se puede usar un ciclo para manipular las variables y usar
menos código.
La sintaxis para declarar un arreglo de una sola dimensión es:
Var
<nomb> : Array [<li>..<ls>] Of <tipo>;
donde <nomb> es el nombre del arreglo, <li> es el valor inferior que puede tomar el índice y <ls>
es el valor superior del índice. Un ejemplo real de declaración de arreglos se muestra a
continuación:
Var
a : Array[1..5] Of Integer;
que produce un arreglo llamado a de cinco variables enteras. La primera es a[1], la segunda es
a[2], y así sucesivamente.
¿Cuál es la utilidad de este esquema? Si se tienen que leer cinco variables declaradas por
separado, con nombres distintos, se usaría algo como esto:
ReadLn(a);
ReadLn(b);
ReadLn(c);
ReadLn(d);
ReadLn(e);
mientras que con arreglos, basta escribir:
For i := 1 To 5 Do
ReadLn(a[i]);
que es evidentemente más cómodo.
A lo largo del curso se han visto programas que suman una cantidad de números usando dos
variables, una para leer cada número y otra para acumular la suma. Este enfoque tiene la
desventaja de que se pierden los valores de los sumandos. El uso de arreglos permite calcular la
suma de los números con una cantidad mínima de código y a la vez conservar cada valor, como
muestra el siguiente programa completo:
Program SumaN;
Uses WinCrt;
Const
n = 5; {Cant. de #s}
Var
nums: Array[1..n] Of Integer;
s, i: Integer;
Begin
For i:=1 To n Do Begin
Write('Número: ');
ReadLn(nums[i]);
s := s + nums[i];
End;
WriteLn('Suma: ', s);
End.
Nótese el uso de una constante para marcar el tamaño del arreglo; dicha constante, naturalmente,
también sirve para controlar el For. De este modo, sólo se hace necesario cambiar un número
para adecuar el programa a la escala apropiada.
Arreglos Unidimensionales (Vectores o Listas) (Variables con subíndices)
Nota: Un arreglo de variables es una colección de variables simples todas del mismo tipo
con un nombre común, llamado el nombre del arreglo.
Ejemplo: Considere las siguientes edades: 20, 19, 18, 26, 23, 18, 23.
Estos siete valores se pueden guardar usando las variables Edad1 = 20, Edad2 = 19, Edad3 =
18, Edad4 = 26, Edad5 = 23, Edad6 = 18 y Edad7 = 23. Otra forma de guardarlos es como
siete elementos del arreglo con nombre Edad.
Se puede hacer referencia a elementos en el arreglo haciendo uso del nombre Edad y el
número del elemento (índice de la variable) entre paréntesis.
Referencia de la variable: Edad(1), Edad(2), Edad(3), ...
Valor de la variable:
20,
19,
18,
...
Ejemplos:
1) (Edad(4) + Edad(2))/2
2) Suma = 0
For i = 1 TO 6
Suma = Suma + Edad(i)
Next i
picOutput.Print "El promedio de las edades es:";Suma/6
http://www.cayey.upr.edu/crivera/sici3007/ArreglosUnidimensionales.htm
Un arreglo (array, disposición, vector o lista, tabla o matriz) es una estructura de datos utilizada
para almacenar un conjunto de datos del mismo tipo, en posiciones consecutivas de memoria.
Un arreglo se identifica por medio de un nombre. Los componentes individuales del arreglo se
denominan elementos y se distinguen entre ellos por el nombre del arreglo seguido de uno o
varios índices o subíndices, entre paréntesis. El identificador o índice, determina la posición de
memoria de un elemento del arreglo.
1.4.1.1 Clasificación de los Arreglos
1.4.1.1.1 Arreglos Unidimensionales. Un arreglo unidimensional es un conjunto finito (número
especifico de elementos), ordenado, de elementos homogéneos (del mismo tipo).
Figura 1.4 Arreglo unidimensional.
Para tener acceso directo a la posición de memoria que contiene el valor de cada elemento del
arreglo, se utiliza la siguiente fórmula:
Dir E[I]=Dir E[Ii]+NumPos*(I-Ii)
Donde:
Dir E[I]: Dirección de memoria del elemento cuyo índice es I.
Dir E[Ii]: Dirección de memoria del elemento inicial del arreglo.
NumPos: Número de posiciones de memoria de que consta la celda.
Ii: Índice del elemento inicial.
Las operaciones con vectores se pueden realizar con elementos individuales o sobre los vectores
completos mediante las instrucciones básicas y estructuras de control.
Las operaciones que se pueden realizar sobre elementos individuales son: asignación y lectura.
Entre las operaciones sobre el vector completo están:
o
o
o
o
Recorrido: Es la manera de acceder de manera sucesiva y
consecutiva a los contenidos de cada elemento del vector.
Inserción: Consiste en introducir en el arreglo el valor del
elemento. Aunque el sistema reserva memoria para cada elemento del arreglo, puede
suceder que alguna posición de memoria se encuentre vacía. Surgen dos operaciones
distintas: añadir una celda de memoria vacía o añadir en una celda de memoria ocupada,
para no dañar el contenido de memoria donde se inserta el nuevo dato, se desplaza este
contenido y todos los siguientes a una posición superior de la misma memoria.
Búsqueda: Consiste en realizar un recorrido del vector, empezando
desde su posición de memoria más baja con el fin de localizar un dato determinado.
Eliminación: Borrar el dato contenido en una de las posiciones del
vector, si es una posición diferente de la última, todos los elementos con posiciones
posteriores retroceden una posición.
Ordenación: Consiste en reorganizar el contenido de cada uno de los elementos del vector
según una secuencia determinada (ascendente o descendente).
1.1.2
Operaciones.
Las operaciones con vectores se pueden realizar con elementos individuales o sobre los vectores
completos, mediante las instrucciones b�cas y las estructuras de control; por ejemplo:
Operaciones sobre los elementos de un vector
Las operaciones sobre los elementos de un vector son:
ASIGNACIӎ.
LECTURA.
ESCRITURA.
ASIGNACIӎ:
Es la acci�e introducir un elemento a una posici�spec�ca del vector. As�ues, si queremos que la
posici� del vector tenga un valor de -8, la operaci� realizar es la siguiente:
A[6]=-8
LECTURA:
Es la operaci� acci�e obtener un valor desde el teclado, para ser introducido en una posici�el
vector:
LEE A[4]
ESCRITURA:
Es la operaci�e sacar un valor de una posici�el vector:
ESCRIBE A[6]
Operaciones sobre vectores completos:
Las operaciones que se pueden realizar sobre vectores completos son:
- RECORRIDO.
- B‫ړ‬QUEDA.
- INSERCIӎ.
- ELIMINACIӎ.
- ORDENAMIENTO.
RECORRIDO:
Es la operaci�e escribir, asignar, leer o llenar de datos el vector completo y se realiza mediante
las siguientes estructuras:
FOR Ind=0 TO 9 DO
FOR Ind=0 TO 9 DO
LEE A[Ind]
ESCRIBE A[Ind]
Ind=1
Ind=1
While Ind<=10 DO
While Ind<=10 DO
LEE A[Ind]
ESCRIBE A[Ind]
Ejemplo:
Dise�n Pseudoc�o que almacene en la memoria de la computadora un vector llamado FIBO de 100
posiciones el cual contendrᠤos primeros 100 n?sdel Fibonacci:
Soluci�/big>
Pseudoc�o Fibonacci
Variables
FIBO : ARRAY [100] DE Entero
A,B,C,I : Entero
INICIO
A_1
B=0
FIBO[0]=0
FIBO[1]=1
FOR I=2 TO 99 DO
INICIO
C=A+B
FIBO[I]=C
B=A
A=C
FIN
FIN
B‫ړ‬QUEDA:
Consiste en determinar si un valor espec�co se encuentra dentro del vector. Se deben examinar
uno a uno todos los elementos, comenzando con el primer elemento del vector y comparando con
el elemento buscado. El pseudoc�o de b?da es el siguiente:
Pseudoc�o B?da
Variables
HALLADO, X, A : Entero
INICIO
HALLADO=0
LEE X
A=0
WHILE HALLADO=0 AND A<>(N? de Elementos del Vector) DO
INICIO
IF VECTOR[A]=X THEN
HALLADO=1
A=A+1
FIN
IF HALLADO=1 THEN
ESCRIBE "El N? fu頥ncontrado"
ELSE
ESCRIBE "El n? no estᠤn el vector"
FIN
ORDENAMIENTO:
La ordenaci� clasificaci�e datos es el proceso de organizar datos en alg?den o secuencia
espec�ca, tal como creciente o decrecente, para datos num鲩cos, o alfab鴩camente para datos
alfanum鲩cos.
Los m鴯dos de ordenaci�e dividen en dos categor�:
Ordenaci�e Arrays.
Ordenaci�e Archivos.
La ordenaci�e Arrays se denomina tambi鮠ordenaci�nterna, ya que se almacena en la memoria
interna de la camputadora a gran velocidad y acceso aleatorio. La ordenaci�e archivos se suele
hacer casi siempre sobre soportes de almacenamiento externo: Discos, Cintas, etc. y por ello, se
denomina ordenaci�xterna. Para este curso s�emplearemos la ordenaci�nterna, es decir, la
ordenaci�e Arrays.
ORDENACIӎ POR BURBUJA O INTERCAMBIO:
El m鴯do de la burbuja es uno de los m�conocidos por su sencillez y facilidad de implementaci�la
idea b�ca del m鴯do es comparar elementos consecutivos en cada paso a lo largo del vector. Cada
vez que se realiza una comparaci�e los elementos se intercambian entre s�n caso de no estar en
orden. Es decir, se examina dos elementos adyacentes X[i] y X[i+1]; en caso de no estar ordenados
X[i] < X[i+1] o bien X[i] > X[i+1] se intercambian los valores de dichos elementos.
Algoritmo de Ordenamiento por Burbuja:
Pseudoc�o BURBUJA
Variables
A:ARRAY[N] DE Entero
X,J,AUX:Entero
INICIO
FOR X=0 TO (N-2) DO
INICIO
FOR J=0 TO ((N-1)-(X+1)) DO
INICIO
IF A[J]>A[J+1] THEN
INICIO
AUX=A[J]
A[J]=A[J+1]
A[J+1]=AUX
FIN
FIN
FIN
FIN
1.1.3
Aplicaciones.
Mezcla de Vectores:
El proceso de mezcla, fusi� intercalaci�onsiste en tomar 2 vectores ordenados y obtener un
nuevo vector tambi鮠ordenado. El algoritmo m�sencillo para resolver el problema es el siguiente:
1.- Situar todos los elementos del primer vector en el nuevo vector.
2.- Situar todos los elementos del segundo vector en el nuevo vector.
3.- Ordenar el nuevo vector.
Esta soluci�iene un inconveniente, no se toma en cuenta que el vector 1 y 2 ya est鮠ordenados,
lo cual genera p鲤ida de tiempo en el proceso.
El algoritmo que toma en cuenta la ordenaci�s el siguiente:
1.- Seleccionar el elemento de valor m�peque� en cualquiera de los dos vectores y situarlo en el
nuevo vector.
2.- Comparar el vector 1 con �ice I y el vector 2 con �ice J y colocar el elemento m�peque�n el
vector 3 con �ice K.
3.- Seguir esta secuencia de comparaciones hasta que los elementos de un vector se hayan agotado
en cuyo momento se copia el resto del otro vector en el nuevo vector.
http://www.itver.edu.mx/comunidad/material/algoritmos/U4-41.htm
1.2 Arreglo bidimensional.
1.2.1
Conceptos básicos.
Este tipo de arreglos al igual que los anteriores es un tipo de dato estructurado, finito ordenado y
homogéneo. El acceso a ellos también es en forma directa por medio de un par de índices.
Los arreglos bidimensionales se usan para representar datos que pueden verse como una tabla con
filas y columnas. La primera dimensión del arreglo representa las columnas, cada elemento
contiene un valor y cada dimensión representa una relación
La representación en memoria se realiza de dos formas : almacenamiento por columnas o por
renglones.
Para determinar el número total de elementos en un arreglo bidimensional usaremos las
siguientes fórmulas:
RANGO DE RENGLONES (R1) = Ls1 - (Li1+1)
RANGO DE COLUMNAS (R2) = Ls2 - (Li2+1)
No. TOTAL DE COMPONENTES = R1 * R2
REPRESENTACION EN MEMORIA POR COLUMNAS
x : array [1..5,1..7] of integer
Para calcular la dirección de memoria de un elemento se usan la siguiente formula:
A[i,j] = base (A) + [((j - li2) R1 + (i + li1))*w]
REPRESENTACION EN MEMORIA POR RENGLONES
x : array [1..5,1..7] of integer
Para calcular la dirección de memoria de un elemento se usan la siguiente formula:
A[i,j] = base (A) + [((i - li1) R2 + (j + li2))*w]
donde:
i = Indice del renglón a calcular
j = Indice de la columna a calcular
li1 = Límite inferior de renglones
li2 = Límite inferior de columnas
w = Número de bytes tipo componente
Una MATRIZ o TABLA, es un arreglo de dos dimensiones, por lo cual se manejan dos índices; el
primer índice se refiere a la fila o renglón y el segundo a la columna; gráficamente lo podemos
entender así:
Col 1 Col 2 Col 3 Col 4
Fila 1
Fila 2
Fila 3
Fila 4
Para hacer referencia a un elemento de la matriz se tiene que indicar con dos índices:
Matriz[Fila][Columna]
Ejemplo:
M[3][4]=7, M[1][2]=4, M[5][3]=ERROR, M[2][1]=8
Col 1
Col 2
Col 3
Col 4
0
4
1
1
8
3
0
0
2
6
1
7
3
7
8
4
Declaración de Matrices
La declaración de una matriz es similar a la de un Areglo Unidimensional (Vector), con la
diferencia de que hay que agregar un índice para referenciar la nueva dimensión de la matriz, la
sintaxis es la siguiente:
NombreDelArreglo: ARRAY [# de Filas][# de Columnas] DE TipoDeDatos
Ejemplo: La declaración de la matriz mostrada en el ejemplo de arriba sería de la siguiente forma:
M: ARRAY [4][4] DE Enteros
Ejemplo con Matrices
Diseñe un Pseucocódigo que contenga en la memoria de la computadora una matriz de 3*3 con
números enteros leídos del teclado.
Pseudocódigo MATRIZ
Variables
MAT: ARRAY [3][3] DE Enteros
Fila, COlumna: Enteros
INICIO
FOR Fila=1 TO 3 DO
INICIO
FOR Columna=1 TO 3 DO
INICIO
ESCRIBE 'Teclee un número: '
LEE MAT[Fila][Columna]
FIN
FIN
FIN
Arreglos bidimensionales: tablas
Siguiendo la misma línea, se pueden relacionar grupos de vectores para formar tablas. Por
ejemplo, supóngase que se quieren almacenar nombres de personas y sus respectivos números de
teléfonos; se puede tener un vector de nombres y uno de números, de modo que el i-ésimo
número de teléfono (en un vector) sea de la i-ésima persona (en el otro vector). Pues bien: existe
un modo de fundir ambos vectores en un solo tipo de variable, una tabla de dos columnas y varias
filas (o viceversa) en la que la posición [1, i] indique el nombre de una persona y la posición [2, i]
indique el número de teléfono de esa persona. Para declarar esta estructura, se escribe:
Var
Tabla: Array[1..2, 1..n] Of String;.
y de manera general, se puede declarar un arreglo bidimensional así:
Var
<nomb>: Array[<li1>..<ls1>, <li2>..<ls2>] Of <tipo>;
De hecho, aunque no están contemplados en el programa del Laboratorio de Elementos de
Computación, se pueden definir más dimensiones; no hay un límite definido a la cantidad de
dimensiones de un arreglo.
Volviendo al ejemplo de la lista de personas y números de teléfono, se puede escribir un
programa completo que use una tabla para leer y guardar esta información:
Program Telefonos;
Uses WinCrt;
Const
n = 5;
Var
Tabla: Array[1..2, 1..n] Of String;
i: Integer;
Begin
For i:=1 To n Do Begin
WriteLn('Persona: ', i);
Write('Nombre: ');
ReadLn(Tabla[1, i]);
Write('Teléfono: ');
ReadLn(Tabla[2,i]);
End;
End.
En este momento, los datos están en la memoria, y el esfuerzo que se requirió para leerlos fue
menor que usando 10 variables independientes (cinco para los nombres y cinco para los
números). Además, con sólo cambiar un valor, el de n, el programa almacena más pares de
nombre y teléfono. Y aunque no se verá aquí, manipular esos datos una vez leídos es igual de
fácil.
Unas consideraciones importantes sobre los arreglos: son racimos o conjuntos de variables, pero
no pueden manejarse como tales. Siempre deberá hacerse referencia a una posición específica del
arreglo; si se desea acceder a una parte del mismo o a su totalidad, deberá hacerse de manera
iterativa, visitando los elementos que lo componen uno a uno.
http://www.intec.edu.do/~rjimenez/guia6.html
Arreglos Bidimensionales. Un arreglo bidimensional es un conjunto de datos del mismo tipo,
estructurado de tal forma que se precisa de dos índices para referenciar cada uno de sus
elementos, el primer índice se refiere a la fila y el segundo se refiere a la columna.
En cuanto al almacenamiento, el sistema reserva memoria para cada uno de sus elementos
destinando en conjunto, un bloque de la misma, este se halla estructurado así: almacenamiento
consecutivo y secuencial de una fila dentro de otra, sin solución de continuidad; almacenamiento
consecutivo y secuencial de una columna dentro de otra, sin solución de continuidad.
Para acceder a la posición de cada elemento del arreglo se tienen las siguientes fórmulas:
o
Almacenamiento por filas:
Dir E[I,J]=Dir E[Ii,Ji]+NumPos*(Nc*(I-1)+(J-1))
o
Almacenamiento por columnas:
Dir E[I,J]=Dir E[Ii,Ji]+NumPos*(Nf*(J-1)+(I-1))
Donde:
Dir E[I,J]: Dirección de memoria del elemento cuyos índices son I,J.
Dir E[Ii,Ji]: Dirección de memoria del elemento inicial del arreglo.
Nc: Número total de columnas.
Nf: Número total de filas.
Ii y Ji: Índices del elemento inicial.
Los arreglos bidimensionales tienen las mismas operaciones que los vectores. los algoritmos
cambian porque es necesario tener el orden según los dos índices.
1.2.2
Operaciones.
1.2.3
Aplicaciones.
1.3 Arreglo Multidimensional.
1.3.1
Conceptos básicos.
Este también es un tipo de dato estructurado, que está compuesto por n dimensiones. Para hacer
referencia a cada componente del arreglo es necesario utilizar n índices, uno para cada dimensión
Para determinar el número de elementos en este tipo de arreglos se usan las siguientes fórmulas:
RANGO (Ri) = lsi - (lii + 1)
No. TOTAL DE ELEMENTOS = R1 * R2* R3 * ...* Rn
donde:
i = 1 ... n
n = No. total de dimensiones
Para determinar la dirección de memoria se usa la siguiente formula:
LOC A[i1,i2,i3,...,in] = base(A) + [(i1-li1)*R3*R4*Rn + (i2li2)*R3*R2*... (in - lin)*Rn]*w
Arreglos multidimensionales
tanto C/C++ como Java permite arreglos con más de una dimensión, aunque los arreglos de tres o
mas dimensiones no son muy utilizados debido a la cantidad de memoria que se necesita para su
almacenamiento. El formato general es:
tipo nombre_arr [ tam1 ][ tam2 ] ... [ tamN];
Por ejemplo para definir un arreglo bidimensional lo hariamos de la siguiente forma:
int tabla[10][20];
Donde se define una matriz llamada tabla con 10 filas y 20 columnas.
Para acceder a los elementos se procede de forma similar al ejemplo del arreglo unidimensional,
esto es:
arreglo_bi[6][2] = 35;
/* Asigna 35 al elemento de la 7ª fila y la 3ª columna*/
num = arreglo_bi[25][16];
Ejemplo 14-1. Arreglo de enteros bidimensionales
A continuación se muestra un ejemplo que asigna al primer elemento de un arreglo bidimensional
cero, al siguiente 1, y así sucesivamente.
main()
{
int t,i,num[3][4];
for(t=0; t<3; ++t)
for(i=0; i<4; ++i)
num[t][i]=(t*4)+i*1;
for(t=0; t<3; ++t)
{
for(i=0; i<4; ++i)
printf("num[%d][%d]=%d ", t,i,num[t][i]);
printf("\n");
}
}
Arreglos Multidimensionales. Conjunto de datos del mismo tipo, estructurados de tal forma que
se necesitan tres o más índices para referenciar cada elemento.
Las reglas de índices, número de elementos y dimensión del arreglo son las mismas que para los
anteriores.
Por ejemplo, el arreglo tridimensional se almacena en la memoria como un vector compuesto por
vectores que representan las filas o columnas de cada una de las páginas
Arreglo Paralelos. En muchas ocasiones se requiere el proceso simultáneo de más de un arreglo,
que teniendo igual número de elementos, el tipo de datos de los mismo es distinto. Estos vectores
o matrices se denominan arreglos paralelos.
http://www.virtual.unal.edu.co/cursos/ingenieria/2001412/capitulos/cap1/141.html
1.3.2
1.3.3
Operaciones.
Aplicaciones.
Las operaciones en arreglos pueden clasificarse de la siguiente forma:






Lectura
Escritura
Asignación
Actualización
Ordenación
Búsqueda
a) LECTURA
Este proceso consiste en leer un dato de un arreglo y asignar un valor a cada uno de sus
componentes.
La lectura se realiza de la siguiente manera:
para i desde 1 hasta N haz
x<--arreglo[i]
b) ESCRITURA
Consiste en asignarle un valor a cada elemento del arreglo.
La escritura se realiza de la siguiente manera:
para i desde 1 hasta N haz
arreglo[i]<--x
c) ASIGNACION
No es posible asignar directamente un valor a todo el arreglo, por lo que se realiza de la manera
siguiente:
para i desde 1 hasta N haz
arreglo[i]<--algún_valor
d) ACTUALIZACION
Dentro de esta operación se encuentran las operaciones de eliminar, insertar y modificar datos.
Para realizar este tipo de operaciones se debe tomar en cuenta si el arreglo está o no ordenado.
Para arreglos ordenados los algoritmos de inserción, borrado y modificación son los siguientes:
1.- Insertar.
Si i< mensaje(arreglo contrario caso En arreglo[i]<--valor i<--i+1
entonces>
2.- Borrar.
Si N>=1 entonces
inicio
i<--1
encontrado<--falso
mientras i<=n y encontrado=falso
inicio
si arreglo[i]=valor_a_borrar entonces
inicio
encontrado<--verdadero
N<--N-1
para k desde i hasta N haz
arreglo[k]<--arreglo[k-1]
fin
en caso contrario
i<--i+1
fin
fin
Si encontrado=falso entonces
mensaje (valor no encontrado)
3.- Modificar.
Si N>=1 entonces
inicio
i<--1
encontrado<--falso
mientras i<=N y encontrado=false haz
inicio
Si arreglo[i]=valor entonces
arreglo[i]<--valor_nuevo
encontrado<--verdadero
En caso contrario
i<--i+1
fin
fin
ANEXOS:
ARREGLOS.
Estructura de datos: es una colección de datos organizados de un modo particular.
Arreglo (ARRAY): es una estructura de datos en la que se almacena una colección de datos
del mismo tipo. Ejemplo notas de los estudiantes.
• Tienen un único nombre de variable, que representa todos los elementos, los cuales se
diferencian por un índice o subíndice.
Ejemplo: NOTAS
nombre del arreglo
NOTAS[1]
nombre del primer elemento del arreglo NOTAS
NOTAS[n]
nombre del elemento n del arreglo NOTAS
1, 2, 3, ... n
índices o subíndices del arreglo
(pueden ser enteros, no negativos, variables o expresiones enteras)
Clasificación de los arreglos: los arreglos se clasifican en UNIDIMENSIONALES (vectores o
listas) y MULTIDIMENSIONALES (Ejemplo, los bidimensionales son las tablas o matrices).
Arreglos unidimensionales o vectores: son una lista o columna de datos del mismo tipo, a
los que colectivamente nos referimos mediante un nombre. Deben cumplir lo siguiente:
• Compuesto por un número de elementos finito.
• Tamaño fijo: el tamaño del arreglo debe ser conocido en tiempo de compilación.
• Homogéneo: todos los elementos son del mismo tipo
• Son almacenados en posiciones contiguas de memoria, cada uno de los cuales se les
puede acceder directamente.
• Cada elemento se puede procesar como si fuese una variable simple ocupando una
posición de memoria.
Ejemplo: Dado un vector denominado Z cada uno de sus elementos se designará por ese
mismo nombre diferenciándose únicamente por su correspondiente subíndice.
En TURBO PASCAL los arreglos unidimensionales se declaran de la siguiente manera:
TYPE
identificador = ARRAY [tipo-subíndice] OF tipo;
Donde: identificador : es el nombre del arreglo
tipo-subíndice: puede ser tipo ordinal (boolean o char), tipo enumerado o tipo
subrango. No pueden ser usados los tipo estandar (real o integer)
tipo: se refiere al tipo de los elementos y puede ser de cualquiera de los tipos
estándar o definido por el usuario.
Otros ejemplos de declaración de arreglos:
TYPE
X = ARRAY [TRUE .. FALSE] OF REAL;
CODIGO = ARRAY [1 .. 10] OF CHAR;
ALBA = ARRAY [0 .. 100] OF 1 .. 999;
NOMBRE = ARRAY [1 .. 60] OF STRING[20];
ETIC = ARRAY [‘A’ ..`F’] OF REAL;
NOT1 = ARRAY [1..6] OF REAL;
http://www.ceidis.ula.ve/cursos/ingenieria/pd_10/clases/Apunt_7
.pdf
Vectores
Un "agregado" es una colección de entidades almacenadas en una unidad. El
"vector" es el mecanismo básico para almacenar una colección de entidades del
mismo tipo.
En Java el vector no es un tipo primitivo, por lo que se comporta de forma muy
similar al resto de los objetos. Así, muchas de las reglas de los objetos pueden
aplicarse también a los vectores (ver objeto).
Cada entidad en el vector puede ser accedida mediante el "operador de
indexación de vectores [ ]". Decimos que el operador [ ] indexa el vector en el
sentido de que especifica qué objeto debe ser accedido. A diferencia de C y C++,
el chequeo de los límites se realiza automáticamente.
En Java, los vectores se indexan comenzando siempre en cero. Así, un vector "x"
de tres elementos almacena x[0], x[1], y a x[2]. El número de elementos que puede
ser almacenado en un vector "x" se obtiene con "x.length". Observe que en esta
ocasión "no hay paréntesis" en ".length". Un bucle típico para recorrer un vector
se basaría en:
for ( i = 0; i <
x.length; i++)
instrucción1;
Declaración de un vector y Asignación en un vector
Un vector es un objeto, así que dada una declaración de vector :
int [ ] vector1;
Aún no se ha asignado memoria para guardar el vector. "vector1" es
simplemente una referencia de un vector, por lo que en este momento es "null"
(vacio). Para generar 100 valores de tipo int, por ejemplo, aplicaríamos la
instrucción "new" como sigue:
vector1 = new int
[100];
existen otras formas de declarar vectores. Por ejemplo, en algunos contextos lo
siguiente es aceptable:
int [ ] vector2 = new int
[100];
También pueden emplearse listas de inicialización, como en C o C++ para
especificar valores iniciales. En el siguiente ejemplo, un vector de cuatro enteros
(int) es almacenado y referenciado por "vector3"; es decir, el vectore3 es
declarado e inicializado :
int [ ] vector3 = {3, 4,
10, 6};
Los corchetes pueden colocarse antes o después del nombre del vector. Como
puede apreciarse en,
int [ ] vector1;
equivale a escribir:
int vector1 [ ] ;
O bién en:
int [ ] vector3 = {3, 4,
10, 6};
equivale a escribir
int vector3 [ ] = {3, 4,
10, 6};
Situándolos antes es más sencillo ver que el nombre corresponde a un objeto de
tipo vector, por lo que éste puede ser un buen estilo de programación.
Declaración de un vector de objetos
La declaración de un vector de objetos ( en lugar de tipos primitivos) requiere la
misma sintaxis. Obsérvese, sin embargo, que cuando se guarda un vector de
objetos, cada objeto alamacena inicialmente una referencia "null". Además cada
una de ellas debe redefinirse para que pase a referenciar a un objeto creado. Por
ejemplo, un vector de cinco botones se construye como sigue:
Button [ ] vectorDeBotones;
vectorDeBotones = new Button [5];
for ( i = 0; i < vectorDeBotones.length; i++)
vectorDeBotones[ i ] = new Button();
Dirección y contenido de un arreglo unidimensional o Vector
Un arreglo es un grupo de posiciones de memorias contiguas, todas las cuales
tienen el mismo nombre y el mismo tipo. Para referirnos a una posición o
elemento en particular del arreglo, especificamos el nombre del arreglo y el
número de ese elemento en el arreglo.
Por los que detectamos conceptos importante, como conceptos importantes en un
vector en el momento de definirlo y que son: nombre del vector, el tipo de dato
que contendrá cada casilla (condición todos del mismo tipo), cuantos elementos
tendrá.
Desde el punto de vista operativo los conceptos importantes son : su dirección y el
contenido. La dirección es la posición en que se encuentran las localidades de
memoria, mientras que el contenido es el valor que se asigna a esa casilla de
memoria. Esto se ejemplifica en la siguiente figura:
En general se puede describir al i-ésimo elemento del vector x con x [i-1] dado
que por ejemplo el elemento 2 se encuentra en la dirección x [1].
Operaciones con vectores
En Java pueden realizarse todas las operaciones permitidas por el tipo de datos
que contiene el vector.
Ejemplo 1:
Tomando el vector de la figura anterior, este está definido como de tipo int, así
que se pueden sumar, restar, multiplicar, dividir (recordar la división entre
enteros), etc. :
a = 1; b = 2;
x[a + b] = x[a + b] + 2;
que equivale a escribir:
a = 4; b = -2;
x[a + b] += 2;
dando en ambos casos como resultado x[3] = 26.
x[0] = (int)
(Math.pow(x[0],2)) ;
dando x[0] = 1.
x[3] = x[3] + x[0] +
x[1];
dando x[3] = 31.
x[2] = x[3] / x[4];
dando x[2] = 2. Es importante recalcar que en este caso ni x[3], ni x[4] alteran su
contenido. Su contenido se tomo para realizar la operación y dejarla en x[2].
Ejemplo 2:
Se tiene un vector que se declara de tipo String :
String [ ] nom = {"Pedro", "William", "Rafael",
"Jesús"};
El vector nom contiene 4 elementos que se encuentran en memoria como sigue:
nom
nom
nom
nom
[0]
[1]
[2]
[3]
Pedro
William
Rafael
Jesús
las cadenas pueden concatenarse (sumarse) como sigue:
x[2] = x[3] + x[2];
dando la cadena x[2] = "Jesús Rafael". Esto último también puede escribirse
como:
x[2] += x[3];
Pueden asignarse valores nuevos como:
x[2] = "Jorge";
x[3] = "Luga";
Si esto ocurre los valores anteriores de x[2] y x[3] ya no existen fueron
substituidos por los valores recien ingresados "Jorge" y "Luga".
http://www.udlap.mx/~ccastane/Syllabus_Progra_Basica/Notas_P
rog_Basica/3_POO/3_1_arreglos_vectores.html
¿Qué es un Array?
Un array (arreglo) es una estructura de datos en la que se almacena una colección de datos del mismo
tipo. Dicho de otro modo, un array es una lista de un número finito de N elementos del mismo tipo que se
caracteriza por:



Almacenar los elementos en posiciones de memoria continua
Tener un único nombre de variable que representa a todos los elementos y éstos a su vez se
diferencian por un indice o subíndice
Acceso directo o aleatorio a los elementos individuales del array.
Salario
nombre del array
Salario [1]
elemento del array
Salario [2]
1,2,..., n subíndice del array
:
Salario [n]
Los componentes individuales de un array se llaman elementos y se distingue entre ellos por el nombre
de array seguido de uno o varios índices o subíndices.
Para poder utilizar arrays en un programa es necesario declararlos antes de hacer cualquier operación
con ellos, indicando él número y el tipo de elementos que contendrá.
Ventas
1
2
4856.20
6253.21
3
5230.50
4
5
6
7
6251.40
7562.00
5423.12
6352.12
Un array se identifica por su nombre y se le asocia con un nombre de variable válida.
Los arrays se clasifican en:



Unidimensionales ( vectores o listas)
Bidimensionales (tablas o matrices)
Multidimensionales
4.2.1 Array Unidimensionales ( vectores).
Un arreglo unidimensional es un tipo de datos estructurado que está formado de una colección finita y
ordenada de datos del mismo tipo. Es la estructura natural para modelar listas de elementos iguales.
Un array de una dimensión (unidimensional) - vector o lista - es un tipo de datos estructurado compuesto
de un número de elemento finito, tamaño fijo y elementos homogéneos.
Finito:indica que hay un último elemento.
Tamaño fijo: significa que el tamaño del array debe ser conocido en tiempo de compilación.
Homogéneo: significa que todos los elementos son del mismo tipo.
Los elementos del array se almacenan en posiciones contiguas de memoria, a cada una de las cuales se
puede acceder directamente y los cuales son representados en forma de una fila o columna como se
muestra a continuación:
Arreglo que almacena las Ventas de 5 empleados:
1
2
3
4
5
Ventas 4856.20 6253.21 5230.50 6251.40 7562.00
Ventas
1
2
4856.20
6253.21
3
5230.50
4
6251.40
5
7562.00
DECLARACION.
La declaración del número y tipo de elementos se realiza de diversas maneras según cada lenguaje. En
Pascal la forma de declarar un arreglo puede ser de dos formas:
1. Como una variable
Var
NombreArreglo: Array [1.. tamaño del vector] of tipo_de_dato;
Consideremos el ejemplo de Ventas anterior:
Var
Ventas : Array [1..5] of real;
2. Declarando primero un nuevo tipo de dato y luego declarar un arreglo de ese tipo
Type
NuevoTipo = Array [1.. tamaño del vector] of tipo_de_dato;
Var
NombreArreglo:NuevoTipo;
Consideremos el ejemplo de Ventas anterior:
Type
Arreglo= array [1..5] of real;
Var
Ventas:Arreglo;
Ejemplos típicos de índices son:




1..10 ---------- enteros
'C'.. ´N´ -------- caracteres
true..false ---- lógicos
azul..marron- enumerados
Nota: tipo_de_dato: puede ser cualquier tipo de datos (INTEGER, REAL, CHAR, etc).
Operadores con arrays ( vectores)
Los vectores (arrays) no se pueden leer/escribir en una sola operación o sentencia. La lectura o escritura
de un array se debe hacer elemento a elemento, y para realizar estas operaciones se deben leer o
visualizar los componentes de un array mediante estructuras repetitivas. Supongamos por ejemplo, que el
vector Notas contiene las 30 calificaciones obtenidas en un examen por los alumnos en la asignatura de
Programación.
Type
ListadeNotas = array [ 1..30 ] of integer;
Var
Notas : ListadeNotas;
LECTURA DE UN VECTOR.
La lectura de un vector se puede realizar con bucles implementados con las estructuras for, while y
repeat.
Bucle for
a) for I:= 1 to 30 do
readln (Notas[1]);
Bucle while
a) I:=1;
While I<=30 do
Begin
Read (Notas[I];
I:=I+1;
end;
Bucle repeat
a) I:=1;
Repeat
Read(Notas[I]);
I:=I+1;
Until I>30;
Ejemplos:
Type
Nombres =
ARRAY [1..7]
Vector =
ARRAY [1..10]
Temperat =
ARRAY [1..5]
OF
STRING;
OF
INTEGER;
OF REAL;
Define el tipo Nombre que serán arreglos unidimensionales
de 7 elementos cadena
Define el tipo Vector que serán arreglos unidimensionales
de 10 elementos enteros
Define el tipo Temperat que serán arreglos
unidimensionales de 5 elementos reales
Una vez definidos los nuevos tipos de datos podrán ser utilizados para definir las variables del nuevo
tipo.
Una vez declarado el arreglo (Variable de tipo arreglo), podemos realizar operaciones con ese vector,
dichas operaciones se pueden realizar con un elemento individual o sobre el vector completo mediante
instrucciones básicas o estructuras de control.
ASIGNACIÓN.
Si se desea, puede darle un valor definido a alguno de los elementos mediante la instrucción siguiente:
Pseudocódigo
Pascal
Ventas [ 4 ] --> 1850.26 Ventas [ 4 ] : = 1850.26
Asigna el valor '1850.26' al elemento 4 del vector VENTAS
LECTURA Y ESCRITURA.
Si se desea leer un solo elemento del arreglo Ventas, por ejemplo, la posición 3, bastaría la siguiente
sentencia:
Pseudocódigo
Leer ( Ventas [ 3 ] )
Escribir ( Ventas [ 3 ] )
Pascal
ReadLn ( Ventas [ 3 ] ) ;
WriteLn ( Ventas [ 3 ] ) ;
Siguiendo con nuestro ejemplo de Ventas, el cual tiene 5 posiciones, pudiéramos pensar que si agrego
las siguientes instrucciones, se habrá leído todo el arreglo:
Pseudocódigo
Pascal
Leer ( Ventas [ 1 ] )
Leer ( Ventas [ 2 ] )
Leer ( Ventas [ 3 ] )
Leer ( Ventas [ 4 ] )
Leer ( Ventas [ 5 ] )
ReadLn ( Ventas [ 1 ] ) ;
ReadLn ( Ventas [ 2 ] ) ;
ReadLn ( Ventas [ 3 ] ) ;
ReadLn ( Ventas [ 4 ] ) ;
ReadLn ( Ventas [ 5 ] ) ;
Sin embargo resultaría poco práctico tener que hacer esto para un arreglo que contiene 50 posiciones (o
quizá más). Por tanto si observamos las líneas anteriores, encontramos que se repiten las mismas
instrucciones, solo cambia el subíndice.
Si empleamos estructuras iterativas, donde las variables de control (por ejemplo X) se utilizan como
subíndices del vector (por ejemplo, Ventas[x]). El incremento del contador del bucle producirá el
tratamiento sucesivo de los elementos del vector y entonces lograremos leer un arreglo de 50 (o más
posiciones) con solo unas cuantas líneas, sin tener que escribir una para cada posición.
Lectura, llenado o carga del vector Ventas de 5 posiciones:
x <-- 1
Mientras x <= 5 hacer
Leer ( ventas [x] )
Mientras
x <-- x + 1
Fin mientras
x <-- 0
Repetir
Repetir x <-- x + 1
Leer ( ventas [x] )
Hasta que x = 5
Desde x <-- 1 hasta 5 hacer
Leer ( ventas [x] )
Desde
Fin desde
La escritura o despliegue es similar a la tabla anterior, sustituyendo las líneas de Leer( Ventas [ x ] ) por la
de Escribir (Ventas [ x ])
x <-- 1
Mientras x <= 5 hacer
Escribir ( ventas [x] )
Mientras
x <-- x + 1
Fin mientras
x <-- 0
Repetir x <-- x + 1
Repetir
Escribir ( ventas [x] )
Hasta que x = 5
Desde x <-- 1 hasta 5 hacer
Escribir ( ventas [x] )
Desde
Fin desde
COPIA DE VECTORES.
Una operación que se suele dar en ocasiones es la copia de los elementos de un vector en otro vector.
Supongamos, por ejemplo, que los vectores alfa y beta se declaran con:
Type
Listareal= array[1..5] of real;
Var
Alfa,beta:Listareal;
Si el vector alfa tiene asignados valores, éstos se pueden copiar en el vector beta por la sentencia.
For I:= 1 to 5 do
Beta[I]:= alfa [I];
Nota: En general, un array puede ser asignado a otro array solo cuando ambos tienen el mismo tipo y el
mismo tamaño. Esto significa que deben ser declarados por el mismo identificador o identificadores
equivalentes.
VALORES MINIMO Y MAXIMO DE UN VECTOR.
Supongamos que se dispone de un vector números enteros que definen en la declaración.
Type
ListaNúmeros = array [1..100] of integer;
Var
Calificaciones, Números : ListaNúmeros;
I,,Maximo, N: Integer;
Begin { Este programa obtiene el elemento mayor del vector Números de N elementos}
N:=10;
Maximo:=0;
For I := 1 to N do
Begin
Write ('dame el valor del elemento', I);
Readln (Números [ I ]);
If Números [ I ] > Maximo then
Maximo := Números [ I ];
End;
Write('El numero mayor del vector Números es: ', Maximo);
End.
Arrays paralelos.
Dos o más arrays que utilizan el mismo subíndice para referirse a términos homólogos se llaman arrays
paralelos. Estos arrays se pueden procesar simultáneamente. Considere el caso de tener que representar
dos vectores, uno para nombres de estudiantes - Nombres - y otro la nota media de un curso - promedia .
Un método lógico es utilizar dos vectores en los que el primer elemento del arreglo Nombres sea el
nombre, sea el nombre del alumno cuya promedio es el primer elemento de Media, y así sucesivamente.
Nombres[1]
Nombres[2]
Nombres[3]
Nombres[4]
Nombres[5]
Mortimer
Juan
Claudia
Alejandra
Gerardo
.
:
.
:
Nombres[30]Noemi
Var
I : integer;
Nombres : array [1..30] of string [20];
Media : array [1..30] of real:
Begin
For I:=1 to 30 do
Begin
Writeln( 'dame el nombre del alumno' );
Readln( Nombre[i] );
Writeln( 'dame el valor de la media' );
Readln( Media[I] );
Writeln( Nombre[I], Media[I]: 8:3 );
End;
End.
Media[1]
Media[2]
Media[3]
Media[4]
Media[5]
.
:
Media[30]
4.234
5.634
8.734
5.734
4.754
.
:
9.224
4.2.2 Array Bidimensionales (Tablas).
Se puede considerar como un vector de vectores o un arreglo de arreglos unidimensionales y constituyen
la forma más simple de los arreglos multidimensionales. Es, por consiguiente, un conjunto de elementos,
todos del mismo tipo (homogéneo), en el cual el orden de los componentes es significativo y el acceso a
ellos también es en forma directa por medio de un par de índices para poder identificar a cada elemento
del arreglo. También se les llama Matriz o Tabla.
Los elementos se referencian con el formato:
T [3,4] elemento de la fila 3 y columna 4.
Los arreglos bidimensionales se usan para representar datos que pueden verse como una tabla con filas
y columnas.
Matriz
1
2
3
4
5
1
2
3
15.2
4
DECLARACIÓN.
Al igual que en los arrays de una dimensión (los vectores), los arrays multidimensionales (tablas) se
crean con declaraciones Type y Var cuando un programa se codifica en Pascal.
Podríamos definir una matriz de 4 renglones por 5 columnas que almacene datos reales de la siguiente
manera:
· Nombre del array
· Tipo del array (recuerde que todos los elementos de un array deben ser del mismo tipo)
· El rango permitido(es decir, el primero y último valor posible) por cada subíndice.
1. Como una variable
Var
Matriz : Array [ 1..4 , 1..5 ] of real;
2. Declarando primero un Nuevo tipo de dato y luego declarar un arreglo de ese tipo
Type
Mat= Array[1..4,1..5] of real;
Var
Matriz:Mat;
Nota: Mat y Matriz, son nombres arbitráreos de identificadores
ASIGNACIÓN.
Se considera que este arreglo tiene dos dimensiones (un subíndice para cada dimensión) y necesita un
valor para cada subíndice, y poder identificar un elemento individual. En notación estándar, normalmente
el primer subíndice se refiere a la fila del arreglo, mientras que el segundo subíndice se refiere a la
columna del arreglo. Es decir, Matriz(I,J), es el elemento de Matriz que ocupa la I-ésima fila y la J-ésima
columna.
Para tener acceso a un elemento de la matriz se tiene que especificar primero el renglón después una
coma y por último la columna a la que se quiere tener acceso.
Ejemplo:
Matriz [ 3, 2] : = 15.2;
LECTURA Y ESCRITURA.
Si se deseara leer un solo elemento de un arreglo bidimensional debe especificarse el renglón y la
columna a que se refiere, por ejemplo, la posición 3,2:
Pseudocódigo
Leer ( Ventas [ 3, 2] )
Escribir ( Ventas [ 3, 2] )
Pascal
ReadLn ( Matriz [ 3, 2] ) ;
WriteLn ( Matriz [ 3, 2] ) ;
Pero si el objetivo es, leer o escribir la matriz completa entonces al igual que con los arreglos
unidimensionales se deben usar estructuras iterativas:
Ejemplo:
Supóngase que una empresa tiene 6 sucursales y en cada una de ellas han contratado 10 empleados.
Se desea registrar los salarios y mostrarlos posteriormente.
Lectura con estructura DESDE
Para x <-- 1 hasta 6 hacer
inicio
escribir ( " sucursal ", x )
para y <-- 1 hasta 10 hacer
Desde
inicio
escribir ( " sueldo del empleado ", y )
leer ( salarios [x, y] )
Fin para y fin para x
Escritura con estructura DESDE
Para x <-- 1 hasta 6 hacer
inicio
escribir ( " sucursal ", x )
para y <-- 1 hasta 10 hacer
Desde
inicio
escribir ( " sueldo del empleado ", y, " es:" )
escribir ( salarios [x, y] )
Fin para y fin para x
Nota: Es importante mencionar que aunque se haya utilizado la estructura Desde, para la lectura y
escritura, bien podría utilizarse cualquiera de las estructuras iterativas (Repetir o Mientras).
4.2.3 Array Multidimensionales.
Hasta ahora toda la información procesada se manipula con una sola columna o lista de entrada, el
llamado vector o array de una dimensión. Sin embargo, en numerosas ocasiones es necesario trabajar
con tablas que tengan diferentes columnas.
Por ejemplo, la siguiente tabla de temperaturas de un mapa del tiempo:
Ciudad
Día
Madrid
Jaén
Granada
Sevilla
1
35
40
38
41
2
34
41
37
40
3
30
38
35
42
4
36
39
37
40
5
29
42
40
39
...
...
Un sistema de estructurar los datos podría ser con cuatro arrays paralelos. Sin embargo, puede utilizarse
un array de dos dimensiones que suele ser un método más eficiente generalmente. En este caso se
puede emplear un array de dos dimensiones (dos subíndices), una dimensión(subíndice) correspondiente
a los nombres de la fila y otro para los nombres de la columna.
Así el ejemplo anterior se puede representar con:
Type
Ciudad=(Madrid,Jaen,Granada,Sevilla);
Var
Temperatura: :array [1..5,ciudad] of integer {los dos arreglos son del mismo tipo}
Los datos se pueden almacenar en el array mediante sentencias de asignación de la forma:
Temperatura [1, Madrid]:=35;
Temperatura [1, Jaen]:=38;
Los arrays se clasifican de acuerdo al número de índices o dimensiones en bidimensionales o
multidimensionales propiamente dichos(más de una dimensión). Los arrays multidimensionales se
declaran de igual modo que los arrays de una dimensión.
http://148.202.148.5/cursos/cc102/int_programacion/MODULOIV/
tema4_2.htm
ARREGLOS:
Un arreglo es una colección de datos del mismo tipo, que se almacenan en posiciones
consecutivas de memoria y reciben un nombre común. Para referirse a un determinado
elemento de un arreglo se deberá utilizar el nombre del arreglo acompañado de un
índice el cual especifica la posición relativa en que se encuentra el elemento.
Los arreglos pueden ser:
unidimensionales (vectores).
Bidimensionales (matrices, tablas).
Multidimensionales(tres dimensiones o más).
ARRAY UNIDIMENSIONALES O VECTORES
Los pasos para la utilización de un arreglo son;
1 Declarar el arreglo: consiste en establecer el nombre, el tamaño y el tipo de los datos
que se van a almacenar en el arreglo ejemplo:
hay que diferenciar dos términos :
tamaño del vector (T): es el numero máximo de elementos que puede contener el
arreglo.
Numero de elementos(N): que indica cuantos elementos hay almacenados en el arreglo
en determinado momento. Nota N<=T.
T=10;
Real:notas[T]
2 Llenar el arreglo con los datos: Se puede hacer en el momento de la declaración
asignando al arreglo los valores que necesitamos almacenar. Ejemplo.
float notas[10] = {2.3 , 3.5 , 4.2 , 3.3 , 3.0 , 4.9 , 4.2 , 3.0 , 2.0 , 1.5 };
ó recorriendo el arreglo así:
para i = 1 hasta N
.......lea notas[i];
fin del para
3 manipular la información guardada en el vector. Para esto es necesario recorrer dicha
estructura y se puede hacer de la siguiente manera.
para i = 0 hasta N
......mostrar notas[i];
fin del para
las operaciones que se pueden realizar con los arreglos son las siguientes:
- lectura (llenar el vector)
- escritura (mostrar el vector)
- asignación (dar valor a una posición específica)
- actualización (inserción , eliminación, modificación )
- ordenación . (burbuja, inserción directa, selección directa, selle y quicksort).
- búsqueda. (secuencial , binaria, hash( por claves) ).
http://ayura.udea.edu.co/~jlsanche/vectores/vectores.htm
ARREGLOS DE UNA DIMENSION
Los arreglos es una herramienta maravillosa, le permite asociar un solo nombre de variable a una colección completa de datos
puede mover el arreglo completo en memoria, copiarlo y además solo haciendo referencia a un solo nombre de variable un arreglo
es un conjunto finito ordenado de elementos homogéneos, la propiedad de ordenación significa que es posible identificar el primero,
segundo, tercero... y el enésimo elemento del arreglo, un arreglo puede ser un conjunto de elementos de tipo cadena en tanto que
otro puede ser de tipo entero.
Sintaxis en pascal:
Type
nombre_arreglo = array [rango_inicial...rango_final] of tipo_arreglo;
Var
Identificador: nombre_arreglo;
ARREGLOS MULTIDIMENSIONALES
Existen grupos de datos que se representan mejor en forma de tabla o matriz cada dos o mas subíndices a esos les llamamos
arreglos multidimensionales se les llama así porque a diferencia de un arreglo bidimensional estos constan de dos o mas
dimensiones.
ARREGLOS BIDIMENSIONALES
Un array bidimensional se puede considerar como un vector de vectores. Es decir un conjunto de elementos todos del mismo tipo,
en el cual el orden de los componentes es significativo y en el que se necesitan especificar dos subíndices para poder identificar
cada elemento del arreglo: Una forma importante de representar datos en un array bidimensional puede verse de forma lógica como
una tabla de filas y columnas
ORDENACIONES
En los vectores con frecuencia es necesario clasificar u ordenar sus elementos en un orden particular La clasificación es una
operación tan frecuente en programas de computación que una gran cantidad de algoritmos se han diseñado para clasificar listas de
elementos con eficacia y rapidez.
La ordenación o clasificación depende del tamaño del vector o array a clasificar, el tipo de datos y la cantidad de memoria
disponible(Esta puede ser de forma creciente o decreciente). La ordenación puede ser de forma ascendente o descendente para
datos numéricos alfabéticos o para datos de caracteres.
METODO DE LA BURBUJA
El algoritmo de clasificación de la burbuja se basa en el proceso de comparar pares de elementos adyacentes e intercambiarlos entre
si hasta que estén todos ordenados, los pasos a dar son:
Comparar el elemento ad1y ad2 si están en orden se mantienen en caso contrario se intercambia entre si.
A continuación se comparan los elementos 2 y 3 de nuevo se intercambia si es necesario.
El proceso continúa hasta que cada elemento del vector ha sido comparado con sus elementos adyacentes y se han realizado los
intercambios necesarios.
01 01 01 01 01 01
36 36 36 36 06 06
24 24 24 06 36 36
10 10 06 24 24 24
06 06 10 10 10 10
12 12 12 12 12 12
y se sigue con 6 iteraciones hasta que este ordenado .
CLASIFICACION POR SHELL
Esta clasificación fue desarrollada por Daniel Shell para evitar la ineficiencia de la clasificación por burbujas para grandes matrices.
La clasificación de Shell se diferencia de la clasificación por Burbuja en que se compara elementos mas separados antes de
comparar los elementos adyacentes.
Esto elimina gran parte del desorden de la matriz en las primeras iteraciones.
La clasificación de Shell utiliza una variable llamada intervalo que inicialmente recibe un valor igual a la mitad del numero de
elementos que hay en la matriz. El valor del intervalo especifica la distancia entre cada par de elementos comparables de la matriz.
.44 .11 .11 .11 .11
.88 .88 .66 .66 .66
.22 .22 .22 .22 .22
.77 .77 .77 .77 .55
.33 .44 .44 .44 .44
.66 .66 .88 .88 .88
.44 .44 .44 .44 .44
.55 .55 .55 .55 .77
Los elementos separados serán inicialmente de un intervalo de 4 para este ejemplo, la primera iteración de la matriz comparara
todos los elementos separados por esa distancia el proceso se repite hasta que no hay intercambios, con un intervalo de 4 y se
vuelve a iniciar calculando el nuevo intervalo hasta que este llegue a 1.
CLASIFICACION POR QUICK SORT
Aunque la eficiencia de la clasificación Shell aumenta a medida que crece el numero de elementos también tiene limitaciones. La
clasificación rápida que es un algoritmo recursivo de clasificación aumenta la velocidad a medida que el numero de elementos se
aproxima a 150 o 200 , de hecho la clasificación rápida es uno de los algoritmos de clasificación que mas se utilizan en la actualidad.
Si la matriz se pasa a la rutina de clasificación rápida el algoritmo seleccionara el valor contenido en [Principiolista+ Finallista]div 2 ;
cualquier valor de la lista será colocado en una lista y los valores que sean mayores o iguales que el separador de lista se colocaran
en una segunda lista como se muestra a continuación:
60
20
10
30
40
50
80
70
0
El orden que debe llevar las iteraciones es desde el punto medio hacia abajo y después de arriba hacia el centro después se sigue
haciendo ciclos con los demás bloques.
BÚSQUEDA SECUENCIAL
Es la técnica de búsqueda mas simple comenzando por la cabeza de la lista se busca un determinado registro examinando cada
registro secuencialmente hasta que se encuentra o la lista es agotada.
Este algoritmo es adecuado tanto para listas secuenciales como para listas enlazadas. La lista no tiene que estar ordenada aunque la
eficiencia de la búsqueda puede mejorarse si lo esta.
ALGORITMO:
Comenzar por el primer elemento de la lista mientras mas elementos en la lista y valor clave no encontrado. Obtener siguiente
elemento de la lista:
Si valor clave = encontrado
Entonces devolver registro encontrado
Si no devolver registro no encontrado
BÚSQUEDA BINARIA
Es uno de los algoritmos mas rápidos que usan los programadores .A diferencia de la búsqueda secuencial que examina elementos
sucesivos de la matriz , la búsqueda binaria reduce el numero de elementos que deben ser examinados.
Con un factor de dos en cada iteración hasta que se encuentra el registro deseado .La disminución de los tiempos de ejecución se
hace mas importante al aumentar el tamaño de la matriz .
La primera iteración de la búsqueda examina toda la matriz. Supongamos que las variables bajo y alto en el siguiente ejemplo
reciben los valores de 0 y 9. La variable índice medio es el elemento medio del intervalo de búsqueda. Valores de [Indice_Medio]
contiene el valor que se va a comparar con el valor deseado.
Valor a buscar =jelipe
Indice_medio =(Alto + Bajo) div 2
(9+0) div 2=4
(9+4) div2= 6
(6+4) div2 =5
Si el valor determinado en valores (índice medio) es igual al valor deseado la búsqueda se completa dando un valor verdadero a la
variable encontrada. Si el valor contenido en valores (Indice Medio) es mayor que el valor deseado el algoritmo de búsqueda se
modificó, ya que el valor indica que no hay razón para seguir buscando en ese punto la lista.
BÚSQUEDA MEDIANTE TRANSFORMACIÓN DE CLAVES HASH
L a búsqueda binaria proporciona un medio para reducir el tiempo requerido de buscar en una lista, sin embargo este método exige
que los datos estén ordenados. Existen otros métodos que pueden aumentar la velocidad de búsqueda en los datos y no necesitan
estar ordenados. Este otro método se denomina Hash o transformación de claves. El método Hash consiste en convertir la clave
dada numérica o alfanumérica en una dirección (índice) dentro del array .La correspondencia entre las claves y la dirección en el
medio de almacenamiento o el array se establece por una función de conversión (Función Hash).
Existen numerosos métodos de transformación de claves todos ellos tienen en común la necesidad de convertir en direcciones .En
esencia la función de conversión equivale a un calculador de direcciones .Cuando se desea localizar un elemento de clave x el
indicador de direcciones indicara en que posición del array estará posicionado el elemento.
TRUNCAMIENTO
Ignora parte de la clave y utiliza la parte restante directamente como índice (Considerando campos no numéricos y sus códigos
numéricos) .Si las claves por ejemplo, son enteros de 8 dígitos y la tabla de transformación tiene 1000 posiciones entonces el
primero, segundo y quinto dígitos desde la derecha pueden formar la función de conversión.
0
Clave=72588495
:
El truncamiento es un método muy rápido pero falla para distribuir las claves de modo uniforme.
PLEGAMIENTO
Consiste en la partición de la clave en diferentes partes y la combinación de las partes en un modo conveniente (a menudo
utilizando suma o multiplicación) para obtener el índice .La clave x se divide en varias partes donde cada parte tienen el mismo
numero de dígitos que la dirección especificada.
1000 000 a 999 Fx=x1+x2+x3...+xn
625 381 94 625+381+94=11100 100
ARITMÉTICA MODULAR
Convertir la clave a un entero , dividir por el tamaño del rango del índice y tomar el resto como resultado. La función de conversión
utilizada es MOD (Modulo o resta de división entera).
Donde el mes el tamaño del arreglo con índices de 0 hasta n-1. Los valores de la función y direcciones van de 0 a n-1 ligeramente
menor al tamaño del array. La mejor elección de los módulos son los números primos.
M=100 F(x)= x mod 100
X=234661234
F(x)=234661234 mod 100 =34
La clave de búsqueda en una cadena de caracteres tal como el nombre para obtener direcciones de conversión el método mas
simple es asignar a cada caracter de la cadena un valor entero (ejemplo A=1, B=2, C=3,etc) y sumar los valores de los caracteres
en la cadena al resultado se le aplica entonces el modulo.
MITAD DEL CUADRADO
Este método consiste en calcular el cuadrado de la clave x. La función de conversión se define como F(x)=C donde C se obtiene
eliminado dígitos de ambos extremos de x2; para todas las claves se deben usar las mismas posiciones de x2. Ejemplo:
Una empresa tiene 80 empleados y cada uno de ellos tiene un numero de identificación de 4 dígitos y el conjunto de direcciones de
memoria varia en un rango de 0 a 100 calcular las direcciones que se obtendrán al aplicar mitad del cuadrado.
Clave x x2 4 y 5
4205 176 82 025 82
7148 510 93 904 93
3350 112 22 500 22
COLISIONES
La función de operación Hash no siempre proporciona valores distintos, puede ser que para dos claves diferentes x1 y x2 se obtenga
la misma dirección. Esta situación se denomina colisión y se debe de encontrar métodos para su correcta resolución.
F (123445678) mod 100= 44
colisión
F (123445880) mod 100= 44
RESOLUCION DE COLISIONES
Si la clave transformada nos da una dirección que ya esta ocupada incrementamos el índice y examinamos el espacio siguiente. Para
buscar un registro usando esta técnica de manejo de colisiones efectuamos la función Hash sobre la clave y luego comparamos la
clave deseada con la real en la posición asignada. Si las claves no coinciden iniciamos una búsqueda secuencial, comenzando con la
siguiente posición del array.
REHASHING
Otra técnica común para la resolución de colisiones se llama rehashing. Si el primer calculo de la función Hash produce una colisión
se utiliza la dirección transformada como entrada para la función rehashing como entrada para la función rehashing y se calcula la
nueva dirección.
Clave mod 100 -> Dirección ocupada
(Dirección +2) mod 100 ->Dirección
55667003 mod 100 =03

+ 2= 5 mod 100 ->05
CUBOS Y ENCADENAMIENTO
Otra alternativa para las técnicas de colisiones es permitir transformar múltiples claves de registros. Una solución es dejar que cada
dirección transformada contenga espacios para múltiples registros en vez de un único registro.
vacío
vacío
Vacío
453614001
302472107
Vacío
vacío
vacío
Vacío
556677003
123450003
Vacío
123456004
445500124
222230504
234056399
vacío
Vacío
Otra forma para evitar este problema es usar la dirección transformada no como posición real del registro sino como un índice en un
array de punteros. Cada puntero accede a una cadena de registros que participan de la misma dirección transformada en vez de
buscar en el array o hacer una retransformación.
MEZCLAS
El proceso de mezclas es muy sencillo, el programa compara y lee los 2 registros y escribe en un archivo recién creado.
A continuación se lee otro registro de datos no entradas este proceso continúa hasta que todos los registros de uno o ambos
archivos hayan sido procesados.
Normalmente uno de los archivos de entrada acaban con sus registros antes que otros. Cuando ocurre esto el proceso continúa
leyendo registros del archivo restante y los escribe en el archivo recién creado.
CLASIFICACION POR MEZCLAS DIRECTAS
Por desgracia algunos algoritmos de clasificación son inaplicables si la continuidad
De datos por ordenar no cabe en la memoria principal de la computadora. Algunos algoritmos de clasificación son inaplicables si la
cantidad de datos por ordenar no cabe en la memoria principal de la computadora pero se le presenta por ejemplo en un dispositivo
de almacenamiento periférico y secuencia como una cinta de disco.
En este caso describimos los datos como un archivo secuencial cuya característica es que en cada momento un componente es
accesible directamente.
Esta es una restricción severa si se compara con las posibilidades que ofrecen los arreglos, por lo tanto hay que aplicar otras
técnicas de clasificación la mas importante es la mezcla, mezclar significa compilar dos o mas secuencias ordenadas en una sola
secuencia ordenada, es una operación mucho mas sencilla que clasificar y sirve como una operación auxiliar en el proceso mas
complejo de la clasificación secuencial. Una manera de clasificar cada base en la mezcla, llamada mezcla directa es la siguiente:
1. - Dividir la secuencia A en dos mitades denominadas B y C.
2. - Mezclar B y C combinando cada elemento en pares ordenados.
3. -Llamar A a la secuencia mezclada y repetir los pasos 1 y 2 esta vez combinando los pares en cuádruplos ordenados.
4. - Repetir los pasos anteriores combinando los cuádruplos en octetos y seguir haciendo esto (Cada vez duplicando las longitudes
de las subsecuencias combinadas hasta que quede ordenado a la secuencia total).
A 44 55 12 42 / 94 18 06 67
B 44 55 12 42
C 94 18 06 67
A 44 94 18 55 /06 12 42 67
B 44 94 18 55
C 06 12 42 67
A 06 12 44 94 / 18 42 55 67
B 06 12 44 94
C 18 42 55 67
A 06 12 18 42 44 55 67 94
INTERCALACION CUADRATICA (SECUENCIAS EQUILIBRADAS)
Este método utiliza la memoria de la computadora para realizar clasificaciones internas y cuatro archivos secuenciales temporales
para trabajar. Supongamos que un archivo de entrada F que se desea ordenar por orden creciente de las claves de sus elementos se
dispone de cuatro archivos secuenciales de trabajo F1, F2, F3 y F4 y que se pueden colocar n elementos en memoria central en un
momento dado en una tabla T de n elementos el proceso es el siguiente:
1. - Lectura del archivo de entrada por lotes de n elementos.
2. -Ordenación de cada uno de estos bloques y escritura alternativa sobre F1 y F2.
3. - Fusión de F1 y F2 en bloques de 2 elementos que se escriben alternativamente sobre F3 y F4.
4. -Fusión de F3 y F4 y escritura alternativa en F1 y F2 en bloques de 4n elementos ordenados.
5. - El proceso consiste en doblar cada vez mas el tamaño de los bloques utilizando las parejas (F1 como F2) y (F3 como F4).
F= 46 66 4 12 7 5 34 32 68 8 99 16 13 14 12 10
F1 = 4 12 46 66 8 16 68 99
F2 = 5 7 32 34 10 12 13 14
F3= 4 5 7 12 32 34 46 66
8 10 12 13 14 16 68 99
F1= 4 5 7 8 10 12 12 13 14 16 32 34 46 66 68 99
F2=Vacio
MEZCLA NATURAL
Primero se pone el apuntador en el primer elemento y se sigue hasta encontrar un elemento menor en el elemento en que se
encuentra un elemento menor se corta el segmento y empieza otro. Estos se acomodan intercambiando los segmentos en F1 y F2.
F= 32 66 | 34 72 84 96 | 48 | 31 | 24 39 | 14
F1= 32 66 48 24 39
F2=34 72 84 96 31 14
F= 32 34 6 | 48 | 24 39 72 84 96 | 31 | 14
F1= 32 34 66 24 39 72 84 96 14
F2= 48 31
http://html.rincondelvago.com/estructura-de-datos_8.html
Unidad 2. Métodos y mensajes.
2.1 Atributos const y static.
Clases
Esta sección muesta la forma en la que se puede usar el especficador const con las clases. Puede
ser interesante crear una constante local a una clase para usarla en expresiones constantes que
serán evaluadas en tiempo de compilación. Sin embargo, el significado del especificador const es
diferente para las clases [8], de modo que debe comprender la opciones adecuadas para crear
miembros constantes en una clase.
También se puede hacer que un objeto completo sea constante (y como se ha visto, el compilador
siempre hace constantes los objetos temporarios). Pero preservar la consistencia de un objeto
constante es más complicado. El compilador puede asegurar la consistencia de las variables de
los tipos del lenguaje pero no puede vigilar la complejidad de una clase. Para garantizar dicha
consistencia se emplean las funciones miembro constantes; que son las únicas que un objeto
constante puede invocar. [PAG:373]
const en las clases
Uno de los lugares donde interesa usar const es para expresiones constantes dentro de las clases.
El ejemplo típico es cuando se define un vector en una clase y se quiere usar const en lugar de
#define para establecer el tamaño del vector y para usarlo al calcular datos concernientes al
vector. El tamaño del vector es algo que desea mantener oculto en la clase, así que si usa un
nombre como size, por ejemplo, se podría usar el mismo nombre en otra clase sin que ocurra un
conflicto. El preprocesador trata todos los #define de forma global a partir del punto donde se
definen, algo que const permite corregir de forma adecuada consiguiendo el efecto deseado.
Se podría pensar que la elección lógica es colocar una constante dentro de la clase. Esto no
produce el resultado esperado. Dentro de una clase const recupera un poco su significado en C.
Asigna espacio de almacenamiento para cada variable y representa un valor que es inicializado y
ya no se puede cambiar. El uso de una constante dentro de una clase significa “Esto es constante
durante la vida del objeto”. Por otra parte, en cada objeto la constante puede contener un valor
diferente.
Por eso, cuando crea una constante ordinaria (no estática) dentro de una clase, no puede darle un
valor inicial. Esta inicialización debe ocurrir en el constructor. Como la constante se debe
inicializar en el punto en que se crea, en el cuerpo del constructor la constante debe estar ya
inicializada. De otro modo, le quedaría la opción de esperar hasta algún punto posterior en el
constructor, lo que significaria que la constante no tendría valor por un momento. Y nada
impediría cambiar el valor de la constante en varios sitios del constructor.
La lista de inicialización del constructor.
Un punto especial de inicialización se llama “lista de inicialización del constructor” y fue
pensada en un principio para su uso en herencia (tratada en el [FIXMEcapítulo 14]). La lista de
inicialización del constructor (que como su nombre indica, sólo aparece en la definición del
constructor) es una lista de llamadas a constructores que aparece después de la lista de
argumentos del constructor y antes de abrir la llave del cuerpo del constructor. [PAG:375] Se
hace así para recordarle que las inicialización de la lista sucede antes de ejecutarse el constructor.
Ese es el lugar donde poner las inicializaciones de todas las constantes de la clase. El modo
apropiado para colocar las constantes en una clase se muestra a continuación:
<xi:include></xi:include>
El aspecto de la lista de inicialización del constructor mostrada arriba puede crear confución al
principio porque no es usual tratar los tipos del lenguaje como si tuvieran constructores.
Constructores para los tipos del lenguaje
Durante el desarrollo del lenguaje se puso más esfuerzo en hacer que los tipos definidos por el
programador se pareciesen a los tipos del lenguaje, pero a veces, cuando se vió útil se hizo que
los tipos empotrados (built-in se pareciesen a los definidos por el programador. En la lista de
inicialización del constructor, puede tratar a los tipos del lenguaje como si tuvieran un
constructor, como aquí:
<xi:include></xi:include>
[PAG:376] Esto es especialmente crítico cuando se inicializan atributos constantes porque se
deben inicializar antes de entrar en el cuerpo de la función. Tiene sentido extender este
“constructor” para los tipos del lenguaje (que simplemente significan asignación) al caso general
que es por lo que la definición float funciona en el código anterior. A menudo es útil encapsular
un tipo del lenguaje en una clase para garantizar la inicialización con el constructor. Por ejemplo,
aquí hay una clase entero:
<xi:include></xi:include>
El vector de enteros declarado en main() se inicializa automaticamente a cero. Esta inicialización
no es necesariamente más costosa que un bucle for o memset(). Muchos compiladores lo
optimizan fácilmente como un proceso muy rápido.
Las constantes en tiempo de compilación dentro de las clases.
El uso anterior de const es interesante y probablemente útil en muchos casos, pero no resuelve el
programa original de “como hacer una constante en tiempo de compilación dentro de una clase”.
La respuesta requiere del uso de un especificador adicional que se explicará completamente en el
[FIXME:capítulo 10]: static. El especificador static, en esta situación significa “hay sólo una
instancia a pesar de que se creen varios objetos de la clase” que es precisamente lo que se
necesita: un atributo de clase que es constante, y que no cambia de un objeto a otro de la misma
clase. Por eso, una static const de un tipo del lenguaje se puede tratar como una constante en
tiempo de compilación.
Hay un aspecto de static const cuando se usa dentro de clases que es un tanto inusual: se debe
indicar el valor inicial en el punto de la definición de la static const. Esto sólo ocurre con static
const y no funciona en otras situaciones porque todos lo otros atributos deben inicializarse en el
constructor o en otras funciones miembro.
A continuación aparece un ejemplo que muestra la creación y uso de una static const llamada
size en una clase que representa una pila de punteros a cadenas.[9]
<xi:include></xi:include>
[PAG:379] Como size se usa para determinar el tamaño del vector stack, es adecuado usar una
constante en tiempo de compilación, pero que queda oculta dentro de la clase.
Conste que push() toma un const string* como argumento, pop() retorna un const string* y
StringStack contiene const string*. Si no fuera así, no podría usar una StringStack para
contener los punteros de icecream. En cualquier caso, también impide hacer algo que cambie los
objetos contenidos en StringStack. Por supuesto, todos los contenedores no están diseñados con
esta restricción.
El enumerado en codigo antiguo
En versiones antiguas de C++ el tipo static const no se permitía dentro de las clases. Esto hacía
que const no pudiese usarse para expresiones constantes dentro de clases. Pero muchos
programadores lo conseguian con una solución típica (normalmente conocida como “enum
hack”) que consiste en usar el tipo enum sin etiqueta y sin instancias. Una enumeración debe
tener establecidos sus valores en tiempo de compilación, es local a una clase y sus valores están
disponibles para expresiones constantes. Por eso, es común ver código como:
<xi:include></xi:include>
Este uso de enum garantiza que no se ocupa almacenamiento en el objeto, y que todos los
símbolos definidos en la enumeración se evaluan en tiempo de compilación. Además se puede
establecer explícitamente el valor de los símbolos:
enum { one = 1, two = 2, three };
utilizando el tipo enum, el compilador continuará contando a partir del último valor, así que el
símbolo three tendrá un valor 3.
En el ejemplo StringStack anterior, la línea:
static const int size = 100;
podriá sustituirse por:
enum { size = 100 };
Aunque es fácil ver esta tícnica en código correcto, el uso de static const fue añadido al lenguaje
precisamente para resolver este problema. En todo caso, no existe ninguna razón abrumadora por
la que deba usar static const en lugar de enum, y en este libro se utiliza enum porque hay más
compiladores que le dan soporte en el momento en el momento en que se escribió este libro.
Objetos y métodos constantes
Las funciones miembro (métodos) se pueden hacer constantes. ¿Qué significa esto?. Para
entenderlo, primero debe comprender el concepto de objeto constante.
Un objeto constante se define del mismo modo para un tipo definido por el usuario que para un
tipo del lenguaje. Por ejemplo:
const int i = 1;
const blob b(2);
Aquí, b es un objeto constante de tipo blob, su constructor se llama con un 2 como argumento.
Para que el compilador imponga que el objeto sea constante, debe asegurar que el objeto no tiene
atributos que vayan a cambiar durante el tiempo de vida del objeto. Puede asegurar fácilmente
que los atributos no públicos no sean modificables, pero. ¿Cómo puede saber que métodos
cambiarán los atributos y cuales son seguros para un objeto constante?
Si declara un método como constante, le está diciendo que la función puede ser invocada por un
objeto constante. Un método que no se declara constante se trata como uno que puede modificar
los atributos del objeto, y el compilador no permitirá que un objeto constante lo utilice.
Pero la cosa no acaba ahí. Sólo porque una función afirme ser const no garantiza que actuará del
modo correcto, de modo que el compilador fuerza que en la definición del método se reitere el
especificador const (la palabra const se convierte en parte del nombre de la función, así que tanto
el compilador como el enlazador comprobarán que no se viole la [FIXME:constancia]). De este
modo, si durante la definición de la función se modifica algún miembro o se llama algún método
no constante, el compilador emitirá un mensaje de error. Por eso, está garantizado que los
miembros que declare const se comportarán del modo esperado.
Para comprender la sintaxis para declarar métodos constantes, primero debe recordar que colocar
const delante de la declaración del método indica que el valor de retorno es constante, así que no
produce el efecto deseado. Lo que hay que hacer es colocar el especificador const después de la
lista de argumentos. Por ejemplo:
<xi:include></xi:include>
[PAG:382] La palabra const debe incluirse tando en la declaración como en la definición del
método o de otro modo el compilador asumirá que es un método diferente. Como f() es un
método constante, si intenta modificar i de alguna forma o llamar a otro método que no sea
constante, el compilador informará de un error.
Puede ver que un miembro constante puede llamarse tanto desde objetos constantes como desde
no constantes de forma segura. Por ello, debe saber que esa es la forma más general para un
método (a causa de esto, el hecho de que los métodos no sean const por defecto resulta
desafortunado). Un método que no modifica ningún atributo se debería escribir como constante y
así se podría usar desde objetos constantes.
Aquí se muestra un ejemplo que compara métodos const y métodos ordinarios:
<xi:include></xi:include>
Ni los constructores ni los destructores pueden ser métodos constantes porque prácticamente
siempre realizarn alguna modificación en el objeto durante la inicialización o la terminación. El
miembro quote() tampoco puede ser constante porque modifica el atributo lastquote (ver la
sentencia de retorno). Por otra parte lastQuote() no hace modificaciones y por eso puede ser
const y puede ser llamado de forma segura por el objeto constante cq.
FIXME mutable: bitwise vs. logical const
¿Qué ocurre si quiere crear un método constante, pero necesita cambiar algún atributo del objeto?
Esto se aplica a veces a la diferencia entre constante bitwise y constante lógica (llamado también
constante memberwise ). Constante bitwise significa que todos los bits del objeto son
permanentes, así que la imagen de bits del objeto nunca cambia. Constante lógica significa que,
aunque el objeto completo es conceptualmente constante puede haber cambios en un member-bymember basis. Si se informa al compilador que un objeto es constante, cuidará celosamente el
objeto para asegurar constancia bitwise. Para conseguir constancia lógica, hay dos formas de
cambiar los atributos con un método constante.
La primera solución es la tradicional y se llama constacia casting away. Esto se hace de un modo
bastante raro. Se toma this (la palabra que inidica la dirección del objeto actual) y se moldea el
puntero a un puntero a objeto de la clase actual. Parece que this ya es un puntero válido. Sin
embargo, dentro de un método const, this es en realidad un puntero constante, así que
moldeándolo a un puntero ordinario se elimina la constancia del objeto para esta operación. Aquí
hay un ejemplo:
<xi:include></xi:include>
Esta aproximación funciona y puede verse en código correcto, pero no es la técnica ideal. El
problema es que esta falta de constancia está oculta en la definición de un método y no hay
ningún indicio en la interface de la clase que haga sospechar que ese dato se modifica a menos
que puede accederse al código fuente (buscando el molde). Para poner todo al descubierto se
debe usar la palabra mutable en la declaración de la clase para indicar que un atributo
determinado se puede cambiar aún perteneciendo a un objeto constante.
<xi:include></xi:include>
De este modo el usuario de la clase puede ver en la declaración que miembros tienen posibilidad
de ser modificados por un método.
[PAG:386] Si un objeto se define como constante es un candidato para ser almacenado en
memoriar de sólo lectura (ROM), que a menudo es una consideración importante en
programación de sistemas empotrados. Para conseguirlo no es suficiente con que el objeto sea
constante, los requisitos son mucha más estrictos. Por supuesto, el objeto debe ser constante
bitwise. Eso es fácil de comprobar si la constancia lógica se implementa mediante el uso de
mutable, pero probablemente el compilador no podrá detectarlo si se utiliza la técnica del
moldeado dentro de un método constante. En resumen:


La clase o estructura no puede tener constructores o destructor definidos por el usuario.
No pueden ser clases base (tratado en el capitulo 14) u objetos miembro con constructores
o destructor definidos por el usuario.
El efecto de una operación de escritura en una parte del objeto constante de un tipo ROMable no
está definido. Aunque un objeto pueda ser colocado en ROM de forma conveniente, no todos lo
requieren.
[8]
Nota del traductor: Esto se conoce como polisemia del lenguaje
[9]
Al termino de este libro, no todos los compiladores permiten esta característica.
http://es.tldp.org/Manuales-LuCAS/doc-pensarenc++/html/ch07s04.html
2.2
Concepto de método.
Un método es un conjunto de instrucciones a las que se les da un
determinado nombre de tal manera que sea posible ejecutarlas en cualquier
momento sin tenerlas que reescribir sino usando sólo su nombre. A estas
instrucciones se les denomina cuerpo del método, y a su ejecución a través
de su nombre se le denomina llamada al método.
La ejecución de las instrucciones de un método puede producir como
resultado un objeto de cualquier tipo. A este objeto se le llama valor de
retorno del método y es completamente opcional, pudiéndose escribir
métodos que no devuelvan ninguno.
La ejecución de las instrucciones de un método puede depender del valor de
unas variables especiales denominadas parámetros del método, de manera
que en función del valor que se dé a estas variables en cada llamada la
ejecución del método se pueda realizar de una u otra forma y podrá producir
uno u otro valor de retorno.
Al conjunto formado por el nombre de un método y el número y tipo de sus
parámetros se le conoce como signatura del método. La signatura de un
método es lo que verdaderamente lo identifica, de modo que es posible
definir en un mismo tipo varios métodos con idéntico nombre siempre y
cuando tengan distintos parámetros. Cuando esto ocurre se dice que el
método que tiene ese nombre está sobrecargado.
http://www.clikear.com/manuales/csharp/c64.asp
Definición de métodos
Para definir un método hay que indicar tanto cuáles son las instrucciones que forman su
cuerpo como cuál es el nombre que se le dará, cuál es el tipo de objeto que puede
devolver y cuáles son los parámetros que puede tomar. Esto se indican definiéndolo
así:
<tipoRetorno> <nombreMétodo>(<parámetros>)
{
<cuerpo>
}
En <tipoRetorno> se indica cuál es el tipo de dato del objeto que el método devuelve, y si
no devuelve ninguno se ha de escribir void en su lugar.
Como nombre del método se puede poner en <nombreMétodo> cualquier identificador
válido. Como se verá más adelante en el Tema 15: Interfaces, también es posible incluir
en <nombreMétodo> información de explicitación de implementación de interfaz, pero por
ahora podemos considerar que siempre será un identificador.
Aunque es posible escribir métodos que no tomen parámetros, si un método los toma
se ha de indicar en <parámetros> cuál es el nombre y tipo de cada uno de ellos,
separándolos con comas si son más de uno y siguiendo la sintaxis que más adelante en
este mismo tema es explica.
El <cuerpo> del método también es opcional, pero si el método retorna algún tipo de
objeto entonces ha de incluir al menos una instrucción return que indique cuál es el
objeto a devolver.
La sintaxis anteriormente vista no es la que se usa para definir métodos abstractos.
Como ya se vio en el Tema 5: Clases, en esos casos lo que se hace es sustituir el
cuerpo del método y las llaves que lo encierran por un simple punto y coma (;) Más
adelante en este tema veremos que eso es también lo que se hace para definir
métodos externos.
A continuación se muestra un ejemplo de cómo definir un método de nombre Saluda
cuyo cuerpo consista en escribir en la consola el mensaje "Hola Mundo" y que devuelva
un objeto int de valor 1:
int Saluda()
{
Console.WriteLine("Hola Mundo");
return 1;
}
2.3
Declaración de métodos.
2.4
Llamadas a métodos (mensajes).
Llamada a métodos
La forma en que se puede llamar a un método depende del tipo de método del que se
trate. Si es un método de objeto (método no estático) se ha de usar la notación:
<objeto>.<nombreMétodo>(<valoresParámetros>)
El <objeto> indicado puede ser directamente una variable del tipo de datos al que
pertenezca el método o puede ser una expresión que produzca como resultado una
variable de ese tipo (recordemos que, debido a la herencia, el tipo del <objeto> puede
ser un subtipo del tipo donde realmente se haya definido el método); pero si desde
código de algún método de un objeto se desea llamar a otro método de ese mismo
objeto, entonces se ha de dar el valor this a <objeto>.
En caso de que sea un método de tipo (método estático), entones se ha de usar:
<tipo>.<nombreMétodo>(<valoresParámetros>)
Ahora en <tipo> ha de indicarse el tipo donde se haya definido el método o algún
subtipo suyo. Sin embargo, si el método pertenece al mismo tipo que el código que lo
llama entonces se puede usar la notación abreviada:
<nombreMétodo>(<valoresParámetros>)
El formato en que se pasen los valores a cada parámetro en <valoresParámetros> a
aquellos métodos que tomen parámetros depende del tipo de parámetro que sea. Esto
se explica en el siguiente apartado.
Tipos de parámetros. Sintaxis de definición
La forma en que se define cada parámetro de un método depende del tipo de
parámetro del que se trate. En C# se admiten cuatro tipos de parámetros: parámetros
de entrada, parámetros de salida, parámetros por referencia y parámetros de número
indefinido.
Parámetros de
Un parámetro de entrada recibe una copia del valor que almacenaría una variable del
tipo del objeto que se le pase. Por tanto, si el objeto es de un tipo valor se le pasará una
copia del objeto y cualquier modificación que se haga al parámetro dentro del cuerpo
del método no afectará al objeto original sino a su copia; mientras que si el objeto es de
un tipo referencia entonces se le pasará una copia de la referencia al mismo y cualquier
modificación que se haga al parámetro dentro del método también afectará al objeto
original ya que en realidad el parámetro referencia a ese mismo objeto original.
Para definir un parámetro de entrada basta indicar cuál el nombre que se le desea dar y
el cuál es tipo de dato que podrá almacenar. Para ello se sigue la siguiente sintaxis:
<tipoParámetro> <nombreParámetro>
Por ejemplo, el siguiente código define un método llamado Suma que toma dos
parámetros de entrada de tipo int llamados par1 y par2 y devuelve un int con su suma:
int Suma(int par1, int par2)
{
return par1+par2;
}
Como se ve, se usa la instrucción return para indicar cuál es el valor que ha de devolver
el método. Este valor es el resultado de ejecutar la expresión par1+par2; es decir, es la
suma de los valores pasados a sus parámetros par1 y par2 al llamarlo.
En las llamadas a métodos se expresan los valores que se deseen dar a este tipo de
parámetros indicando simplemente el valor deseado. Por ejemplo, para llamar al
método anterior con los valores 2 y 5 se haría <objeto>.Suma(2,5), lo que devolvería el
valor 7.
Todo esto se resume con el siguiente ejemplo:
using System;
class ParámetrosEntrada
{
public int a = 1;
public static void F(ParémetrosEntrada p)
{
p.a++;
}
public static void G(int p)
{
p++;
}
public static void Main()
{
int obj1 = 0;
ParámetrosEntrada obj2 = new ParámetrosEntrada();
G(obj1);
F(obj2);
Console.WriteLine("{0}, {1}", obj1, obj2.a);
}
}
Este programa muestra la siguiente salida por pantalla:
0, 2
Como se ve, la llamada al método G() no modifica el valor que tenía obj1 antes de
llamarlo ya que obj1 es de un tipo valor (int) Sin embargo, como obj2 es de un tipo
referencia (ParámetrosLlamadas) los cambios que se le hacen dentro de F() al pasárselo
como parámetro sí que le afectan.
Parámetros de salida
Un parámetro de salida se diferencia de uno de entrada en que todo cambio que se le
realice en el código del método al que pertenece afectará al objeto que se le pase al
llamar dicho método tanto si éste es de un tipo por como si es de un tipo referencia.
Esto se debe a que lo que a estos parámetros se les pasa es siempre una referencia al
valor que almacenaría una variable del tipo del objeto que se les pase.
Cualquier parámetro de salida de un método siempre ha de modificarse dentro del
cuerpo del método y además dicha modificación ha de hacerse antes que cualquier
lectura de su valor. Si esto no se hiciese así el compilador lo detectaría e informaría de
ello con un error. Por esta razón es posible pasar parámetros de salida que sean
variables no inicializadas, pues se garantiza que en el método se inicializarán antes de
leerlas. Además, tras la llamada a un método se considera que las variables que se le
pasaron como parámetros de salida ya estarán inicializadas, pues dentro del método
seguro que se las inicializació.
Nótese que este tipo de parámetros permiten diseñar métodos que devuelvan múltiples
objetos: un objeto se devolvería como valor de retorno y los demás se devolverían
escribiendos en los parámetros de salida.
Los parámetros de salida se definen de forma parecida a los parámetros de entrada
pero se les ha de añadir la palabra reservada out. O sea, se definen así:
out <tipoParámetro> <nombreParámetro>
Al llamar a un método que tome parámetros de este tipo también se ha preceder el
valor especificado para estos parámetros del modificador out. Una utilidad de esto es
facilitar la legibilidad de las llamadas a métodos. Por ejemplo, dada una llamada de la
forma:
a.f(x, out z)
Es fácil determinar que lo que se hace es llamar al método f() del objeto a pasándole x
como parámetro de entrada y z como parámetro de salida. Además, también se puede
deducir que el valor de z cambiará tras la llamada.
Sin embargo, la verdadera utilidad de forzar a explicitar en las llamadas el tipo de paso
de cada parámetro es que permite evitar errores derivados de que un programador
pase una variable a un método y no sepa que el método la puede modificar. Teniéndola
que explicitar se asegura que el programador sea consciente de lo que hace.
Parámetros por referencia
Un parámetro por referencia es similar a un parámetro de salida sólo que no es
obligatorio modificarlo dentro del método al que pertenece, por lo que será obligatorio
pasarle una variable inicializada ya que no se garantiza su inicialización en el método.
Los parámetros por referencia se definen igual que los parámetros de salida pero
sustituyendo el modificador out por el modificador ref. Del mismo modo, al pasar valores
a parámetros por referencia también hay que precederlos del ref.
Parámetros de número indefinido
C# permite diseñar métodos que puedan tomar cualquier número de parámetros. Para
ello hay que indicar como último parámetro del método un parámetro de algún tipo de
tabla unidimensional o dentada precedido de la palabra reservada params. Por ejemplo:
static void F(int x, params object[] extras)
{}
Todos los parámetros de número indefinido que se pasan al método al llamarlo han de
ser del mismo tipo que la tabla. Nótese que en el ejemplo ese tipo es la clase primigenia
object, con lo que se consigue que gracias al polimorfismo el método pueda tomar
cualquier número de parámetros de cualquier tipo. Ejemplos de llamadas válidas serían:
F(4);
// Pueden pasarse 0 parámetros indefinidos
F(3,2);
F(1, 2, "Hola", 3.0, new Persona());
F(1, new object[] {2,"Hola", 3.0, new Persona});
El primer ejemplo demuestra que el número de parámetros indefinidos que se pasen
también puede ser 0. Por su parte, los dos últimos ejemplos son totalmente
equivalentes, pues precisamente la utilidad de palabra reservada params es indicar que
se desea que la creación de la tabla object[] se haga implícitamente.
Es importante señalar que la prioridad de un método que incluya el params es inferior a la
de cualquier otra sobrecarga del mismo. Es decir, si se hubiese definido una sobrecarga
del método anterior como la siguiente:
static void F(int x, int y)
{}
Cuando se hiciese una llamada como F(3,2) se llamaría a esta última versión del
método, ya que aunque la del params es también aplicable, se considera que es menos
prioritaria.
Sobrecarga de tipos de parámetros
En realidad los modificadores ref y out de los parámetros de un método también forman
parte de lo que se conoce como signatura del método, por lo que esta clase es válida:
class Sobrecarga
{
public void f(int x)
{}
public void f(out int x)
{}
}
Nótese que esta clase es correcta porque cada uno de sus métodos tiene una signatura
distinta: el parámetro es de entrada en el primero y de salida en el segundo.
Sin embargo, hay una restricción: no puede ocurrir que la única diferencia entre la
signatura de dos métodos sea que en uno un determinado parámetro lleve el
modificador ref y en el otro lleve el modificador out. Por ejemplo, no es válido:
class SobrecargaInválida
{
public void f(ref int x)
{}
public void f(out int x)
{}
}
2.5
Tipos de métodos.
Métodos externos
Un método externo es aquél cuya implementación no se da en el fichero fuente en que
es declarado. Estos métodos se declaran precediendo su declaración del modificador
extern. Como su código se da externamente, en el fuente se sustituyen las llaves donde
debería escribirse su cuerpo por un punto y coma (;), quedando una sintaxis de la
forma:
extern <nombreMétodo>(<parámetros>);
La forma en que se asocie el código externo al método no está definida en la
especificación de C# sino que depende de la implementación que se haga del lenguaje.
El único requisito es que no pueda definirse un método como abstracto y externo a la
vez, pero por todo lo demás puede combinarse con los demás modificadores, incluso
pudiéndose definir métodos virtuales externos.
La forma más habitual de asociar código externo consiste en preceder la declaración
del método de un atributo de tipo System.Runtime.InteropServices.DllImport que indique en
cuál librería de enlace dinámico (DLL) se ha implementado. Este atributo requiere que
el método externo que le siga sea estático, y un ejemplo de su uso es:
using System.Runtime.InteropServices;
DllImport
public class Externo
{
[DllImport("kernel32")]
// Aquí está definido
public static extern void CopyFile(string fuente, string
destino);
public static void Main()
{
CopyFile("fuente.dat", "destino.dat");
}
}
El concepto de atributo se explica detalladamente en el Tema 14:Atributos. Por ahora
basta saber que los atributos se usan de forman similar a los métodos sólo que no
están asociados a ningún objeto ni tipo y se indican entre corchetes ([]) antes de
declaraciones de elementos del lenguaje. En el caso concreto de DllImport lo que indica
el parámetro que se le pasa es cuál es el fichero (por defecto se considera que su
extensión es .dll) donde se encuentra la implementación del método externo a
continuación definido.
Lo que el código del ejemplo anterior hace es simplemente definir un método de nombre
CopyFile() cuyo código se corresponda con el de la función CopyFile() del fichero
kernel32.dll del API Win32. Este método es llamado en Main() para copiar el fichero de
nombre fuente.dat en otro de nombre destino.dat. Nótese que dado que CopyFile() se ha
declarado como static y se le llama desde la misma clase donde se ha declarado, no es
necesario precederlo de la notación <nombreClase>. para llamarlo.
Como se ve, la utilidad principal de los métodos externos es permitir hacer llamadas a
código nativo desde código gestionado, lo que puede ser útil por razones de eficiencia
o para reutilizar código antiguamente escrito pero reduce la portabilidad de la
aplicación.
http://www.programacion.com/tutorial/csharp/9/
2.5.1
Métodos const, static.
El modificador "static" indica que un atributo solo se instancia una vez. Es decir, todos los objetos de esa
clase comparten ese mismo valor o método, que se crea con la primera instancia de la clase.
Un método "static" puede ser ejecutado tambien desde el nombre de la clase, sin tener que hacer una
instancia de la clase manualmente. De esta forma trabajan los métodos:
System.out.println ();
Integer.parseInt ();
etc.
Ahora bien, hay que tener cuidado, porque no se puede llamar a métodos no estaticos (métodos de
instancia) desde métodos estaticos (métodos de clase).
http://www.javahispano.org/faq.thread.action?forum=102&thread
=214848691&id=214848691
Ver archivo: metodoconst.pdf
2.5.2
Métodos normales y volátiles.
2.6
Referencia this.
Constructores y referencia this
Un constructor, básicamente inicilializa los atributos de nuestro objeto.
Cuando no se declara explícitamente un constructor , los atributos se inicializan por defecto.
En la clase siguiente se crearán tres constructores diferentes, según qué atributos nos interesa
inicializar con un valor específico.
class Libro
{
public String titulo;
public String autor;
public int paginas;
public int precio;
}
El primer constructor recibe como parámetro el título del libro.
public Libro(String aux_titulo)
{
titulo = aux_titulo;
}
El segundo constructor, recibe como parámetros el título y el autor del libro. Cuando los
parámetros se llaman igual que los atributos del objeto, se usa la referencia this que hace
referencia sobre el objeto actual, no permitiendo la confusión de nombres.
public Libro(String titulo , String autor)
{
this.titulo = titulo;
this.autor = autor;
}
Por último, el tercer constructor permite inicializar todos los atributos. Para ahorrar código, se
hace una llamada al segundo constructor mediante la referencia this , para inicilizar los atributos
título y autor.
public Libro(String titulo , String autor, int paginas , int precio)
{
this(titulo , autor);
this.paginas = paginas;
this.precio = precio;
}
Implementación
Se crearán tres objetos de tipo Libro usando los tres constructores , y se va a imprimir los
atributos de los tres objetos.
class Libro
{
public String titulo;
public String autor;
public int paginas;
public int precio;
/* -- constructores -- */
public Libro(String aux_titulo)
{
titulo = aux_titulo;
}
public Libro(String titulo , String autor)
{
this.titulo = titulo;
this.autor = autor;
}
public Libro(String titulo , String autor, int paginas , int precio)
{
this(titulo , autor);
this.paginas = paginas;
this.precio = precio;
}
/* -- metodo que muestra la informacion en forma tabulada -- */
public static void impLibro(Libro aux)
{
System.out.print("\n " + aux.titulo);
System.out.print("\t\t");
if (aux.autor != null)
System.out.print(aux.autor);
System.out.print("\t\t");
if (aux.paginas != 0)
System.out.print(aux.paginas);
System.out.print("\t\t");
if (aux.precio != 0)
System.out.print(aux.precio);
}
public static void main(String arg[ ])
{
Libro a = new Libro("Los miserables");
Libro b = new Libro("El contrato social","Rousseau");
Libro c = new Libro("La divina comedia","Dante",320,8900);
System.out.println("\n\n Titulo\t\t\tAutor\t\tPaginas\t\tPrecio");
System.out.println(" ------\t\t\t-----\t\t-------\t\t------");
impLibro(a);
impLibro(b);
impLibro(c);
System.out.println("\n");
}
}
http://pjsml.50megs.com/java/objbasic.html
La referencia this
En ocasiones es conveniente disponer de una referencia que apunte al propio objeto que se está
manipulando. Esto se consigue con la palabra reservada this. this es una referencia implicita
que tienen todos los objetos y que apunta a si mismo. Por ejemplo:
class Circulo {
Punto centro;
int radio;
. . .
Circulo elMayor(Circulo c) {
if (radio > c.radio) return this;
else return c;
}
}
El método elMayor devuelve una referencia al círculo que tiene mayor radio, comparando los
radios del Circulo c que se recibe como argumento y el propio. En caso de que el propio resulte
mayor el método debe devolver una referencia a si mismo. Esto se consigue con la expresión
return this.
http://www.arrakis.es/~abelp/ApuntesJava/ClasesIV.htm#La%20r
eferencia%20this
La referencia this
Java incluye un valor de referencia especial llamado this, que se utiliza dentro de cualquier
método para referirse al objeto actual. El valor this se refiere al objeto sobre el que ha sido
llamado el método actual. Se puede utilizar this siempre que se requiera una referencia a un
objeto del tipo de una clase actual. Si hay dos objetos que utilicen el mismo código,
seleccionados a través de otras instancias, cada uno tiene su propio valor único de this.
Un refinamiento habitual es que un constructor llame a otro para construir la instancia
correctamente. El siguiente constructor llama al constructor parametrizado MiPunto(x,y) para
terminar de iniciar la instancia:
MiPunto() {
this( -1, -1 ); // Llama al constructor parametrizado
}
En Java se permite declarar variables locales, incluyendo parámetros formales de métodos, que se
solapen con los nombres de las variables de instancia.
No se utilizan x e y como nombres de parámetro para el método inicia, porque ocultarían las
variables de instancia x e y reales del ámbito del método. Si lo hubiésemos hecho, entonces x se
hubiera referido al parámetro formal, ocultando la variable de instancia x:
void inicia2( int x, int y ) {
x = x; // Ojo, no modificamos la variable de instancia!!!
this.y = y; // Modificamos la variable de instancia!!!
}
http://pisuerga.inf.ubu.es/lsi/Invest/Java/Tuto/II_5.htm
2.7
Forma de pasar argumentos.
Parámetros y argumentos
§1 Sinopsis
Las palabras parámetro y argumento, aunque de significado similar, tiene distintas
connotaciones semánticas: Se denominan parámetros los objetos declarados en el
prototipo 4.4.1 (que deben corresponder con la definición 4.4.2). Cuando se realiza
una llamada a la función, los "valores" pasados se denominan argumentos. A veces
se utilizan también las expresiones argumentos formales, para los parámetros y
argumentos actuales para los valores pasados.
Parámetros (en prototipo o definición)  argumentos formales
Valores pasados (en tiempo de ejecución)  argumentos actuales
§2 La sintaxis utilizada para la declaración de la lista de parámetros formales es similar
a la utilizada en la declaración de cualquier identificador. A continuación se exponen
varios ejemplos:
int func(void) {...}
// sin parámetros
inf func() {...}
// ídem.
int func(T1 t1, T2 t2, T3 t3=1) {...} // tres parámetros
simples,
// uno con
argumento por defecto
int func(T1* ptr1, T2& tref) {...}
// los argumentos
son un puntero y
// una referencia.
// Petición de uso
int func(register int i) {...}
de registro para
// argumento
(entero)
int func(char *str,...) {...}
/* Una cadena y
cierto número de otros
argumentos, o un número fijo de argumentos de
tipos variables */
§3 Los argumentos son siempre objetos. Sus tipos pueden ser: escalares; estructuras;
uniones, o enumeraciones; clases definidas por el usuario; punteros o referencias a
estructuras y uniones, o punteros a funciones, a clases o a matrices. El tipo void está
permitido como único parámetro formal. Significa que la función no recibe ningún
argumento.
Nota: Recuerde que cuando coloquialmente se dice que se pasa una matriz como
argumento de una función, en realidad se está pasando un puntero a su primer
elemento ( 4.3.8).
§3.1 Es un error realizar una declaración en la lista de parámetros [2]:
int func (int x, class C{...} c) { ... }
// Error!!
§4 Todos los parámetros de una función tienen ámbito del bloque de la propia función y
la misma duración automática que la función ( 4.1.5).
§5 El único especificador de almacenamiento que se permite es register ( 4.1.8b).
En la declaración de parámetros también pueden utilizarse los modificadores volatile
( 4.1.9) y const ( 3.2.1c). Este último se utiliza cuando se pasan argumentos por
referencia y queremos garantizar que la función no modificará el valor recibido.
Ejemplo:
int dimension(X x1, const X& x2)
modificar!!
// x2 NO se puede
§6 Argumentos por defecto
C++ permite tener valores por defecto para los parámetros. Esto supone que, si no se
pasa el parámetro correspondiente, se asume un valor predefinido [1]. La forma de
indicarlo es declararlo en el prototipo de la función, como se muestra en el ejemplo
(ambas expresiones son equivalentes).
float mod (float x, float y = 0);
float mod (float, float = 0);
Más tarde no es necesario, ni posible (§6.1
ejemplo, es válido:
), indicarlo de nuevo en la definición. Por
float mod (float, float = 0);
// prototipo
...
float mod (float x, float y = 0) { return x + y; } // Error!!
float mod (float x, float y) { return x + y; }
//
definición Ok
Si declaración y definición están en una sola sentencia entonces si es necesario indicar
los valores por defecto. Ejemplo, en la sentencia:
float mod (float x, float y = 0) { return pow(x*x + y*y,
0.5); }
la ausencia de un segundo argumento en la invocación hace que se adopte para él un
valor 0 (podría haber sido cualquier otro valor). En este contexto la función mod
aceptaría estas dos llamadas como correctas y de resultados equivalentes:
m1 = mod (2.0 , 0);
m2 = mod (2.0);
§6.1 Un argumento por defecto no puede ser repetido o cambiado en una siguiente
declaración dentro del mismo ámbito. Por ejemplo:
void func (int x = 5);
...
void func (int x = 5);
por defecto
{
void func (x = 7);
oculta a la anterior
}
visible
void func (x = 7);
defecto
// Error: repetición de argumento
// nuevo ámbito
// L.4 Correcto: esta función
// el ámbito anterior vuelve a ser
// Error: cambiar argumento por
Nota: A pesar de que la expresión de la línea 4 es correcta, tenga en cuenta que
las declaraciones en ámbitos anidados que ocultan declaraciones del mismo
nombre en ámbitos externos suele ser fuente de errores y confusiones.
Es muy de tener en cuenta esta regla en la definición de clases, ya que en ellas es
frecuente que la declaración de métodos se realice en un punto (el cuerpo de la clase),
y la definición se realice en otro ("off-line", fuera del cuerpo de la clase). En caso que el
método tenga argumentos por defecto recuerde no repetirlos más tarde en la definición.
§6.2 La gramática de C++ exige que los parámetros con valores por defecto deben ser
los últimos en la lista de parámetros, y que si en una ocasión falta algún argumento, los
que le siguen también deben faltar (adoptar también los valores por defecto).
Nota: como puede verse, C++ no admite la posibilidad de otros lenguajes de
saltarse parámetros por defecto incluyendo una coma sin ningún valor en la
posición correspondiente, por ejemplo:
int somefunc
....
x = somefunc
aceptable en
x = somefunc
(int, int = 1, char, long);
// Incorrecto!!
( 33, , 'c', 3.14);
C++
(int, char*, char* = 0);
// Error! No
Observe que en este último caso, el espacio de separación entre char* y = es
importante, ya que *= es un operador de asignación ( 4.9.2). Así pues:
x = somefunc (int, char*, char*= 0);
// Error!
§6.3 Los argumentos por defecto de métodos (funciones-miembro de clases 4.11) no
pueden ser otros miembros a no ser que sean estáticos ( 4.11.7). Ejemplo:
class C {
int v1;
void foo(char, int = v1);
};
class B {
static int v1;
void foo(char, int = v1);
}
// Error!!
// Ok.
Un posible diseño de la clase C podría ser:
class C {
int v1;
void foo(char, int = -1);
};
// Ok
más tarde, en la definición del método hacer:
void C::foo(char a, int x) {
if (x == -1) x = v1;
...
}
§6.4 Los argumentos por defecto no pueden ser otros argumentos:
x = somefunc (int x, int y = x);
// Error!
§6.5 Los argumentos pasados por referencia (§7.3 ) solo pueden adoptar valores por
defecto estáticos, globales, o de un subespacio cualificado. Ejemplo:
namespace ALPHA {
long n = 20.10L;
}
long n = 10.10L;
void f1 (long& lg = ALPHA::n);
void f2 (long& lg = n);
void f3 (long lg = 10.2L);
void f4 (long& lg = 10.2L );
//
//
//
//
Ok.
Ok.
Ok.
Error!!
§7 Argumentos: por valor y por referencia
Existen dos formas de pasar argumentos a las funciones: por valor y por referencia.
El primero es utilizado por defecto con la declaración usual de parámetros. En el paso
"por valor", se crean copias de los argumentos pasados a la función, los cuales, junto a
las variables locales (incluyendo el posible valor devuelto) y la dirección de vuelta a la
rutina que efectúa la invocación, son pasados a la pila en la secuencia de llamada.
Más tarde, cuando termina su ejecución definitivamente, es decir, cuando el control
vuelve a la función que la invocó, toda esta información es sacada de la pila mediante la
secuencia de retorno (y se pierde). Estos procesos suponen un consumo de tiempo y
espacio (memoria), a veces considerable.
§7.1 Paso por valor
Hemos visto que el paso de parámetros por valor significa que existen copias de los
argumentos formales (estas copias son variables locales de la función llamada), y que
una función no puede alterar ninguna variable de la función que la invocó.
La única excepción es el caso de las matrices. Cuando se utiliza una matriz
como argumento en la llamada a una función, el valor pasado es un puntero a la
dirección de memoria del principio de la matriz ( 4.3.2).
§7.1.1 Cuando los argumentos pasan por valor pero no hay concordancia entre el tipo
de los argumentos actuales y los argumentos formales utilizados en la declaración de la
función, entonces se produce un modelado de tipo antes de la asignación.
Supongamos el ejemplo:
void func(int x) {
entero
x = x * 2;
}
float f = 3.14;
func(f);
asignación a 'x'
// definición de func.
Acepta un
// f es promovido a int antes de
// x == 6
Lo que sucede en estos casos es que la copia local f en func (x) es modificada para
hacerla coincidir con el tipo esperado por la función, mientras que el valor original (f)
permanece inalterado.
§7.2 Pasar un puntero
En C clásico, cuando se desea que la función llamada pueda alterar el valor de
variables de la función que la invoca, o ahorrar el espacio que supone la copia local de
los argumentos (que pueden ser estructuras de datos muy grandes), la solución
consistía en utilizar punteros a las variables respectivas como argumentos para la
función (en vez de pasar las variables en sí mismas). A su vez, la función llamada
debía declarar el parámetro como puntero, y acceder a la variable indirectamente a
través de él. En otras palabras: Cuando en C se desea que un valor X pase a una
función F y que esta pueda alterar el valor de X en la función que la invocó, el
argumento utilizado es &X (la dirección de X). De esta forma, aunque F recibe una
copia de &X, puede alterar el valor original a través de esta dirección. Esta técnica
puede tener sus ventajas. Por ejemplo, si X es una estructura muy grande, pero puede
tener efectos colaterales peligrosísimos y ser una fuente de errores difíciles de detectar.
§7.3 Paso por referencia
Por supuesto, C++ permite utilizar la técnica del C clásico descrita arriba, pero también
utilizar el paso de argumentos por referencia (en realidad es una variante semántica
del proceso anteriormente descrito). Para ello se utiliza el declarador de referencia &
( 4.2.3).
Las referencias presentan las ventajas de los punteros, en el sentido que permiten
modificar los valores de los objetos pasados como argumento, y de que permiten
ahorrar espacio si hay que pasar objetos muy grandes, pero no presentan los peligros
potenciales de aquellos. En caso necesario las referencias pueden declararse
constantes, indicando así que la función invocada no modificará estos valores. En
estos casos, la utilización de referencias obedece casi exclusivamente a razones de
eficacia en el mecanismo de llamada ( 4.2.3).
Nota: En ocasiones el paso por referencia tiene una justificación de tipo físico. Es
el caso en que los objetos utilizados como argumento representan dispositivos
físicos. Por ejemplo, ficheros externos o dispositivos de comunicación. En estas
circunstancias, el objeto no puede ser copiado alegremente por el mecanismo de
invocación de funciones (que utilizaría el constructor-copia de la clase) si se
utilizaran pasos por valor y es necesario recurrir al paso por referencia.
Compare las tres implementaciones de la función pru:
Implementación-1:
Sistema clásico, paso "por valor"
int pru1(int n) {
return 3 * n;
}
...
int x, i = 4;
x = pru1(i);
int& ry = i;
x = pru (ry);
// n entero; pasa "por valor"
// ahora: x = 12, i = 4
// ahora: x = 12, i = 4
Observe que la última sentencia no es un paso por referencia, sino por valor (a pesar de
que el argumento actual sea una referencia).
Implementación-2: Sistema clásico, paso de "punteros por valor" (seudo-referencia)
void pru2(int* np) {
valor"
*np = (*np) * 3;
}
. . .
int x = 4;
pru2(&x);
// np puntero-a-entero; pasa "por
// ahora x = 12
Observe que en este caso, pasar el valor &x (dirección de x) como argumento, es
equivalente a pasar un puntero a dicha variable (que es lo exigido en la definición de
pru2). Es decir, la última línea se puede sustituir por las siguientes:
int* ptr = &x
pru2(ptr);
Implementación-3:
// define puntero-a-x
// pasa el puntero como argumento
Sistema C++, paso "por referencia"
void pru3(int& n) { // n tipo "referencia-a-int"; pasa "por
referencia"
n = 3 * n;
}
. . .
int x = 4;
pru3(x);
// ahora x = 12
Atención a la sintaxis: Aquí la invocación a pru3 tiene la forma: pru3(x) no
pru3(&x) como en el caso anterior. Es decir, la notificación al compilador de que el
argumento pasa por referencia hay que hacerla en la definición de la función, y no es
necesario indicarlo en el momento de la invocación.
En este último caso, la declaración int& n como parámetro de la función pru3,
establece que este n sea declarado como "referencia-a-entero", de forma que cuando
se pasa el argumento x, la función crea un valor n que es una especie de alias o espejo
de x, de forma que la expresión n = 3*n tiene el mismo efecto que x = 3*x.
§7.3.1 Ya hemos visto (Referencias 4.2.3) que cuando, en la declaración de una
referencia, el iniciador es una constante, o un objeto de tipo diferente que el
referenciado, se crea un objeto temporal para el que la referencia actúa como un alias.
Esta creación de objetos temporales es lo que permite la conversión de tipos referenciaa-tipoX cuando se utilizan como parámetros de funciones y no hay concordancia entre
el valor recibido y el esperado (suponiendo que exista posibilidad de conversión). Este
sería el mecanismo utilizado en el siguiente caso:
void pru(int& n) { // n tipo "referencia-a-int" (pasa "por
referencia")
n = 3 * n;
}
. . .
float f = 4.1;
pru(f);
// ahora f = 12
§7.3.2 Ejemplo
En el programa que sigue se muestra un caso de paso por referencia y acceso a
subespacios.
#include <iostream.h>
namespace ALPHA {
class CAlpha {
int x;
public:
int getx(void) { return x; }
void putx(int i) { x = i; }
};
CAlpha CA1;
// instancia de CAlpha (en ALPHA)
}
int func (ALPHA::CAlpha& ca, int i);
/* prototipo: ca es la
referencia
a un objeto de la clase CAlpha en ALPHA
*/
int main (void) {
// ========================
int x = 0;
cout << "x = " << x << endl;
ALPHA::CA1.putx(10);
// invocación al método putx de CA1
x = func(ALPHA::CA1, 3);
cout << "x = " << x << endl;
}
int func (ALPHA::CAlpha& ca, int i) {
return (i + ca.getx());
}
// definición
Salida:
x = 0
x = 13
http://www.zator.com/Cpp/E4_4_5.htm
Cómo se pasan los argumentos?
Una de las propiedades importantes de las funciones y de las subrutinas es que la
información puede ser pasada a ellas cuando son llamadas y devolverla, al lugar donde
fueron llamadas, cuando finalice su ejecución. El paso de esta información será llevado
a cabo por los llamados argumentos.
Existe una correspondencia uno a uno entre los argumentos del programa, llamados
argumentos actuales, y los argumentos de las subrutinas o funciones, llamados falsos
argumentos. Estos últimos, no tienen que tener el mismo nombre que los otros y la
correspondencia es temporal, es decir, sólo es válida durante la llamada al
procedimiento. Además, el número de ambos argumentos tiene que coincidir, salvo que
declaremos a algún falso argumento como opcional, es decir, que pongamos, por
ejemplo:
Real, opcional :: x
De esta forma el argumento x, tanto puede ser pasado como argumento, como no.
Por otro lado, también es posible identificar a los argumentos con una letra, de esta
forma podremos cambiar la correspondencia por defecto entre los argumentos, y
realizarla de una forma más clara. Por ejemplo, podríamos llamar a una función Func
(a, b, c) de las siguientes formas:
Func (5, a = 1, c = 0) Func (5, 0, a = 1) o Func (1, 5, 0)
Como vemos es posible identificar a algunos argumentos y a otros no. En estos casos,
los argumentos que no estén identificados tendrán que ir ordenados entre sí. En
cualquiera de los casos, a = 1, b = 5 y c = 0.
Por otra parte, el tipo y el parámetro de clase de cada argumento actual deben coincidir
con el de su correspondiente falso argumento.
En resumen, podemos decir que existen dos formas típicas de pasar argumentos, por
referencia y por valor. A continuación pasaremos a detallarlas.
Argumentos por referencia
Un argumento actual que es una variable, sólo podrá ser pasado por referencia. La
referencia al correspondiente falso argumento de la subrutina provoca que el ordenador
lo considere como el argumento actual correspondiente. Si en la subrutina se cambia el
falso argumento estos cambios también afectarán al argumento actual. Por ello es una
mala práctica en la programación, realizar cambios en los argumentos de entrada, que
tengan lugar en una subrutina o en una función.
Argumentos por valor
Un argumento actual que sea una constante o una expresión más complicada que una
variable, sólo podrá ser pasado por valor al correspondiente falso argumento. Así, el
falso argumento no podrá cambiar su valor durante la ejecución del procedimiento.
http://www.cesga.es/telecursos/F90/sec2/cap2/Tema2_Cap2_1.html
¿Qué son los argumentos de una función? Top
Una función es una porción de código independiente del exterior (es decir de otros programas) y
que se puede re-utilizar (es decir llamar muchas veces con datos diferentes). Aunque el concepto
de función es general y existe en todos los lenguajes de programación, aquí se hará referencia a
las funciones de C/C++.
Se distingue entre argumentos formales y argumentos actuales. Los argumentos formales son
los que aparecen en la definición de la función, mientras que los actuales son los que aparecen en
la llamada a la función. Se podría también decir que los argumentos formales son los argumentos
vistos desde dentro de la función, y los argumentos actuales son los argumentos vistos desde el
programa que llama a la función, esto es desde fuera de la función. Los argumentos formales son
siempre variables de la función que recogen los valores que se les pasan desde el exterior; los
argumentos actuales pueden ser variables o expresiones cuyos valores se pasan a los argumentos
formales.
Cuando una función tiene valor de retorno puede ser utilizada en una sentencia aritmética (por
ejemplo, la función seno en la expresión: y=sin(x)*x-1.0;). En este caso el valor de retorno se
sustituye en el lugar ocupado por la llamada a la función en dicha sentencia aritmética. Cuendo
una función no tiene valor de retorno, se llama simplemente colocando la llamada en una línea
del programa seguida de punto y coma (por ejemplo: permutar(&x, &y);).
¿Qué es pasar un argumento por valor? Top
En C/C++ el paso de argumentos a una función se hace de la siguiente manera. En el programa
que llama a la función, se evalúan los argumentos actuales y sus valores se pasan a las variables
de la función que constituyen los argumentos formales. En realidad siempre se pasan copias de
dichos valores. Los argumentos formales (los argumentos vistos desde dentro de la función son
variables nuevas, que se crean cuando la función es llamada, y que toman los valores que les
pasan los argumentos actuales.
Cuando de dice que en C los argumentos de las funciones se pasan siempre por valor, lo que se
quiere decir es que las variables argumentos formales reciben copias de los valores de los
argumentos actuales, pero son siempre variables diferentes, propias de la función. Por eso, si se
modifica un argumento formal dentro de la función, se está modificando una copia del argumento
actual y si el argumento actual es una variable, dicha variable no queda modificada. Esto es un
mecanismo de seguridad de C, de modo que una función no pueda modificar facilmente las
variables externas a ella.
¿Qué es pasar un argumento por referencia? Top
En muchas ocasiones, las funciones tienen que modificar las variables pasadas como argumentos
actuales. Como el valor de retorno es único, ésta es la única forma por ejemplo de que una
función trasmita al exterior varios resultados en una sola llamada. Cuando se desea que la función
modifique los argumentos actuales (las variables externas a la función que aparecen en la
llamada), hay que pasárselos por referencia. ¿Qué quiere decir esto: que a veces se pasan copias
de los argumentos actuales y otras veces se pasan los originales? Ya se verá en otro lugar que en
C++ es así (a través de un tipo de variables que no tiene C: las variables reference), pero en C a
las funciones siempre se les pasan copias de los argumentos actuales.
¿Como puede una función modificar una variable externa si las funciones sólo pueden recibir
copias de las variables del programa que llama a la función? Esto se puede conseguir por medio
de los punteros. La forma de modificar una variable externa es disponer de su dirección en
memoria. Las funciones de C son capaces de recibir copias de las direcciones en memoria de las
variables externas, pero para modificar el contenido de una dirección de memoria la dirección o
una copia de esa dirección son igual de válidas.
http://www1.ceit.es/Asignaturas/Informat2/C/FAQs-C/Faqs_c.htm#argsFunc
2.8
Devolver un valor desde un método.
Los métodos son miembros de las clases y pueden realizar una serie de acciones, o devolver
un valor ya sea que tengan que calcularlo o no. Pueden recibir o no parametros y pueden
devolver o no devolver parametros.
+ Declaración:
tipoRetorno + NombreDelMétodo + [Parámetros] + { cuerpo }
+ Ejemplo:
int Suma(int numero1, int numero 2)
{
return numero1 + numero2;
}
+ Uso:
[valor de retorno] miMétodo([Parametros]);
+ Ejemplo:
resultado = Suma(numero1,numero2);
Los métodos pueden recibir diversos valores desde el exterior, de hecho pueden recibir
tantos valores como sea necesario, los valores que se le pasan a un método pueden ser por
valor o por referencia, los valores que un método recibe por valor, los recibe como una copia
del dato que puede modificar a su gusto sin que se vean afectadas las demás copias de
dicho dato, en cambio los valores que un método recibe por referencia son las direcciones a
donde se encuentra el dato, al cambiar el dato que ahí donde una de estas referencias
apunta cambia el valor para todos aquellos que lo estan utilizando, un ejemplo muy sencillo
de esto es, en el mundo real un paso de valor por referencia se da si tenemos apuntada la
dirección de un amigo, dicha dirección es una referencia al lugar donde vive, es por eso que
si vamos al lugar donde apunta la referencia nos encontraremos en la casa de nuestro
amigo, una vez allí, si rompemos un vidrio cualquier persona que llegue a ese lugar
encontrara la ventana rota, en cambio con los pasos de parámetro por valor se tiene una
copia del objeto en si, es decir, en el ejemplo anterior en vez de haber tenido una dirección,
habría tenido una casa exactamente igual a la de mi amigo, por lo que si hubiera roto un
vidrio, solo mi casa se habría visto afectada.
En C# si pasamos un valor a un método se pasa por valor a menos que se le indique lo
contrario. Debemos recordar que no ahi limite al número de parámetros que puede recibir o
devolver un método pero si no se va a recibir o a devolver ningún valor se puede usar la
palabra void para indicarlo. En C# los métodos son sumamente flexibles, es por esto que
podemos devolver múltiples valores y nos permite sobrecargarlos, las palabras claves ref y
out son las que nos permiten que un método retorne más de un valor al método que lo
invoco. La sobrecarga de métodos nos permite que un método se comporte de manera
diferente en función al tipo de parametros que recibe y/o del número de argumentos que le
acompañan.
Ejemplos de sobrecarga de métodos:
int i1=2,i2=3;
float f1=2.14,f2=5.25;
int suma( int n1, int n2 ) //Método 1
{
return n1+n2;
}
float suma( float n1, float n2 ) //Método 2
{
return n1+n2;
}
float suma( int n1, float n2 ) //Método 3
{
return n1+n2;
}
resultado = suma(i1+i2); //Llamada 1
resultado = suma(f1+f2); //Llamada 2
resultado = suma(i1+f1); //Llamada 3
En este ejemplo en ninguno de los casos ahi perdida de precisión debido a que el método
haciendo uso de la sobrecarga se adapta a los parametros que recibe y actúa en
consecuencia a ello, es decir si el método suma recibe dos enteros como en la llamada 1
devuelve un número entero empleando para ello el método 1, si recibe dos flotantes
devuelve un flotante utilizando para ello el método 2 y si recibe primero un flotante y luego
un entero responde utilizando el método 3.
http://www.elguille.info/colabora/NET2005/cursoCS_vecrado/Vecrado_CursoCSh
arp003.htm
Métodos
Los métodos son funciones que pueden ser llamadas dentro de la clase o por otras clases. La
implementación de un método consta de dos partes, una declaración y un cuerpo. La declaración en
Java de un método se puede expresar esquemáticamente como:
tipoRetorno nombreMetodo( [lista_de_argumentos] ) {
cuerpoMetodo
}
En C++, el método puede declararse dentro de la definición de la clase, aunque también puede
colocarse la definición completa del método fuera de la clase, convirtiéndose en una función inline.
En Java, la definición completa del método debe estar dentro de la definición de la clase y no se
permite la posibilidad de métodos inline, por lo tanto, Java no proporciona al programador
distinciones entre métodos normales y métodos inline.
Los métodos pueden tener numerosos atributos a la hora de declararlos, incluyendo el control de
acceso, si es estático o no estático, etc. La sintaxis utilizada para hacer que un método sea estático y
su interpretación, es semejante en Java y en C++. Sin embargo, la sintaxis utilizada para establecer
el control de acceso y su interpretación, es muy diferente en Java y en C++.
La lista de argumentos es opcional, tanto en Java como en C++, y en los dos casos puede limitarse a
su mínima expresión consistente en dos paréntesis, sin parámetro alguno en su interior.
Opcionalmente, C++ permite utilizar la palabra void para indicar que la lista de argumentos está
vacía, en Java no se usa. Los parámetros, o argumentos, se utilizan para pasar información al cuerpo
del método.
La sintaxis de la declaración completa de un método es la que se muestra a continuación con los
items opcionales en itálica y los items requeridos en negrilla:
especificadorAcceso static abstract
final native synchronized tipoRetorno nombreMetodo( lista_de_argumentos )
throws listaEscepciones
especificadorAcceso, determina si otros objetos pueden acceder al método y cómo pueden hacerlo.
Está soportado en Java y en C++, pero la sintaxis e interpretación es considerablemente diferente.
static, indica que los métodos pueden ser accedidos sin necesidad de instanciar un objeto del tipo
que determina la clase. C++ y Java son similares en el soporte de esta característica.
abstract, indica que el método no está definido en la clase, sino que se encuentra declarado ahí
para ser definido en una subclase (sobreescrito). C++ también soporta esta capacidad con una
sintaxis diferente a Java, pero con similar interpretación.
final, evita que un método pueda ser sobreescrito.
native, son métodos escritos es otro lenguaje. Java soporta actualmente C y C++.
synchronized, se usa en el soporte de multithreading, que se verá también en este Tutorial.
lista_de_argumentos, es la lista opcional de parámentros que se pueden pasar al método
throws listaExcepciones, indica las excepciones que puede generar y manipular el método. También
se verán en este Tutorial a fondo las excepciones en Java.
Valor de Retorno de un Método
En Java es imprescindible que a la hora de la declaración de un método, se indique el tipo de dato
que ha de devolver. Si no devuelve ningún valor, se indicará el tipo void como retorno.
Los métodos y funciones en C++ pueden devolver una variable u objeto, bien sea por valor (se
devuelve una copia), por puntero o por referencia. Java no soporta punteros, así que no puede
devolver nada por puntero. Todos los tipos primitivos en Java se devuelven por valor y todos los
objetos se devuelven por referencia. El retorno de la referencia a un objeto en Java es similar a
devolver un puntero a un objeto situado en memoria dinámica en C++, excepto que la sintaxis es
mucho más simple en Java, en donde el item que se devuelve es la dirección de la posición en
memoria dinámica donde se encuentra almacenado el objeto.
Para devolver un valor se utiliza la palabra clave return. La palabra clave return va seguida de una
expresión que será evaluada para saber el valor de retorno. Esta expresión puede ser compleja o
puede ser simplemente el nombre de un objeto, una variable de tipo primitivo o una constante.
El ejemplo java506.java ilustra el retorno por valor y por referencia.
// Un objeto de esta clase sera devuelto por referencia
class miClase {
int varInstancia = 10;
}
Si un programa Java devuelve una referencia a un objeto y esa referencia no es asignada a ninguna
variable, o utilizada en una expresión, el objeto se marca inmediatamente para que el reciclador de
memoria en su siguiente ejecución devuelve la memoria ocupada por el objeto al sistema,
asumiendo que la dirección no se encuentra ya almacenada en ninguna otra variable. En C++, si un
programa devuelve un puntero a un objeto situado en memoria dinámica y el valor de ese puntero
no se asigna a una variable, la posibilidad de devolver la memoria al sistema se pierde y se producirá
un memory leak, asumiendo que la dirección no está ya disponible para almacenar ninguna otra
variable.
Tanto en Java como en C++ el tipo del valor de retorno debe coincidir con el tipo de retorno que se
ha indicado en la declaración del método; aunque en Java, el tipo actual de retorno puede ser una
subclase del tipo que se ha indicado en la declaración del método, lo cual no se permite en C++. En
Java esto es posible porque todas las clases heredan desde un objeto raíz común a todos ellos:
Object.
En general, se permite almacenar una referencia a un objeto en una variable de referencia que sea
una superclase de ese objeto. También se puede utilizar un interfaz como tipo de retorno, en cuyo
caso, el objeto retornado debe implementar dicho interfaz.
Nombre del Método
El nombre del método puede ser cualquier identificador legal en Java. Java soporta el concepto de
sobrecarga de métodos, es decir, permite que dos métodos compartan el mismo nombre pero con
diferente lista de argumentos, de forma que el compilador pueda diferenciar claramente cuando se
invoca a uno o a otro, en función de los parámetros que se utilicen en la llamada al método.
El siguiente fragmento de código muestra una clase Java con cuatro métodos sobrecargados, el
último no es legal porque tiene el mismo nombre y lista de argumentos que otro previamente
declarado:
class MiClase {
. . .
void miMetodo( int x,int y ) { . . . }
void miMetodo( int x ) { . . . }
void miMetodo( int x,float y ) { . . . }
// void miMetodo( int a,float b ) { . . . } // no válido
}
Todo lenguaje de programación orientado a objetos debe soportar las características de
encapsulación, herencia y polimorfismo. La sobrecarga de métodos es considerada por algunos
autores como polimorfismo en tiempo de compilación.
En C++, dos versiones sobrecargadas de una misma función pueden devolver tipos diferentes. En
Java, los métodos sobrecargados siempre deben devolver el mismo tipo.
Métodos de Instancia
Cuando se incluye un método en una definición de una clase Java sin utilizar la palabra clave static,
estamos generando un método de instancia. Aunque cada objeto de la clase no contiene su propia
copia de un método de instancia (no existen múltiples copias del método en memoria), el resultado
final es como si fuese así, como si cada objeto dispusiese de su propia copia del método.
Cuando se invoca un método de instancia a través de un objeto determinado, si este método
referencia a variables de instancia de la clase, en realidad se están referenciando variables de
instancia específicas del objeto específico que se está invocando.
La llamada a los métodos de instancia en Java se realiza utilizando el nombre del objeto, el operador
punto y el nombre del método.
miObjeto.miMetodoDeInstancia();
En C++, se puede acceder de este mismo modo o utilizando una variable puntero que apunte al
objeto
miPunteroAlObjeto->miMetodoDeInstancia();
Los métodos de instancia tienen acceso tanto a las variables de instancia como a las variables de
clase, tanto en Java como en C++.
Métodos Estáticos
Cuando una función es incluida en una definición de clase C++, o un método e incluso en una
definición de una clase Java, y se utiliza la palabra static, se obtiene un método estático o método
de clase.
Lo más significativo de los métodos de clase es que pueden ser invocados sin necesidad de que haya
que instanciar ningún objeto de la clase. En Java se puede invocar un método de clase utilizando el
nombre de la clase, el operador punto y el nombre del método.
MiClase.miMetodoDeClase();
En C++, hay que utilizar el operador de resolución de ámbito para poder invocar a un método de
clase:
MiClase::miMetodoDeClase();
En Java, los métodos de clase operan solamente como variables de clase; no tienen acceso a
variables de instancia de la clase, a no ser que se cree un nuevo objeto y se acceda a las variables
de instancia a través de ese objeto.
Si se observa el siguiente trozo de código de ejemplo:
class Documento extends Pagina {
static int version = 10;
int numero_de_capitulos;
static void annade_un_capitulo() {
numero_de_capitulos++; // esto no funciona
}
static void modifica_version( int i ) {
version++;
// esto si funciona
}
}
la modificación de la variable numero_de_capitulos no funciona porque se está violando una de las
reglas de acceso al intentar acceder desde un método estático a una variable no estática.
Todas las clases que se derivan, cuando se declaran estáticas, comparten la misma página de
variables; es decir, todos los objetos que se generen comparten la misma zona de memoria. Los
métodos estáticos se usan para acceder solamente a variables estáticas.
class UnaClase {
int var;
UnaClase() {
var = 5;
}
unMetodo() {
var += 5;
}
}
En el código anterior, si se llama al método unMetodo() a través de un puntero a función, no se
podría acceder a var, porque al utilizar un puntero a función no se pasa implícitamente el puntero al
propio objeto (this). Sin embargo, sí se podría acceder a var si fuese estática, porque siempre
estaría en la misma posición de memoria para todos los objetos que se creasen de la clase
UnaClase.
Paso de parámetros
En C++, se puede declarar un método en una clase y definirlo luego dentro de la clase (bajo ciertas
condiciones) o definirlo fuera de la clase. A la hora de declararlo, es necesario indicar el tipo de
argumentos que necesita, pero no se requiere indicar sus nombres (aunque pueda hacerse). A la
hora de definir el método sí tiene que indicarse el nombre de los argumentos que necesita el método.
En Java, todos los métodos deben estar declarados y definidos dentro de la clase, y hay que indicar
el tipo y nombre de los argumentos o parámetros que acepta. Los argumentos son como variables
locales declaradas en el cuerpo del método que están inicializadas al valor que se pasa como
parámetro en la invocación del método.
En Java, todos los argumentos de tipos primitivos deben pasarse por valor, mientras que los objetos
deben pasarse por referencia. Cuando se pasa un objeto por referencia, se está pasando la dirección
de memoria en la que se encuentra almacenado el objeto.
Si se modifica una variable que haya sido pasada por valor, no se modificará la variable original que
se haya utilizado para invocar al método, mientras que si se modifica una variable pasada por
referencia, la variable original del método de llamada se verá afectada de los cambios que se
produzcan en el método al que se le ha pasado como argumento.
El ejemplo java515.java se ilustra el paso de parámetros de tipo primitivo y también el paso de
objetos, por valor y por referencia, respectivamente.
// Esta clase se usa para instanciar un objeto referencia
class MiClase {
int varInstancia = 100;
}
// Clase principal
class java515 {
// Función para ilustrar el paso de parámetros
void pasoVariables( int varPrim,MiClase varRef ) {
System.out.println( "--> Entrada en la funcion pasoVariables" );
System.out.println( "Valor de la variable primitiva: "+varPrim );
System.out.println( "Valor contenido en el objeto: "+
varRef.varInstancia );
System.out.println( "-> Modificamos los valores" );
varRef.varInstancia = 101;
varPrim = 201;
System.out.println( "--> Todavia en la funcion pasoVariables" );
System.out.println( "Valor de la variable primitiva: "+varPrim );
System.out.println( "Valor contenido en el objeto: "+
varRef.varInstancia );
}
public static void main( String args[] ) {
// Instanciamos un objeto para acceder a sus métodos
java515 aObj = new java515();
// Instanciamos un objeto normal
MiClase obj = new MiClase();
// Instanciamos una variable de tipo primitivo
int varPrim = 200;
System.out.println( "> Estamos en main()" );
System.out.println( "Valor de la variable primitiva: "+varPrim );
System.out.println( "Valor contenido en el objeto: "+
obj.varInstancia );
// Llamamos al método del objeto
aObj.pasoVariables( varPrim,obj );
System.out.println( "> Volvemos a main()" );
System.out.println( "Valor de la variable primitiva, todavia : "+
varPrim );
System.out.println( "Valor contenido ahora en el objeto: "+
obj.varInstancia );
}
}
En C++, se puede pasar como parámetro un puntero que apunte a una función dentro de otra
función, y utilizar este puntero en la segunda función para llamar a la primera. Esta capacidad no
está directamente soportada en Java. Sin embargo, en algunos casos, se puede conseguir casi lo
mismo encapsulando la primero función como un método de instancia de un objeto y luego pasar el
objeto a otro método, donde el primer método se puede ejecutar a través del objeto.
Tanto en Java como en C++, los métodos tienen acceso directo a las variables miembro de la clase.
El nombre de un argumento puede tener el mismo nombre que una variable miembro de la clase. En
este caso, la variable local que resulta del argumento del método, oculta a la variable miembro de la
clase.
Cuando se instancia un método se pasa siempre una referencia al propio objeto que ha llamado al
método, es la referencia this.
http://www.itapizaco.edu.mx/paginas/JavaTut/froufe/parte5/cap5
-5.html
2.9
Estructura del código.
Ver archivo: art_tecn.pdf
Ver archivo: estructuradelcodigo.pdf
Unidad 3. Constructor, destructor.
3.5
Conceptos de métodos constructor y
destructor.
Podemos imaginar que la construcción de objetos tiene tres fases: (I) instanciación,
que aquí representa el proceso de asignación de espacio al objeto, de forma que este
tenga existencia real en memoria. (II) Asignación de recursos. Por ejemplo, un
miembro puede ser un puntero señalando a una zona de memoria que debe ser
reservada; un "handle" a un fichero; el bloqueo de un recurso compartido o el
establecimiento de una línea de comunicación. (III) Iniciación, que garantiza que los
valores iniciales de todas sus propiedades sean correctos (no contengan basura).
La correcta realización de estas fases es importante, por lo que los diseñadores del
lenguaje decidieron asignar esta tarea a un tipo especial de funciones (métodos)
denominadas constructores. En realidad, la consideraron tan importante que, como
veremos a continuación, si el programador no declara ninguno explícitamente, el
compilador se encarga de definir un constructores de oficio , encargándose de
utilizarlo cada vez que es necesario. Aparte de las invocaciones explícitas que pueda
realizar el programador, los constructores son frecuentemente invocados de forma
implícita por el compilador.
Es significativo señalar que las fases anteriores se realizan en un orden, aunque todas
deben ser felizmente completadas cuando finaliza la labor del constructor.
§2 Descripción
Para empezar a entender como funciona el asunto, observe este sencillo ejemplo en el
que se definen sendas clases para representar complejos; en una de ellas definimos
explícitamente un constructor; en otra dejamos que el compilador defina un constructor
de oficio:
#include <iostream>
using namespace std;
class CompleX {
// Una clase para representar
complejos
public:
float r; float i;
// Partes real e imaginaria
CompleX(float r = 0, float i = 0) { // L.7: construtor
explícito
this->r = r; this->i = i;
cout << "c1: (" << this->r << "," << this->i << ")" <<
endl;
}
};
class CompX {
// Otra clase análoga
public:
float r; float i;
// Partes real e imaginaria
};
void main() {
CompleX c1;
CompleX c2(1,2);
CompX c3;
cout << "c3: (" << c3.r <<
}
// ======================
// L.18:
// L.19:
// L.20:
"," << c3.i << ")" << endl;
Salida:
c1: (0,0)
c2: (1,2)
c3: (6.06626e-39,1.4013e-45)
Comentario:
En la clase CompleX definimos explícitamente un constructor que tiene argumentos por
defecto ( ), no así en la clase CompX en la que es el propio compilador el que define
un constructor de oficio.
Es de destacar la utilización explícita del puntero this ( 4.11.6) en la definición del
constructor (Ls.8-9). Ha sido necesario hacerlo así para distinguir las propiedades i, j
de las variables locales en la función-constructor (hemos utilizado deliberadamente los
mismos nombres en los argumentos, pero desde luego, podríamos haber utilizado otros
:-)
En la función main se instancian tres objetos; en todos los casos el compilador realiza
una invocación implícita al constructor correspondiente. En la declaración de c1, se
utilizan los argumentos por defecto para inicializar adecuadamente sus miembros; los
valores se comprueban en la primera salida.
La declaración de c2 en L.19 implica una invocación del constructor por defecto
pasándole los valores 1 y 2 como argumentos. Es decir, esta sentencia equivaldría a:
c2 = CompleX::CompleX(1, 2); // Hipotética invocación
explícita al constructor
Nota: En realidad esta última sentencia es sintácticamente incorrecta; se trata solo
de un recurso pedagógico, ya que no es posible invocar de esta forma al
constructor de una clase ( 4.11.2d). Una alternativa correcta a la declaración de
L.19 sería:
CompleX c2 = CompleX(1,2);
El resultado de L.19 puede verse en la segunda salida.
Finalmente, en L.20 la declaración de c3 provoca la invocación del constructor de oficio
construido por el propio compilador. Aunque la iniciación del objeto con todos sus
miembros es correcta, no lo es su inicialización ( 4.1.2). En la tercera salida vemos
como sus miembros adoptan valores arbitrarios. En realidad se trata de basura
existente en las zonas de memoria que les han sido adjudicadas.
El corolario inmediato es deducir lo que ya señalamos en la página anterior: adunque el
constructor de oficio inicia adecuadamente los miembros abstractos ( 4.11.2d), no
hace lo mismo con los escalares. Además, por una u otra causa, en la mayoría de los
casos de aplicaciones reales es imprescindible la definición explícita de uno o varios de
estos constructores ( ).
§3 Técnias de buena construcción
Recordar que un objeto no se considera totalmente construido hasta que su constructor
ha concluido satisfactoriamente. En los casos que la clase contenga sub-objetos o
derive de otras, el proceso de creación incluye la invocación de los constructores de las
subclases o de las super-clases en una secuencia ordenada que se detalla más
adelante .
Los constructores deben ser diseñados de forma que no puedan (ni áun en caso de
error) dejar un objeto a medio construir. En cas que no sea posible alistar todos los
recursos exigidos por el objeto, antes de terminar su ejecucón debe preverse un
mecanismo de destrucción y liberación de los recursos que hubiesen sido asignados.
Para esto es posible utilizar el mecanismo de excepciones.
§4 Invocación de constructores
Al margen de la particularidad que representan sus invocaciones implícitas, en general
su invocación sigue las pautas del resto de los métodos. Ejemplos:
X x1;
constructor
X::X();
[4]
X x2 = X::X()
X x3 = X();
constructor [5]
X x4();
anterior [6]
// L.1: Ok. Invocación implícita del
// Error: invocación ilegal del constructor
// Error: invocación ilegal del constructor
// L.4: Ok. Invocación legal del
// L.5: Ok. Variación sintáctica del
Nota: Observe como la única sentencia válida con invocación explícita al
constructor (L.4) es un caso de invocación de función miembro muy especial desde
el punto de vista sintáctico (esta sintaxis no está permitida con ningún otro tipo de
función-miembro, ni siquiera con funciones estáticas o destructores). La razón es
que los constructores se diferencian de todos los demás métodos no estáticos de
la clase en que no se invocan sobre un objeto (aunque tienen puntero this
4.11.6). En realidad se asemejan a los dispositivos de asignación de memoria, en
el sentido que son invocados desde un trozo de memoria amorfa y la convierten en
una instancia de la clase [7].
Como ocurre con los tipos básicos (preconstruidos en el lenguaje), si deseamos crear
objetos persistentes de tipo abstracto (definidos por el usuario), debe utilizarse el
operador new ( 4.9.20). Este operador está íntimamente relacionado con los
constructores. De hecho, para invocar la creación de un objeto a traves de él, debe
existir un constructor por defecto ( ).
Si nos referimos a la clase CompleX definida en el ejemplo ( ), las sentencias:
{
CompleX* pt1 = new(CompleX);
CompleX* pt2 = new(CompleX)(1,2);
}
provocan la creación de dos objetos automáticos, los punteros pt1 y pt2, así como la
creación de sendos objetos (anónimos) en el montón. Observe que ambas sentencias
suponen un invocación implícita al constructor. La primera al constructor por defecto sin
argumentos, la segunda con los argumentos indicados. En consecuencia producirán
las siguientes salidas:
c1: (0,0)
c1: (1,2)
Observe también, y esto es importante, que los objetos pt1 y pt2 son destruidos
automáticamente al salir de ámbito el bloque. No así los objetos señalados por estos
punteros (ver comentario al respecto 4.11.2d2).
§5 Propiedades de los constructores
Aunque los constructores comparten muchas propiedades de los métodos normales,
tienen algunas características que las hace ser un tanto especiales. En concreto, se
trata de funciones que utilizan rutinas de manejo de memoria en formas que las
funciones normales no suelen utilizar.
§5.1 Los constructores se distinguen del resto de las funciones de una clase porque
tienen el mismo nombre que esta. Ejemplo:
class X {
public:
X();
};
// definición de la clase X
// constructor de la clase X
§5.2 No se puede obtener su dirección, por lo que no pueden declararse punteros a
este tipo de métodos.
§5.3 No pueden declararse virtuales (
class C {
...
virtual C();
};
4.11.8a). Ejemplo:
// Error !!
La razón está en la propia idiosincrasia de este tipo de funciones. En efecto, veremos
que declarar que un método es virtual ( 4.11.8a) supone indicar al compilador que el
modo concreto de operar la función será definido más tarde, en una clase derivada; sin
embargo, un constructor debe conocer el tipo exacto de objeto que debe crear, por lo
que no puede ser virtual.
§5.4 Otras peculiaridades de los constructores es que se declaran sin devolver nada, ni
siquiera void, lo que no es óbice para que el resultado de su actuación (un objeto) si
pueda ser utilizado como valor devuelto por una función:
class C { ... };
...
C foo() {
return C();
}
§5.5 No pueden ser heredados, aunque una clase derivada puede llamar a los
constructores y destructores de la superclase siempre que hayan sido declarados
public o protected ( 4.11.2a). Como el resto de las funciones (excepto main), los
constructores también pueden ser sobrecargados; es decir: Una clase puede tener
varios constructores.
En estos casos, la invocación (incluso implícita) del constructor adecuado se efectuará
según los argumentos involucrados. Es de destacar que en ocasiones, esta
multiplicidad de constructores puede conducir a situaciones realmente curiosas; incluso
se ha definido una palabra clave, explicit, para evitar los posibles efectos colaterales
( ).
§5.5 Un constructor no puede ser friend (
4.11.2a1) de ninguna otra clase.
§5.6 Una peculiaridad sintáctica de este tipo de funciones es la posibilidad de incluir
iniciadores ( 4.11.2d3), una forma de expresar la inicialización de variables fuera del
cuerpo del constructor. Ejemplo:
class X {
const int i;
char c;
public:
X(int entero, char caracter): i(entero), c(caracter) { };
};
§5.7 Como en el resto de las funciones, los constructores pueden tener argumentos por
defecto. Por ejemplo, el constructor:
X::X(int, int = 0)
puede aceptar uno o dos argumentos. Cuando se utiliza con uno, el segundo se
supone que es un cero int.
De forma análoga, el constructor
X::X(int = 5, int = 6)
puede aceptar dos, uno o ningún argumento, con sus correspondientes valores por
defecto para cuando faltan.
Observe que un constructor sin argumentos, como X::X(), no debe ser confundido
con X::X(int=0), que puede ser llamado sin argumentos o con uno; aunque en
realidad siempre tendrá un argumento. En otras palabras: Que una función pueda ser
invocada sin argumentos no implica necesariamente que no los acepte.
§5.8 Cuando se definen constructores deben evitarse ambigüedades. Es el caso de
los constructores por defecto del ejemplo siguiente:
class X {
public:
X();
X(int i = 0);
};
int main() {
X uno(10);
X dos;
X::X(int = 0)
return 0;
}
// Ok; usa el constructor X::X(int)
// Error: ambigüedad cual usar? X::X() o
§5.9 Los constructores de las variables globales son invocados por el módulo inicial
antes de que sea llamada la función main y las posibles funciones que se hubiesen
instalado mediante la directiva #pragma startup ( 1.5).
§5.10 Los objetos locales se crean tan pronto como se inicia su ámbito. También se
invoca implícitamente un constructor cuando se crea o copia un objeto de la clase
(incluso temporal). El hecho de que al crear un objeto se invoque implícitamente un
constructor por defecto si no se invoca ninguno de forma explícita, garantiza que
siempre que se instancie un objeto será inicializado adecuadamente.
En el ejemplo que sigue se muestra claramente como se invoca el constructor tan
pronto como se crea un objeto.
#include <iostream>
using namespace std;
class A {
// definición de una clase
public:
int x;
A(int i = 1) {
// constructor por defecto
x = i;
cout << "Se ha creado un objeto" << endl;
}
};
int main() {
// =========================
A a;
// se instancia un objeto
cout << "Valor de a.x: " << a.x << endl;
return 0;
}
Salida:
Se ha creado un objeto
Valor de a.x: 1
§5.11 El constructor de una clase no puede admitir la propia clase como argumento (se
daría lugar a una definición circular). Ejemplo:
class X {
public:
X(X);
};
// Error: ilegal
§5.12 Los parámetros del constructor pueden ser de cualquier tipo, y aunque no puede
aceptar su propia clase como argumento. En cambio si pueden aceptar una referencia
a objetos de su propia clase, en cuyo caso se denomina constructor-copia (su sentido
y justificación lo exponemos con más detalle en el apartado correspondiente
4.11.2d4).
Ejemplo:
class X {
public:
X(X&);
};
// Ok. correcto
Aparte del referido constructor-copia, existe otro tipo de constructores de nombre
específico: el constructor oficial y el constructor por defecto ( ).
§6 Constructor oficial
Si el programador no define explícitamente ningún constructor, el compilador
proporciona uno por defecto al que llamaremos oficial o de oficio. Es público, "inline"
( 4.11.2a), y definido de forma que no acepta argumentos. Es el responsable de que
funcionen sin peligro secuencias como esta:
class A {
int x;
};
...
A a;
// C++ ha creado un constructor "de oficio"
// invocación implícita al constructor de oficio
Recordemos que el constructor de oficio invoca implícitamente los constructores de
oficio de todos los miembros. Si algunos miembros son a su vez objetos abstractos, se
invocan sus constructores. Así sucesivamente con cualquier nivel de complejidad hasta
llegar a los tipos básicos (preconstruidos en el lenguaje 2.2) cuyos constructores son
también invocados. Recordar que los constructores de los tipos básicos inician
(reservan memoria) para estos objetos, pero no los inicializan con ningún valor
concreto. Por lo que en principio su contenido es impredecible (basura) [1]. Dicho en
otras palabras: el constructor de oficio se encarga de preparar el ambiente para que el
objeto de la clase pueda operar, pero no garantiza que los datos contenidos sean
correctos. Esto último es responsabilidad del programador y de las condiciones de
"runtime". Por ejemplo:
struct Nombre {
char* nomb;
};
struct Equipo {
Nombre nm;
size_t sz;
};
struct Liga {
int year;
char categoria;
Nombre nLiga;
Equipo equipos[10];
};
...
Liga primDiv;
En este caso la última sentencia inicia primDiv mediante una invocación al constructor
por defecto de Liga, que a su vez invoca a los constructores por defecto de Nombre y
Equipo. para crear los miembros nLiga y equipos (el constructor de Equipo es
invocado diez veces, una por cada miembro de la matriz). A su vez, cada invocación a
Equipo() produce a su vez una invocación al constructor por defecto de Nombre
(size_t es un tipo básico y no es invocado su constructor 4.9.13). Los miembros
nLiga y equipos son iniciados de esta forma, pero los miembros year y categoria
no son inicializados ya que son tipos simples, por lo que pueden contener basura.
Si el programador define explícitamente cualquier constructor, el constructor oficial
deja de existir. Pero si omite en él la inicialización de algún tipo abstracto, el compilador
añadirá por su cuenta las invocaciones correspondientes a los constructores por defecto
de los miembros omitidos ( Ejemplo).
§6.1 Constructor trivial
Un constructor de oficio se denomina trivial si cumple las siguientes condiciones:



La clase correspondiente no tiene funciones virtuales ( 4.11.8a) y no deriva de
ninguna superclase virtual.
Todos los constructores de las superclases de su jerarquía son triviales
Los constructores de sus miembros no estáticos que sean clases son también
triviales
§7 Constructor por defecto
Constructor por defecto de la clase X es aquel que "puede" ser invocado sin
argumentos, bien porque no los acepte, bien porque disponga de argumentos por
defecto ( 4.4.5).
Como hemos visto en el epígrafe anterior, el constructor oficial creado por el
compilador si no hemos definido ningún constructor es también un constructor por
defecto, ya que no acepta argumentos.
Tenga en cuenta que diversas posibilidades funcionales y sintácticas de C++ precisan
de la existencia de un constructor por defecto (explícito u oficial). Por ejemplo, es el
responsable de la creación del objeto x en una declaración del tipo X x;.
§8 Un constructor explícito puede ser imprescindible
En el primer ejemplo ( ), el programa ha funcionado aceptablemente bien utilizando el
constructor de oficio en una de sus clases, pero existen ocasiones en que es
imprescindible que el programador defina uno explícitamente, ya que el suministrado
automáticamente por el compilador no es adecuado.
Consideremos una variación del citado ejemplo en la que definimos una clase para
contener las coordenadas de puntos de un plano en forma de matrices de dos
dimensiones:
#include <iostream>
using namespace std;
class Punto {
public: int coord[2];
};
int main() {
// ==================
Punto p1(10, 20);
// L.8:
cout << "Punto p1; X == " << coord[0] << "; Y == " <<
coord[1] << endl;
}
Este programa produce un error de compilación en L.8. La razón es que si necesitamos
este tipo de inicialización del objeto p1, utilizando una lista de argumentos, es
imprescindible la existencia de un constructor explícito ( 4.11.2d3). Es decir, la
versión correcta del programa seria:
#include <iostream>
using namespace std;
class Punto {
public: int coord[2];
Punto(int x = 0, int y = 0) {
coord[0] = x; coord[1] = y;
}
};
// construtor explícito
// inicializa
int main() {
// ==================
Punto p1(10, 20);
// L.8: Ok.
cout << "Punto p1; X == " << coord[0] << "; Y == " <<
coord[1] << endl;
}
§8.1 La anterior no es por supuesto la única causa que hace necesaria la existencia de
constructores explícitos. Más frecuente es el caso de que algunas de las variables de la
clase deban ser persistentes. Por ejemplo: supongamos que en el caso anterior
necesitamos que la matriz que almacena las coordenadas necesite este tipo de
almacenamiento. En este caso, puesto que la utilización del especificador static
aplicado a miembros de clase puede tener efectos colaterales indeseados ( 4.11.7), el
único recurso es situar el almacenamiento en el montón ( 1.3.2), para lo que
utilizamos el operador new ( 4.9.20) en un constructor definido al efecto. La
definición de la clase tendría el siguiente aspecto:
class Punto {
public: int* coord;
Punto(int x = 0, int y =
defecto
coord = new int[2];
coord[0] = x; coord[1]
cout << "Creado punto;
<< coord[0] << ";
}
};
0) {
// construtor por
// asigna espacio
// inicializa
= y;
X == "
Y == " << coord[1] << endl;
Posteriormente se podrían instanciar objetos de la clase Punto mediante expresiones
como:
Punto p1;
Punto p2(3, 4);
argumentos
Punto p3 = Punto(5, 6);
argumentos
Punto* ptr1 = new(Punto)
argumentos
Punto* ptr2 = new(Punto)(7, 8)
argumentos
// invocación implícita
// invocación implícita con
// invocación explícita con
// invocación implícita sin
// invocación implícita con
§9 Orden de construcción
Dentro de una clase los constructores de sus miembros son invocados antes que el
constructor existente dentro del cuerpo de la propia clase. Esta invocación se realiza en
el mismo orden en que se hayan declarado los elementos. A su vez, cuando una clase
tiene más de una clase base (herencia múltiple 4.11.2c), los constructores de las
clases base son invocados antes que el de la clase derivada y en el mismo orden que
fueron declaradas. Por ejemplo en la inicialización:
class Y {...}
class X : public Y {...}
X one;
los constructores son llamados en el siguiente orden:
Y();
X();
// constructor de la clase base
// constructor de la clase derivada
En caso de herencia múltiple:
class X : public Y, public Z
X one;
los constructores de las clase-base son llamados primero y en el orden de declaración:
Y();
Z();
X();
// constructor de la primer clase base
// constructor de la segunda clase base
// constructor de la clase derivada
Nota: Al tratar de la destrucción de objetos ( 4.11.2d2), veremos que los
destructores son invocados exactamente en orden inverso al de los constructores.
§9.1 Los constructores de clases base virtuales ( 4.11.8a) son invocados antes que
los de cualquier clase base no virtual. Si la jerarquía contiene múltiples clases base
virtuales, sus constructores son invocados en el orden de sus declaraciones. A
continuación de invocan los constructores del resto de las clase base, y por último el
constructor de la clase derivada.
§9.2 Si una clase virtual deriva de otra no virtual, primero se invoca el constructor de la
clase base (no virtual), de forma que la virtual (derivada) pueda ser construida
correctamente. Por ejemplo, el código:
class X : public Y, virtual public Z
X one;
origina el siguiente orden de llamada en los constructores:
Z();
Y();
X();
// constructor de la clase base virtual
// constructor de la clase base no virtual
// constructor de la clase derivada
Un ejemplo más complicado:
class
class
class
class
base;
base2;
level1 : public base2, virtual public base;
level2 : public base2, virtual public base;
class toplevel : public level1, virtual public level2;
toplevel view;
El orden de invocación de los constructores es el siguiente:
base();
base2();
level2();
base2();
level1();
//
//
//
//
//
//
//
clase virtual de jerarquía más alta
base es construida solo una vez
base no virtual de la base virtual level2
debe invocarse para construir level2
clase base virtual
base no virtual de level1
otra base no virtual
toplevel();
§9.3 Si una jerarquía de clases contiene múltiples instancias de una clase base virtual,
dicha base virtual es construida solo una vez. Aunque si existen dos instancias de la
clase base: virtual y no virtual, el constructor de la clase es invocado solo una vez para
todas las instancias virtuales y después una vez para cada una de las instancias no
virtuales.
§9.4 En el caso de matrices de clases, los constructores son invocados en orden
creciente de subíndices.
§10 Los constructores y las funciones virtuales
Debido a que los constructores de las clases-base son invocados antes que los de las
clases derivadas, y a la propia naturaleza del mecanismo de invocación de funciones
virtuales ( 4.11.8a), el mecanismo virtual está deshabilitado en los constructores, por
lo que es peligroso incluir invocaciones a tales funciones en ellos, ya que podrían
obtenerse resultados no esperados a primera vista.
Considere los resultados del ejemplo siguiente, donde se observa que la versión de la
función fun invocada no es la que cabría esperar en un funcionamiento normal del
mecanismo virtual.
#include <string>
#include <iostream>
using namespace std;
class B {
// superclase
public:
virtual void fun(const string& ss) {
cout << "Funcion-base: " << ss << endl;
}
B(const string& ss) {
// constructor de superclase
cout << "Constructor-base\n";
fun(ss);
}
};
class D : public B {
// clase derivada
string s;
// private por defecto
public:
void fun(const string& ss) { cout << "Funcion-derivada\n";
s = ss; }
D(const string& ss) :B(ss) { // constructor de subclase
cout << "Constructor-derivado\n";
}
};
int main() {
D d("Hola mundo");
constructor D
}
// =============
// invocación implícita a
Salida:
Constructor-base
Funcion-base: Hola mundo
Constructor-derivado
Nota: La invocación de destructores ( 4.11.2d2) se realiza en orden inverso a
los constructores: Las clases derivadas se destruyen antes que las clases-base
[2]. Por esta razón el mecanismo virtual también está deshabilitado en los
destructores (lo que no tiene nada que ver con que los destructores puedan ser en
sí mismos funciones virtutales 4.11.2d2). Así pues, en la ejecución de un
destructor solo se invocan las definiciones locales de las funciones implicadas. De
lo contrario se correría el riesgo de referenciar la parte derivada del objeto que ya
estaría destruida.
§11 Constructores de conversión
Normalmente a una clase con constructor de un solo parámetro puede asignársele un
valor que concuerde con el tipo del parámetro. Este valor es automáticamente
convertido de forma implícita en un objeto del tipo de la clase a la que se ha asignado.
Por ejemplo: la definición:
class X {
public:
X();
X(int);
X(const char*, int = 0);
};
// constructor C-1
// constructor C-2
// constructor C-3
en la que se han definido dos constructores que pueden ser utilizados con un solo
argumento, permite que las siguientes asignaciones sean legales:
void f() {
X a;
X b = X();
X c = X(1);
X d(1);
X e = X("Mexico");
X f("Mexico");
X g = 1;
X h = "Madrid";
a = 2;
}
//
//
//
//
//
//
//
//
//
Ok invocado C-1
Ok idem.
Ok invocado C-2
Ok igual que el anterior
Ok invocado C-3
Ok igual que el anterior
L.1 Ok.
L.2 Ok.
L.3 Ok.
La explicación de las tres últimas sentencias es la siguiente:
En L.1, el compilador intenta convertir el Rvalue (que aquí es una constante numérica
entera de valor 1) en el tipo del Lvalue, que aquí es la declaración de un nuevo objeto
(una instancia de la clase). Como necesita crear un nuevo objeto, utilizará un
constructor, de forma que busca si hay uno adecuado en X que acepte como argumento
el tipo situado a la derecha. El resultado es que el compilador supone un constructor
implícito a la derecha de L.1:
X a = X(1);
// interpretación del compilador para L.1
El proceso se repite en la sentencia L.2 que es equivalentes a:
X B = X("Madrid");
// L.2bis
La situación en L.3 es completamente distinta, ya que en este caso ambos operandos
son objetos ya construidos. Para poder realizar la asignación, el compilador intenta
convertir el tipo del Rvalue al tipo del Lvalue, para lo cual, el mecanismo de conversión
de tipos busca si existe un constructor adecuado en X que acepte el operando
derecho. Caso de existir se creará un objeto temporal tipoX que será utilizado como
Rvalue de la asignación. La asignación propiamente dicha es realizada por el operador
correspondiente (explícito o implícito) de X. La página adjunta incluye un ejemplo que
muestra gráficamente el proceso seguido ( Ejemplo)
Este tipo de conversión automática se realiza solo con constructores que aceptan un
argumento o que son asimilables (como C-2), y suponen una conversión del tipo
utilizado como argumento al tipo de la clase. Por esta razón son denominadas
conversiones mediante constructor, y a este tipo de constructores constructores de
conversión ("Converting constructor"). Su sola presencia habilita no solo la conversión
implícita, también la explícita. Ejemplo:
class X {
public:
X(int);
};
// constructor C-2
la mera existencia del constructor C-2 en la clase X, permite las siguientes
asignaciones:
void f() {
X a = X(1)
constructor
X a = 1;
a = 2;
a = (X) 2;
tradicional)
a = static_cast<X>(2);
C++)
}
// L1: Ok. invocación explícita al
// Ok. invocación implícita X(1)
// Ok. invocación implícita X(2)
// Ok. casting explícito (estlo
// Ok. casting explícito (estilo
Si eliminamos el constructor C-2 de la declaración de la clase, todas estas sentencias
serían erróneas.
Observe que en L1 cabría hacerse una pregunta: ¿Se trata de la invocación del
constructor o un modelado explícito al estilo tradicional?. La respuesta es que se trata
de una invocación al constructor, y que precisamente el modelado (explícito o implícito)
se apoya en la existencia de este tipo de constructores para realizar su trabajo.
[1] Las razones de este trato desigual entre ambos tipos de miembros se
debe a la herencia de C y a consideraciones de eficiencia.
http://www.zator.com/Cpp/E4_11_2d1.htm
Los destructores son un tipo especial de función miembro, estrechamente
relacionados con los constructores. Son también funciones que no devuelven nada (ni
siquiera void). Tampoco aceptan ningún parámetro, ya que la destrucción de un objeto
no acepta ningún tipo de opción o especificación particular y es idéntica para todos los
objetos de la clase. Los destructores no pueden ser heredados, aunque una clase
derivada puede llamar a los destructores de su superclase si no han sido declarados
privados (son públicos o protegidos). Lo mismo que ocurre con los constructores,
tampoco puede obtenerse su dirección, por lo que no es posible establecer punteros a
este tipo de funciones.
La misión más común de los destructores es liberar la memoria asignada por los
constructores, aunque también puede ser desasignar y/o liberar determinados recursos
asignados por estos. Por ejemplo, cerrar un fichero o desbloquear un recurso
compartido previamente bloqueado por el constructor.
Se ha señalado que si el programador no define uno explícitamente, el compilador
C++ proporciona un destructor de oficio, que es declarado público y puede ser
invocado sin argumentos. Por lo general en la mayoría de los casos este destructor de
oficio es suficiente, por lo que el programador no necesita definir uno por sí mismo, a no
ser que la clase incluya la inicialización de objetos persistentes ( 1.3.2). Por ejemplo
matrices que necesiten del operador new en el constructor para su inicialización, en
cuyo caso es responsabilidad del programador definir un destructor adecuado (ver
ejemplo ).
Los destructores son invocados automáticamente (de forma implícita) por el programa
en multitud de ocasiones; de hecho es muy raro que sea necesario invocarlos
explícitamente. Su misión es limpiar los miembros del objeto antes que el propio objeto
se auto-destruya.
§2 Declaración
Los destructores se distinguen porque tienen el mismo nombre que la clase a que
pertenecen precedido por la tilde ~ para simbolizar su estrecha relación con los
constructores que utilizan el mismo nombre (son el "complemento" de aquellos).
Ejemplo:
class X {
public:
~X();
};
...
X::~X() {
...
}
// destructor de la clase X
// definición (off-line) del destructor
§2.1 Ejemplo:
La clase Punto definida en el epígrafe anterior ( 4.11.2d1) sería un buen exponente
del caso en que es necesario definir un destructor explícito que se encargue de las
correcta destrucción de los miembros. En efecto, manteniendo aquella definición, una
sentencia del tipo:
{
...
Punto p1(2,3);
...
}
provoca la creación de un objeto en memoria dinámica. El miembro coord es un
puntero-a-int que señala un área en el montón capaz para albergar dos enteros.
Cuando la ejecución sale del ámbito del bloque en que se ha creado el objeto, es
invocado el destructor de oficio y el objeto es destruido, incluyendo su único
componente, el puntero coord; sin embargo el área señalada por este permanece
reservada en el montón, y por tanto irremediablemente perdida.
La forma sensata de utilizar tales objetos sería modificando la definición de la clase
para añadirle un destructor adecuado. La versión correcta tendría el siguiente aspecto:
class Punto {
public: int* coord;
Punto(int x = 0, int y = 0) {
coord = new int[2];
coord[0] = x; coord[1] = y;
}
~Punto() {
delete [] coord;
// L.8:
};
// construtor
// destructor
En este caso, la sentencia de la línea 8 provoca que al ser invocado el destructor del
objeto, se desasigne el área del montón señalada por el puntero (recuerde que, al igual
que el resto de las funciones miembro, los destructores también tienen un argumento
oculto this, por lo que la función sabe sobre que objeto tiene que operar en cada caso).
§3 Invocación
Como hemos señalado, los destructores son invocados automáticamente por el
compilador, y es muy raro que sea necesario invocarlos explícitamente.
§3.1 Invocación explícita de destructores
En caso necesario los destructores pueden ser invocados explícitamente de dos
formas: Indirectamente, mediante una llamada a delete ( 4.9.21) o directamente
utilizando el nombre cualificado completo.
Ejemplo:
class X {...};
...
{
X obj1;
X* ptr = new(X)
X* pt2 = &obj1;
clase X
...
pt2–>X::~X();
destructor
// pt2->~X();
anterior
// obj1.~X();
X::~X();
destructor [1]
// X es una clase
// L.4: objeto automático
// L.5: objeto persistente
// Ok: pt2 es puntero a obj1 de la
// L.8: Ok: llamada legal del
L.9: Ok: variación sintáctica de la
L.10: Ok otra posibilidad análoga
// L.11: Error: llamada ilegal al
delete ptr;
destructor
}
// L.12: Ok. invocación implícita al
Comentario:
L.4 crea el objeto obj1 en la pila ( 1.3.2), se trata de un objeto automático, y en
cuanto el bloque salga de ámbito, se producirá automáticamente una llamada a su
destructor que provocará su eliminación. Sin embargo, el objeto anónimo señalado por
ptr es creado en el montón.
Observe que mientras ptr es también un objeto automático, que será eliminado al salir
del bloque, el objeto al que señala es persistente y su destructor no será
automáticamente invocado al salir de ámbito el bloque. Como se ve en el punto
siguiente, en estos casos es imprescindible una invocación explícita al destructor
mediante el operador delete (cosa que hacemos en L.12), en caso contrario, el espacio
ocupado por el objeto será perdido.
Es muy importante advertir que la invocación explícita al destructor de obj1 en L.8 (o
su versiones equivalentes L.9 y L.10) son correctas, aunque muy peligrosas [2]. En
efecto, en L.8 se produce la destrucción del objeto, pero en el estado actual de los
compiladores C++, que no son suficientemente "inteligentes" en este sentido [3], al salir
el bloque de ámbito vuelven a invocar los destructores de los objetos automáticos
creados en su interior, por lo que se producirá un error de ejecución irrecuperable
(volcado de memoria si corremos bajo Linux).
§3.1.1 Los objetos que han sido creados con el operador new ( 4.9.20) deben
destruirse obligatoriamente con una llamada explícita al destructor. Ejemplo:
#include <stdlib.h>
class X {
// clase
public:
...
~X(){};
// destructor de la clase
};
void* operator new(size_t size, void *ptr) {
return ptr;
}
char buffer[sizeof(X)];
// matriz de caracteres, del
tamaño de X
void main() {
X* ptr1 = new X;
new
X* ptr2;
ptr2 = new(&buffer) X;
buffer
// ========================
// puntero a objeto X creado con
// puntero a objeto X
// se inicia con la dirección de
...
delete ptr1;
ptr2–>X::~X();
espacio de buffer
}
// delete destruye el puntero
// llamada directa, desasignar el
§3.2 Invocación implícita de destructores
Además de las posibles invocaciones explícitas, cuando una variable sale del ámbito
para el que ha sido declarada, su destructor es invocado de forma implícita. Los
destructores de las variables locales son invocados cuando el bloque en el que han sido
declarados deja de estar activo. Por su parte, los destructores de las variables globales
son invocados como parte del procedimiento de salida ( 1.5) después de la función
main ( 4.4.4).
§3.2.1 En el siguiente ejemplo se muestra claramente como se invoca el destructor
cuando un objeto sale de ámbito al terminar el bloque en que ha sido declarado.
#include <iostream>
using namespace std;
class A {
public:
int x;
A(int i = 1) { x = i; } // constructor por defecto
~A() {
// destructor
cout << "El destructor ha sido invocado" << endl;
}
};
int main() {
//
{
A a;
//
cout << "Valor de a.x: "
}
//
destructor de a
return 0;
}
=========================
se instancia un objeto
<< a.x << endl;
punto de invocación del
Salida:
Valor de a.x: 1
El destructor ha sido invocado
§3.2.2 En el ejemplo que sigue se ha modificado ligeramente el código anterior para
demostrar como el destructor es invocado incluso cuando la salida de ámbito se realiza
mediante una sentencia de salto (omitimos la salida, que es idéntica a la anterior):
#include <iostream>
using namespace std;
class A {
public:
int x;
A(int i = 1) { x = i; } // constructor por defecto
~A() {
// destructor
cout << "El destructor ha sido invocado" << endl;
}
};
int main() {
// =========================
{
A a;
// se instancia un objeto
cout << "Valor de a.x: " << a.x << endl;
goto FIN;
}
FIN:
return 0;
}
§3.2.3 Una tercera versión, algo más sofisticada, nos muestra como la invocación del
destructor se realiza incluso cuando la salida de ámbito se realiza mediante el
mecanismo de salto del manejador de excepciones, y como la invocación se realiza
para cualquier objeto, incluso temporal, que deba ser destruido ( Ejemplo).
Recordar que que cuando los punteros a objetos salen de ámbito, no se invoca
implícitamente ningún destructor para el objeto, por lo que se hace necesaria una
invocación explícita al operador delete ( 4.9.21) para destruir el objeto (§3.1.1 ).
§4 Propiedades de los destructores
Cuando se tiene un destructor explícito, las sentencias del cuerpo se ejecutan antes
que la destrucción de los miembros. A su vez la invocación de los destructores de los
miembros se realiza exactamente en orden inverso en que se realizó la invocación de
los constructores correspondientes ( 4.11.2d1). La destrucción de los miembros
estáticos se ejecuta después que la destrucción de los miembros no estáticos.
Los destructores no pueden ser declarados const ( 3.2.1c) o volatile ( 3.2.1d),
aunque pueden ser invocados desde estos objetos. Tampoco pueden ser declarados
static ( 4.11.7), lo que supondría poder invocarlos sin la existencia de un objeto que
destruir.
§5 Destructores virtuales
Como cualquier otra función miembro, los destructores pueden ser declarados virtual
( 4.11.8a). El destructor de una clase derivada de otra cuyo destructor es virtual,
también es virtual ( 4.11.8a).
La existencia de un destructor virtual permite que un objeto de una subclase pueda ser
correctamente destruido por un puntero a su clase-base [4]. Ejemplo:
class B {
...
virtual ~B();
};
// Superclase (polimórfica)
class D : public B {
...
~D();
};
void func() {
B* ptr = new D;
subclase
delete ptr;
}
// Destructor virtual
// Subclase (deriva de B)
// destructor también virtual
// puntero a superclase asignado a objeto de
// Ok: delete es necesario siempre que se usa new
Comentario
Aquí el mecanismo de llamada de las funciones virtuales permite que el operador
delete invoque al destructor correcto, es decir, al destructor ~D de la subclase aunque
se invoque mediante el puntero ptr a la superclase B*. Si el destructor no hubiese
sido virtual no se hubiese invocado el destructor derivado ~D, sino el de la superclase
~B, dando lugar a que los miembros privativos de la subclase no hubiesen sido
desasignados. Tendríamos aquí un caso típico de "misteriosas" pérdidas de memoria,
tan frecuentes en los programas C++ como difíciles de depurar.
A pesar de todo, el mecanismo de funciones virtuales puede ser anulado utilizando un
operador de resolución adecuado ( 4.11.8a). En el ejemplo anterior podría haberse
puesto:
void func() {
D d1, d2;
foo(d1, d2);
}
void foo(B& b1, B& b2) {
// referencias a la superclase!!
b1.~B();
// invocación virtual a ~D()
b2.B::~B();
// invocacion estática a B::~B()
}
§5.1 En el siguiente ejemplo se refiere a un caso concreto de la hipótesis anterior.
Muestra como virtual afecta el orden de llamada a los destructores. Sin un destructor
virtual en la clase base, no se produciría una invocación al destructor de la clase
derivada.
#include <iostream>
class color {
// clase base
public:
virtual ~color() {
// destructor virtual
std::cout << "Destructor de color\n";
}
};
class rojo : public color { // clase derivada (hija)
public:
~rojo() {
// también destructor virtual
std::cout << "Destructor de rojo\n";
}
};
class rojobrillante : public rojo { // clase derivada
(nieta)
public:
~rojobrillante() {
// también destructor virtual
std::cout << "Destructor de rojobrillante\n";
}
};
int main() {
color *palette[3];
// matriz de tres punteros a
tipo color
palette[0] = new rojo;
// punteros a tres objetos en
algún sitio
palette[1] = new rojobrillante;
palette[2] = new color;
// llamada a los destructores de rojo y color (padre).
delete palette[0];
std::cout << std::endl;
// llamada a destructores de rojobrillante, rojo (padre) y
color (abuelo)
delete palette[1];
std::cout << std::endl;
// llamada al destructor de la clase raíz
delete palette[2];
return 0;
}
Salida del programa:
Destructor de rojo
Destructor de color
Destructor de rojobrillante
Destructor de rojo
Destructor de color
Destructor de color
Comentario
Si los destructores no se hubiesen declarado virtuales las sentencias delete
palette[0], delete palette[1], y delete palette[2] solamente hubiesen
invocado el destructor de la clase color. Lo que no hubiese destruido correctamente
los dos primeros elementos, que son del tipo rojo y rojobrillante.
§6 Los destructores y las funciones virtuales
El mecanismo de llamada de funciones virtuales está deshabilitado en los destructores
por las razones ya expuestas al tratar de los constructores ( 4.11.2d1)
§7 Los destructores y exit
Cuando se invoca exit ( &1.5.1) desde un programa, no son invocados los
destructores de ninguna variable local del ámbito actual. Las globales son destruidas
en su orden normal.
§8 Los destructores y abort
Cuando se invoca la función abort ( &1.5.1) en cualquier punto de un programa no se
invoca ningún destructor, ni aún para las variables de ámbito global.
http://www.zator.com/Cpp/E4_11_2d2.htm
Constructores
Un constructor es una función especial que sirve para construir o inicializar objetos. En
C++ la inicialización de objetos no se puede realizar en el momento en que son
declarados; sin embargo, tiene una característica muy importante y es disponer de una
función llamada constructor que permite inicializar objetos en el momento en que se
crean.
Un constructor es una función que sirve para construir un nuevo objeto y asignar valores
a
sus
miembros
dato.
Se
caracteriza
por:
Tener
el
mismo
nombre
de
la
clase
que
inicializa
- Puede definirse inline o fuera de la declaración de la clase
No
devuelve
Puede
admitir
parámetros
como
cualquier
- Puede existir más de un constructor, e incluso no existir
otra
valores
función
Si no se define ningún constructor de una clase, el compilador generará un constructor
por defecto. El constructor por defecto no tiene argumentos y simplemente sitúa ceros en
cada byte de las variables instancia de un objeto. Si se definen constructores para una
clase,
el
constructor
por
defecto
no
se
genera.
Un constructor del objeto se llama cuando se crea el objeto implícitamente: nunca se
llama explícitamente a las funciones constructoras. Esto significa que se llama cuando se
ejecuta la declaración del objeto. También, para objetos locales, el constructor se llama
cada vez que la declaración del objeto se encuentra. En objetos globales, el constructor
se
llama
cuando
se
arranca
el
programa.
El constructor por defecto es un constructor que no acepta argumentos. Se llama cuando
se
define
una
instancia
pero
no
se
especifica
un
valor
inicial.
Se pueden declarar en una clase constructores múltiples, mientras tomen parte diferentes
tipos o número de argumentos. El compilador es entonces capaz de determinar
automáticamente a qué constructor llamar en cada caso, examinando los argumentos.
Los argumentos por defecto se pueden especificar en la declaración del constructor. Los
miembros dato se inicializarán a esos valores por defecto, si ningún otro se especifica.
C++ ofrece un mecanismo alternativo para pasar valores de parámetros a miembros
dato. Este mecanismo consiste en inicializar miembros dato con parámetros.
class prueba
{
tipo1 d1;
tipo2 d2;
tipo3 d3;
public:
prueba(tipo1 p1, tipo2 p2, tipo3 p3):d1(p1),d2(p2),d3(p3)
{ }
};
Un constructor que crea un nuevo objeto a partir de uno existente se llama constructor
copiador o de copias. El constructor de copias tiene sólo un argumento: una referencia
constante a un objeto de la misma clase. Un constructor copiador de una clase complejo
es:
complejo::complejo(const complejo &fuente)
{
real=fuente.real;
imag=fuente.imag;
}
Si no se incluye un constructor de copia, el compilador creará un constructor de copia por
defecto. Este sistema funciona de un modo perfectamente satisfactorio en la mayoría de
los casos, aunque en ocasiones puede producir dificultades. El constructor de copia por
defecto inicializa cada elemento de datos del objeto a la izquierda del operador = al valor
del elmento dato equivalente del objeto de la derecha del operador =. Cuando no hay
punteros invicados, eso funciona bien. Sin embargo, cuando se utilizan punteros, el
constructor de copia por defecto inicializará el valor de un elemento puntero de la
izquierda del operador = al del elemento equivalente de la derecha del operador; es decir
que los dos punteros apuntan en la misma dirección. Si ésta no es la situación que se
desea, hay que escribir un constructor de copia.
5.14 Destructores
Un destructor es una función miembro con igual nombre que la clase, pero precedido por
el carácter ~. Una clase sólo tiene una función destructor que, no tiene argumentos y no
devuelve ningún tipo. Un destructor realiza la operación opuesta de un constructor,
limpiando el almacenamiento asignado a los objetos cuando se crean. C++ permite sólo
un destructor por clase. El compilador llama automáticamente a un destructor del objeto
cuando el objeto sale fuera del ámbito. Si un destructor no se define en una clase, se
creará
por
defecto
un
destructor
que
no
hace
nada.
Normalmente los destructores se declaran public.
5.15 Creación y supresión dinámica de objetos
Los operadores new y delete se pueden utilizar para crear y destruir objetos de una clase,
así
como
dentro
de
funciones
constructoras
y
destructoras.
Un objetro de una determinada clase se crea cuando la ejecución del programa entra en
el ámbito en que está definida y se destruye cuando se llega al final del ámbito. Esto es
válido tanto para objetos globales como locales, ya que los objetos globales se crean al
comenzar el programa y se destruyen al salir de él. Sin embargo, se puede crear un
objeto también mediante el operador new, que asigna la memoria necesaria para alojar el
objeto y devuelve su dirección, en forma de puntero, al objeto en cuestión.
Los constructores normalmente implican la aplicación de new.
p =new int(9) //p es un puntero a int inicializado a 9
cadena cad1("hola");
cadena *cad2=new cadena;
Un objeto creado con new no tiene ámbito, es decir, no se destruye automáticamente al
salir fuera del ámbito, sino que existe hasta que se destruye explícitamente mediante el
operador delete.
class cadena
{
char *datos;
public:
cadena(int);
~cadena();
};
cadena::cadena(int lon)
{
datos=new char[lon];
}
cadena::~cadena()
{
delete datos;
}
http://programarenc.webcindario.com/Cplus/capitulo5.htm
Constructores y Destructores
Constructor
Código PHP:
void __construct ( [varios args [, ...]])
PHP 5 permite a los desarrolladores (programadores) declarar metodos constructores para
las clases. Las clases que tienen un método constructor llaman a dicho método cada vez que
se crea una instancia de ellas. Por eso, es conveniente para cualquier inicialización que el
objeto pueda necesitar antes de ser usado.PHP 5 allows developers to declare constructor methods for
classes. Classes which have a constructor method call this method on each newly-created object, so it is suitable for any
initialization that the object may need before it is used.
Nota: Los constructores de las superclases no son llamados por defecto si sus subclases
definen un constructor. Se require una llamada a parent::__construct() dentro del
constructor de la subclase para que ordene la ejecucion del constructor de su clase madre.
Note: Parent constructors are not called implicitly if the child class defines a constructor.
In order to run a parent constructor, a call to parent::__construct() within the child constructor is required.
Ejemplo 19-6. Usando nuevo constructor unificado
Código PHP:
<?php
class ClaseBase {
function __construct() {
print "Constrcutor en la clase Base\n";
}
}
class SubClase extends ClaseBase {
function __construct() {
parent::__construct();
print "Constructor en SubClase\n";
}
}
$obj = new ClaseBase();
$obj = new SubClase();
?>
Por compatibilidad con versiones anteriores, si PHP5 no puede encontrar una funcion
__construct() para una clase determinada, buscara un constructor al viejo estilo, mediante
un metodo con el mismo nombre de la clase. En efecto, esto quiere decir que el unico caso
en el que habría problemas de compatibilidad es si la clase tuvo un metodo llamado
__construct () que ha sido usado con un sentido diferente.
For backwards compatibility, if PHP 5 cannot find a __construct() function for a given class, it will search for the old-style
constructor function, by the name of the class. Effectively, it means that the only case that would have compatibility issues
is if the class had a method named __construct() which was used for different semantics.
Aca necesito correccion
Destructor
Código PHP:
void __destruct ( void )
PHP 5 introduce el conpecto destructor similar al de otros lenguajes orientados a objetos,
como C++. El metodo destructor sera llamado tan pronto como todas las referencias a un
objeto en particular sean removidas o cuando el objeto es explicitamente destruido.
PHP 5 introduces a destructor concept similar to that of other object-oriented languages, such as C++. The destructor
method will be called as soon as all references to a particular object are removed or when the object is explicitly destroyed.
Ejemplo 19-7. Ejemplo de Destructor
Código PHP:
<?php
class MiClaseDestruible {
function __construct() {
print "En el constructor\n";
$this->name = "MiClaseDestruible";
}
function __destruct() {
print "Destruyendo ... " . $this->name . "\n";
}
}
$obj = new MiClaseDestruible();
?>
Asi como los constructores, los destructores de las superclases no seran llamados
implicitamente por el motor interprete. Para ordenar la ejecucion del destructor de una
superclase, uno tendria que tener en forma explicita la llamada parent::__destruct () en
el destructor de la subclase.
Like constructors, parent destructors will not be called implicitly by the engine. In order to run a parent destructor, one
would have to explicitly call parent::__destruct() in the destructor body.
http://www.forosdelweb.com/showpost.php?p=914250&postcount
=13
3.6 Declaración de métodos constructor y
destructor.
3.7 Aplicaciones de constructores y destructores.
3.8 Tipos de constructores y destructores.
Unidad 4. Sobrecarga.
4.4 Conversión de tipos.
Conversión automática de tipos
En C y C++, si el compilador encuentra una expresión o una llamada a función que usa un tipo
que no es el que requiere, puede usualmente realizar una conversión automática de tipos desde el
tipo que tiene hasta el tipo que necesita. En C++, puede conseguir este mismo efecto para los
tipos definidos por el usuario creando funciones de conversión de tipos automática. Estas
funciones se pueden ver en dos versiones:un tipo particular de constructores y un operador
sobrecargado.
6.1. Conversión por constructor
Si define un constructor que toma como su ónico argumento un objeto(o referencia) de otro tipo,
ese constructor permite al compilador realizar una conversión automática de tipos. Por ejemplo:
//: C12:AutomaticTypeConversion.cpp
// Type conversion constructor
class One {
public:
One() {}
};
class Two {
public:
Two(const One&) {}
};
void f(Two) {}
int main() {
One one;
f(one); // Wants a Two, has a One
} ///:~
Cuando el compilador ve f() llamada con un objeto One, mira en la declaración de f() y nota
que requiere un Two. Entonces busca si hay alguna manera de conseguir un Two de un One, y
encuentra el constructor Two::Two(One) al cual llama. El objeto resultante Two es pasado a f().
En este caso, la conversión automática de tipos le ha salvado del problema de definir dos
versiones sobrecargadas de f(). Sin embargo el coste es la llamada oculta al constructor de Two
lo cual puede importar si está preocupado por la eficiencia de las llamadas a f(),
6.1.1. Prevenir la conversión por constructor
Hay veces que la conversión automática de tipos via constructor puede ocasionar problemas. Para
desactivarlo, modifique el constructor anteponiéndole la palabra reservada explicit(que sólo
funciona con constructores). Así se ha hecho para modificar el constructor de la clase Two en el
ejemplo anterior:
//: C12:ExplicitKeyword.cpp
// Using the "explicit" keyword
class One {
public:
One() {}
};
class Two {
public:
explicit Two(const One&) {}
};
void f(Two) {}
int main() {
One one;
//! f(one); // No auto conversion allowed
f(Two(one)); // OK -- user performs conversion
} ///:~
Haciendo el constructor de Two explicito, se le dice al compilador que no realice ninguna
conversión automática de tipos usando ese constructor en particular(otros constructores no
explicitos en esa clase pueden todavia realizar conversiones automáticas). Si el usuario quiere
que ocurra esa conversión, debe escribir el codigo necesario. En el codigo de arriba,
f(Two(one)) crea un objeto temporal de tipo Two desde one, justo como el compilador hizo en la
versión previa.
6.2. Conversión por operador
La segunda manera de producir conversiones automáticas de tipos es a través de la sobrecarga de
operadores. Puede crear una función miembro que tome el tipo actual y lo convierta en el tipo
deseado usando la palabras reservada operator seguida del tipo al que quiere convertir. Esta
forma de sobrecarga de operadores es ónica porque parece que no se especifica un tipo de retorno
- el tipo de retorno es el nombre del operador que está sobrecargando. He aquí un ejemplo:
//: C12:OperatorOverloadingConversion.cpp
class Three {
int i;
public:
Three(int ii = 0, int = 0) : i(ii) {}
};
class Four {
int x;
public:
Four(int xx) : x(xx) {}
operator Three() const { return Three(x); }
};
void g(Three) {}
int main() {
Four four(1);
g(four);
g(1); // Calls Three(1,0)
} ///:~
Con la técnica del constructor, la clase destino realiza la conversión, pero con los operadores, la
realiza la clase origen. Lo valioso dela técnica del constructor es que puede añadir una nueva ruta
de conversión a un sistema existente mientras está creando una nueva clase. Sin embargo,
creando un constructor con un ónico argumento siempre define una conversión automática de
tipos(incluso si requiere más de un argumento si el resto de los argumentos tiene un valor por
defecto), que puede no ser lo que desea(en cuyo caso puede desactivarlo usando explicit).
Además, no hay ninguna manera de usar una conversión por constructor desde un tipo definido
por el usuario a un tipo incorporado;esto es posible solo con la sobrecarga de operadores.
6.2.1. Reflexividad
Una de las razones mas normales para usar operadores sobrecargados globales en lugar de
operadores miembros es que en la versión global, la conversión automática de tipos puede
aplicarse a cualquiera de los operandos, mientras que con objetos miembro, el operando de la
parte izquierda debe ser del tipo apropiado. Si quiere que ambos operandos sean convertidos, la
versión global puede ahorrarle un montón de código. He aquí un pequeño ejemplo:
//: C12:ReflexivityInOverloading.cpp
class Number {
int i;
public:
Number(int ii = 0) : i(ii) {}
const Number
operator+(const Number& n) const {
return Number(i + n.i);
}
friend const Number
operator-(const Number&, const Number&);
};
const Number
operator-(const Number& n1,
const Number& n2) {
return Number(n1.i - n2.i);
}
int main() {
Number a(47), b(11);
a + b; // OK
a + 1; // 2nd arg converted to
//! 1 + a; // Wrong! 1st arg not
a - b; // OK
a - 1; // 2nd arg converted to
1 - a; // 1st arg converted to
} ///:~
Number
of type Number
Number
Number
La clase Number tiene tanto un miembro operator+ como un firiend operator-. Dado que
hay un constructor que acepta un argumento int simple, un int puede ser convertido
automáticamente a un Number, pero sólo bajo las condiciones adecuadas. En main(), puede ver
que añadir un Number a otro Number funciona bien dado que tiene una correspondencia exacta
con el operador sobrecargado. Además, cuando el compilador ve un Number seguido de un + y
de un int, puede emparejarlo a la función miembro Number::operator+ y convertir el
argumentoint a un Number usando el constructor. Pero cuando ve un int, un + y un Number, no
sabe que hacer porque todo lo que tiene es Number::operator+ el cual requiere que el operando
de la izquierda sea ya un objeto Number. Así que, el compilador emite un error.
Con friend operator- las cosas son diferentes. El compilador necesita rellenar ambos
argumentos como quiera que pueda; no está restringido a tener un Number como argumento de la
parte izquierda. Asi que si ve:
1 - a
puede convertir el primer argumento a un Number usando el constructor.
A veces querrá ser capaz de restringir el uso de sus operadores haciéndolos miembros. Por
ejemplo, cuando multiplique una matriz por un vector, el vector debe ir en la derecha. Pero si
quiere que sus operadores sean capaces de convertir cualquier argumento, haga el operador una
función friend.
Afortunadamente, el compilador cogerá la expresión
1-1
y convertirá ambos argumentos a objetos Number y despues llamará a operator-. Eso
significaría que el código C existente pudiera empezar a funcionar de forma diferente. El
compilador encaja la posibilidad mas simple primero, la cual es el operador incorporado para la
expresión
1-1
.
6.3. Ejemplo de conversión de tipos
Un ejemplo en el que la conversión automática de tipos es extremadamente útil es con cualquier
clase que encapsule una cadena de caracteres(en este caso, simplemente implementaremos la
clase usando la clase estándar de C++ string dado que es simple). Sin la conversión automática de
tipos, si quiere usar todas las funciones existentes de string de la librería estándar de C, tiene que
crear una función miembro para cada una, así:
//: C12:Strings1.cpp
// No auto type conversion
#include "../require.h"
#include <cstring>
#include <cstdlib>
#include <string>
using namespace std;
class Stringc {
string s;
public:
Stringc(const string& str = "") : s(str) {}
int strcmp(const Stringc& S) const {
return ::strcmp(s.c_str(), S.s.c_str());
}
// ... etc., for every function in string.h
};
int main() {
Stringc s1("hello"), s2("there");
s1.strcmp(s2);
} ///:~
Aquí, sólo se crea la función strcmp(), pero tendría que crear las correspondientes funciones
para cada una de <cstring> que necesitará. Afortunadamente, puede proporcionar una
conversión automática de tipos permitiendo el acceso a todas las funciones de cstring.
//: C12:Strings2.cpp
// With auto type conversion
#include "../require.h"
#include <cstring>
#include <cstdlib>
#include <string>
using namespace std;
class Stringc {
string s;
public:
Stringc(const string& str = "") : s(str) {}
operator const char*() const {
return s.c_str();
}
};
int main() {
Stringc s1("hello"), s2("there");
strcmp(s1, s2); // Standard C function
strspn(s1, s2); // Any string function!
} ///:~
Ahora cualquier función que tome un argumento char* puede tomar también un argumento
Stringc porque el compilador sabe como crear un char* de un Stringc.
6.4. Las trampas de la conversión automática de tipos
Dado que el compilador debe elegir como realizar una conversión de tipos, puede meterse en
problemas si no usted no diseña las conversiones correctamente. Una situación obvia y simple
sucede cuando una clase X que puede convertirse a sí misma en una clase Y con un operator
Y(). Si la clase Y tiene un constructor que toma un argumento simple de tipo X, esto representa la
conversión de tipos por identidad. El compilador ahora tiene dos formas de ir de X a Y, asi que se
generará una error de ambigüedad cuando esa conversión ocurra:
//: C12:TypeConversionAmbiguity.cpp
class Orange; // Class declaration
class Apple {
public:
operator Orange() const; // Convert Apple to Orange
};
class Orange {
public:
Orange(Apple); // Convert Apple to Orange
};
void f(Orange) {}
int main() {
Apple a;
//! f(a); // Error: ambiguous conversion
} ///:~
La solución obvia a este problema es no hacerla. Simplemente proporcione una ruta ónica para la
conversión automática de un tipo a otro.
Un problema más difícil de eliminar sucede cuando proporciona conversiones automáticas a más
de un tipo. Esto se llama a veces acomodamiento:
//: C12:TypeConversionFanout.cpp
class Orange {};
class Pear {};
class Apple {
public:
operator Orange() const;
operator Pear() const;
};
// Overloaded eat():
void eat(Orange);
void eat(Pear);
int main() {
Apple c;
//! eat(c);
// Error: Apple -> Orange or Apple -> Pear ???
} ///:~
La clase Apple tiene conversiones automáticas a Orange y a Pear. El elemento capcioso sobre
esto es que no hay problema hasta que alguien inocentemente crea dos versiones sobrecargadas
de eat(). (Con sólo una versión el codigo en main() funciona correctamente).
De nuevo la solución - y el lema general de la conversión automática de tipos- es proveer solo
una ónica conversión automática de un tipo a otro. Puede tener conversiones a otros tipos, sólo
que no deberían ser automaticas. Puede crear llamadas a funciones explicitas con nombres como
makeA() y makeB().
6.4.1. Actividades ocultas
La conversión automática de tipos puede producir mas actividad subyacente de la que podría
esperar. Mire esta modificación de CopyingVsInitialization.cpp como un juego de
inteligencia:
//: C12:CopyingVsInitialization2.cpp
class Fi {};
class Fee {
public:
Fee(int) {}
Fee(const Fi&) {}
};
class Fo {
int i;
public:
Fo(int x = 0) : i(x) {}
operator Fee() const { return Fee(i); }
};
int main() {
Fo fo;
Fee fee = fo;
} ///:~
No hay un constructor para crear Fee fee de un objeto Fo. Sin embargo, Fo tiene una conversión
automática de tipos a Fee. No hay un constructor de copia para crear un Fee de un Fee, pero esta
es una de las funciones especiales que el compilador puede crear por usted. (El constructor por
defecto, el constructor de copia y operator=) y el destructor puede sintetizarse automáticamente
por el compilador. Asi que para la relativamente inocua expresión:
Fee fee = fo;
el operador de conversión automática es llamado, y se crea un constructor de copia.
Use la conversión automática de tipos con precaución. Como con toda la sobrecarga de
operadores, es excelente cuando reduce la tarea de codificación significativamente, pero no vale
la pena usarla de forma gratuita.
http://arco.inf-cr.uclm.es/~dvilla/pensar_en_C++/ch12s06.html
http://www.geocities.com/usmindustrial/Economica.htm
4.5 Sobrecarga de métodos.
Sobrecarga de métodos
§1 Sinopsis
Lo mismo que ocurre con las funciones normales ( 4.4.1), en la definición de
funciones-miembro puede ocurrir que varias funciones compartan el mismo nombre,
pero difieran en el tipo y/o número de argumentos. Ejemplo:
class hotel {
char * nombre;
int capacidad;
public:
void put(char*);
void put(long);
void put(int);
}
La función put, definida tres veces con el mismo nombre está sobrecargada; el
compilador sabe en cada caso que definición usar por la naturaleza de los argumentos
(puntero-a-char, long o int).
Nota: No confundir estos casos de sobrecarga en métodos con el polimorfismo;
este último caso se refiere a jerarquías de clases en las que pueden existir
diversas definiciones de métodos con los mismos argumentos, son las
denominadas funciones virtuales ( 4.11.8a).
§2 Adecuación automática de argumentos
Una característica interesante y a nuestro entender más desafortunada del lenguaje
(por la posibilidad de errores difíciles de detectar), es que en la invocación de funciones,
sean estas miembros de clase o no, el compilador intenta automáticamente adecuar el
tipo del argumento actual con el formal ( 4.4.5) es decir: adecuar el argumento
utilizado con el que espera la función según su definición.
Este comportamiento puede esquematizarse como sigue:
int funcion (int i) { /* ... */
....
otraFuncion (){
int x = funcion(5);
argumentos
int y = funcion(int('a'));
explícita
int z = funcion('a');
automática!!
...
}
}
// L.1:
// L.4: Ok. concordancia de
// L.5: Ok. promoción
// L.6: Ok. promoción
Es evidente que en L.4 y L.5 los argumentos formales y los actuales concuerdan, bien
porque se trata del mismo tipo, bien porque se ha realizado una promoción explícita (
4.9.9).
Lo que no resulta ya tan evidente, ni deseable a veces, es que en L.6 el compilador
autorice la invocación a funcion() pasando un tipo distinto del esperado. Esta
promoción del argumento actual realizada de forma automática por el compilador (sin
que medie ningún error o aviso -"Warning"- de compilación) puede ser origen de
errores, y de que el programa use inadvertidamente una función distinta de la deseada.
En este sentido podemos afirmar que el lenguaje C++ es débilmente tipado.
Posiblemente esta característica sea una de las desafortunadas herencias del C clásico
[1], y es origen incluso de ambigüedades que no deberían existir. Por ejemplo, no es
posible definir las siguientes versiones de un constructor:
class Entero {
public: int x;
Entero(float fl = 1.0) { x = int(fl); }
Entero(char c) { x = int(c); }
}
A pesar de su evidente disparidad, el compilador interpreta que existe ambigüedad en
los tipos, ya que char puede ser provomido a float.
§2.1 Para complicar la cosa
Pero la conformación de argumentos no acaba con lo expuesto. Si en la invocación de
una función f1 no existe correspondencia entre el tipo de un argumento actual x, y el
formal y, el compilador puede buscar automáticamente en el ámbito si existe una
función f2 que acepte el argumento discordante x y devuelva el tipo y deseado. En
cuyo caso realizará una invocación implícita a dicha función f2(x) y aplicará el resultado
y como argumento a la función primitiva. El mecanismo involucrado puede ser
sintetizado en el siguiente esquema:
class C {
// L.1
public: int x;
...
};
int getx (C c1, C c2) { return c1.x + c2.x; }
C fun (float f= 1.0) {
// L.6:
C ct = { f };
return ct;
}
...
unaFuncion () {
int x = 10;
C c1;
...
int z = getx(x, c1);
// L.15
...
}
// L.5
La invocación a getx en L.15 es correcta, a pesar de no existir una definición
concordante para esta función cuyos argumentos formales son dos objetos tipo C, y en
este caso el primer argumento x es un int. El mecanismo utilizado por el compilador es
el siguiente:
Dentro del ámbito de visibilidad existe una función fun (definida en L.6) que acepta un
float y devuelve un objeto del tipo necesitado por getx. Aunque el argumento x
disponible no es precisamente un float, puede ser promovido fácilmente a dicho tipo.
En consecuencia el compilador utiliza en L.15 la siguiente invocación:
int z = getx( fun( float(x) ), c1);
Este tipo de adecuaciones automáticas son realizadas por el compilador tanto con
funciones-miembro [2] como con funciones normales. Considere cuidadosamente el
ejemplo que sigue, e intente justificar el proceso seguido para obtener cada uno de sus
resultados.
#include <iostream>
using namespace std;
int x = 10;
long lg = 10.0;
class Entero {
public:
int x;
int getx (int i) { return x * i; }
int getx () { return x; }
int getx (Entero e1, Entero e2) { return e1.x + e2.x; }
Entero(float f= 1.0) { x = f; }
};
int getx (int i) { return x * i; }
int getx () { return x; }
int getx (Entero e1, Entero e2) { return e1.getx() +
e2.getx(); }
Entero fun (float f= 1.0) {
Entero e1 = { f }; return e1;
}
void main () {
// ==========================
Entero c1 = Entero(x);
cout << "E1 = " << c1.getx(0) << endl;
cout << "E2 = " << c1.getx() << endl;
cout << "E3 = " << c1.getx('a') << endl;
cout << "E4 = " << c1.getx(lg) << endl;
cout << "E5 = " << c1.getx(x, c1) << endl;
cout << "E6 = " << c1.getx('a', c1) << endl;
cout << "F1 = " << getx(0) << endl;
cout << "F2 = " << getx() << endl;
cout << "F3 = " << getx('a') << endl;
cout << "F4 = " << getx(lg) << endl;
cout << "F5 = " << getx(x, c1) << endl;
cout << "F6 = " << getx('a', c1) << endl;
}
Salida (reformateada en dos columnas):
E1
E2
E3
E4
=
=
=
=
0
10
970
100
F1
F2
F3
F4
=
=
=
=
0
10
970
100
E5 = 20
F5 = 20
E6 = 107
F6 = 107
http://www.zator.com/Cpp/E4_11_2a2.htm
4.6 Sobrecarga de operadores.
SOBRECARGA DE OPERADORES EN C++
Aunque la sobrecarga de operadores en C++ es un tema bastante básico y aparece en cualquier
libro, hay algunos tipos de sobrecarga (la de operadores globales y la del cast) que descubrí bastante
tarde y que me parecieron bastante útiles. Por este motivo escribo este tutorial, aunque
posiblemente no encuentres en él nada que no puedas encontrar en un buen libro de C++ (como por
ejemplo, "Programación Orientada a Objetos con C++", de Francisco Javier Ceballos).
Los temas que voy a tratar son:
1.
2.
3.
4.
5.
6.
Una sobrecarga normalita: el operator +
operadores globales: nuevamente el operator +
El operador de cast
Algunos detalles sobre el operator =
Los operadores new y delete
El ejemplo
Para nuestro ejemplo vamos a hacer parte de una clase ComplejoC que representa un número
complejo. Únicamente redefiniremos los operadores de suma, de cast a double y para sacarlo por
pantalla con cout .
¿Qué es la sobrecarga de operadores?
En cualquier lenguaje de programación, y C++ no es menos, se puden hacer operaciones con los tipos
básicos del lenguaje: se pueden sumar enteros, compararlos, etc. Lo siguiente es perfectamente
válido en C++
int a=3;
int b=4;
int c;
c = a + b;
Si estos tipos no son los básicos del lenguaje, no se puede hacer sumas con ellos. Por ejemplo, si
ClaseC es una clase que tengo definida en C++, el siguiente código dará error.
ClaseC a;
ClaseC b;
ClaseC c;
c = a + b;
C++ permite que hagamos estas cosas si definimos en algún sitio cómo se suman esas clases.
Definiendo cómo se hacen las operaciones, podremos escribir las operaciones con nuestras clases de
la misma forma que si se trataran de tipos básicos del lenguaje.
De hecho podemos definir cualquier operador que nos dé C++, desde algunos muy normales como
suma, resta, multiplicación, mayor qué, etc, a otros un poco más raros como el operador () o el [], de
forma que ClaseC[i] o ClaseC(i,j) pueden devolver lo que nosostros queramos.
Sobrecarga del operador suma
Si tenemos una clase ComplejoC que representa un complejo, para poder sumar dos de estas clases
simplemente poniendo un +, como con cualquier tipo básico de C++, debemos sobrecargar el operador
+, darle una nueva funcionalidad.
La sobrecarga de un operador suma se hace de la siguiente manera
class ComplejoC
{
public:
ComplejoC operator + (double sumando);
double
ComplejoC operator + (const double sumando[]);
array
ComplejoC operator + (const ComplejoC &sumando);
sí.
};
// Permite sumar un ComplejoC con un
// Permite sumar un ComplejoC con un
// Permite sumar dos ComplejoC entre
Aquí estamos redefiniendo tres operadores suma para que nos permita sumar a nuestra clase
ComplejoC cosas como un double, un array de double (que supondremos contiene dos double, aunque
no tenemos forma de comprobarlo) y otra clase ComplejoC .
En estas funciones hemos puesto const en los parámetros para no poder cambiarlos dentro del
código. En la tercera función pasamos ComplejoC por referencia (el &), para evitar que se hagan
copias innecesarias. En la primera función no es necesario nada de esto, puesto que es un simple
double. En la segunda, ponemos const para no poder modificar el array, pero no es necesaria la
referencia, puesto que los arrays son punteros.
A la hora de implementar debemos tener cuidado con los const que hemos puesto. Por ejemplo, en
el tercer operador +, recibimos un sumando que es const. El código que implementemos dentro no
puede modificar dicho sumando, ni puede llamar a ningún método de ese sumando que no esté
declarado como const. Si lo intentamos, el compilador dará error. Supongamos que ComplejoC tiene
un atributo double x (parte real) y un método dameX() para obtener dicho atributo, este método
debe estar declarado const para poder llamarlo desde nuestro operator +. Más o menos esto:
class ComplejoC
{
public:
double dameX() const;
sumando.dameX().
};
// Atención al const del final. Sin el no podemos llamar a
de forma que en el operator + podemos llamar a sumando.dameX().
Una vez implementados estos métodos, podemos hacer operaciones como
ComplejoC a;
ComplejoC b;
b = a + 1.0;
// Aprovechando la primera sobrecarga
double c[] = {1.0, 2.0};< BR > b = a + c;
b = a + b;
// Aprovechando la segunda sobrecarga
// Aprovechando la tercera sobrecarga
Cuando el compilador lee a + 1.0, lo interpreta como a.operator + (1.0), es decir, la llamada al
operador suma al que se le pasa como parámetro un double . De la misma forma sucede con los otros
dos operadores suma.
Sin embargo, esto nos da un pequeño problema. ¿Qué pasa si ponemos 1.0 + a?. Debería ser lo
mismo, pero al compilador le da un pequeño problema. Intenta llamar al método operator + de 1.0, que
no existe, puesto que 1.0 ni es una clase ni tiene métodos. Para solucionar este problema tenemos la
sobrecarga de operadores globales.
Sobrecarga de operadores suma globales
Un operador global es una función global que no pertenece a ninguna clase y que nos indica cómo
operar con uno o dos parámetros (depende del tipo de operador).
En nuestro ejemplo de las sumas, para poder poner los sumandos al revés, deberíamos definir las
siguientes funciones globales:
ComplejoC operator + (double sumando1, const ComplejoC &sumando2);
ComplejoC operator + (double sumando1[], const ComplejoC &sumando2);
Estas funciones le indican al compilador cómo debe sumar los dos parámetros y qué devuelve. Con
ellas definidas e implementadas, ya podemos hacer
b = 1.0 + a;
b = c + a;
// c era un array de double
Esta sobrecarga es especialmente útil cuando tratamos con una clase ya hecha y que no podemos
modificar. Por ejemplo, cout es de la clase ostream y no podemos modificarla, sin embargo nos sería
de utilidad sobrecargar el operador << de ostream de forma que pueda escribir nuestros números
complejos. La siguiente llamada nos dará error mientras no redefinamos el operator << de ostream .
cout << a << endl;
// a es un ComplejoC
Con la sobrecarga de operadores globales podemos definir la función
ostream &operator << (ostream &salida, const ComplejoC &valor);
Con esta función definida, el complejo se escribirá en pantalla como indique dicha función. Esta
función deberá escribir la parte real e imaginaria del complejo en algún formato, utilizando algo como
salida << valor.dameX() << " + " << valor.dameY() << "j";
El operador devuelve un ostream, que será un return cout. De esta forma se pueden encadenar las
llamadas a cout de la forma habitual.
cout << a << " + " << b << endl;
Primero se evalúa operator << (cout, a), que escribe a en pantalla y devuelve un cout, con lo que la
expresión anterior quedaría, después de evaluar esto
cout << " + " << b << endl;
y así consecutivamente.
Hay que tener en cuenta que estos operadores globales no son de la clase, así que sólo pueden
acceder a métodos y atributos públicos de la misma.
El operador cast
Un operador interesante es el operador "cast". En C++, si tenemos dos tipos básicos distintos,
podemos pasar de uno a otro haciendo un cast (siempre que sean compatibles de alguna forma). Por
ejemplo
int a;
double b;
a = (int)b;
El cast consiste en poner delante, entre paréntesis, el tipo que queramos que sea. En algunos casos,
como en el de este ejemplo, el cast se hace automáticamente y no es necesario ponerlo. Puede que de
un "warning" en el compilador avisando de que perderemos los decimales.
En principio, con las clases no se puede hacer cast a otros tipos, pero es posible declarar
operadores que lo hagan. La sintaxis sería:
class ComplejoC
{
public:
operator double ();
}
// Permite hacer un cast de ComplejoC a double
Con este podemos hacer cast de nuestra clase a un double . Es nuestro problema decidir cómo se
hace ese cast. En el código de ejemplo que hay más abajo se ha definido como la operación módulo del
número complejo.
double a;
ComplejoC b;
a = (double)b;
En el operator cast se pone operator seguido del tipo al que se quiere hacer el cast. No se pone el
tipo del valor devuelto, puesto que ya está claro. Si ponemos operator double, hay que devolver un
double .
En el operator cast no se pone parámetro, puesto que el parámetro recibido será una instancia de
la clase.
Conviene tener cuidado con definir muchos operadores cast, puesto que el compilador los tendrá
todos presentes y será capaz, encadenando unos con otros, de hacer cast entre tipos que no tienen
nada que ver. Por ejemplo, si sumamos un ComplejoC con un DibujoC (que no tienen nada que ver) y
ambos tienen cast e int, es posible que el compilador los transforme ambos en int y luego los sume
como enteros.
¿Cómo hacemos un cast al revés?. Es decir, ¿Cómo podemos convertir un double a ComplejoC?. El
asunto es sencillo, basta hacer un constructor que admite un double como parámetro.
class ComplejoC
{
public:
ComplejoC (double valor);
};
Este constructor sirve para lo que ya sabemos
ComplejoC a(3.0);
o podemos usarlo para hacer un cast de double aComplejoC
ComplejoC a;
a = (ComplejoC)3.0;
El operador igual
El operator = es como los demás. Simplemente un pequeño detalle. C++ por defecto tiene el
operador igual definido para clases del mismo tipo. Por ejemplo, sin necesidad de redefinir nada,
podemos hacer
ComplejoC a;
ComplejoC b;
a = b;
Este igual por defecto lo único que hace es copiar el contenido del uno sobre el otro, como si
fueran bytes, sin saber qué atributos está copiando ni qué significan. Para clases sencillas, que solo
tienen atributos que no son punteros, esto es más que suficiente.
Si embargo, si algún atributo es un puntero, tenemos que tener mucho cuidado con lo que hacemos.
Supongamos que ClaseC tiene un atributo Atributo que es un puntero. Supongamos también que en el
constructor de la clase, se hace new del puntero para que tenga algo y en el destructor se hace el
delete correspondiente.
ClaseC
{
public:
ClaseC () {Atributo = new int[3]; }
~ClaseC () {delete [] Atributo; }
protected:
int *Atributo;
};
// Se crea un array de tres enteros
// Se libera el array.
Si ahora hacemos esto
ClaseC *a = new ClaseC();
ClaseC *b = new ClaseC();
// a tiene ahora un array de 3 enteros en su interior
// b tiena ahora otro array de 3 enteros en su interior
*a = *b;
sitio
// Se copia el Atributo de b sobre el de a, es decir, ahora a->Atributo apunta al mismo
delete b;
// Ahora si que la hemos liado.
// que b->Atributo
Cuando hacemos a=b, con el operador igual por defecto de C++, se hace que el puntero Atributo de
a apunte al mismo sitio que el de b. El array original de a->Atributo lo hemos perdido, sigue ocupando
memoria y no tenemos ningún puntero a él para liberarlo.
Cuando hacemos delete b, el destructor de b se encarga de liberar su array. Sin embargo, al
puntero a->Atributo nadie le avisa de esta liberación, se queda apuntando a una zona de memoria que
ya no es válida. Cuando intentemos usar a->Atributo más adelante, puede pasar cualquier cosa
(cambios aleatorios de variables, caidas del programa, etc).
En el tema de punteros tienes un poco más detallado todo esto. Allí se habla de estructuras, pero
también se aplica a clases.
La forma de solucionar esto, es definiendo nosotros un operator = que haga una copia real del
array, liberando previamente el nuestro o copiando encima los datos.
ClaseC
{
public:
ClaseC &operator = (const ClaseC &original)
{
int i;
// Damos por supuesto que ambos arrays existen y son de tamaño 3
for (i=0;i<3;i++)
Atributo[i] = original.Atributo[i];
}
};
Sobrecarga del operador new y delete
Otro operador interesante de sobrecargar es el new y el delete. Si los sobrecargamos dentro de
la clase, cada vez que hagamos un new a nuestra clase se llamará a nuestro operador.
Más interesante es la sobrecarga de los operadores new y delete globales. Sobrecargando estos
operadores se llamará a nuestras funciones cada vez que hagamos un new o un delete de cualquier
cosa (clases o variables). Esta característica nos permite hacer contabilidad de punteros, para ver si
liberamos todo lo que reservamos o liberamos lo mismo más veces de la cuenta.
El código de ejemplo
Con esto, aunque no he entrado mucho en detalles, está todo más o menos dicho: Se pueden
sobrecargar operadores en una clase, operadores globales fuera de las clases y algunos operadores
interesantes/curiosos como los operadores de cast, new o delete .
En el siguiente código de ejemplo se implementa la clase ComplejoC, pero sólo los operadores de
suma indicados, algunos constructores, operador para cout y el módulo.
En Complejo.h y Complejo.cc tienes la clase. En Prueba.cc tienes el programa principal que hace
varias cosas con la clase. Para compilarlo todo, tienes el Makefile. Puedes descargar los cuatro
ficheros, quitarles la extensión .txt y compilar con make .
http://www.geocities.com/chuidiang/sobrecarga/sobrecarga.html
Sobrecarga de operadores
12.1. Sobrecarga de operadores
12.1.1. ¿ Qué es la sobrecarga de operadores ?
La sobrecarga de operadores es la capacidad para transformar los operadores de un lenguaje
como por ejemplo el +, -, etc, cuando se dice transformar se refiere a que los operandos que
entran en juego no tienen que ser los que admite el lenguaje por defecto. Mediante esta tecnica
podemos sumar dos objetos creados por nosotros o un objeto y un entero, en vez de limitarnos a
sumar numeros enteros o reales, por ejemplo.
La sobrecarga de operadores ya era posible en c++ y en otros lenguajes, pero sorprendentemente
java no lo incorpora asi que podemos decir que esta caracteristica es una ventaja de c# respesto a
java, aunque mucha gente esta posibilidad no lo considera una ventaja por que complica el
codigo.
A la hora de hablar de operadores vamos a distinguir entre dos tipos, los unarios y los binarios.
Los unarios son aquellos en que solo se requiere un operando, por ejemplo a++, en este caso el
operando es 'a' y el operador '++'. Los operadores binarios son aquellos que necesitan dos
operadores, por ejemplo a+c , ahora el operador es '+' y los operandos 'a' y 'c'. Es importante esta
distincion ya que la programacion se hara de forma diferente
Los operadores que podemos sobrecargar son los unarios, +, -, !, ~, ++, --; y los binarios +, -, *, /,
%, &, |, ^, <<, >>. Es importante decir que los operadores de comparacion, ==, !=, <, >, <=, >=,
se pueden sobrecargar pero con la condicion que siempre se sobrecargue el complementario, es
decir si sobrecargamos el == debemos sobrecargar el !=.
12.1.2. Sobrecargando operadores en la practica
Para mostrar la sobrecarga vamos a usar el repetido ejemplo de los numeros complejos, ( aunque
tambien valdria el de las coordenadas cartesianas ). Como se sabe los numeros complejos tienen
dos partes, la real y la imaginaria, cuando se suma dos numeros complejos su resultado es la
suma de las dos partes, para ello se va a crear una clase llamada ComplexNum que contendra
ambas partes. Sin esta técnica no se podria sumar dos objetos de este tipo con este practico
método, ya que esta clase no es válida como operando de los operadores de c#.
Empecemos con el código de la clase de numeros imaginarios.
public class ComplexNum{
private float _img;
private float _real;
public ComplexNum(float real, float img) {
_img = img;
_real = real;
}
public ComplexNum() {
_img = 0;
_real = 0;
}
public float getReal(){
return this._real;
}
public float getImg() {
return this._img;
}
public String toString() {
if (_img >= 0 )
return _real + "+" + _img +"i";
else
return _real + "" + _img + "i";
}
}
En el ejemplo hemos puesto la clase, con un par de constructores , dos getters para obtener los
datos privados de la clase y un metodo que nos transfoma el numero complejo a cadena para que
se pueda visualizarlo facilmente, a esta clase la iremos añadiendo métodos para que tenga
capacidad de usar operadores sobrecargados.
12.1.2.1. Operadores binarios
Para empezar vamos a sobrecargar el operador suma('+') para que al sumar dos objetos de la clase
ComplexNum, es decir dos numeros complejos obtengamos otro numero complejo que sera la
suma de ambas partes. Cabe destacar que los prototipos para sobrecargar operadores seran:
public static Operando operator+(Operando a, Operando b)
Este es el prototipo para el operador +, el resto de operadores binarios van a seguir el mismo
patron. Por tanto el código del método de sobrecarga será:
public static ComplexNum operator+(ComplexNum a, ComplexNum b) {
return new ComplexNum(a.getReal() + b.getReal(), a.getImg() +
b.getImg());
}
Este método sobrecarga el operador suma para que podamos sumar dos numeros complejos. Un
dato a tener en cuenta es que los métodos que sobrecargan operadores deben ser static. Como se
ve en el código los operandos son 'a' y 'b', que se reciben como parametro y el resultado de la
operacion es otro numero complejo que es el que retorna el método. Por tanto se limita a crear un
nuevo numero complejo con ambas partes operadas. De la misma forma podemos crear la
sobrecarga del operador resta('-') para que lleve a cabo la misma función
public static ComplexNum operator-(ComplexNum a, ComplexNum b) {
return new ComplexNum(a.getReal() - b.getReal(), a.getImg() b.getImg());
}
Como vemos el metodo es identico solo q sustituyendo los + por -. En este caso el trabajo que
hacemos dentro del metodo es trivial pero podria ser tan complejo como se quiera.
12.1.2.2. Operadores Unarios
En esta sección se vera como sobrecargar los operadores unarios, es decir aquellos que toman un
solo operando, como por ejemplo a++. El prototipo de los métodos que van a sobrecargar
operadores unarios será:
public static Operando operator++(Operando a)
Como antes sustituyendo el ++ por cualquier operador unario. El ejemplo dentro de nuestra clase
de numeros complejos sería:
public static ComplexNum operator++(ComplexNum a) {
float auximg = a.getImg();
float auxreal = a.getReal();
return new ComplexNum(++auxreal, ++auximg);
}
A primera vista puede quedar la duda si estamos sobrecargando la operacion ++a o a++. Este
aspecto se encarga el compilador de resolverlo, es decir, se sobrecarga la operacion ++ y el
compilador se encarga de sumar y asignar o asignar y sumar. Este problema no ocurria en C++,
cosa que teniamos que manejar nosotros
Como hemos dicho antes la operacion que hagamos dentro del metodo que sobrecarga el
operador es totalmente libre, se puede poder el ejemplo de multiplicar dos matrices lo que es mas
complejo que sumar dos numeros complejos
http://www.monohispano.org/tutoriales/csharp/c831.html
Unidad 5. Herencia.
5.10
Introducción a la herencia.
Introducción
Una de las propiedades más importantes de la POO es la abstracción de datos. Esta
propiedad se manifiesta esencialmente en la encapsulación, que es la responsable de
gestionar la complejidad de grandes programas al permitir la definición de nuevos tipos
de
datos:
las
clases.
Sin embargo, la clase no es suficiente por sí sola para soportar diseño y programación
orientada a objetos. Se necesita un medio para relacionar unas clases con otras. Un
medio son las clases anidadas, pero sólo se resuelve parcialmente el problema, ya que
las clases anidadas no tienen las características requeridas para relacionar totalmente
una clase con otra. Se necesita un mecanismo para crear jerarquías de clases en las que
la clase A sea afín a la clase B, pero con la posibilidad de poder añadirle también
características
propias.
Este
mecanismo
es
la
herencia.
La herencia es, seguramente, la característica más potente de la POO, después de las
clases. Por herencia conocemos el proceso de crear nuevas clases, llamadas clases
derivadas,
a
partir
de
una
clase
base.
En C++ la herencia se manifiesta con la creación de un tipo definido por el usuario
(clase), que puede heredar las características de otra clase ya existente o derivar las
suyas a otra nueva clase. Cuando se hereda, las clases derivadas reciben las
características (estructuras de datos y funciones) de la clase original, a las que se pueden
añadir nuevas características o modificar las características heredadas. El compilador
hace realmente una copia de la clase base en la nueva clase derivada y permite al
programador añadir o modificar miembros sin alterar el código de la clase base.
La derivación de clases consigue la reutilización efectiva del código de la clase base para
sus necesidades. Si se tiene una clase base totalmente depurada, la herencia ayuda a
reutilizar ese código en una nueva clase. No es necesario comprender el código fuente
de la clase original, sino sólo lo que hace.
7.2 Clases derivadas
En C++, la herencia simple se realiza tomando una clase existente y derivando nuevas
clases de ella. La clase derivada hereda las estructuras de datos y funciones de la clase
original. Además, se pueden añadir nuevos miembros a las clases derivadas y los
miembros
heredados
pueden
ser
modificados.
Una clase utilizada para derivar nuevas clases se denomina clase base, clase padre,
superclase o ascendiente. Una clase creada de otra clase se denomina clase derivada o
subclase.
Se pueden construir jerarquías de clases, en las que cada clase sirve como padre o raíz
de una nueva clase.
7.3 Conceptos fundamentales de derivación
C++ utiliza un sistema de herencia jerárquica. Es decir, se hereda una clase de otra,
creando nuevas clases a partir de las clases ya existentes. Sólo se pueden heredar
clases,
no
funciones
ordinarias
n
variables,
en
C++.
Una clase derivada hereda todos los miembros dato excepto, miembros dato estáticos,
de
cada
una
de
sus
clases
base.
Una clase derivada hereda las funciones miembro de su clase base. Esto significa que se
hereda la capacidad para llamar a funciones miembro de la clase base en los objetos de
la clase derivada.
http://programarenc.webcindario.com/Cplus/capitulo7.htm
.Los siguientes elementos de la clase no se heredan:
- Constructores
- Destructores
- Funciones amigas
- Funciones estáticas de la clase
- Datos estáticos de la clase
- Operador de asignación sobrecargado
Las clases base diseñadas con el objetivo principal de ser heredadas por otras se
denominan clases abstractas. Normalmente, no se crean instancias a partir de clases
abstractas, aunque sea posible.
7.4 La herencia en C++
En C++ existen dos tipos de herencia: simple y múltiple. La herencia simple es aquella en
la que cada clase derivada hereda de una única clase. En herencia simple, cada clase
tiene un solo ascendiente. Cada clase puede tener, sin embargo, muchos descendientes.
La herencia múltiple es aquella en la cual una clase derivada tiene más de una clase
base. Aunque el concepto de herencia múltiple es muy útil, el diseño de clases suele ser
más complejo.
7.5 Creación de una clase derivada
Cada clase derivada se debe referir a una clase base declarada anteriormente.
La
declaración
de
una
clase
derivada
tiene
la
siguiente
sintaxis:
class clase_derivada:<especificadores_de_acceso> clase_base
{...};
Los especificadores de acceso pueden ser: public, protected o private.
7.6 Clases de derivación
Los especificadores de acceso a las clases base definen los posibles tipos de derivación:
public, protected y private. El tipo de acceso a la clase base especifica cómo recibirá la
clase derivada a los miembros de la clase base. Si no se especifica un acceso a la clase
base, C++ supone que su tipo de herencia es privado.
- Derivación pública (public). Todos los miembros public y protected de la clase base son
accesibles en la clase derivada, mientras que los miembros private de la clase base son
siempre inaccesibles en la clase derivada.
- Derivación privada (private). Todos los miembros de la clase base se comportan como
miembros privados de la clase derivada. Esto significa que los miembros public y
protected de la clase base no son accesibles más que por las funciones miembro de la
clase derivada. Los miembros privados de la clase siguen siendo inaccesibles desde la
clase derivada.
- Derivación protegida (protected). Todos los miembros public y protected de la clase
base se comportan como miembros protected de la clase derivada. Estos miembros no
son, pues, accesibles al programa exterior, pero las clases que se deriven a continuación
podrán acceder normalmente a estos miembros (datos o funciones).
7.7 Constructores y destructores en herencia
Una clase derivada puede tener tanto constructores como destructores, aunque tiene el
problema adicional de que la clase base puede tomar ambas funciones miembro
especiales.
Un constructor o destructor definido en la clase base debe estar coordinado con los
encontrados en una clase derivada. Igualmente importante es el movimiento de valores
de los miembros de la clase derivada a los miembros que se encuentran en la base. En
particular, se debe considerar cómo el constructor de la clase base recibe valores de la
clase
derivada
para
crear
el
objeto
completo.
Si un constructor se define tanto para la clase base como para la clase derivada, C++
llama primero al constructor base. Después que el constructor de la base termina sus
tareas,
C++
ejecuta
el
constructor
derivado.
Cuando una clase base define un constructor, éste se debe llamar durante la creación de
cada instancia de una clase derivada, para garantizar la buena inicialización de los datos
miembro que la clase derivada hereda de la clase base. En este caso la clase derivada
debe definir a su vez un constructor que llama al constructor de la clase base
proporcionándole
los
argumentos
requeridos.
Un constructor de una clase derivada debe utilizar un mecanismo de pasar aquellos
argumentos requeridos por el correspondiente constructor de la clase base.
derivada::derivada(tipo1
x,
tipo2
y):base
(x,y)
{...}
Otro aspecto importante que es necesario considerar es el orden en el que el compilador
C++ inicializa las clases base de una clase derivada. Cuando El compilador C++ inicializa
una instancia de una clase derivada, tiene que inicializar todas las clases base primero.
Por definición, un destructor de clases no toma parámetros. Al contrario que los
constructores, una función destructor de una clase derivada se ejecuta antes que el
destructor de la clase base.
7.8 Redefinición de funciones miembro heredadas
Se pueden utilizar funciones miembro en una clase derivada que tengan el mismo
nombre
que
otra
de
una
clase
base.
La redefinición de funciones se realiza mediante funciones miembro sobrecargadas en la
clase derivada. Una función miembro redefinida oculta todas las funciones miembro
heredadas del mismo nombre. Por tanto cuando en la clase base y en la clase derivada
hay una función con el mismo nombre en las dos, se ejecuta la función de la clase
derivada.
7.9 Herencia múltiple
Es la propiedad con la cual una clase derivada puede tener más de una clase base. Es
más adecuada para definir objetos que son compuestos, por naturaleza, tales como un
registro
personal,
un
objeto
gráfico.
Sólo es una extensión de la sintaxis de la clase derivada. Introduce una cierta
complejidad en el lenguaje y el compilador, pero proporciona grandes beneficios.
class
ejemplo:
private
base1,
private
base2
{...};
La aplicación de clases base múltiples introduce un conjunto de ambigüedades a los
programas C++. Una de las más comunes se da cuando dos clases base tienen
funciones con el mismo nombre, y sin embargo, una clase derivada de ambas no tiene
ninguna función con ese nombre. ¿ Cómo acceden los objetos de la clase derivada a la
función correcta de la clase base ? El nombre de la función no es suficiente, ya que el
compilador no puede deducir cuál de las dos funciones es la invocada.
Si se tiene una clase C que se deriva de dos clases base A y B, ambas con una función
mostrar() y se define un objeto de la clase C: C objetoC, la manera de resolver la
ambigüedad es utilizando el operador de resolución de ámbito:
objetoC.A::mostrar();
objetoC.B::mostrar();
//se refiere a la versión de //mostrar() de la clase A
//se refiere a la versión de //mostrar() de la clase B
7.10 Constructores y destructores en herencia múltiple
El uso de funciones constructor y destructor en clases derivadas es más complejo que en
una clase simple. Al crear clases derivadas con una sola clase base, se observa que el
constructor de la clase base se llama siempre antes que el constructor de la clase
derivada.
Además se dispone de un mecanismo para pasar los valores de los parámetros
necesarios al constructor base desde el constructor de la clase derivada. Así, la sentencia
siguiente pasa el puntero a carácter x y el valor del entero y a la clase base:
derivada (char *x, int y, double z): base(x,y)
Este método se expande para efectuar más de una clase base.La sentencia:
derivada (char *x,int y, double z): base1(x),base2(y)
Llama al constructor base1 y pasa el valor de cadena de caracteres x y al constructor
base2 le proporciona un valor entero y. Como con la herencia simple, los constructores
de la clase base se llaman antes de que se ejecuten al constructor de la clase derivada.
Los constructores se llaman en el orden en que se declaran las clases base.
De modo similar, las relaciones entre el destructor de la clase derivada y el destructor de
la clase base se mantiene. El destructor derivado se llama antes de que se llame a los
destructores de cualquier base. Los destructores de las clases base se llaman en orden
inverso al orden en que los objetos base se crearon.
7.11 Herencia repetida
La primera regla de la herencia múltiple es que una clase derivada no puede heredar más
de una vez de una sola clase, al menos no directamente. Sin embargo, es posible que
una clase se pueda derivar dos o más veces por caminos distintos, de modo que se
puede reprtir una clase. La propiedad de recibir por herencia una misma clase más de
una vez se conoce como herencia repetida.
7.12 Clases base virtuales
Una clase base virtual es una clase que es compartida por otras clases base con
independencia del número de veces que esta clase se produce en la jerarquía de
derivación. Suponer , por ejemplo, que la clase T se deriva de las clases C y D cada una
de las cuales se deriva de la clase A. Esto significa que la clase T tiene dos instancias de
A en su jerarquía de derivación. C++ permite instancias múltiples de la misma clase base.
Sin embargo, si sólo se desea una instancia de la clase A para la clase T, entonces se
debe
declarar
A
como
una
clase
base
virtual.
Las clases base virtuales proporcionan un método de anular el mecanismo de herencia
múltiple, permitiendo especificar una clase que es una clase base compartida.
Una clase derivada puede tener instancias virtuales y no virtuales de la misma clase
base.
INTRODUCCIÓN A LA HERENCIA
La herencia es uno de los mecanismos de la programación orientada a objetos, por medio de la
cual una clase se deriva de otra de manera que extiende su funcionalidad. Una de sus funciones
más importantes es la de proveer polimorfismo y late binding.
Tipos de herencia


Herencia sencilla: Un objeto puede extender las características de otro
objeto y de ningún otro, es decir, solo puede tener un padre.
Herencia múltiple: Un objeto puede extender las características de uno
o más objetos, es decir, puede tener varios padres. En este aspecto
hay discrepancias entre los diseñadores de lenguajes. Algunos de ellos
han preferido no admitir la herencia múltiple por las posibles
coincidencias en nombres de métodos o datos miembros. Por ejemplo
C++ admite herencia múltiple, Java y Ada sólo herencia simple.
Obtenido de
"http://es.wikipedia.org/wiki/Herencia_%28programaci%C3%B3n_orientada
_a_objetos%29"
Herencia en PHP
Hablaremos de esta peculiar característica para hacer copias independientes y
personalizadas de clases ya construidas, propia de la programación orientada a
objetos.
La programación orientada a objetos tiene un mecanismo llamado herencia por el que se
pueden definir clases a partir de otras clases. Las clases realizadas a partir de otra clase o
mejor dicho, que extienden a otra clase, se llaman clases extendidas o clases derivadas.
Las clases extendidas heredan todos los atributos y métodos de la clase base. Además,
pueden tener tantos atributos y métodos nuevos como se desee.
Para ampliar el ejemplo que venimos desarrollando, la clase Caja, vamos a crear una clase
extendida llamada Caja_tematica. Esta clase hereda de caja, pero además tiene un "tema",
que es la descripción del tipo de cosas que metemos en la caja. Con esto podemos tener
varias cajas, cada una con cosas de un tema concreto.
class Caja_tematica extends Caja{
var $tema;
}
function define_tema($nuevo_tema){
$this->tema = $nuevo_tema;
}
En esta clase heredamos de Caja, con lo que tenemos a nuestra disposición todos los
atributos y métodos de la clase base. Además, se ha definido un nuevo atributo, llamado
$tema, y un método, llamado define_tema(), que recibe el tema con el que se desea
etiquetar la caja.
Podríamos utilizar la clase Caja_tematica de manera similar a como lo hacíamos con la clase
Caja original.
$micaja_tematica = new Caja_tematica();
$micaja_tematica->define_tema("Cables y contectores");
$micaja_tematica->introduce("Cable de red");
$micaja_tematica->introduce("Conector RJ45");
$micaja_tematica->muestra_contenido();
En este caso, el resultado que se obtiene es parecido al que se obtiene para la clase base.
Sin embargo, cuando se muestra el contenido de una caja, lo más interesante sería que se
indicara también el tipo de objetos que contiene la caja temática. Para ello, tenemos que
redefinir el método muestra_contenido().
Redefinir métodos en clases extendidas
Redefinir métodos significa volver a codificarlos, es decir, volver a escribir su código para la
clase extendida. En este caso, tenemos que redefinir el método muestra_contenido() para
que muestre también el tema de la caja.
Para redefinir un método, lo único que debemos hacer es volverlo a escribir dentro de la
clase extendida.
function muestra_contenido(){
echo "Contenido de la caja de <b>" . $this->tema . "</b>: " . $this->contenido;
}
En este ejemplo hemos codificado de nuevo el método entero para mostrar los datos
completos.
En algunas ocasiones es muy útil apoyarse en la definición de un método de la clase base
para realizar las acciones de la clase extendida. Por ejemplo, para este ejemplo, tenemos
que definir un constructor para la clase Caja_tematica, en el que también se inicialice el
tema de la caja. Como ya existe un método constructor en la clase base, no merece la pena
reescribir el código de éste, lo mejor es llamar al constructor que había definido en la clase
Caja original, con lo que se inicializarán todos los datos de la clase base, y luego realizar la
inicialización para los atributos de la propia clase extendida.
Para llamar a un método de la clase padre dentro del código de un método que estamos
redefiniendo, utilizamos una sintaxis como esta:
function Caja_tematica($alto=1,$ancho=1,$largo=1,$color="negro",$tema="Sin clasificación"){
parent::Caja($alto,$ancho,$largo,$color);
$this->tema=$tema;
}
Aquí vemos la redefinición del constructor, de la clase Caja, para la clase Caja_tematica. El
constructor hace primero una llamada al constructor de la clase base, a través de una
referencia a "parent". Luego inicializa el valor del atributo $tema, que es específico de la
Caja_tematica.
En la misma línea de trabajo, podemos redefinir el método muestra_contenido()
apoyándonos en el que fue declarado en la clase base. El código quedaría como sigue:
function muestra_contenido(){
echo "Contenido de la caja de <b>" . $this->tema . "</b>: ";
parent::muestra_contenido();
}
HERENCIA
La herencia en C++
En C++ existen dos tipos de herencia: simple y múltiple. La herencia simple es aquella en la que cada clase derivada hereda de una
única clase. En herencia simple, cada clase tiene un solo ascendiente. Cada clase puede tener, sin embargo, muchos descendientes.
La herencia múltiple es aquella en la cual una clase derivada tiene más de una clase base. Aunque el concepto de herencia múltiple
es muy útil, el diseño de clases suele ser más complejo.
http://html.rincondelvago.com/clases-derivadas.html
LA HERENCIA
A. Introducción
La verdadera potencia de la programación orientada a objetos radica en su capacidad para reflejar
la abstracción que el cerebro humano realiza automáticamente durante el proceso de aprendizaje
y el proceso de análisis de información.
Las personas percibimos la realidad como un conjunto de objetos interrelacionados. Dichas
interrelaciones, pueden verse como un conjunto de abstracciones y generalizaciones que se han
ido asimilando desde la niñez. Así, los defensores de la programación orientada a objetos afirman
que esta técnica se adecua mejor al funcionamiento del cerebro humano, al permitir descomponer
un problema de cierta magnitud en un conjunto de problemas menores subordinados del primero.
La capacidad de descomponer un problema o concepto en un conjunto de objetos relacionados
entre sí, y cuyo comportamiento es fácilmente identificable, puede ser muy útil para el desarrollo
de programas informáticos.
B. Jerarquía
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.
D. 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
}
}
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á 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?).
http://pisuerga.inf.ubu.es/lsi/Invest/Java/Tuto/II_6.htm

Herencia
La herencia es un mecanismo que permite la definición de una clase a partir de la
definición de otra ya existente. La herencia permite compartir automáticamente
métodos y datos entre clases, subclases y objetos.
La herencia está fuertemente ligada a la reutilización del código en la OOP. Esto
es, el código de cualquiera de las clases puede ser utilizado sin más que crear
una clase derivada de ella, o bien una subclase.
Hay dos tipos de herencia: Herencia Simple y Herencia Múltiple. La primera indica
que se pueden definir nuevas clases solamente a partir de una clase inicial
mientras que la segunda indica que se pueden definir nuevas clases a partir de
dos o más clases iniciales. Java sólo permite herencia simple.
http://www.fib.unam.mx/pp/profesores/carlos/java/java_basico3_4.html
http://www.dcp.com.ar/poo/poop2.htm
5.11
Herencia simple.
Existen dos tipos de Herencia:
Simple: Una clase se deriva de sólo una clase base.
Múltiple: Una clase se deriva de más de una clase base.
Nuestro ejemplo será de herencia simple.
Ante todo una pregunta: ¿qué representa nuestra clase CEmpleado?.
Bueno, la definimos con la intensión que represente un empleado cualquiera. Empleados existen
muchos, sea cual sea la empresa en la que trabajan, todos tienen actividades específicas, por
ejemplo un obrero, un jefe de sector, un gerente, etc. CEmpleado es una clase, (por como la
diseñamos, aunque sea muy simple), que no hace diferencias entre los empleados, es, digamos,
una clase general.
Ahora bien, por lo general, un gerente, (que como dijimos, es un empleado), tiene atributos
particulares que lo hace diferente a otro empleado, por ejemplo tiene a su cargo un departamento,
tiene una secretaria a su disposición, etc. Por lo tanto tenemos un pequeño problema con nuestra
clase CEmpleado para poder representar un gerente, puesto que se limita solamente al Apellido y
el Salario, nos faltarían datos miembros para el departamento que tiene a su cargo y la secretaria.
Podríamos definir una nueva clase CGerente con los datos miembros Apellido, Salario, Dpto,
Secretaria y las funciones miembros ObtenerApellido(), ObtenerSalario(), ObtenerDpto() y
ObtenerSecretaria(). Sí y no estaría mal, pero sería redundante, pues podríamos derivar la nueva
clase CGerente de CEmpleado, puesto que los datos y funciones miembros nos resultan útiles.
Derivemos, entonces, la clase CGerente de CEmpleado:
#include <iostream.h>
#include <string.h>
//Definición de la clase CEmpleado
//***************************************
class CEmpleado
{
protected:
char ape[20];
double sueldo;
public:
CEmpleado()
{
strcpy(ape, "");
sueldo=0;
}
CEmpleado(char ap[20], double s)
{
strcpy(ape, ap);
sueldo=s;
}
char* ObtenerApellido();
double ObtenerSueldo();
};
//funciones miembros de CEmpleado
char* CEmpleado::ObtenerApellido ()
{
return ape;
}
double CEmpleado::ObtenerSueldo ()
{
return sueldo;
}
//***********************************************
//Definición de la clase CGerente heredada de CEmpleado
//********************************************************************
class CGerente:public CEmpleado (1)
{
char dpto[20];
char secretaria[20];
public:
CGerente(char n[20], double s, char d[20], char sec[20])
{
strcpy(ape, n);
sueldo=s;
strcpy(dpto,d);
strcpy(secretaria, sec);
}
char* ObtenerSecretaria();
char* ObtenerDpto();
};
//Funciones miembros de CGerente
char* CGerente::ObtenerSecretaria()
{
return secretaria;
}
char* CGerente::ObtenerDpto ()
{
return dpto;
}
Justamente en (1) es donde tiene lugar la derivación de CGerente de CEmpleado:
class CGerente:public CEmpleado
La definición de CGerente con :public CEmpleado indica que CGerente heredará de CEmpleado,
de forma pública, todos los datos y funciones miembros.
El operador public en esta sentencia permite definir la accesibilidad de los datos miembros
derivados.
Una clase hereda de otra todos los datos y funciones miembros que no sean privados.
Lo que sigue es una tabla que permite reconocer la accesibilidad en clases derivadas:
si el modo de
derivación es:
private
y el miembro en la clase
base es:
se obtiene un
miembro:
private
inaccesible
protected
private
public
private
protected
public
private
inaccesible
protected
protected
public
protected
private
inaccesible
protected
protected
public
public
Como reglas de esta tabla se extrae:
1) Los datos miembros privados no son derivables sea cual sea el modo de derivación.
2) Derivando en modo privado se obtienen miembros privados.
3) Derivando en modo protegido se obtienen miembros protegidos.
4) Derivando en modo público se respetan las características de los miembros de la clase base.
En la función main se podría escribir, para probar la funcionalidad de nuestra nueva clase
derivada, lo siguiente:
void main(void)
{
CGerente g("Perez", 2500.60, "Sistemas", "Juana");
cout << g.ObtenerApellido()<<endl;
cout << g.ObtenerSueldo()<<endl;
cout << g.ObtenerDpto()<<endl;
cout << g.ObtenerSecretaria()<<endl;
}
Creamos una instancia de CGerente y probamos los métodos de la clase.
Hay que destacar que cuando definimos la clase CGerente no escribimos nada con respecto a los
atributos Apellido y Salario y los métodos ObtenerApellido() y ObtenerSalario(), y sin embargo
los podemos usar. Esto es la herencia. La posibilidad de poder reutilizar código.
En la clase derivada sólo hay que definir los datos y funciones miembros exclusivos de esa clase,
(en nuestro ejemplo el nombre del Dpto y la secretaria y las funciones que permiten obtener estos
valores).
5.12
Herencia múltiple.
La herencia multiple Una de las oportunidades que nos ofrece el lenguaje c++ es la posibilidad de
que un objeto tenga la herencia de mas de una clase; esta ventaja fue considerada por los
desarrolladores de Java como una pega y la quitaron, e incluso hay desarrolladores de c++ que
prefieren evitar este tipo de herencia ya que puede complicar mucho la depuracion de programas
Para ilustrar un caso de herencia multiple hemos definido la superclase Habitante; de ella heredan
dos clases distintas: Humano (que hablan) y Animal (que matan). Ahora queremos definir un ente
que tiene propiedades de esas dos clases: Militar, ya que el militar habla y ademas mata. Como
podemos definirlo? con una herencia multiple. Vamos la definicion de la superclase o clase padre
Habitante Notas de la logia POO Conviene definir todos los metodos de un clase como const
siempre que en el metodo no se modifiquen los atributos. Tu resistencia es inutil. unete a nosotros
o muere. Definir metodos como const le facilitara el trabajo al compilador y al programador.
Nota el codigo necesita revision y testeo
/**
* Habitante.hpp
* Clase que define el objeto habitante
*
* Pello Xabier Altadill Izura
*
*/
using namespace std;
#include <iostream>
class Habitante {
private:
char *nombre;
int edad;
public:
Habitante();
virtual ~Habitante();
Habitante(const Habitante &);
virtual void dormir();
// setter/getter o accessors
virtual char *getNombre() const { return this->nombre;}
// inline
virtual void setNombre(char *nombre) { this->nombre = nombre; } //
inline
virtual int getEdad() const { return this->edad;} // inline
virtual void setEdad(int edad) { this->edad = edad; } // inline
};
Y su implementacion
/**
* Habitante.cpp
* Programa que implementa la clase habitante
*
* Pello Xabier Altadill Izura
* Compilacion: g++ -c Habitante.cpp
*
*/
#include "Habitante.hpp"
// Constructor
Habitante::Habitante() {
cout << "-clase habitante- Habitante construido."<< endl;
}
// Destructor
Habitante::~Habitante() {
cout << "-clase habitante- Habitante "<< this->getNombre() << "
destruido."<< endl;
}
// constructor copia
Habitante::Habitante(const Habitante & original) {
nombre = new char;
original.getNombre();
}
// metodo dormir
void Habitante::dormir() {
cout << "-clase habitante- zzzzzZZZZzzzzz zzz" << endl;
}
Humano La clase Humano, que hereda de Habitante
/**
* Humano.hpp
* Clase que define el objeto humano
*
* Pello Xabier Altadill Izura
*
*/
#include "Habitante.hpp"
// hereda atributos y metodos de la superclase Habitante
class Humano : public Habitante {
private:
char *idioma;
public:
Humano();
virtual ~Humano();
Humano(const Humano &);
virtual void hablar(char *bla) const;
// setter/getter o accessors
virtual char *getIdioma() const { return this->idioma;} // inline
virtual void setIdioma(char *idioma) { this->idioma = idioma; } //
inline
};
Y su implementacion
/**
* Humano.cpp
* Fichero que implementa el objeto humano
*
* Pello Xabier Altadill Izura
*
*/
#include "Habitante.hpp"
// Constructor
Humano::Humano() {
cout << "-clase Humano- Humano construido."<< endl;
}
// Destructor
Humano::~Humano() {
cout << "-clase Humano- Humano "<< this->getNombre() << "
destruido."<< endl;
}
// constructor copia
Humano::Humano(const Humano & original) {
idioma = new char;
idioma = original.getIdioma();
}
// metodo hablar
void Humano::hablar(char *bla) const {
cout << "-clase Humano-" << this->getNombre() << " dice: " << bla <<
endl;
}
Animal La clase Animal, que hereda de Habitante
/**
* Animal.hpp
* Clase que define el objeto Animal
*
* Pello Xabier Altadill Izura
*
*/
#include "Habitante.hpp"
// hereda atributos y metodos de la superclase Habitante
class Animal : public Habitante {
private:
int patas;
public:
Animal();
virtual ~Animal();
Animal(const Animal &);
virtual void matar() const;
// setter/getter o accessors
virtual int getPatas() const { return this->patas;} // inline
virtual void setPatas(int patas) { this->patas = patas; } // inline
};
Y su implementacion
/**
* Animal.cpp
* Programa que implementa la clase Animal
*
* Pello Xabier Altadill Izura
* Compilacion: g++ -c Animal.cpp
*
*/
#include "Animal.hpp"
// Constructor
Animal::Animal() {
cout << "-clase Animal- Animal construido."<< endl;
}
// Destructor
Animal::~Animal() {
cout << "-clase Animal- Animal "<< this->getNombre() << "
destruido."<< endl;
}
// constructor copia
Animal::Animal(const Animal & original) {}
// metodo matar
void Animal::matar() const {
cout << "-clase Animal-" << this->getNombre() << " Matar! Matar!
Matar! " << endl;
}
La herencia multiple! Aqui esta la clase Militar, que hereda de Humano y Animal.
/**
* Militar.hpp
* Clase que define el objeto Militar
*
* Pello Xabier Altadill Izura
*
*/
// Herencia multiple de Humano y Animal
class Militar : public Animal { //, public Humano {
private:
char *rango;
public:
Militar();
~Militar();
Militar(const Militar &);
// sobrescribe metodos
void matar() const;
void hablar(char *bla) const;
// un metodo poco probable entre cualquier uniformado...
void razonar() const;
// setter/getter o accessors
char *getRango() const { return this->rango;}
void setRango(char *rango) { this->rango = rango;}
};
Y su implementacion
/**
* Militar.cpp
* Programa que implementa la clase Militar
*
* Pello Xabier Altadill Izura
* Compilacion: g++ -c Habitante.cpp
* g++ -c Humano.cpp
* g++ -c Animal.cpp
* g++ Militar.cpp Habitante.o Humano.o Animal.o -o Militar
*/
#include "Militar.hpp"
// Constructor
Militar::Militar() {
cout << "-clase Militar- Militar construido."<< endl;
}
// Destructor
Militar::~Militar() {
cout << "-clase Militar- Militar "<< this->getNombre() << "
destruido."<< endl;
}
// constructor copia
Militar::Militar(const Militar & original) {
cout << "-clase Militar- Militar copia creada."<< endl;
}
// metodo razonar
void Militar::razonar() const {
cout << "-clase Militar-" << this->getNombre() << " Error: OVERFLOW "
<< endl;
}
// metodo hablar
void Militar::hablar(char *bla) const {
cout << "-clase Militar-" << this->getRango() << " " << this>getNombre() << " dice: ";
cout << bla << endl;
}
// metodo matar
void Militar::matar() const {
cout << "-clase Militar-" << this->getRango() << " Matar! " << endl;
cout << "-clase Militar- Somos... agresores por la paz " << endl;
}
// Aqui haremos multiples pruebas...
int main () {
return 0;
}
http://es.tldp.org/Manuales-LuCAS/doc-tutorialc++/html/c295.html
Declaración por heréncia múltiple
§1 Sinopsis
La tercera forma de crear una nueva clase es por herencia múltiple (también llamada
agregación o composición [1] ). Consiste en el ensamblando una nueva clase con los
elementos de otras clases-base. C++ permite crear clases derivadas que heredan los
miembros de una o más clases antecesoras. Es clásico señalar el ejemplo de un
coche, que tiene un motor; cuatro ruedas; cuatro amortiguadores, etc. Elementos estos
pertenecientes a la clase de los motores, de las ruedas, los amortiguadores, etc.
Como en el caso de la herencia simple, aparte de los miembros heredados de cada
clase antecesora, la nueva clase también puede tener miembros privativos ( 4.11.2b)
§2 Sintaxis
Cuando se declara una clase D derivada de varias clases base: B1, B2, ... se utiliza una
lista de las bases directas ( 4.11.2b) separadas por comas. La sintaxis general es:
class-key <info> nomb-clase <: lista-base> { <lista-miembros>
};
El significado de cada miembro se indicó al tratar de la declaración de una clase (
4.11.2). En este caso, la declaración de D seria:
class-key <info> D : <B1, B2, ...> { <lista-miembros> };
D hereda todos los miembros de las clases antecesoras B1, B2, etc, y solo puede
utilizar los miembros que derivan de públicos y protegidos en dichas clases. Resulta
así que un objeto de la clase derivada contiene sub-objetos de cada una de las clases
antecesoras.
§2.1 Restricciones
Tenga en cuenta que las clases antecesoras no pueden
repetirse, es decir:
class B { ... };
class D : B, B, ... { ... };
Ilegal!
//
Aunque la clase antecesora no puede ser base directa
más que una vez, si puede repetirse como base indirecta.
Es la situación recogida en el siguiente ejemplo cuyo
esquema se muestra en la figura 1:
class
class
class
class
B { ... };
C1 : public B { ... };
C2 : public B { ... };
D : public C1, C2 { ... };
Aquí la clase D tiene miembros heredados de sus
antecesoras D1 y D2, y por consiguiente, dos sub-objetos
de la base indirecta B.
Fig. 1
El mecanismo sucintamente descrito, constituye lo que se
denomina herencia múltiple ordinaria (o simplemente
herencia). Como se ha visto, tiene el inconveniente de
que si las clases antecesoras contienen elementos
comunes, estos se ven duplicados en los objetos de la
subclase. Para evitar estos problemas, existe una
variante de la misma, la herencia virtual ( 4.11.2c1),
en la que cada objeto de la clase derivada no contiene
todos los objetos de las clases-base si estos están
duplicados.
Fig. 2
Las dependencias derivadas de la herencia múltiple suele ser expresada también
mediante un grafo denominado DAG ("Direct acyclic graph"), que tiene la ventaja de
mostrar claramente las dependencias en casos de composiciones complicadas. La
figura 2 muestra el DAG correspondiente al ejemplo anterior. En estos grafos las
flechas indican el sentido de la herencia, de forma que A --> B indica que A deriva
directamente de B. En nuestro caso se muestra como la clase D contiene dos subobjetos de la superclase B.
Nota: La herencia múltiple es uno de los puntos peliagudos del lenguaje C++ (y de
otros que también implementan este tipo de herencia). Hasta el extremo que
algunos teóricos consideran que esta característica debe evitarse, ya que además
de las teóricas, presenta también una gran dificultad técnica para su
implementación en los compiladores. Por ejemplo, surge la cuestión: Si dos
clases A y B conforman la composición de una subclase D, y ambas tienen
propiedades con el mismo nombre, ¿Que debe resultar en la subclase D?
Miembros duplicados, o un miembro que sean la agregación de las propiedades de
A y B?. Como veremos a continuación, el creador del C++ optó por un diseño que
despeja cualquier posible ambigüedad, aunque ciertamente deriva en una serie de
reglas y condiciones bastante intrincadas.
§3 Ambigüedades
La herencia múltiple puede originar situaciones de ambigüedad cuando una subclase
contiene versiones duplicadas de sub-objetos de clases antecesoras o cuando clases
antecesoras contienen miembros del mismo nombre:
class B {
public:
int b;
int b0;
};
class C1 : public B {
public:
int b;
int c;
};
class C2 : public B {
public:
int b;
int c;
};
class D: public C1, C2 {
public:
D() {
c = 10;
// L1: Error
?
C1::c = 110;
// L2: Ok.
C2::c = 120;
// L3: Ok.
b = 12; Error!!
// L4: Error
C1::b = 11;
// L5: Ok.
C1::B::b
C2::b = 12;
// L6: Ok.
C2::B::b
C1::B::b = 10;
// L7: Error
B::b = 10;
// L8: Error
una única base B
b0 = 0;
// L9: Error
C1::b0 = 1;
// L10: Ok.
C2::b0 = 2;
// L11: Ok.
ambigüedad C1::c o C2::c
ambigüedad
C1::b domina sobre
C2::b domina sobre
de sintaxis!
ambigüedad. No existe
ambigüedad
}
};
Los errores originados en el constructor de la clase D
son muy ilustrativos sobre los tipos de ambiguedad
que puede originar la herencia múltiple (podrían
haberse presentado en cualquier otro método D::f()
de dicha clase).
En principio, a la vista de la figura 1 , podría parecer
que las ambiguedades relativas a los miembros de D
deberían resolverse mediante los correspondientes
especificadores de ámbito:
C1::B::m //
heredados de
C2::B::m //
heredados de
C1::n
//
privativos
C2::n
//
privativos
miembros m en C1
B
miembros m en C2
B
miembros n en C1
miembros n en C2
Como puede verse en la sentencia L7 , por
desgracia el asunto no es axactamente así (otra de las
inconsistencias del lenguaje). El motivo es que el
esquema mostrado en la figura es méramente
Fig. 3
conceptual, y no tiene que corresponder
necesariamente con la estructura de los objetos creados por el compilador. En realidad
un objeto suele ser una región continua de memoria. Los objetos de las clases
derivadas se organizan concatenando los sub-objetos de las bases directas, y los
miembros privativos si los hubiere; pero el orden de los elementos de su interior no
está garantizado (depende de la implementación). La figura 3 muestra una posible
organización de los miembros en el interior de los objetos del ejemplo.
El crador del lenguaje indica al respecto [2] que las relaciones contenidas en un grafo
como el de la figura 2 representan información para el programador y para el
compilador, pero que esta información no existe en el código final. El punto importante
aquí es entender que la organización interna de los objetos obtenidos por herencia
múltiple es idéntico al de los obtenidos por herencia simple. El compilador conoce la
situación de cada miembro del objeto en base a su posición, y genera el código
correspondiente sin indirecciones u otros mecanismos innecesarios (disposición del
objeto D en la figura 3).
§3.1 Desde el punto de vista de la sintaxis de acceso, cualquier miembro m privativo de
D (zona-5) de un objeto d puede ser referenciado como d.m. Cualquier otro miembro
del mismo nombre (m) en alguno de los subobjetos queda eclipsado por este. Se dice
que este identificador domina a los demás [3].
Nota: Este principio de dominancia funciona también en los subobjetos C1 y C2.
Por ejemplo: si un identificador n en el subobjeto C1 está duplicado en la parte
privativa de C1 y en la parte heredada de B, C1::n tienen preferencia sobre
C1::B::n.
Cualquier objeto c privativo de los subobjetos C1 o C2 (zonas 2 y 4) podría ser
accedido como d.c. Pero en este caso existe ambigüedad sobre cual de las zonas se
utilizará. Para resolverla se utiliza el especificador de ámbito: C1::c o C2::c. Este es
justamente el caso de las sentencias L1/L3 del ejemplo:
c = 10;
// L1: Error ambigüedad C1::c o C2::c
C1::c = 110;
C2::c = 120;
// L2: Ok.
// L3: Ok.
?
Es también el caso de las sentencias L4/L6. Observe que en este caso no existe
ambigüedad respecto a los identificadores b heredados (zonas 1, y 2) porque los de las
zonas 2 y 4 tienen preferencia sobre los de las zonas 1 y 2.
b = 12; Error!!
C1::b = 11;
C1::B::b
C2::b = 12;
C2::B::b
// L4: Error ambigüedad
// L5: Ok.
C1::b domina sobre
// L6: Ok.
C2::b domina sobre
Es interesante señalar que estos últimos, los identificadores b de las zonas 1 y 2
(heredados de B) no son accesibles porque siempre quedan ocultos por los miembros
dominantes, y la gramática C++ no ofrece ninguna forma que permita hacerlo en la
disposición actual del ejemplo. Son los intentos fallidos señalados en L7 y L8:
C1::B::b = 10;
B::b = 10;
una unica base B
// L7: Error de sintaxis!
// L8: Error ambigüedad. No existe
El error de L8 se refiere a que existen dos posibles candidatos (zonas 1 y 2). Al tratar
de la herencia virtual ( 4.11.2c1) veremos un método de resolver (parcialmente) este
problema.
Cuando no existe dominancia, los identificadores b0 de las zonas 1 y 2 si son visibles,
aunque la designación directa no es posible porque existe ambigüedad sobre la zona 12 a amplear. Es el caso de las sentencias L9/L11:
b0 = 0;
C1::b0 = 1;
C2::b0 = 2;
// L9: Error ambigüedad
// L10: Ok.
// L11: Ok.
§4 Modificadores de acceso
Los modificadores de acceso en la lista-base pueden ser cualquiera de los señalados
al referirnos a la herencia simple (public, protected y private 4.11.2b-1), y pueden
ser distintos para cada uno de los ancestros. Ejemplo:
class D : public B1, private B2, ... { <lista-miembros> };
struct T : private D, E { <lista-miembros> };
// Por defecto E equivale a
'public E'
Inicio.
Para empezar, es necesario definir dos términos normalmente usados al tratar la herencia. Cuando
una clase hereda otra, la clase que se hereda se llama clase base. La clase que hereda se llama
clase derivada. La clase base define todas las cualidades que serán comunes a cualquier clase
derivada. Otro punto importante es el acceso a la clase base. El acceso a la clase base pude tomar
3 valores, public, private y protected.
Si el acceso es public, todos los atributos de la clase base son públicos para la derivada.
Si el acceso es private, los datos son privados para la clase base la derivada no tiene acceso.
Si el acceso es protected, datos privados para la base y derivada tiene acceso, el resto sin acceso.
EJEMPLO: para comprobar los distintos tipos de acceso.
#include <iostream.h>
#include <stdio.h>
#include <conio.h>
class miclase{
int a;
protected:
int b;
public:
int c;
miclase(int n,int m){a=n;b=m;}
int obten_a(){return a;}
int obten_b(){return b;}
};
void main()
{
miclase objeto(10,20);
clrscr();
objeto.c=30;
// objeto.b=30; error,sin acceso.
// objeto.a=30; error,sin acceso.
cout<<objeto.obten_a() <<"\n";
cout<<objeto.obten_b() <<"\n";
cout<<objeto.c;
getch();
}
FORMATO DE LA CLASE DERIVADA:
class nombre_derivada:acceso nombre_base{
cuerpo;
};
EJEMPLO: Herencia pública.
#include <iostream.h>
#include <stdio.h>
#include <conio.h>
class base{
int x;
public:
void obten_x(int a){x=a;}
void muestra_x(){cout<< x;}
};
class derivada:public base{
int y;
public:
void obten_y(int b){y=b;}
void muestra_y(){cout<<y;}
};
void main()
{
derivada obj;
clrscr();
obj.obten_x(10);
obj.obten_y(20);
obj.muestra_x();
cout<<"\n";
obj.muestra_y();
getch();
}
EJEMPLO: Herencia con acceso privado.
#include <iostream.h>
#include <stdio.h>
#include <conio.h>
class base{
int x;
public:
void obten_x(int a){x=a;}
void muestra_x(){cout<<x <<"\n";}
};
class derivada:private base{
int y;
public:
void obten_xy(int a,int b){obten_x(a);y=b;}
void muestra_xy(){muestra_x();cout<<y<<"\n";}
};
void main()
{
clrscr();
derivada ob;
ob.obten_xy(10,20);
ob.muestra_xy();
// ob.obten_x(10); error,sin acceso.
// ob.muestra_x(); error,sin acceso.
getch();
}
HERENCIA MULTIPLE: Existen dos métodos en los que una clase derivada puede heredar
más de una clase base. El primero, en el que una clase derivada puede ser usada como la clase
base de otra clase derivada, creándose una jerarquía de clases. El segundo, es que una clase
derivada puede heredar directamente más de una clase base. En esta situación se combinan dos o
más clases base para facilitar la creación de la clase derivada.
SINTAXIS: Para construir la derivada mediante varias clases base.
class derivada:acceso nomb_base1,nomb_base2,nomb_baseN{
cuerpo;
};
SINTAXIS: Para crear herencia múltiple de modo jerárquico.
class derivada1:acceso base{
cuerpo;
};
class derivada2:acceso derivada1{
cuerpo;
};
class derivadaN:acceso derivada2{
cuerpo;
};
EJEMPLO: Herencia de tipo jerárquica.
#include <iostream.h>
#include <stdio.h>
#include <conio.h>
class base_a{
int a;
public:
base_a(int x){a=x;}
int ver_a(){return a;}
};
class deriva_b:public base_a{
int b;
public:
deriva_b(int x, int y):base_a(x){b=y;}
int ver_b(){return b;}
};
class deriva_c:public deriva_b{
int c;
public:
deriva_c(int x,int y,int z):deriva_b(x,y){c=z;}
void ver_todo()
{
cout<<ver_a()<<" "<<ver_b()<<" "<<c;
}
};
void main()
{
clrscr();
deriva_c ob(1,2,3);
ob.ver_todo();
cout<<"\n";
cout<<ob.ver_a()<<" "<<ob.ver_b();
getch();
}
El caso de los constructores es un poco especial. Se ejecutan en orden descendente, es decir
primero se realiza el constructor de la clase base y luego el de las derivadas. En las destructoras
ocurre en orden inverso, primero el de las derivadas y luego el de la base.
EJEMPLO: Múltiple heredando varias clases base.
#include <iostream.h>
#include <stdio.h>
#include <conio.h>
class B1{
int a;
public:
B1(int x){a=x;}
int obten_a(){return a;}
};
class B2{
int b;
public:
B2(int x){b=x;}
int obten_b(){return b;}
};
class C1:public B1,public B2{
int c;
public:
C1(int x,int y,int z):B1(z),B2(y)
{
c=x;
}
void muestra()
{
cout<<obten_a()<<" "<<obten_b()<<" ";
cout<<c<<"\n";
}
};
void main()
{
clrscr();
C1 objeto(1,2,3);
objeto.muestra();
getch();
}
http://www.mailxmail.com/curso/informatica/cplusplus2/capitulo13.htm
LA HERENCIA
A. Introducción
La verdadera potencia de la programación orientada a objetos radica en su capacidad para reflejar
la abstracción que el cerebro humano realiza automáticamente durante el proceso de aprendizaje
y el proceso de análisis de información.
Las personas percibimos la realidad como un conjunto de objetos interrelacionados. Dichas
interrelaciones, pueden verse como un conjunto de abstracciones y generalizaciones que se han
ido asimilando desde la niñez. Así, los defensores de la programación orientada a objetos afirman
que esta técnica se adecua mejor al funcionamiento del cerebro humano, al permitir descomponer
un problema de cierta magnitud en un conjunto de problemas menores subordinados del primero.
La capacidad de descomponer un problema o concepto en un conjunto de objetos relacionados
entre sí, y cuyo comportamiento es fácilmente identificable, puede ser muy útil para el desarrollo
de programas informáticos.
B. Jerarquía
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.
D. 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
}
}
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á 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.
http://pisuerga.inf.ubu.es/lsi/Invest/Java/Tuto/II_6.htm
5.13
Clase base y clase derivada.
HERENCIA:.
Una de las características más importantes de la POO, es la capacidad de derivar clases a partir de
las clases existentes, (o sea obtener una nueva clase a partir de otra). Este procedimiento se
denomina Herencia, puesto que la nueva clase "hereda" los miembros, (datos y funciones) de sus
clases ascendientes y puede anular alguna de las funciones heredadas. La herencia permite
reutilizar el código en clases descendientes.
Cuando una clase se hereda de otra clase, la clase original se llama clase base y la nueva clase se
llama clase derivada.
Quizás lo más difícil al escribir programas que utilicen clases, es el diseño de las mismas, lo que
involucra una abstracción del objeto que van a representar, pero más difícil aún es diseñar una
clase que luego sirva como "base" para nuevas clases derivadas. Aquí además de la abstracción
hay que seguir ciertas reglas de accesibilidad, que más adelante de describen.
Veamos un ejemplo:
Uno podría definir una clase CEmpleado, (a partir de aquí le agregaremos una C al comienzo del
nombre de la clase para guardar una relación estrecha con la convención utilizada por Microsoft
en su MFC).
Esta clase, básica, solamente tendrá dos datos miembros: el Apellido y el Salario, dos
constructores comunes y dos métodos, (o funciones miembros), que nos permitirán obtener el
Apellido y el Salario del empleado.
Así podría ser la clase CEmpleado:
class CEmpleado
{
protected:
char ape[20];
double sueldo;
public:
CEmpleado()
{
strcpy(ape, "");
sueldo=0;
}
CEmpleado(char ap[20], double s)
{
strcpy(ape, ap);
sueldo=s;
}
char* ObtenerApellido();
double ObtenerSueldo();
};
//funciones miembros de CEmpleado
char* CEmpleado::ObtenerApellido ()
{
return ape;
}
double CEmpleado::ObtenerSueldo ()
{
return sueldo;
}
Un programa que cree una instancia de esta clase, deberá usar alguno de los dos constructores, se
puede aprovechar el constructor que recibe como parámetros los valores iniciales para el Apellido
y el Salario, ya que el otro inicializa con cadena vacía y 0, no es muy útil.
Luego si uno quiere saber cual es el apellido del empleado deberá usar la función
ObtenerApellido() ya que los datos miembros son protegidos y "no se ven" en el programa, (esto
es "encapsulamiento"). Ofrecemos funciones para acceder a los datos miembros, a modo de
protección de la información.
Las funciones ObtenerApellido() y ObtenerSalario() están definidas fuera de la clase, aunque
podrían haber sido InLine ya que son muy cortitas, sólo retornan el dato miembro solicitado.
Así que, como ejemplo se podría escribir, dentro de la función main(), lo siguiente:
CEmpleado e("Perez", 1260.35);
cout << "Apellido: " << e.ObtenerApellido() << endl;
cout <<"Salario: " << e.ObtenerSalario() << endl;
Hasta aquí una clase sencilla y hasta muy poco útil diría, pero nos va a servir para ejemplificar un
caso sencillo de herencia.
5.13.1 Definición.
Introducción
La herencia es una propiedad esencial de la Programación Orientada a Objetos que consiste en la
creación de nuevas clases a partir de otras ya existentes. Este término ha sido prestado de la
Biología donde afirmamos que un niño tiene la cara de su padre, que ha heredado ciertas facetas
físicas o del comportamiento de sus progenitores.
La herencia es la característica fundamental que distingue un lenguaje orientado a objetos, como
el C++ o Java, de otro convencional como C, BASIC, etc. Java permite heredar a las clases
características y conductas de una o varias clases denominadas base. Las clases que heredan de
clases base se denominan derivadas, estas a su vez pueden ser clases bases para otras clases
derivadas. Se establece así una clasificación jerárquica, similar a la existente en Biología con los
animales y las plantas.
La herencia ofrece una ventaja importante, permite la reutilización del código. Una vez que una
clase ha sido depurada y probada, el código fuente de dicha clase no necesita modificarse. Su
funcionalidad se puede cambiar derivando una nueva clase que herede la funcionalidad de la
clase base y le añada otros comportamientos. Reutilizando el código existente, el programador
ahorra tiempo y dinero, ya que solamente tiene que verificar la nueva conducta que proporciona
la clase derivada.
La programación en los entornos gráficos, en particular Windows, con el lenguaje C++, es un
ejemplo ilustrativo. Los compiladores como los de Borland y Microsoft proporcionan librerías
cuyas clases describen el aspecto y la conducta de las ventanas, controles, menús, etc. Una de
estas clases denominada TWindow describe el aspecto y la conducta de una ventana, tiene una
función miembro denominada Paint, que no dibuja nada en el área de trabajo de la misma.
Definiendo una clase derivada de TWindow, podemos redefinir en ella la función Paint para que
dibuje una figura. Aprovechamos de este modo la ingente cantidad y complejidad del código
necesario para crear una ventana en un entorno gráfico. Solamente, tendremos que añadir en la
clase derivada el código necesario para dibujar un rectángulo, una elipse, etc.
En el lenguaje Java, todas las clases derivan implícitamente de la clase base Object, por lo que
heredan las funciones miembro definidas en dicha clase. Las clases derivadas pueden redefinir
algunas de estas funciones miembro como toString y definir otras nuevas.
Para crear un applet, solamente tenemos que definir una clase derivada de la clase base Applet,
redefinir ciertas funciones como init o paint, o definir otras como las respuestas a las acciones
sobre los controles.
Los programadores crean clases base:
1. Cuando se dan cuenta que diversos tipos tienen algo en común, por
ejemplo en el juego del ajedrez peones, alfiles, rey, reina, caballos y
torres, son piezas del juego. Creamos, por tanto, una clase base y
derivamos cada pieza individual a partir de dicha clase base.
2. Cuando se precisa ampliar la funcionalidad de un programa sin tener
que modificar el código existente.
Las clases pueden introducirse de muchas formas, comenzando por la que dice que representan un intento de abstraer el mundo
real. Pero desde el punto de vista del programador clásico, lo mejor es considerarlas como "entes" que superceden las estructuras C
en el sentido de que tanto los datos como los instrumentos para su manipulación (funciones) se encuentran encapsulados en ellos.
La idea es empaquetar juntos los datos y la funcionalidad, de ahí que tengan dos tipos de componentes (aquí se prefiere llamarlos
miembros). Por un lado las propiedades, también llamadas variables o campos (fields), y de otro los métodos, también llamados
procedimientos o funciones [1]; más formalmente: variables de clase y métodos de clase.
La terminología utilizada en la Programación Orientada a Objetos POO (OOP en inglés), no es demasiado consistente, y a veces
induce a cierto error a los programadores que se acercan por primera vez con una cultura de programación procedural. De hecho,
estas cuestiones semánticas suponen una dificultad adicional en el proceso de entender los conceptos subyacentes en la POO, sus
ventajas y su potencial como herramienta.
Las clases C++ ofrecen la posibilidad de extender los tipos predefinidos en el lenguaje (básico y derivado). Cada clase representa un
nuevo tipo; un nuevo conjunto de objetos caracterizado por ciertos valores (propiedades) y las operaciones (métodos) disponibles
para crearlos, manipularlos y destruirlos. Más tarde se podrán declarar objetos pertenecientes a dicho tipo (clase) del mismo modo
que se hace para las variables simples tradicionales.
Considerando que son vehículos para manejo y manipulación de información, las clases han sido comparadas en ocasiones con los
sistemas tradicionales de manejo de datos DBMS ("DataBase Management System"); aunque de un tipo muy especial, ya que sus
características les permiten operaciones que están absolutamente prohibidas a los sistemas DBMS clásicos.
La mejor manera de entender las clases es considerar que se trata simplemente de tipos de datos cuya única peculiaridad es que
pueden ser definidos por el usuario. Generalmente se trata de tipos complejos, constituidos a su vez por elementos de cualquier tipo
(incluso otras clases). La definición que puede hacerse de ellos no se reduce a diseñar su "contenido"; también pueden definirse su
álgebra y su interfaz. Es decir: como se opera con estos tipos y como los ve el usuario (que puede hacer con ellos). El propio
inventor del lenguaje señala que la principal razón para definir un nuevo tipo es separar los detalles poco relevantes de la
implementación de las propiedades que son verdaderamente esenciales para utilizarlos correctament
CLASES DERIVADAS.
En C++, la herencia simple se realiza tomando una clase existente y derivando nuevas clases de ella. La clase derivada hereda las
estructuras de datos y funciones de la clase original. Además, se pueden añadir nuevos miembros a las clases derivadas y los
miembros heredados pueden ser modificados.
Una clase utilizada para derivar nuevas clases se denomina clase base, clase padre, superclase o ascendiente. Una clase creada de
otra clase se denomina clase derivada o subclase.
Se pueden construir jerarquías de clases, en las que cada clase sirve como padre o raíz de una nueva clase.
Conceptos fundamentales de derivación
C++ utiliza un sistema de herencia jerárquica. Es decir, se hereda una clase de otra, creando nuevas clases a partir de las clases ya
existentes. Sólo se pueden heredar clases, no funciones ordinarias n variables, en C++. Una clase derivada hereda todos los
miembros dato excepto, miembros dato estático, de cada una de sus clases base. Una clase derivada hereda la función miembro de
su clase base. Esto significa que se hereda la capacidad para llamar a funciones miembro de la clase base en los objetos de la clase
derivada.
Los siguientes elementos de la clase no se heredan:
- Constructores
- Destructores
- Funciones amigas
- Funciones estáticas de la clase
- Datos estáticos de la clase
- Operador de asignación sobrecargado
Las clases base diseñadas con el objetivo principal de ser heredadas por otras se denominan clases abstractas. Normalmente, no se
crean instancias a partir de clases abstractas, aunque sea posible.
5.13.2 Declaración.
La clase base
ventana: Ventana.java, VentanaTitulo.java, VentanaApp.java
Vamos a poner un ejemplo del segundo tipo, que simule la utilización de liberías de clases para
crear un interfaz gráfico de usuario como Windows 3.1 o Windows 95.
Supongamos que tenemos una clase que describe la conducta de una ventana muy simple, aquella
que no dispone de título en la parte superior, por tanto no puede desplazarse, pero si cambiar de
tamaño actuando con el ratón en los bordes derecho e inferior.
La clase Ventana tendrá los siguientes miembros dato: la posición x e y de la ventana, de su
esquina superior izquierda y las dimensiones de la ventana: ancho y alto.
public class Ventana {
protected int x;
protected int y;
protected int ancho;
protected int alto;
public Ventana(int x, int y, int ancho, int alto) {
this.x=x;
this.y=y;
this.ancho=ancho;
this.alto=alto;
}
//...
}
Las funciones miembros, además del constructor serán las siguientes: la función mostrar que
simula una ventana en un entorno gráfico, aquí solamente nos muestra la posición y las
dimensiones de la ventana.
public void mostrar(){
System.out.println("posición
: x="+x+", y="+y);
System.out.println("dimensiones : w="+ancho+", h="+alto);
}
La función cambiarDimensiones que simula el cambio en la anchura y altura de la ventana.
public void cambiarDimensiones(int dw, int dh){
ancho+=dw;
alto+=dh;
}
El código completo de la clase base Ventana, es el siguiente
package ventana;
public class Ventana {
protected int x;
protected int y;
protected int ancho;
protected int alto;
public Ventana(int x, int y, int ancho, int alto) {
this.x=x;
this.y=y;
this.ancho=ancho;
this.alto=alto;
}
public void mostrar(){
System.out.println("posición
: x="+x+", y="+y);
System.out.println("dimensiones : w="+ancho+", h="+alto);
}
public void cambiarDimensiones(int dw, int dh){
ancho+=dw;
alto+=dh;
}
}
Objetos de la clase base
Como vemos en el código, el constructor de la clase base inicializa los cuatro miembros dato.
Llamamos al constructor creando un objeto de la clase Ventana
Ventana ventana=new Ventana(0, 0, 20, 30);
Desde el objeto ventana podemos llamar a las funciones miembro públicas
ventana.mostrar();
ventana.cambiarDimensiones(10, 10);
ventana.mostrar();
La clase derivada
Incrementamos la funcionalidad de la clase Ventana
definiendo una clase derivada denominada
VentanaTitulo. Los objetos de dicha clase tendrán todas
las características de los objetos de la clase base, pero
además tendrán un título, y se podran desplazar (se
simula el desplazamiento de una ventana con el ratón).
La clase derivada heredará los miembros dato de la clase base y las funciones miembro, y tendrá
un miembro dato más, el título de la ventana.
public class VentanaTitulo extends Ventana{
protected String titulo;
public VentanaTitulo(int x, int y, int w, int h, String nombre) {
super(x, y, w, h);
titulo=nombre;
}
extends es la palabra reservada que indica que la clase VentanaTitulo deriva, o es una subclase,
de la clase Ventana.
La primera sentencia del constructor de la clase derivada es una llamada al constructor de la clase
base mediante la palabra reservada super. La llamada
super(x, y, w, h);
inicializa los cuatro miembros dato de la clase base Ventana: x, y, ancho, alto. A continuación, se
inicializa los miembros dato de la clase derivada, y se realizan las tareas de inicialización que
sean necesarias. Si no se llama explícitamente al constructor de la clase base Java lo realiza por
nosotros, llamando al constructor por defecto si existe.
La función miembro denominada desplazar cambia la posición de la ventana, añadiéndoles el
desplazamiento.
public void desplazar(int dx, int dy){
x+=dx;
y+=dy;
}
Redefine la función miembro mostrar para mostrar una ventana con un título.
public void mostrar(){
super.mostrar();
System.out.println("titulo
}
: "+titulo);
En la clase derivada se define una función que tiene el mismo nombre y los mismos parámetros
que la de la clase base. Se dice que redefinimos la función mostrar en la clase derivada. La
función miembro mostrar de la clase derivada VentanaTitulo hace una llamada a la función
mostrar de la clase base Ventana, mediante
super.mostrar();
De este modo aprovechamos el código ya escrito, y le añadimos el código que describe la nueva
funcionalidad de la ventana por ejemplo, que muestre el título.
Si nos olvidamos de poner la palabra reservada super llamando a la función mostrar, tendríamos
una función recursiva. La función mostrar llamaría a mostrar indefinidamente.
public void mostrar(){ //¡ojo!, función recursiva
System.out.println("titulo
: "+titulo);
mostrar();
}
La definición de la clase derivada VentanaTitulo, será la siguiente.
package ventana;
public class VentanaTitulo extends Ventana{
protected String titulo;
public VentanaTitulo(int x, int y, int w, int h, String nombre) {
super(x, y, w, h);
titulo=nombre;
}
public void mostrar(){
super.mostrar();
System.out.println("titulo
: "+titulo);
}
public void desplazar(int dx, int dy){
x+=dx;
y+=dy;
}
}
Objetos de la clase derivada
Creamos un objeto ventana de la clase derivada VentanaTitulo
VentanaTitulo ventana=new VentanaTitulo(0, 0, 20, 30, "Principal");
Mostramos la ventana con su título, llamando a la función mostrar, redefinida en la clase
derivada
ventana.mostrar();
Desde el objeto ventana de la clase derivada llamamos a las funciones miembro definidas en
dicha clase
ventana.desplazar(4, 3);
Desde el objeto ventana de la clase derivada podemos llamar a las funciones miembro definidas
en la clase base.
ventana.cambiarDimensiones(10, -5);
Para mostrar la nueva ventana desplazada y cambiada de tamaño escribimos
ventana.mostrar();
Modificadores de acceso
Ya hemos visto el significado de los modificadores de acceso public y private, así como el
control de acceso por defecto a nivel de paquete, cuando no se especifica nada. En la herencia,
surge un nuevo control de acceso denominado protected.
Hemos puesto protected delante de los miebros dato x e y de la clase base Ventana
public class Ventana {
protected int x;
protected int y;
//...
}
En la clase derivada la función miembro desplazar accede a dichos miembros dato
public class VentanaTitulo extends Ventana{
//...
public void desplazar(int dx, int dy){
x+=dx;
y+=dy;
}
}
Si cambiamos el modificador de acceso de los miembros x e y de la clase base Ventana de
protected a private, veremos que el compilador se queja diciendo que los miembro x e y no son
accesibles.
Los miembros ancho y alto se pueden poner con acceso private sin embargo, es mejor dejarlos
como protected ya que podrían ser utilizados por alguna función miembro de otra clase derivada
de VentanaTitulo. Dentro de una jerarquía pondremos un miembro con acceso private, si
estamos seguros de que dicho miembro solamente va a ser usado por dicha clase.
Como vemos hay cuatro modificadores de acceso a los miembros dato y a los métodos: private,
protected, public y default (por defecto, o en ausencia de cualquier modificador). La herencia
complica aún más el problema de acceso, ya que las clases dentro del mismo paquete tienen
diferentes accesos que las clases de distinto paquete
Los siguientes cuadros tratan de aclarar este problema
Clases dentro del mismo paquete
Modificador de
Heredado Accesible
acceso
Por defecto (sin
Si
Si
modificador)
private
No
No
protected
Si
Si
public
Si
Si
Clases en distintos paquetes
Modificador de
Heredado Accesible
acceso
Por defecto (sin
No
No
modificador)
private
No
No
protected
Si
No
public
Si
Si
Desde el punto de vista práctico, cabe reseñar que no se heredan los miembros privados, ni
aquellos miembros (dato o función) cuyo nombre sea el mismo en la clase base y en la clase
derivada.
La clase base Object
La clase Object es la clase raíz de la cual derivan todas las clases. Esta derivación es implícita.
La clase Object define una serie de funciones miembro que heredan todas las clases. Las más
importantes son las siguientes
public class Object {
public boolean equals(Object obj) {
return (this == obj);
}
protected native Object clone() throws CloneNotSupportedException;
public String toString() {
return getClass().getName() + "@" + Integer.toHexString(hashCode());
}
protected void finalize() throws Throwable { }
//otras funciones miembro...
}
Igualdad de dos objetos:
Hemos visto que el método equals de la clase String cuando compara un string y cualquier otro
objeto. El método equals de la clase Object compara dos objetos uno que llama a la función y
otro es el argumento de dicha función.
Representación en forma de texto de un objeto
El método toString imprime por defecto el nombre de la clase a la que pertenece el objeto y su
código (hash). Esta función miembro se redefine en la clase derivada para mostrar la información
que nos interese acerca del objeto. La clase Fraccion redefine toString para mostrar el numerador
y el denominador separados por la barra de dividir. En la misma página, hemos mejorado la clase
Lista para mostrar los datos que se guardan en los objetos de dicha clase, redefiniendo toString.
La función toString se llama automáticamente siempre que pongamos un objeto como argumento
de la función System.out.println o concatenado con otro string.
Duplicación de objetos
El método clone crea un objeto duplicado (clónico) de otro objeto. Más adelante estudiremos en
detalle la redefinición de esta función miembro y pondremos ejemplos que nos muestren su
utilidad.
Finalización
El método finalize se llama cuando va a ser liberada la memoria que ocupa el objeto por el
recolector de basura (garbage collector). Normalmente, no es necesario redefinir este método en
las clases, solamente en contados casos especiales. La forma en la que se redefine este método es
el siguiente.
class CualquierClase{
//..
protected void finalize() trows Throwable{
super.finalize();
//código que libera recursos externos
}
}
La primera sentencia que contenga la redefinición de finalize ha de ser una llamada a la función
del mismo nombre de la clase base, y a continuación le añadimos cierta funcionalidad,
habitualmente, la liberación de recursos, cerrar un archivo, etc.
http://www.sc.ehu.es/sbweb/fisica/cursoJava/fundamentos/herencia/herencia.htm
Jerarquía, clases base y clases derivadas:
Una de las principales propiedades de las clases es la herencia. Esta propiedad nos permite
crear nuevas clases a partir de clases existentes, conservando las propiedades de la clase
original y añadiendo otras nuevas.
La nueva clase obtenida se conoce como clase derivada, y las clases a partir de las cuales se
deriva, clases base. Además, cada clase derivada puede usarse como clase base para obtener
una nueva clase derivada. Y cada clase derivada puede serlo de una o más clases base. En este
último caso hablaremos de derivación múltiple.
Esto nos permite crear una jerarquía de clases tan compleja como sea necesario.
Bien, pero ¿que ventajas tiene derivar clases?.
En realidad, ese es el principio de la programación orientada a objetos. Esta propiedad nos
permite encapsular diferentes partes de cualquier objeto real o imaginario, y vincularlo con
objetos más elaborados del mismo tipo básico, que heredarán todas sus características. Lo
veremos mejor con un ejemplo.
Un ejemplo muy socorrido es de las personas. Supongamos que nuestra clase base para
clasificar a las personas en función de su profesión sea "Persona". Presta especial atención a la
palabra "clasificar", es el punto de partida para buscar la solución de cualquier problema que
se pretenda resolver usando POO. Lo primero que debemos hacer es buscar categorías,
propiedades comunes y distintas que nos permitan clasificar los objetos, y crear lo que después
serán las clases de nuestro programa. Es muy importante dedicar el tiempo y atención
necesarios a esta tarea, de ello dependerá la flexibilidad, reutilización y eficacia de nuestro
programa.
Ten en cuenta que las jerarquías de clases se usan especialmente en la resolución de problemas
complejos, es difícil que tengas que recurrir a ellas para resolver problemas sencillos.
Siguiendo con el ejemplo, partiremos de la clase "Persona". Independientemente de la
profesión, todas las personas tienen propiedades comunes, nombre, fecha de nacimiento,
género, estado civil, etc.
La siguiente clasificación debe ser menos general, supongamos que dividimos a todas las
personas en dos grandes clases: empleados y estudiantes. (Dejaremos de lado, de momento, a
los estudiantes que además trabajan). Lo importante es decidir qué propiedades que no hemos
incluido en la clase "Persona" son exclusivas de los empleados y de los estudiantes. Por
ejemplo, los ingresos por nómina son exclusivos de los empleados, la nota media del curso, es
exclusiva de los estudiantes. Una vez hecho eso crearemos dos clases derivadas de Persona:
"Empleado" y "Estudiante".
Haremos una nueva clasificación, ahora de los empleados. Podemos clasificar a los empleados
en ejecutivos y comerciales (y muchas más clases, pero para el ejemplo nos limitaremos a esos
dos). De nuevo estableceremos propiedades exclusivas de cada clase y crearemos dos nuevas
clases derivadas de "Empleado": "Ejecutivo" y "Comercial".
Ahora veremos las ventajas de disponer de una jerarquía completa de clases.



Cada vez que creemos un objeto de cualquier tipo derivado, por
ejemplo de tipo Comercial, estaremos creando en un sólo objeto un
Comercial, un Empleado y una Persona. Nuestro programa puede tratar a
ese objeto como si fuera cualquiera de esos tres tipos. Es decir, nuestro
comercial tendrá, además de sus propiedades como comercial, su nómina
como empleado, y su nombre, edad y género como persona.
Siempre podremos crear nuevas clases para resolver nuevas
situaciones. Consideremos el caso de que en nuestra clasificación
queremos incluir una nueva clase "Becario", que no es un empleado, ni
tampoco un estudiante; la derivaríamos de Persona. También podemos
considerar que un becario es ambas cosas, sería un ejemplo de derivación
múltiple, podríamos hacer que la clase derivada Becario, lo fuera de
Empleado y Estudiante.
Podemos aplicar procedimientos genéricos a una clase en concreto,
por ejemplo, podemos aplicar una subida general del salario a todos los
empleados, independientemente de su profesión, si hemos diseñado un
procedimiento en la clase Empleado para ello.
Veremos que existen más ventajas, aunque este modo de diseñar aplicaciones tiene también
sus inconvenientes, sobre todo si diseñamos mal alguna clase.
Derivar clases, sintaxis:
La forma general de declarar clases derivadas es la siguiente:
class <clase_derivada> :
[public|private] <base1> [,[public|private] <base2>] {};
En seguida vemos que para cada clase base podemos definir dos tipos de acceso, public o
private. Si no se especifica ninguno de los dos, por defecto se asume que es private.


public: los miembros heredados de la clase base conservan el tipo de
acceso con que fueron declarados en ella.
private: todos los miembros heredados de la clase base pasan a ser
miembros privados en la clase derivada.
De momento siempre declararemos las clases base como public, al menos hasta que veamos la
utilidad de hacerlo como privadas.
Veamos un ejemplo sencillo basado en la idea del punto anterior:
// Clase base Persona:
class Persona {
public:
Persona(char *n, int e);
const char *LeerNombre(char *n) const;
int LeerEdad() const;
void CambiarNombre(const char *n);
void CambiarEdad(int e);
protected:
char nombre[40];
int edad;
};
// Clase derivada Empleado:
class Empleado : public Persona {
public:
Empleado(char *n, int e, float s);
float LeerSalario() const;
void CambiarSalario(const float s);
protected:
float salarioAnual;
};
Podrás ver que hemos declarado los datos miembros de nuestras clases como protected. En
general es recomendable declarar siempre los datos de nuestras clases como privados, de ese
modo no son accesibles desde el exterior de la clase y además, las posibles modificaciones de
esos datos, en cuanto a tipo o tamaño, sólo requieren ajustes de los métodos de la propia clase.
Pero en el caso de estructuras jerárquicas de clases puede ser interesante que las clases
derivadas tengan acceso a los datos miembros de las clases base. Usar el acceso protected nos
permite que los datos sean inaccesibles desde el exterior de las clases, pero a la vez, permite
que sean accesibles desde las clases derivadas.
http://c.conclase.net/curso/index.php?cap=036
Creación de una clase derivada
Cada clase derivada se debe referir a una clase base declarada anteriormente. La declaración de una clase derivada tiene la
siguiente sintaxis:
Class clase_derivada:<especificadores_de_acceso> clase_base {...};
Los especificadotes de acceso pueden ser: public, protected o private.
Clases de derivación
Los especificadores de acceso a las clases base definen los posibles tipos de derivación: public, protected y private. El tipo de acceso
a la clase base especifica cómo recibirá la clase derivada a los miembros de la clase base. Si no se especifica un acceso a la clase
base, C++ supone que su tipo de herencia es privado.
- Derivación pública (public). Todos los miembros public y protected de la clase base son accesibles en la clase derivada, mientras
que los miembros private de la clase base son siempre inaccesibles en la clase derivada.
- Derivación privada (private). Todos los miembros de la clase base se comportan como miembros privados de la clase derivada.
Esto significa que los miembros public y protected de la clase base no son accesibles más que por las funciones miembro de la clase
derivada. Los miembros privados de la clase siguen siendo inaccesibles desde la clase derivada.
- Derivación protegida (protected). Todos los miembros public y protected de la clase base se comportan como miembros protected
de la clase derivada. Estos miembros no son, pues, accesibles al programa exterior, pero las clases que se deriven a continuación
podrán acceder normalmente a estos miembros (datos o funciones).
CREACIÓN DE UNA CLASE.
Sintaxis
La construcción de una clase partiendo desde cero, es decir, cuando no deriva de una clase previa, tiene la siguiente sintaxis (que
es un caso particular de la sintaxis general:
Class-key <info> nomb-clase {<lista-miembros>};
Ejemplo
class Hotel { int habitd; int habits; char stars[5]; };
Es significativo que la declaración (y definición) de una clase puede efectuarse en cualquier punto del programa, incluso en el
cuerpo de otra clase.
Salvo que se trate de una declaración adelantada, el bloque <lista-miembros>, también denominado cuerpo de la clase, debe
existir, y declarar en su interior los miembros que constituirán la nueva clase, incluyendo especificadotes de acceso (explícitos o por
defecto) que especifican aspectos de la accesibilidad actual y futura (en los descendientes) de los miembros de la clase.
la cuestión de la accesibilidad de los miembros está estrechamente relacionada con la herencia, por lo que hemos preferido trasladar
la explicación de esta importante propiedad al capítulo dedicado a la herencia.
Quién puede ser miembro
La lista de miembros es una secuencia de declaraciones de propiedades de cualquier tipo, incluyendo enumeraciones, campos de
bits etc.; así como declaración y definición de métodos, todos ellos con especificadotes opcionales de acceso y de tipo de
almacenamiento. Auto, extern y register no son permitidos; si en cambio static y const. Los elementos así definidos se denominan
miembros de la clase. Hemos dicho que son de dos tipo: propiedades de clase y métodos de clase.
Es importante advertir que los elementos constitutivos de la clase deben ser completamente definidos para el compilador en el
momento de su utilización. Esta advertencia solo tiene sentido cuando se refiere a utilización de tipos abstractos como miembros de
clases, ya que los tipos simples (preconstruidos en el lenguaje) quedan perfectamente definidos con su declaración. Ver a
continuación una aclaración sobre este punto.
Ejemplo:
Class Vuelo {
// Vuelo es la clase
Char nombre [30];
// nombre es una propiedad
Int. capacidad;
Enum modelo {B747, DC10};
Char origen[8];
Char destino [8];
Char fecha [8];
Void despegue (&operación}; // despegue es un método
Void crucero (&operación);
};
Los miembros pueden ser de cualquier tipo con una excepción: No pueden ser la misma clase que se está definiendo (lo que daría
lugar a una definición circular), por ejemplo:
Class Vuelo {
Char nombre [30];
Class Vuelo;
// Ilegal
...
Sin embargo, si es lícito que un miembro sea puntero al tipo de la propia clase que se está declarando:
class Vuelo {
Char nombre [30];
Vuelo* ptr;
...
En la práctica esto significa que un miembro ptr de un objeto c1 de una clase C, es un puntero que puede señalar a otro objeto c2
de la misma clase.
Nota: Esta posibilidad es muy utilizada, pues permite construir árboles y listas de objetos (unos enlazan con otros). Precisamente en
el capítulo dedicado a las estructuras auto referenciadas, se muestra la construcción de un árbol binario utilizando una estructura
que tiene dos elementos que son punteros a objetos del tipo de la propia estructura (recuerde que las estructuras C++ son un caso
particular de clases.
También es lícito que se utilicen referencias a la propia clase:
class X {
int i;
char c;
public:
X(const X& ref, int x = 0); { // Ok. correcto
i = ref.i;
};
c = ref.c;
De hecho, un grupo importante de funciones miembro, los constructores-copia, se caracterizan precisamente por aceptar una
referencia a la clase como primer argumento.
Las clases pueden ser miembros de otras clases, clases anidadas. Por ejemplo:
class X {
// clase contenedora (exterior)
public:
int x;
class Xa {
// clase dentro de clase (anidada)
public:
int x;
};
};
Ver aspectos generales en: clases dentro de clases.
Las clases pueden ser declaradas dentro de funciones, en cuyo caso se denominan clases locales, aunque presentan algunas
limitaciones. Ejemplo:
void foo() {
// función contenedora
...
int x;
class C {
// clase local
public:
int x;
};
}
También pueden ser miembros las instancias de otras clases (objetos):
class Vertice {
public: int x, y;
};
class Triangulo {
// Clase contenedora
public:
Vertice va, vb, vc;
// Objetos dentro de una clase
};
Es pertinente recordar lo señalado al principio: que los miembros de la clase deben ser perfectamente conocidos por el compilador
en el momento de su utilización. Por ejemplo:
class Triangulo {
...
Vertice v;
// Error: Vertice no definido
};
class Vertice {...};
En estos casos no es suficiente realizar una declaración adelantada de Vértice:
class Vertice;
class Triangulo {
public:
Vértice v;
// Error: Información insuficiente de Vértice
};
Class Vértice {...};
Ya que el compilador necesita una definición completa del objeto v para insertarlo como miembro de la clase Triangulo.
La consecuencia es que importa el orden de declaración de las clases en el fuente. Debe comenzarse definiendo los tipos más
simples (que no tienen dependencia de otros) y seguir en orden creciente de complejidad (clases que dependen de otras clases para
su definición). También se colige que deben evitarse definiciones de clases mutuamente dependientes:
class A {
...
B b1;
};
class B {
...
A a1;
};
ya que conducirían a definiciones circulares como las señaladas antes
Los miembros de la clase deben ser completamente declarados dentro del cuerpo, sin posibilidad de que puedan se añadidos fuera
de él. Las definiciones de las propiedades se efectúan generalmente en los constructores (un tipo de función-miembro), aunque
existen otros recursos inicialización de miembros. La definición de los métodos puede realizarse dentro o fuera del cuerpo (
funciones inline. Ejemplo:
class C {
int x;
char c;
void foo();
};
int C::y;
// Error!! declaración off-line
void C::foo() { ++x; } // Ok. definición off-line
Las funciones-miembro, denominadas métodos, pueden ser declaradas inline, static virtual, const y explicit si son constructores. Por
defecto tienen enlazado externo.
Clases vacías
Los miembros pueden faltar completamente, en cuyo caso tendremos una clase vacía. Ejemplo:
Class empty {};
La clase vacía es una definición completa y sus objetos son de tamaño distinto de cero, por lo que cada una de sus instancias tiene
existencia independiente. Suelen utilizarse como clases-base durante el proceso de desarrollo de aplicaciones. Cuando se sospecha
que dos clases pueden tener algo en común, pero de momento no se sabe exactamente que.
Inicialización de miembros
Lo mismo que ocurre con las estructuras, que a fin de cuentas son un tipo de clase en su declaración solo está permitido señalar
tipo y nombre de los miembros, sin que se pueda efectuar ninguna asignación, ni aún en el caso de que se trate de una constante.
Así pues, en el bloque <lista-miembros> no pueden existir asignaciones. Por ejemplo:
class C {
...
int x = 33;
// Asignación ilegal !!
...
};
Las únicas excepciones permitidas son la asignación a constantes estáticas enteras y los enumeradores (ver a continuación), ya que
los miembros estáticos tienen unas características muy especiales. Ejemplo:
class C {
...
static const int kte = 33;
// Ok:
static const kt1 = 33.0
// Error: No entero
cont int kt2 = 33;
static kt3 = 33;
// Error: No estática
// Error: No constante
static const int kt4 = f(33); // Error: inicializador no constante
};
El sitio idóneo para situar las asignaciones a miembros es en el cuerpo de las funciones de clase (métodos). En especial las
asignaciones iniciales (que deben efectuarse al instanciar un objeto de la clase) tienen un sitio específico en el cuerpo de ciertos
métodos especiales denominados constructores. En el epígrafe "Inicializar miembros" se ahonda en esta cuestión.
Si es posible utilizar y definir un enumerador (que es una constante simbólica dentro de una clase. Por ejemplo:
class C {
...
enum En { E1 = 3, E2 = 1, E3, E4 = 0};
...
};
En ocasiones es posible utilizar un enumerador para no tener que definir una constante estática.
Ejemplo-1 Las tres formas siguientes serían aceptables:
class C {
static const int k1 = 10;
char v1[k1];
enum e {E1 = 10};
char v2[E1];
enum {KT = 20};
char v3[KT];
...
};
Ejemplo-2:
class CAboutDlg : public CDialog {
...
enum { IDD = IDD_ABOUTBOX };
...
};
La definición de la clase CAboutDlg pertenece a un caso real tomado de MS VC++. El enumerador anónimo es utilizado aquí como
un recurso para inicializar la propiedad IDD con el valor IDD_ABOUTBOX que es a su vez una constante simbólica para el
compilador. De no haberse hecho así, se tendría que haber declarado IDD como constante estática, en cambio la forma adoptada la
convierte en una variable enumerada anónima que solo puede adoptar un valor (otra forma de designar al mismo concepto).
Ejemplo-3:
Class C {
...
enum {CERO = 0, UNO = 1, DOS = 2, TRES = 3};
};
...
void foo(C& c1) {
std::cout << c1.CERO;
// -> 0
Std::cout << c1.TRES;
// -> 3
}
Téngase en cuenta que las clases son tipos de datos que posteriormente tienen su concreción en objetos determinados.
Precisamente una de las razones de ser de las variables de clase, es que pueden adoptar valores distintos en cada instancia concreta
de la clase. Por esta razón, a excepción de las constantes y los miembros estáticos, no tiene mucho sentido asignar valores
concretos a las variables de clase, ya que los valores concretos los reciben las instancias, bien por asignación directa, o a través de
los constructores.
En el apartado dedicado a Inicialización de miembros volvemos sobre la cuestión, exponiendo con detalle la forma de realizar estas
asignaciones, en especial cuando se trata de constantes.
http://html.rincondelvago.com/clases-derivadas.html
5.14
Parte protegida.
5.14.1 Propósito de la parte protegida.
5.15
Redefinición de los miembros de las clases
derivadas.
5.16
Clases virtuales y visibilidad.
5.17
Constructores y destructores en clases
derivadas.
Las clases pueden introducirse de muchas formas, comenzando por la que dice que representan un intento de abstraer el mundo
real. Pero desde el punto de vista del programador clásico, lo mejor es considerarlas como "entes" que superceden las estructuras C
en el sentido de que tanto los datos como los instrumentos para su manipulación (funciones) se encuentran encapsulados en ellos.
La idea es empaquetar juntos los datos y la funcionalidad, de ahí que tengan dos tipos de componentes (aquí se prefiere llamarlos
miembros). Por un lado las propiedades, también llamadas variables o campos (fields), y de otro los métodos, también llamados
procedimientos o funciones [1]; más formalmente: variables de clase y métodos de clase.
La terminología utilizada en la Programación Orientada a Objetos POO (OOP en inglés), no es demasiado consistente, y a veces
induce a cierto error a los programadores que se acercan por primera vez con una cultura de programación procedural. De hecho,
estas cuestiones semánticas suponen una dificultad adicional en el proceso de entender los conceptos subyacentes en la POO, sus
ventajas y su potencial como herramienta.
Las clases C++ ofrecen la posibilidad de extender los tipos predefinidos en el lenguaje (básico y derivado). Cada clase representa un
nuevo tipo; un nuevo conjunto de objetos caracterizado por ciertos valores (propiedades) y las operaciones (métodos) disponibles
para crearlos, manipularlos y destruirlos. Más tarde se podrán declarar objetos pertenecientes a dicho tipo (clase) del mismo modo
que se hace para las variables simples tradicionales.
Considerando que son vehículos para manejo y manipulación de información, las clases han sido comparadas en ocasiones con los
sistemas tradicionales de manejo de datos DBMS ("DataBase Management System"); aunque de un tipo muy especial, ya que sus
características les permiten operaciones que están absolutamente prohibidas a los sistemas DBMS clásicos.
La mejor manera de entender las clases es considerar que se trata simplemente de tipos de datos cuya única peculiaridad es que
pueden ser definidos por el usuario. Generalmente se trata de tipos complejos, constituidos a su vez por elementos de cualquier tipo
(incluso otras clases). La definición que puede hacerse de ellos no se reduce a diseñar su "contenido"; también pueden definirse su
álgebra y su interfaz. Es decir: como se opera con estos tipos y como los ve el usuario (que puede hacer con ellos). El propio
inventor del lenguaje señala que la principal razón para definir un nuevo tipo es separar los detalles poco relevantes de la
implementación de las propiedades que son verdaderamente esenciales para utilizarlos correctament
CLASES DERIVADAS.
En C++, la herencia simple se realiza tomando una clase existente y derivando nuevas clases de ella. La clase derivada hereda las
estructuras de datos y funciones de la clase original. Además, se pueden añadir nuevos miembros a las clases derivadas y los
miembros heredados pueden ser modificados.
Una clase utilizada para derivar nuevas clases se denomina clase base, clase padre, superclase o ascendiente. Una clase creada de
otra clase se denomina clase derivada o subclase.
Se pueden construir jerarquías de clases, en las que cada clase sirve como padre o raíz de una nueva clase.
Conceptos fundamentales de derivación
C++ utiliza un sistema de herencia jerárquica. Es decir, se hereda una clase de otra, creando nuevas clases a partir de las clases ya
existentes. Sólo se pueden heredar clases, no funciones ordinarias n variables, en C++. Una clase derivada hereda todos los
miembros dato excepto, miembros dato estático, de cada una de sus clases base. Una clase derivada hereda la función miembro de
su clase base. Esto significa que se hereda la capacidad para llamar a funciones miembro de la clase base en los objetos de la clase
derivada.
Los siguientes elementos de la clase no se heredan:
- Constructores
- Destructores
- Funciones amigas
- Funciones estáticas de la clase
- Datos estáticos de la clase
- Operador de asignación sobrecargado
Las clases base diseñadas con el objetivo principal de ser heredadas por otras se denominan clases abstractas. Normalmente, no se
crean instancias a partir de clases abstractas, aunque sea posible.
La herencia en C++
En C++ existen dos tipos de herencia: simple y múltiple. La herencia simple es aquella en la que cada clase derivada hereda de una
única clase. En herencia simple, cada clase tiene un solo ascendiente. Cada clase puede tener, sin embargo, muchos descendientes.
La herencia múltiple es aquella en la cual una clase derivada tiene más de una clase base. Aunque el concepto de herencia múltiple
es muy útil, el diseño de clases suele ser más complejo.
Creación de una clase derivada
Cada clase derivada se debe referir a una clase base declarada anteriormente. La declaración de una clase derivada tiene la
siguiente sintaxis:
Class clase_derivada:<especificadores_de_acceso> clase_base {...};
Los especificadotes de acceso pueden ser: public, protected o private.
Clases de derivación
Los especificadores de acceso a las clases base definen los posibles tipos de derivación: public, protected y private. El tipo de acceso
a la clase base especifica cómo recibirá la clase derivada a los miembros de la clase base. Si no se especifica un acceso a la clase
base, C++ supone que su tipo de herencia es privado.
- Derivación pública (public). Todos los miembros public y protected de la clase base son accesibles en la clase derivada, mientras
que los miembros private de la clase base son siempre inaccesibles en la clase derivada.
- Derivación privada (private). Todos los miembros de la clase base se comportan como miembros privados de la clase derivada.
Esto significa que los miembros public y protected de la clase base no son accesibles más que por las funciones miembro de la clase
derivada. Los miembros privados de la clase siguen siendo inaccesibles desde la clase derivada.
- Derivación protegida (protected). Todos los miembros public y protected de la clase base se comportan como miembros protected
de la clase derivada. Estos miembros no son, pues, accesibles al programa exterior, pero las clases que se deriven a continuación
podrán acceder normalmente a estos miembros (datos o funciones).
CREACIÓN DE UNA CLASE.
Sintaxis
La construcción de una clase partiendo desde cero, es decir, cuando no deriva de una clase previa, tiene la siguiente sintaxis (que
es un caso particular de la sintaxis general:
Class-key <info> nomb-clase {<lista-miembros>};
Ejemplo
class Hotel { int habitd; int habits; char stars[5]; };
Es significativo que la declaración (y definición) de una clase puede efectuarse en cualquier punto del programa, incluso en el
cuerpo de otra clase.
Salvo que se trate de una declaración adelantada, el bloque <lista-miembros>, también denominado cuerpo de la clase, debe
existir, y declarar en su interior los miembros que constituirán la nueva clase, incluyendo especificadotes de acceso (explícitos o por
defecto) que especifican aspectos de la accesibilidad actual y futura (en los descendientes) de los miembros de la clase.
la cuestión de la accesibilidad de los miembros está estrechamente relacionada con la herencia, por lo que hemos preferido trasladar
la explicación de esta importante propiedad al capítulo dedicado a la herencia.
Quién puede ser miembro
La lista de miembros es una secuencia de declaraciones de propiedades de cualquier tipo, incluyendo enumeraciones, campos de
bits etc.; así como declaración y definición de métodos, todos ellos con especificadotes opcionales de acceso y de tipo de
almacenamiento. Auto, extern y register no son permitidos; si en cambio static y const. Los elementos así definidos se denominan
miembros de la clase. Hemos dicho que son de dos tipo: propiedades de clase y métodos de clase.
Es importante advertir que los elementos constitutivos de la clase deben ser completamente definidos para el compilador en el
momento de su utilización. Esta advertencia solo tiene sentido cuando se refiere a utilización de tipos abstractos como miembros de
clases, ya que los tipos simples (preconstruidos en el lenguaje) quedan perfectamente definidos con su declaración. Ver a
continuación una aclaración sobre este punto.
Ejemplo:
Class Vuelo {
// Vuelo es la clase
Char nombre [30];
// nombre es una propiedad
Int. capacidad;
Enum modelo {B747, DC10};
Char origen[8];
Char destino [8];
Char fecha [8];
Void despegue (&operación}; // despegue es un método
Void crucero (&operación);
};
Los miembros pueden ser de cualquier tipo con una excepción: No pueden ser la misma clase que se está definiendo (lo que daría
lugar a una definición circular), por ejemplo:
Class Vuelo {
Char nombre [30];
Class Vuelo;
...
// Ilegal
Sin embargo, si es lícito que un miembro sea puntero al tipo de la propia clase que se está declarando:
class Vuelo {
Char nombre [30];
Vuelo* ptr;
...
En la práctica esto significa que un miembro ptr de un objeto c1 de una clase C, es un puntero que puede señalar a otro objeto c2
de la misma clase.
Nota: Esta posibilidad es muy utilizada, pues permite construir árboles y listas de objetos (unos enlazan con otros). Precisamente en
el capítulo dedicado a las estructuras auto referenciadas, se muestra la construcción de un árbol binario utilizando una estructura
que tiene dos elementos que son punteros a objetos del tipo de la propia estructura (recuerde que las estructuras C++ son un caso
particular de clases.
También es lícito que se utilicen referencias a la propia clase:
class X {
int i;
char c;
public:
X(const X& ref, int x = 0); { // Ok. correcto
i = ref.i;
};
c = ref.c;
De hecho, un grupo importante de funciones miembro, los constructores-copia, se caracterizan precisamente por aceptar una
referencia a la clase como primer argumento.
Las clases pueden ser miembros de otras clases, clases anidadas. Por ejemplo:
class X {
// clase contenedora (exterior)
public:
int x;
class Xa {
// clase dentro de clase (anidada)
public:
int x;
};
};
Ver aspectos generales en: clases dentro de clases.
Las clases pueden ser declaradas dentro de funciones, en cuyo caso se denominan clases locales, aunque presentan algunas
limitaciones. Ejemplo:
void foo() {
// función contenedora
...
int x;
class C {
// clase local
public:
int x;
};
}
También pueden ser miembros las instancias de otras clases (objetos):
class Vertice {
public: int x, y;
};
class Triangulo {
// Clase contenedora
public:
Vertice va, vb, vc;
// Objetos dentro de una clase
};
Es pertinente recordar lo señalado al principio: que los miembros de la clase deben ser perfectamente conocidos por el compilador
en el momento de su utilización. Por ejemplo:
class Triangulo {
...
Vertice v;
// Error: Vertice no definido
};
class Vertice {...};
En estos casos no es suficiente realizar una declaración adelantada de Vértice:
class Vertice;
class Triangulo {
public:
Vértice v;
// Error: Información insuficiente de Vértice
};
Class Vértice {...};
Ya que el compilador necesita una definición completa del objeto v para insertarlo como miembro de la clase Triangulo.
La consecuencia es que importa el orden de declaración de las clases en el fuente. Debe comenzarse definiendo los tipos más
simples (que no tienen dependencia de otros) y seguir en orden creciente de complejidad (clases que dependen de otras clases para
su definición). También se colige que deben evitarse definiciones de clases mutuamente dependientes:
class A {
...
B b1;
};
class B {
...
A a1;
};
ya que conducirían a definiciones circulares como las señaladas antes
Los miembros de la clase deben ser completamente declarados dentro del cuerpo, sin posibilidad de que puedan se añadidos fuera
de él. Las definiciones de las propiedades se efectúan generalmente en los constructores (un tipo de función-miembro), aunque
existen otros recursos inicialización de miembros. La definición de los métodos puede realizarse dentro o fuera del cuerpo (
funciones inline. Ejemplo:
class C {
int x;
char c;
void foo();
};
int C::y;
// Error!! declaración off-line
void C::foo() { ++x; } // Ok. definición off-line
Las funciones-miembro, denominadas métodos, pueden ser declaradas inline, static virtual, const y explicit si son constructores. Por
defecto tienen enlazado externo.
Clases vacías
Los miembros pueden faltar completamente, en cuyo caso tendremos una clase vacía. Ejemplo:
Class empty {};
La clase vacía es una definición completa y sus objetos son de tamaño distinto de cero, por lo que cada una de sus instancias tiene
existencia independiente. Suelen utilizarse como clases-base durante el proceso de desarrollo de aplicaciones. Cuando se sospecha
que dos clases pueden tener algo en común, pero de momento no se sabe exactamente que.
Inicialización de miembros
Lo mismo que ocurre con las estructuras, que a fin de cuentas son un tipo de clase en su declaración solo está permitido señalar
tipo y nombre de los miembros, sin que se pueda efectuar ninguna asignación, ni aún en el caso de que se trate de una constante.
Así pues, en el bloque <lista-miembros> no pueden existir asignaciones. Por ejemplo:
class C {
...
int x = 33;
// Asignación ilegal !!
...
};
Las únicas excepciones permitidas son la asignación a constantes estáticas enteras y los enumeradores (ver a continuación), ya que
los miembros estáticos tienen unas características muy especiales. Ejemplo:
class C {
...
static const int kte = 33;
// Ok:
static const kt1 = 33.0
// Error: No entero
cont int kt2 = 33;
static kt3 = 33;
// Error: No estática
// Error: No constante
static const int kt4 = f(33); // Error: inicializador no constante
};
El sitio idóneo para situar las asignaciones a miembros es en el cuerpo de las funciones de clase (métodos). En especial las
asignaciones iniciales (que deben efectuarse al instanciar un objeto de la clase) tienen un sitio específico en el cuerpo de ciertos
métodos especiales denominados constructores. En el epígrafe "Inicializar miembros" se ahonda en esta cuestión.
Si es posible utilizar y definir un enumerador (que es una constante simbólica dentro de una clase. Por ejemplo:
class C {
...
enum En { E1 = 3, E2 = 1, E3, E4 = 0};
...
};
En ocasiones es posible utilizar un enumerador para no tener que definir una constante estática.
Ejemplo-1 Las tres formas siguientes serían aceptables:
class C {
static const int k1 = 10;
char v1[k1];
enum e {E1 = 10};
char v2[E1];
enum {KT = 20};
char v3[KT];
...
};
Ejemplo-2:
class CAboutDlg : public CDialog {
...
enum { IDD = IDD_ABOUTBOX };
...
};
La definición de la clase CAboutDlg pertenece a un caso real tomado de MS VC++. El enumerador anónimo es utilizado aquí como
un recurso para inicializar la propiedad IDD con el valor IDD_ABOUTBOX que es a su vez una constante simbólica para el
compilador. De no haberse hecho así, se tendría que haber declarado IDD como constante estática, en cambio la forma adoptada la
convierte en una variable enumerada anónima que solo puede adoptar un valor (otra forma de designar al mismo concepto).
Ejemplo-3:
Class C {
...
enum {CERO = 0, UNO = 1, DOS = 2, TRES = 3};
};
...
void foo(C& c1) {
std::cout << c1.CERO;
// -> 0
Std::cout << c1.TRES;
// -> 3
}
Téngase en cuenta que las clases son tipos de datos que posteriormente tienen su concreción en objetos determinados.
Precisamente una de las razones de ser de las variables de clase, es que pueden adoptar valores distintos en cada instancia concreta
de la clase. Por esta razón, a excepción de las constantes y los miembros estáticos, no tiene mucho sentido asignar valores
concretos a las variables de clase, ya que los valores concretos los reciben las instancias, bien por asignación directa, o a través de
los constructores.
En el apartado dedicado a Inicialización de miembros volvemos sobre la cuestión, exponiendo con detalle la forma de realizar estas
asignaciones, en especial cuando se trata de constantes.
NATURALEZA: TIPO U OBJETOS.
Gestión dinámica de tipos.
GLib incluye un sistema dinámico de tipos, que no es más que una base de datos en la que se van registrando las distintas clases.
En esa base de datos, se almacenan todas las propiedades asociadas a cada tipo registrado, información tal como las funciones de
inicialización del tipo, el tipo base del que deriva, el nombre del tipo, etc. Todo ello identificado por un identificador único, conocido
como GType.
Tipos basados en clases (objetos).
Para crear instancias de una clase, es necesario que el tipo haya sido registrado anteriormente, de forma que esté presente en la
base de datos de tipos de GLib!. El registro de tipos se hace mediante una estructura llamada GTypeInfo, que tiene la siguiente
forma:
Tipos no instanciables (fundamentales).
Muchos de los tipos que se registran no son directamente instanciables (no se pueden crear nuevas instancias) y no están basados
en una clase. Estos tipos se denominan tipos “fundamentales” en la terminología de GLib! y son tipos que no están basados en
ningún otro tipo, tal y como sí ocurre con los tipos instanciables (u objetos).
Entre estos tipos fundamentales se encuentran algunos viejos conocidos, como por ejemplo gchar y otros tipos básicos, que son
automáticamente registrados cada vez que se inicia una aplicación que use GLib!.
Como en el caso de los tipos instanciables, para registrar un tipo fundamental es necesaria una estructura de tipo GTypeInfo, con la
diferencia que para los tipos fundamentales bastará con rellenar con ceros toda la estructura.
La mayor parte de los tipos no instanciables están diseñados para usarse junto con GValue, que permite asociar fácilmente un tipo
GType con una posición de memoria. Se usan principalmente como simples contenedores genéricos para tipos sencillos (números,
cadenas, estructuras, etc.).
Implementación de nuevos tipos.
En el apartado anterior se mostraba la forma de registrar una nueva clase en el sistema de objetos de GLib!, y se hacía referencia a
distintas estructuras y funciones de inicialización. En este apartado, se va a indagar con más detalle en ellos.
Tanto los tipos fundamentales como los no fundamentales quedan definidos por la siguiente información:

Tamaño de la clase.

Funciones de inicialización (constructores en C++!).

Funciones de destrucción (destructores en C++!), llamadas de finalización en la jerga de GLib!.

Tamaño de las instancias.

Normas de instanciación de objetos (uso del operador new en C++!).

Funciones de copia.
Toda esta información queda almacenada, como se comentaba anteriormente, en una estructura de tipo GTypeInfo. Todas las
clases deben implementar, aparte de la función de registro de la clase (my_object_get_type), al menos dos funciones que se
especificaban en la estructura GTypeInfo a la hora de registrar la clase. Estas funciones son:

my_object_class_init: función de inicialización de la clase, donde se inicializará todo lo relacionado con la clase en sí, tal
como las señales que tendrá la clase, los manejadores de los métodos virtuales si los hubiera, etc.

my_object_init: función de inicialización de las instancias de la clase, que será llamada cada vez que se solicite la creación
de una nueva instancia de la clase. En esta función, las tareas a desempeñar son todas aquellas relacionadas con la
inicialización de una nueva instancia de la clase, tales como los valores iniciales de las variables internas de la instancia.
http://html.rincondelvago.com/clases-derivadas.html
Constructores
Contenido
1 Sinopsis
8 Necesidad de un constructor
explícito
2 Descripción
9 Orden de construcción
3 Técnicas de buena construcción
10 Constructores y funciones virtuales
4 Invocación de constructores
11 Constructores de conversión
5 Propiedades de los constructores
12 Constructor explicit
6 Constructor oficial
13 Constructores privados y protegidos
7 Constructor por defecto
§1 Sinopsis
Podemos imaginar que la construcción de objetos tiene tres fases: (I) instanciación,
que aquí representa el proceso de asignación de espacio al objeto, de forma que este
tenga existencia real en memoria. (II) Asignación de recursos. Por ejemplo, un
miembro puede ser un puntero señalando a una zona de memoria que debe ser
reservada; un "handle" a un fichero; el bloqueo de un recurso compartido o el
establecimiento de una línea de comunicación. (III) Iniciación, que garantiza que los
valores iniciales de todas sus propiedades sean correctos (no contengan basura).
La correcta realización de estas fases es importante, por lo que los diseñadores del
lenguaje decidieron asignar esta tarea a un tipo especial de funciones (métodos)
denominadas constructores. En realidad, la consideraron tan importante que, como
veremos a continuación, si el programador no declara ninguno explícitamente, el
compilador se encarga de definir un constructores de oficio , encargándose de
utilizarlo cada vez que es necesario. Aparte de las invocaciones explícitas que pueda
realizar el programador, los constructores son frecuentemente invocados de forma
implícita por el compilador.
Es significativo señalar que las fases anteriores se realizan en un orden, aunque todas
deben ser felizmente completadas cuando finaliza la labor del constructor.
§2 Descripción
Para empezar a entender como funciona el asunto, observe este sencillo ejemplo en el
que se definen sendas clases para representar complejos; en una de ellas definimos
explícitamente un constructor; en otra dejamos que el compilador defina un constructor
de oficio:
#include <iostream>
using namespace std;
class CompleX {
// Una clase para representar
complejos
public:
float r; float i;
// Partes real e imaginaria
CompleX(float r = 0, float i = 0) { // L.7: construtor
explícito
this->r = r; this->i = i;
cout << "c1: (" << this->r << "," << this->i << ")" <<
endl;
}
};
class CompX {
// Otra clase análoga
public:
float r; float i;
// Partes real e imaginaria
};
void main() {
CompleX c1;
CompleX c2(1,2);
CompX c3;
cout << "c3: (" << c3.r <<
}
Salida:
c1: (0,0)
// ======================
// L.18:
// L.19:
// L.20:
"," << c3.i << ")" << endl;
c2: (1,2)
c3: (6.06626e-39,1.4013e-45)
Comentario:
En la clase CompleX definimos explícitamente un constructor que tiene argumentos por
defecto ( ), no así en la clase CompX en la que es el propio compilador el que define
un constructor de oficio.
Es de destacar la utilización explícita del puntero this ( 4.11.6) en la definición del
constructor (Ls.8-9). Ha sido necesario hacerlo así para distinguir las propiedades i, j
de las variables locales en la función-constructor (hemos utilizado deliberadamente los
mismos nombres en los argumentos, pero desde luego, podríamos haber utilizado otros
:-)
En la función main se instancian tres objetos; en todos los casos el compilador realiza
una invocación implícita al constructor correspondiente. En la declaración de c1, se
utilizan los argumentos por defecto para inicializar adecuadamente sus miembros; los
valores se comprueban en la primera salida.
La declaración de c2 en L.19 implica una invocación del constructor por defecto
pasándole los valores 1 y 2 como argumentos. Es decir, esta sentencia equivaldría a:
c2 = CompleX::CompleX(1, 2); // Hipotética invocación
explícita al constructor
Nota: En realidad esta última sentencia es sintácticamente incorrecta; se trata solo
de un recurso pedagógico, ya que no es posible invocar de esta forma al constructor
de una clase ( 4.11.2d). Una alternativa correcta a la declaración de L.19 sería:
CompleX c2 = CompleX(1,2);
El resultado de L.19 puede verse en la segunda salida.
Finalmente, en L.20 la declaración de c3 provoca la invocación del constructor de oficio
construido por el propio compilador. Aunque la iniciación del objeto con todos sus
miembros es correcta, no lo es su inicialización ( 4.1.2). En la tercera salida vemos
como sus miembros adoptan valores arbitrarios. En realidad se trata de basura
existente en las zonas de memoria que les han sido adjudicadas.
El corolario inmediato es deducir lo que ya señalamos en la página anterior: aunque el
constructor de oficio inicia adecuadamente los miembros abstractos ( 4.11.2d), no
hace lo mismo con los escalares. Además, por una u otra causa, en la mayoría de los
casos de aplicaciones reales es imprescindible la definición explícita de uno o varios de
estos constructores ( ).
§3 Técnias de buena construcción
Recordar que un objeto no se considera totalmente construido hasta que su constructor
ha concluido satisfactoriamente. En los casos que la clase contenga sub-objetos o
derive de otras, el proceso de creación incluye la invocación de los constructores de las
subclases o de las super-clases en una secuencia ordenada que se detalla más
adelante .
Los constructores deben ser diseñados de forma que no puedan (ni áun en caso de
error) dejar un objeto a medio construir. En cas que no sea posible alistar todos los
recursos exigidos por el objeto, antes de terminar su ejecucón debe preverse un
mecanismo de destrucción y liberación de los recursos que hubiesen sido asignados.
Para esto es posible utilizar el mecanismo de excepciones.
§4 Invocación de constructores
Al margen de la particularidad que representan sus invocaciones implícitas, en general
su invocación sigue las pautas del resto de los métodos. Ejemplos:
X x1;
constructor
X::X();
[4]
X x2 = X::X()
X x3 = X();
constructor [5]
X x4();
anterior [6]
// L.1: Ok. Invocación implícita del
// Error: invocación ilegal del constructor
// Error: invocación ilegal del constructor
// L.4: Ok. Invocación legal del
// L.5: Ok. Variación sintáctica del
Nota: Observe como la única sentencia válida con invocación explícita al
constructor (L.4) es un caso de invocación de función miembro muy especial desde
el punto de vista sintáctico (esta sintaxis no está permitida con ningún otro tipo de
función-miembro, ni siquiera con funciones estáticas o destructores). La razón es
que los constructores se diferencian de todos los demás métodos no estáticos de la
clase en que no se invocan sobre un objeto (aunque tienen puntero this 4.11.6).
En realidad se asemejan a los dispositivos de asignación de memoria, en el sentido
que son invocados desde un trozo de memoria amorfa y la convierten en una
instancia de la clase [7].
Como ocurre con los tipos básicos (preconstruidos en el lenguaje), si deseamos crear
objetos persistentes de tipo abstracto (definidos por el usuario), debe utilizarse el
operador new ( 4.9.20). Este operador está íntimamente relacionado con los
constructores. De hecho, para invocar la creación de un objeto a traves de él, debe
existir un constructor por defecto ( ).
Si nos referimos a la clase CompleX definida en el ejemplo ( ), las sentencias:
{
CompleX* pt1 = new(CompleX);
CompleX* pt2 = new(CompleX)(1,2);
}
provocan la creación de dos objetos automáticos, los punteros pt1 y pt2, así como la
creación de sendos objetos (anónimos) en el montón. Observe que ambas sentencias
suponen un invocación implícita al constructor. La primera al constructor por defecto sin
argumentos, la segunda con los argumentos indicados. En consecuencia producirán
las siguientes salidas:
c1: (0,0)
c1: (1,2)
Observe también, y esto es importante, que los objetos pt1 y pt2 son destruidos
automáticamente al salir de ámbito el bloque. No así los objetos señalados por estos
punteros (ver comentario al respecto 4.11.2d2).
§5 Propiedades de los constructores
Aunque los constructores comparten muchas propiedades de los métodos normales,
tienen algunas características que las hace ser un tanto especiales. En concreto, se
trata de funciones que utilizan rutinas de manejo de memoria en formas que las
funciones normales no suelen utilizar.
§5.1 Los constructores se distinguen del resto de las funciones de una clase porque
tienen el mismo nombre que esta. Ejemplo:
class X {
public:
X();
};
// definición de la clase X
// constructor de la clase X
§5.2 No se puede obtener su dirección, por lo que no pueden declararse punteros a
este tipo de métodos.
§5.3 No pueden declararse virtuales (
class C {
...
virtual C();
};
4.11.8a). Ejemplo:
// Error !!
La razón está en la propia idiosincrasia de este tipo de funciones. En efecto, veremos
que declarar que un método es virtual ( 4.11.8a) supone indicar al compilador que el
modo concreto de operar la función será definido más tarde, en una clase derivada; sin
embargo, un constructor debe conocer el tipo exacto de objeto que debe crear, por lo
que no puede ser virtual.
§5.4 Otras peculiaridades de los constructores es que se declaran sin devolver nada, ni
siquiera void, lo que no es óbice para que el resultado de su actuación (un objeto) si
pueda ser utilizado como valor devuelto por una función:
class C { ... };
...
C foo() {
return C();
}
§5.5 No pueden ser heredados, aunque una clase derivada puede llamar a los
constructores y destructores de la superclase siempre que hayan sido declarados
public o protected ( 4.11.2a). Como el resto de las funciones (excepto main), los
constructores también pueden ser sobrecargados; es decir: Una clase puede tener
varios constructores.
En estos casos, la invocación (incluso implícita) del constructor adecuado se efectuará
según los argumentos involucrados. Es de destacar que en ocasiones, esta
multiplicidad de constructores puede conducir a situaciones realmente curiosas; incluso
se ha definido una palabra clave, explicit, para evitar los posibles efectos colaterales
( ).
§5.5 Un constructor no puede ser friend (
4.11.2a1) de ninguna otra clase.
§5.6 Una peculiaridad sintáctica de este tipo de funciones es la posibilidad de incluir
iniciadores ( 4.11.2d3), una forma de expresar la inicialización de variables fuera del
cuerpo del constructor. Ejemplo:
class X {
const int i;
char c;
public:
X(int entero, char caracter): i(entero), c(caracter) { };
};
§5.7 Como en el resto de las funciones, los constructores pueden tener argumentos por
defecto. Por ejemplo, el constructor:
X::X(int, int = 0)
puede aceptar uno o dos argumentos. Cuando se utiliza con uno, el segundo se
supone que es un cero int.
De forma análoga, el constructor
X::X(int = 5, int = 6)
puede aceptar dos, uno o ningún argumento, con sus correspondientes valores por
defecto para cuando faltan.
Observe que un constructor sin argumentos, como X::X(), no debe ser confundido
con X::X(int=0), que puede ser llamado sin argumentos o con uno; aunque en
realidad siempre tendrá un argumento. En otras palabras: Que una función pueda ser
invocada sin argumentos no implica necesariamente que no los acepte.
§5.8 Cuando se definen constructores deben evitarse ambigüedades. Es el caso de
los constructores por defecto del ejemplo siguiente:
class X {
public:
X();
X(int i = 0);
};
int main() {
X uno(10);
X dos;
X::X(int = 0)
return 0;
}
// Ok; usa el constructor X::X(int)
// Error: ambigüedad cual usar? X::X() o
§5.9 Los constructores de las variables globales son invocados por el módulo inicial
antes de que sea llamada la función main y las posibles funciones que se hubiesen
instalado mediante la directiva #pragma startup ( 1.5).
§5.10 Los objetos locales se crean tan pronto como se inicia su ámbito. También se
invoca implícitamente un constructor cuando se crea o copia un objeto de la clase
(incluso temporal). El hecho de que al crear un objeto se invoque implícitamente un
constructor por defecto si no se invoca ninguno de forma explícita, garantiza que
siempre que se instancie un objeto será inicializado adecuadamente.
En el ejemplo que sigue se muestra claramente como se invoca el constructor tan
pronto como se crea un objeto.
#include <iostream>
using namespace std;
class A {
// definición de una clase
public:
int x;
A(int i = 1) {
// constructor por defecto
x = i;
cout << "Se ha creado un objeto" << endl;
}
};
int main() {
// =========================
A a;
// se instancia un objeto
cout << "Valor de a.x: " << a.x << endl;
return 0;
}
Salida:
Se ha creado un objeto
Valor de a.x: 1
§5.11 El constructor de una clase no puede admitir la propia clase como argumento (se
daría lugar a una definición circular). Ejemplo:
class X {
public:
X(X);
};
// Error: ilegal
§5.12 Los parámetros del constructor pueden ser de cualquier tipo, y aunque no puede
aceptar su propia clase como argumento. En cambio si pueden aceptar una referencia
a objetos de su propia clase, en cuyo caso se denomina constructor-copia (su sentido
y justificación lo exponemos con más detalle en el apartado correspondiente
4.11.2d4).
Ejemplo:
class X {
public:
X(X&);
};
// Ok. correcto
Aparte del referido constructor-copia, existe otro tipo de constructores de nombre
específico: el constructor oficial y el constructor por defecto ( ).
§6 Constructor oficial
Si el programador no define explícitamente ningún constructor, el compilador
proporciona uno por defecto al que llamaremos oficial o de oficio. Es público, "inline"
( 4.11.2a), y definido de forma que no acepta argumentos. Es el responsable de que
funcionen sin peligro secuencias como esta:
class A {
int x;
};
...
A a;
// C++ ha creado un constructor "de oficio"
// invocación implícita al constructor de oficio
Recordemos que el constructor de oficio invoca implícitamente los constructores de
oficio de todos los miembros. Si algunos miembros son a su vez objetos abstractos, se
invocan sus constructores. Así sucesivamente con cualquier nivel de complejidad hasta
llegar a los tipos básicos (preconstruidos en el lenguaje 2.2) cuyos constructores son
también invocados. Recordar que los constructores de los tipos básicos inician
(reservan memoria) para estos objetos, pero no los inicializan con ningún valor
concreto. Por lo que en principio su contenido es impredecible (basura) [1]. Dicho en
otras palabras: el constructor de oficio se encarga de preparar el ambiente para que el
objeto de la clase pueda operar, pero no garantiza que los datos contenidos sean
correctos. Esto último es responsabilidad del programador y de las condiciones de
"runtime". Por ejemplo:
struct Nombre {
char* nomb;
};
struct Equipo {
Nombre nm;
size_t sz;
};
struct Liga {
int year;
char categoria;
Nombre nLiga;
Equipo equipos[10];
};
...
Liga primDiv;
En este caso la última sentencia inicia primDiv mediante una invocación al constructor
por defecto de Liga, que a su vez invoca a los constructores por defecto de Nombre y
Equipo. para crear los miembros nLiga y equipos (el constructor de Equipo es
invocado diez veces, una por cada miembro de la matriz). A su vez, cada invocación a
Equipo() produce a su vez una invocación al constructor por defecto de Nombre
(size_t es un tipo básico y no es invocado su constructor 4.9.13). Los miembros
nLiga y equipos son iniciados de esta forma, pero los miembros year y categoria
no son inicializados ya que son tipos simples, por lo que pueden contener basura.
Si el programador define explícitamente cualquier constructor, el constructor oficial
deja de existir. Pero si omite en él la inicialización de algún tipo abstracto, el compilador
añadirá por su cuenta las invocaciones correspondientes a los constructores por defecto
de los miembros omitidos ( Ejemplo).
§6.1 Constructor trivial
Un constructor de oficio se denomina trivial si cumple las siguientes condiciones:



La clase correspondiente no tiene funciones virtuales ( 4.11.8a) y no deriva de ninguna
superclase virtual.
Todos los constructores de las superclases de su jerarquía son triviales
Los constructores de sus miembros no estáticos que sean clases son también triviales
§7 Constructor por defecto
Constructor por defecto de la clase X es aquel que "puede" ser invocado sin
argumentos, bien porque no los acepte, bien porque disponga de argumentos por
defecto ( 4.4.5).
Como hemos visto en el epígrafe anterior, el constructor oficial creado por el
compilador si no hemos definido ningún constructor es también un constructor por
defecto, ya que no acepta argumentos.
Tenga en cuenta que diversas posibilidades funcionales y sintácticas de C++ precisan
de la existencia de un constructor por defecto (explícito u oficial). Por ejemplo, es el
responsable de la creación del objeto x en una declaración del tipo X x;.
§8 Un constructor explícito puede ser imprescindible
En el primer ejemplo ( ), el programa ha funcionado aceptablemente bien utilizando el
constructor de oficio en una de sus clases, pero existen ocasiones en que es
imprescindible que el programador defina uno explícitamente, ya que el suministrado
automáticamente por el compilador no es adecuado.
Consideremos una variación del citado ejemplo en la que definimos una clase para
contener las coordenadas de puntos de un plano en forma de matrices de dos
dimensiones:
#include <iostream>
using namespace std;
class Punto {
public: int coord[2];
};
int main() {
// ==================
Punto p1(10, 20);
// L.8:
cout << "Punto p1; X == " << coord[0] << "; Y == " <<
coord[1] << endl;
}
Este programa produce un error de compilación en L.8. La razón es que si necesitamos
este tipo de inicialización del objeto p1, utilizando una lista de argumentos, es
imprescindible la existencia de un constructor explícito ( 4.11.2d3). Es decir, la
versión correcta del programa seria:
#include <iostream>
using namespace std;
class Punto {
public: int coord[2];
Punto(int x = 0, int y = 0) {
coord[0] = x; coord[1] = y;
}
};
// construtor explícito
// inicializa
int main() {
// ==================
Punto p1(10, 20);
// L.8: Ok.
cout << "Punto p1; X == " << coord[0] << "; Y == " <<
coord[1] << endl;
}
§8.1 La anterior no es por supuesto la única causa que hace necesaria la existencia de
constructores explícitos. Más frecuente es el caso de que algunas de las variables de la
clase deban ser persistentes. Por ejemplo: supongamos que en el caso anterior
necesitamos que la matriz que almacena las coordenadas necesite este tipo de
almacenamiento. En este caso, puesto que la utilización del especificador static
aplicado a miembros de clase puede tener efectos colaterales indeseados ( 4.11.7), el
único recurso es situar el almacenamiento en el montón ( 1.3.2), para lo que
utilizamos el operador new ( 4.9.20) en un constructor definido al efecto. La
definición de la clase tendría el siguiente aspecto:
class Punto {
public: int* coord;
Punto(int x = 0, int y =
defecto
coord = new int[2];
coord[0] = x; coord[1]
cout << "Creado punto;
<< coord[0] << ";
}
};
0) {
// construtor por
// asigna espacio
= y;
// inicializa
X == "
Y == " << coord[1] << endl;
Posteriormente se podrían instanciar objetos de la clase Punto mediante expresiones
como:
Punto p1;
Punto p2(3, 4);
argumentos
Punto p3 = Punto(5, 6);
argumentos
Punto* ptr1 = new(Punto)
argumentos
Punto* ptr2 = new(Punto)(7, 8)
argumentos
// invocación implícita
// invocación implícita con
// invocación explícita con
// invocación implícita sin
// invocación implícita con
§9 Orden de construcción
Dentro de una clase los constructores de sus miembros son invocados antes que el
constructor existente dentro del cuerpo de la propia clase. Esta invocación se realiza en
el mismo orden en que se hayan declarado los elementos. A su vez, cuando una clase
tiene más de una clase base (herencia múltiple 4.11.2c), los constructores de las
clases base son invocados antes que el de la clase derivada y en el mismo orden que
fueron declaradas. Por ejemplo en la inicialización:
class Y {...}
class X : public Y {...}
X one;
los constructores son llamados en el siguiente orden:
Y();
X();
// constructor de la clase base
// constructor de la clase derivada
En caso de herencia múltiple:
class X : public Y, public Z
X one;
los constructores de las clase-base son llamados primero y en el orden de declaración:
Y();
Z();
X();
// constructor de la primer clase base
// constructor de la segunda clase base
// constructor de la clase derivada
Nota: Al tratar de la destrucción de objetos ( 4.11.2d2), veremos que los
destructores son invocados exactamente en orden inverso al de los constructores.
§9.1 Los constructores de clases base virtuales ( 4.11.8a) son invocados antes que
los de cualquier clase base no virtual. Si la jerarquía contiene múltiples clases base
virtuales, sus constructores son invocados en el orden de sus declaraciones. A
continuación de invocan los constructores del resto de las clase base, y por último el
constructor de la clase derivada.
§9.2 Si una clase virtual deriva de otra no virtual, primero se invoca el constructor de la
clase base (no virtual), de forma que la virtual (derivada) pueda ser construida
correctamente. Por ejemplo, el código:
class X : public Y, virtual public Z
X one;
origina el siguiente orden de llamada en los constructores:
Z();
Y();
X();
// constructor de la clase base virtual
// constructor de la clase base no virtual
// constructor de la clase derivada
Un ejemplo más complicado:
class base;
class base2;
class level1 : public base2, virtual public base;
class level2 : public base2, virtual public base;
class toplevel : public level1, virtual public level2;
toplevel view;
El orden de invocación de los constructores es el siguiente:
base();
base2();
level2();
base2();
level1();
//
//
//
//
//
//
//
clase virtual de jerarquía más alta
base es construida solo una vez
base no virtual de la base virtual level2
debe invocarse para construir level2
clase base virtual
base no virtual de level1
otra base no virtual
toplevel();
§9.3 Si una jerarquía de clases contiene múltiples instancias de una clase base virtual,
dicha base virtual es construida solo una vez. Aunque si existen dos instancias de la
clase base: virtual y no virtual, el constructor de la clase es invocado solo una vez para
todas las instancias virtuales y después una vez para cada una de las instancias no
virtuales.
§9.4 En el caso de matrices de clases, los constructores son invocados en orden
creciente de subíndices.
§10 Los constructores y las funciones virtuales
Debido a que los constructores de las clases-base son invocados antes que los de las
clases derivadas, y a la propia naturaleza del mecanismo de invocación de funciones
virtuales ( 4.11.8a), el mecanismo virtual está deshabilitado en los constructores, por
lo que es peligroso incluir invocaciones a tales funciones en ellos, ya que podrían
obtenerse resultados no esperados a primera vista.
Considere los resultados del ejemplo siguiente, donde se observa que la versión de la
función fun invocada no es la que cabría esperar en un funcionamiento normal del
mecanismo virtual.
#include <string>
#include <iostream>
using namespace std;
class B {
// superclase
public:
virtual void fun(const string& ss) {
cout << "Funcion-base: " << ss << endl;
}
B(const string& ss) {
// constructor de superclase
cout << "Constructor-base\n";
fun(ss);
}
};
class D : public B {
// clase derivada
string s;
// private por defecto
public:
void fun(const string& ss) { cout << "Funcion-derivada\n";
s = ss; }
D(const string& ss) :B(ss) { // constructor de subclase
cout << "Constructor-derivado\n";
}
};
int main() {
D d("Hola mundo");
constructor D
}
// =============
// invocación implícita a
Salida:
Constructor-base
Funcion-base: Hola mundo
Constructor-derivado
Nota: La invocación de destructores ( 4.11.2d2) se realiza en orden inverso a los
constructores: Las clases derivadas se destruyen antes que las clases-base [2].
Por esta razón el mecanismo virtual también está deshabilitado en los destructores
(lo que no tiene nada que ver con que los destructores puedan ser en sí mismos
funciones virtutales 4.11.2d2). Así pues, en la ejecución de un destructor solo se
invocan las definiciones locales de las funciones implicadas. De lo contrario se
correría el riesgo de referenciar la parte derivada del objeto que ya estaría
destruida.
§11 Constructores de conversión
Normalmente a una clase con constructor de un solo parámetro puede asignársele un
valor que concuerde con el tipo del parámetro. Este valor es automáticamente
convertido de forma implícita en un objeto del tipo de la clase a la que se ha asignado.
Por ejemplo: la definición:
class X {
public:
X();
X(int);
X(const char*, int = 0);
};
// constructor C-1
// constructor C-2
// constructor C-3
en la que se han definido dos constructores que pueden ser utilizados con un solo
argumento, permite que las siguientes asignaciones sean legales:
void f() {
X a;
X b = X();
X c = X(1);
X d(1);
X e = X("Mexico");
X f("Mexico");
X g = 1;
X h = "Madrid";
a = 2;
}
//
//
//
//
//
//
//
//
//
Ok invocado C-1
Ok idem.
Ok invocado C-2
Ok igual que el anterior
Ok invocado C-3
Ok igual que el anterior
L.1 Ok.
L.2 Ok.
L.3 Ok.
La explicación de las tres últimas sentencias es la siguiente:
En L.1, el compilador intenta convertir el Rvalue (que aquí es una constante numérica
entera de valor 1) en el tipo del Lvalue, que aquí es la declaración de un nuevo objeto
(una instancia de la clase). Como necesita crear un nuevo objeto, utilizará un
constructor, de forma que busca si hay uno adecuado en X que acepte como argumento
el tipo situado a la derecha. El resultado es que el compilador supone un constructor
implícito a la derecha de L.1:
X a = X(1);
// interpretación del compilador para L.1
El proceso se repite en la sentencia L.2 que es equivalentes a:
X B = X("Madrid");
// L.2bis
La situación en L.3 es completamente distinta, ya que en este caso ambos operandos
son objetos ya construidos. Para poder realizar la asignación, el compilador intenta
convertir el tipo del Rvalue al tipo del Lvalue, para lo cual, el mecanismo de conversión
de tipos busca si existe un constructor adecuado en X que acepte el operando
derecho. Caso de existir se creará un objeto temporal tipoX que será utilizado como
Rvalue de la asignación. La asignación propiamente dicha es realizada por el operador
correspondiente (explícito o implícito) de X. La página adjunta incluye un ejemplo que
muestra gráficamente el proceso seguido ( Ejemplo)
Este tipo de conversión automática se realiza solo con constructores que aceptan un
argumento o que son asimilables (como C-2), y suponen una conversión del tipo
utilizado como argumento al tipo de la clase. Por esta razón son denominadas
conversiones mediante constructor, y a este tipo de constructores constructores de
conversión ("Converting constructor"). Su sola presencia habilita no solo la conversión
implícita, también la explícita. Ejemplo:
class X {
public:
X(int);
};
// constructor C-2
la mera existencia del constructor C-2 en la clase X, permite las siguientes
asignaciones:
void f() {
X a = X(1)
constructor
X a = 1;
a = 2;
a = (X) 2;
tradicional)
a = static_cast<X>(2);
C++)
}
// L1: Ok. invocación explícita al
// Ok. invocación implícita X(1)
// Ok. invocación implícita X(2)
// Ok. casting explícito (estlo
// Ok. casting explícito (estilo
Si eliminamos el constructor C-2 de la declaración de la clase, todas estas sentencias
serían erróneas.
Observe que en L1 cabría hacerse una pregunta: ¿Se trata de la invocación del
constructor o un modelado explícito al estilo tradicional?. La respuesta es que se trata
de una invocación al constructor, y que precisamente el modelado (explícito o implícito)
se apoya en la existencia de este tipo de constructores para realizar su trabajo.
Temas relacionados:
Operadores de conversión (
4.9.18k)
Conversión automática a tipos simples (
4.13.6)
§12 Constructor explicit
El problema es que en ocasiones el comportamiento descrito en el epígrafe anterior
puede resultar indeseable y enmascarar errores. Es posible evitarlo declarando el
constructor de la clase con la palabra clave explicit, dando lugar a los denominados
constructores explicit [3]. En estos casos, los objetos de la clase solo podrán recibir
asignaciones de objetos del tipo exacto. Cualquier otra asignación provocará un error
de compilación.
§12.1 La sintaxis de utilización es:
explicit <declaración de constructor de un solo parámetro>
Aplicándolo al ejemplo anterior:
class X {
public:
explicit X(int);
// constructor C-2b
explicit X(const char*, int = 0); // constructor C-3b
};
...
void f() {
X a = 1;
// L.1 Error!!
X B = "Madrid";
// L.2 Error!!
a = 2;
// L.3 Error!!
}
Ahora los objetos de la clase X, dotada con constructores implicit, solo pueden recibir
asignaciones de objetos del mismo tipo:
void f() {
X a = X(1);
X b = X("Madrid", 0);
a = (X) 2;
}
// L.1 Ok.
// L.2 Ok.
// L.3 Ok.
En L.3 se ha utilizado una conversión de tipos ("Casting") explícita ( 4.9.9). Para
realizarla el mecanismo de conversión busca si en la clase X existe un constructor que
acepte como argumento el tipo de la derecha, con lo que estaríamos en el caso de L.1.
§13 Constructores privados y protegidos
Cuando los constructores no son públicos (son privados o protegidos 4.11.2b-1), no
pueden ser accedidos desde el exterior, por lo que no pueden ser invocados explícita ni
implícitamente al modo tradicional (§4 ). Ejemplo:
class C {
int x;
C(int n=0): x(n) {}
// privado por defecto
};
...
void foo() {
C c(1);
// Error!! cannot access private member
}
Además, como los clases derivadas necesitan invocar los constructores de las
superclases para instanciar sus objetos, caso de no ser protegidos o públicos también
pueden existir limitaciones para su creación. Ejemplo:
class B {
int x;
B (): x(10) {}
};
class D : public B {
...
};
void foo() {
D d;
// Error!! no appropriate default constructor
available
...
}
Puesto que los miembros private o protected no pueden ser accedidos desde el
exterior de la clase, este tipo de constructores se suelen utilizar siempre a través de
funciones-miembro públicas con objeto de garantizar cierto control sobre los objetos
creados. El esquema de utilización sería el siguiente:
class C {
C(int n) { /* constructor privado */ }
public:
static C makeC(int m) {
...
return C(m);
}
...
};
void foo() {
C c = C::makeC(1);
}
// Ok.
Observe que makeC() es estática para que pueda ser invocada con independencia de
la existencia de cualquier objeto ( 4.11.7). Observe también que mientras una
expresión como:
C c = C(1);
es una invocación al constructor. En cambio la sentencia
C c = C::makeC(1);
es una invocación al operador de asignación ya que la función devuelve un objeto que
será tomado como Rvalue de la asignación.
Esta técnica, que utiliza constructores privados o protegidos junto con métodos públicos
para accederlos, puede prevenir algunas conversiones de tipo no deseadas, pudiendo
constituir una alternativa al uso de constructores explicit (§12 ).
http://www.zator.com/Cpp/E4_11_2d1.htm
:: CONSTRUCTORES ::
Cuando una clase base tiene un constructor y una clase
derivada también, al crear el objeto, se llama primero al
constructor de la clase base, y cuando la ejecución de éste
termina, se llama al constructor de la clase derivada.
Gracias a que el constructor de la clase base es llamado, es
posible inicializar la clase base, desde el constructor de la clase
derivada. Esto se logra pasando una lista de los constructores
de las clases base, con sus respectivos parametros.
class base
{
//Cuerpo de la clase base
};
class derivada :[public/private/..] base
{
//Cuerpo de la clase derivada
};
:: DESTRUCTORES ::
En el caso de los destructores, el orden en que son llamados, es inverso al de los constructores, es decir
es llamado primero el destructor de la clase derivada, después son llamados los destructores de las
clases miembro y, luego el destructor de la clase base. Como los destructores no requieren argumentos,
no hay que usar una sintaxis especial.
:: EJEMPLO ::
En la sintaxis para la declaración de constructores para clases derivadas, encontramos después de los
parametros del constructor de la clase derivada, en este caso la clase CBiblioteca, dos puntos y un
llamado al constructor de la clase base con los parametros que se requieran.
//Clase mueble
class CMueble
{
public:
//Constructor con parametros
CMueble(int Alto,int Ancho);
//Declaracion de la clase
};
class CBiblioteca :public CMueble
{
//Cuerpo de la clase derivada
public:
CBiblioteca(int Alto,int Ancho);
};
//Constructor clase derivada
CBiblioteca::CBiblioteca(int Alto,int Ancho):
CMueble(Alto,Ancho)
{
}
http://ieee.udistrital.edu.co/concurso/programacion_orientada_objetos/poo/herenci1.html
5.18
Aplicaciones.
Unidad 6. Polimorfismo y
reutilización
6.9 Concepto del polimorfismo.
POLIMORFISMO Y REUTILIZACIÓN.
Otro concepto interesante, con Importantes aportaciones en áreas tales como la flexibilidad o la
legibilidad del software, es el de polimorfismo. Tras este termino, un tanto oscuro, subyace una
idea bastante simple. En su más amplia expresión, el polimorfismo puede definirse como:
"el mecanismo que permite definir e Invocar funciones idénticas en denominación e interfaz,
pero con implementaron diferente".
Esta definición introduce un aspecto muy importante del polimorfismo: la asociación, o vinculo,
entre cada llamada a una de estas funciones polimorfismo y la implementación concreta
finalmente invocada. Cuando este vinculo puede establecerse en tiempo de compilación, se suele
hablar de vinculación estatica (static binding). Por contra, cuando la implementación a emplear,
s610 puede determinarse en tiempo de ejecución, el termino empleado es el de vinculación
dinámica (dynamic binding).
En C++, por ejemplo, la vinculación dinámica de las llamadas a funciones polimórficas (en C++
reciben el calificativo de funciones virtuales) se consigue en base a la posibilidad que ofrece este
lenguaje de utilizar un puntero a objetos de una clase como puntero a objetos de cualquiera de las
clases descendientes de la anterior. Así, cuando la llamada a una función virtual, definida en una
clase y en una o varias de sus descendientes, se realiza sobre un objeto que viene referenciado
mediante un puntero a la clase padre, el compilador es incapaz de determinar que implementación
debe asociar a la llamada, ya que desconoce cual será la clase del objeto en el momento de su
ejecución. Dicha determinación debe quedar aplazada, por tanto, hasta ese instante.
Como se puede observar, el concepto de polimorfismo en C++, y en general en casi todos los
lenguajes de programación basados en el paradigma de objeto, esta estrechamente ligado al
concepto de herencia, dado que las funciones polimórficas sólo pueden definirse entre clases que
guardan entre sí una relación de parentesco (clases con un antecesor común).
Aunque el concepto de polimorfismo es una de las principales innovaciones del desarrollo
orientado a objetos, posee antecedentes históricos en otros mecanismos más sencillos, como son
la conversión forzada (casting) y la sobrecarga de identificadores, ideados con el fin de introducir
un cierto grado de flexibilidad en el manejo de tipos de datos heterogéneos.
En el siguiente apartado se desarrolla un pequeño ejemplo de aplicación de ambos conceptos, en
el que quedan claramente de manifiesto las ventajas potenciales de una correcta utilización de los
mismos. Para su implementación se ha elegido el lenguaje C ++, fundamentalmente por dos
razones:


La amplia difusión que esta adquiriendo este lenguaje en los ambientes
de desarrollo.
La enorme similitud de su sintaxis con la del lenguaje C, del cual deriva
http://www.tid.es/presencia/publicaciones/comsid/esp/articulos/vol23/herencia/herencia.html
Polimorfismo: En un sentido literal, Polimorfismo significa la cualidad de tener más de
una forma. En el contexto de POO, el Polimorfismo se refiere al hecho de que una
simple operación puede tener diferente comportamiento en diferentes objetos. En otras
palabras, diferentes objetos reaccionan al mismo mensaje de modo diferente. Los
primeros lenguajes de POO fueron interpretados, de forma que el Polimorfismo se
contemplaba en tiempo de ejecución. Por ejemplo, en C++, al ser un lenguaje
compilado, el Polimorfismo se admite tanto en tiempo de ejecución como en tiempo de
compilación
Decimos entonces que:
El tema de la Programación Orientada a Objetos (Object Oriented Programming O-O-P)
sigue siendo para el que escribe un territorio inquietante, interesante y en gran medida
desconocido, como parece ser también para la gran mayoría de los que estamos en el
campo de la programación. Sin tratar de excluir a aquellos que han afrontado este
desarrollo desde el punto de vista académico y formal (maestrías y doctorados) el tema
se antoja difícil para los no iniciados. Con este breve artículo me dirigiré en particular a
la gran base de programadores prácticos que andamos en búsqueda de mejores
herramientas de desarrollo de programas, que faciliten el trabajo de nuestros usuarios y
a la vez disminuyan la gran cantidad de considerandos que aparecen al empeñarnos en
un proyecto de cómputo.
Como muchos de ustedes, me topé con el concepto de O-O-P como parte de esa
búsqueda y al explorarlo apareció el gusanillo de la curiosidad. A lo largo de mi
actividad como programador, y cuando se dio la necesidad, no tuve ningún problema en
convertir mis habilidades de programación en FORTRAN de IBM 1130 al BASIC de la
PDP, pues sólo era cuestión de aprender la sintaxis del lenguaje, ya que las estrategias
de programación y los algoritmos eran iguales. Posteriormente, al manejar el PASCAL
se requirió un importante esfuerzo en entender la filosofía de las estructuras, lo cual
modificaba la manera de ver (conceptualizar) a los datos y a las partes constitutivas de
un programa.
Posteriormente aparece el QuickBasic, que adopté inmediatamente por la familiaridad
con el BASIC (ley del menor esfuerzo). Ofrecía estructuras de datos (tipos y registros
complejos), además de estructuras de instrucciones en procedimientos y módulos;
editor "inteligente" que revisa la sintaxis y ejecución de las instrucciones mientras se
edita el programa, generación de ejecutable una vez terminado (.EXE), existencia de
bibliotecas externas y enlace con módulos objeto generados en otro lenguaje. ¿Qué
más podía yo pedir?
Pero la necesidad de estar en la ola de moda es más fuerte que el sentido común. Las
aplicaciones en Windows siempre han despertado la envidia de los programadores, al
hacer ver sus programas pálidos e insulsos por comparación. Solución: programar en
Windows.
Originalmente programar en Windows representaba un largo y tedioso camino para
dominar las complejas herramientas de desarrollo. Sólo recientemente han aparecido
desarrolladores de aplicaciones para Windows que le permiten al programador pintar
sus ventanas y realizar los enlaces entre los objetos con programación tradicional,
evitando en gran medida involucrarse con los conceptos complicados de los objetos.
Sin embargo no dejaron de inquietarme algunos conceptos marcados por O-O-P, según
los cuales serán los pilares del futuro de la programación de componentes y de objetos
distribuidos en redes, en donde la actual programación cliente/servidor pareciera por
comparación el FORTRAN o el COBOL de ahora.
Pidiendo perdón de antemano a los puristas de las definiciones y conceptos de O-O-P,
expondré el resultado de mis propias indagaciones sobre este campo, esperando que al
paciente lector y posible programador le resulte menos complicado que a mí asimilar los
elementos básicos de O-O-P.
Los principales conceptos que se manejan en la Programación Orientada a Objetos
son: 1. encapsulado, 2. herencia y 3. Polimorfismo.
Según esto, la encapsulación es la creación de módulos autosuficientes que contienen
los datos y las funciones que manipulan dichos datos. Se aplica la idea de la caja negra
y un letrero de "prohibido mirar adentro". Los objetos se comunican entre sí
intercambiando mensajes. De esta manera, para armar aplicaciones se utilizan los
objetos cuyo funcionamiento está perfectamente definido a través de los mensajes que
es capaz de recibir o mandar. Todo lo que un objeto puede hacer está representado por
su interfase de mensajes. Para crear objetos, el programador puede recurrir a diversos
lenguajes como el C++, el Smalltalk, el Visual Objects y otros. Si se desea solamente
utilizar los objetos y enlazarlos en una aplicación por medio de la programación
tradicional se puede recurrir al Visual Basic, al CA-Realizer, al Power Builder, etc.
El concepto de herencia me pareció sencillo de entender una vez que capté otro
concepto de O-O-P: las clases. En O-O-P se acostumbra agrupar a los objetos en
clases. Esto es muy común en la vida diaria. Todos nosotros tendemos a clasificar los
objetos comunes por clases. Manejamos la clase mueble, la clase mascota, la clase
alimento, etc. Obviamente en el campo de la programación esta clasificación es más
estricta. ¿Cuál es el sentido de las clases? Fundamentalmente evitar definir los objetos
desde cero y facilitar su rehuso. Si trabajamos con clases, al querer definir un nuevo
objeto, partimos de alguna clase definida anteriormente, con lo que el objeto en
cuestión hereda las características de los objetos de su clase. Imaginemos que
creamos una clase "aves" y describimos las características de las aves (plumas, pico,
nacen de huevo, etc.). Más adelante necesitamos una clase "pingüino". Como
pertenece a "aves" no requerimos volver a declarar lo descrito sino marcamos que
"pingüino" es una subclase de "aves" con lo que "pingüino" hereda todas sus
características. A continuación sólo declaramos los detalles que determinan lo que
distingue a "pingüino" de "aves". Asimismo podemos declarar "emperador" como una
subclase de "pingüino", con lo que "emperador" heredará todas las características de
las superclases "pingüino" y "aves" más las características que nosotros declaremos en
particular para "emperador". En un programa (imaginario por supuesto) yo puedo utilizar
estas clases (aves, pingüino y emperador). El hecho de colocar a un individuo en
particular en estas clases es lo que se llama objeto y se dice que es una instancia de
una clase. Así, si yo coloco a Fredy (un pingüino emperador) en mi programa, se dice
que el objeto Fredy es una instancia de la clase emperador. Fredy aparecerá en mi
programa con todas las características (herencia) de aves, de pingüino y de emperador.
Por otra parte, entender el concepto de Polimorfismo implicó un buen número de horas
de indagación y búsqueda de ejemplos. Espero que éste resulte claro: supóngase que
declaramos un objeto llamado Suma. Este objeto requiere dos parámetros (o datos)
como mensaje para operar. En la programación tradicional tendríamos que definir el tipo
de datos que le enviamos, como por ejemplo dos números enteros, dos números reales,
etc. En O-O-P el tipo de dato se conoce hasta que se ejecuta el programa.
http://www.monografias.com/trabajos/refercomp/refercomp.shtml
Funciones virtuales: polimorfismo.
En esta ocasión vamos abordar un concepto muy importante de la programación orientada a objetos: el
polimorfismo. Esta característica de la POO, permite que podamos construirnos métodos para nuestras
clase derivadas que parten de una misma clase base, para que adopten comportamientos totalmente
distintos. Es un concepto realmente potente y que se lleva a cabo mediante la utilización de funciones
virtuales. Nosotros ya las hemos utilizado. Si te acuerdas, en el capítulo anterior declarábamos la clase
que redefiníamos como virtual. ¡Hemos utilizado el polimorfismo y sin enterarnos!.
Una función virtual es un mecanismo que permite a clases derivadas redefinir a las funciones de las
clases base. Por tanto, hasta ahora, nos debemos de quedar con que el polimorfismo es una acción que
se puede implementar en clases distintas pero que tienen en común el hecho de heredar de una clase
base común. Dicho método, pese a ser común para los objetos derivados, es tratado de forma distinta y,
por tanto, dando resultados también diferentes dependiendo de con qué clase lo invoquemos. ¿Cómo
conseguirlo?. Para poder implementar el polimorfismo tenemos las funciones virtuales. Las funciones
virtuales se definen en la clase base y son las que serán redefinidas, luego, en las clases derivadas.
La declaración de una función virtual se consigue mediante la palabra clave virtual precediendo a la
declaración de la función. Por ejemplo:
virtual void PonInformacion(void);
En este caso, hemos definido una función virtual llamada PonInformacion que pertenece a una clase
base y que podrá ser redefinida, completamente, en una clase derivada para que actúe de forma
totalmente distinta a cómo lo hace en la clase base. Es muy importante que la función de la clase
base que vamos a redefinir lleve el identificador virtual pues de lo contrario, la cosa no funcionará
como es de esperar. Veremos estos problemas más adelante.
A continuación vamos a poner un ejemplo en el que, utilizando la función de arriba, vamos a ver cómo
podemos hacer para que una clase que herede dicha función y produzca resultados totalmente distintos a
los que se han definido en la clase base para ella. Así mismo, utilizamos new y delete para refrescar la
memoria (y nunca mejor dicho :-). Recordad que con new y delete podemos crear objetos (o variables)
dinámicamente y de forma muy sencilla.
#include <iostream.h>
class ClaseBase
{
// Esta es la clase base.
// Definimos una función de miembro para la clase base
// que además es virtual e "inline"
public:
vitual void PonInformacion(void) { cout << "\n¡Hola!"; }
};
class ClaseDerivada : public ClaseBase
{
//
//
//
//
Hemos definido una clase derivada de la clase ClaseBase.
Esta clase, va a utilizar el concepto de polimorfismo
redefiniendo por completo la función PonInformacion
que hereda de la clase ClaseBase. También es "inline".
public:
void PonInformacion(void) { cout << "\n¡Adios!"; }
};
int main(void)
{
ClaseBase *base = new ClaseBase;
ClaseDerivada *derivada = new ClaseDerivada;
// Ahora llamamos a los métodos de cada una para observar
// que el resultado es distinto.
base->PonInformacion();
derivada->PonInformacion();
delete base;
delete derivada;
return 0;
}
Bueno, el resultado está claro, ¿no?. Mientras que la clase declarada como ClaseBase e instanciada con
base, pone en pantalla "¡Hola!", la clase derivada, que ha redefinido la función virtual PonInformacion,
pone "¡Adiós!". Es un ejemplo, en definitiva, muy sencillo en el que se puede ver cómo hacer que una
función ya definida en una clase base cambie totalmente según nuestras necesidades: es el
polimorfismo.
Otra de las cosas que vimos en el capítulo anterior era el hecho de utilizar dentro de la función de la clase
derivada, a la función virtual de la clase base. Si nosotros hubiéramos implementado así a la función
virtual de la clase derivada:
void PonInformacion(void)
{
// Suponemos que la declaración es inline ya que si no fuera
// inline deberíamos de poner
// void ClaseDerivada::PonInformacion(void)
ClaseBase::PonInformacion();
cout << "\n¡Adios!";
}
Cuando llamáramos a la función PonInformacion() por medio de la clase derivada, esto es, cuando
hiciéramos:
derivada->PonInformacion();
Lo que nos saldría por pantalla sería:
¡Hola!
¡Adios!
En contraposición a lo que nos sale en el programa original en el que no llamamos a la función de la
clase base, es decir, en el ejemplo original saldría nada más ¡Adiós!. Esto es así porque dentro del
cuerpo PonInformacion() que está implementado en la clase ClaseDerivada, llamamos antes de nada a la
función de la clase base ClaseBase que se trata como una función heredada más.
Aclarando todo un poco
Si recuerdas el capítulo anterior del curso, habrás observado que, en cierta forma, ya hemos utilizado el
polimorfismo, es decir, en el anterior capítulo redefiníamos funciones miembro de una clase base
utilizando sus características comunes a la clase derivada en la que trabajábamos pero añadiendo una
serie de especificaciones extra para que "además se hiciera otra cosa".
Hasta ahora hemos tratado funciones que son virtuales simples. Recordemos que una función virtual es
aquella que, habiéndose declarado en una clase base, vuelve a declararse y a implementarse en una
clase derivada, es decir, se redefine en la clase derivada. De esta forma, cuando el objeto instanciado a
una clase derivada, llama a esa función virtual, lo que se hace es llamar a la función de la clase derivada
no a la función de la clase base (a no ser que, dentro de la función de la clase derivada, se llame a la
función de la clase base).
El ejemplo clásico de utilización de funciones virtuales está en el de crear una clase base llamada Forma
con una función de miembro para dibujar y de nombre Dibujar. Si nosotros creamos dos clases derivadas
de la clase base Forma llamadas Circulo y Rectangulo, respectivamente, y redefinimos la función virtual
Dibujar en cada una de las clases base, cuando la llamemos desde cada una de las instancias a la
función Dibujar, el compilador sabrá a qué función llamar.
Cabe decir también, y esto es importante pues puede dar lugar a confusiones, que si tenemos una
función en una clase base que no esté marcada como virtual y después creamos en una clase derivada
otra función con el mismo nombre los resultados no van a ser los esperados... Lo que hará el compilador
será llamar directamente a la función implementada en la clase base y pasar "olímpicamente" de la
implementación de la clase derivada. ¿y si, habiendo declarado una función en la clase base como
virtual, luego se nos olvida redefinirla en la clase derivada?. Bueno, en este caso, se llamará a la función
de la clase base y no pasará nada.
Polimorfismo
Otro concepto de la OOP es el polimorfismo. Un objeto solamente tiene una
forma (la que se le asigna cuando se construye ese objeto) pero la referencia a
objeto es polimórfica porque puede referirse a objetos de diferentes clases (es
decir, la referencia toma múltiples formas). Para que esto sea posible debe
haber una relación de herencia entre esas clases. Por ejemplo,
considerando la figura anterior de herencia se tiene que:
o
o
o
o
o
Una referencia a un objeto
a un objeto de la clase A.
Una referencia a un objeto
a un objeto de la clase A.
Una referencia a un objeto
a un objeto de la clase A.
Una referencia a un objeto
a un objeto de la clase D.
Una referencia a un objeto
a un objeto de la clase A.
de la clase B también puede ser una referencia
de la clase C también puede ser una referencia
de la clase D también puede ser una referencia
de la clase E también puede ser una referencia
de la clase E también puede ser una referencia
http://www.fi-b.unam.mx/pp/profesores/carlos/java/java_basico3_5.html
6.10
Clases abstractas.
6.10.1 Definición.
Funciones virtuales puras implican clases abstractas.
Se puede decir que las funciones virtuales puras son aquellas que, para implementarse, han de ser
redefinidas, es decir, que no sólo tienen sino que deben ser redefinidas. Aquellas clases que tengan una
función virtual pura se denominan clases abstractas y tienen la gran particularidad de que de ellas no se
puede crear instancias u objetos.
Quedamos, pues, en que para crear una clase abstracta sólo hace falta definir una función virtual
pura.
¿Y cómo definimos una función virtual pura?. Para definir una función virtual pura, tenemos que asignar a
la función un puntero NULL o, lo que es lo mismo, un valor 0. Suponiendo que queremos implementar
una función llamada Forma como virtual pura, deberíamos de poner así:
virtual void Forma() = 0;
La clase que contenga una sola función virtual pura pasa a ser una clase abstracta independientemente
de que el resto de sus funciones no sean virtuales puras. Ya veis que fácil es crear una función virtual
pura y que poder tienen al implicar también la creación de una clase abstracta que, recordad, no puede
ser nunca instanciada.
Las funciones virtuales puras han de ser definidas cuando queramos crear una verdadera clase raíz de
una serie de clases derivadas ciertamente importante. No conviene abusar de esta característica del C++
a no ser que vuestro diseño lo necesite necesariamente y estéis delante de un proyecto con una
complejidad notable.
http://usuarios.lycos.es/macedoniamagazine/poo6.htm
Clases abstractas
§1 Sinopsis
La abstracción es un recurso de la mente (quizás el más
característico de nuestra pretendida superioridad respecto del
mundo animal). Por su parte los lenguajes de programación
permiten expresar la solución de un problema de forma
comprensible simultáneamente por la máquina y el humano.
Son como un puente entre la abstracción de la mente y una serie
de instrucciones ejecutables por un dispositivo electrónico. En
consecuencia, la capacidad de abstracción es una característica
deseable de los lenguajes artificiales, pues cuanto mayor sea,
mayor será su aproximación al lado humano, es decir, con la
imagen existente en la mente del programador. En este sentido,
la introducción de las clases en los lenguajes orientados a
objetos ha representado un importante avance respecto de la
programación tradicional y dentro de ellas, las denominadas
clases abstractas son las que representan el mayor grado de
abstracción.
De hecho, las clases abstractas presentan un nivel de
"abstracción" tan elevado que no sirven para instanciar objetos
de ellas. Representan los escalones más elevados de algunas
jerarquías de clases y solo sirven para derivar otras clases, en las
que se van implementando detalles y concreciones, hasta que
finalmente presentan un nivel de definición suficiente para poder
instanciar objetos concretos. Se suelen utilizar en aquellos casos
en que se quiere que una serie de clases mantengan una cierta
característica o interfaz común. Por esta razón a veces se dice
de ellas que son "pura interfaz".
Resulta evidente en el ejemplo de la figura que los diversos tipos
de motores tienen características diferentes. Realmente tienen
poco en común un motor eléctrico de corriente alterna y una
turbina de vapor. Sin embargo, la construcción de una jerarquía
en la que todos motores desciendan de un ancestro común, la
clase abstracta "Motores", presenta la ventaja de unificar la
interfaz. Aunque evidentemente su definición será tan
"abstracta", que no pueda ser utilizada para instanciar
directamente ningún tipo de motor. El creador del lenguaje dice
de ellas que soportan la noción de un concepto general del que
solo pueden utilizarse variantes más concretas [2].
§2 Clases abstractas
Una clase abstracta es la que tiene al menos una función
virtual pura (como hemos visto, una función virtual es
especificada como "pura" haciéndola igual a cero
4.11.8a).
Nota: Recordemos que las clases que tienen al menos una
función virtual (o virtual pura) se denominan clases
polimórficas ( 4.11.8). Resulta por tanto que todas las
clases abstractas son también polimórficas, pero no
necesariamente a la inversa.
§3 Reglas de uso:

Una clase abstracta solo puede ser usada como clase base
para otras clases, pero no puede ser instanciada para crear
un objeto 1.

Una clase abstracta no puede ser utilizada como
argumento o como retorno de una función 2.

Si puede declararse punteros-a-clase abstracta 3 [1].

Se permiten referencias-a-clase abstracta, suponiendo que
el objeto temporal no es necesario en la inicialización 4.
§4 Ejemplo:
class Figura {
// clase abstracta (C.A.)
point centro;
...
public:
getcentro() { return center; }
mover(point p) { centro = p; dibujar(); }
virtual void rotar(int) = 0;
// función virtual pura
virtual void dibujar() = 0;
// función virtual pura
virtual void brillo() = 0;
// función virtual pura
...
};
...
Figura x;
// ERROR: intento de instanciar
una C.A.1
Figura* sptr;
// Ok: puntero a C.A. 3
Figura f();
// ERROR: función NO puede
devolver tipo C.A. 2
int g(Figura s);
// ERROR: C.A. NO puede ser
argumento de función 2
Figura& h(Figura&); // Ok: devuelve tipo "referenciaa-C.A." 4
int h(Figura&);
// Ok: "referencia-a-C.A." si
puede ser argumento 4
§5 Suponiendo que A sea una clase abstracta y que D sea una
clase derivada inmediata de ella, cada función virtual pura fvp
de A, para la que D no aporte una definición, se convierte en
función virtual pura para D, en cuyo caso D resulta ser también
una clase abstracta.
Por ejemplo, suponiendo la clase Figura definida previamente:
class Circulo : public Figura { // Circulo deriva de
una C.A.
int radio;
// privado por defecto
public:
void rotar(int);
// convierte rotar en función
no virtual
};
En esta clase el método heredado Circulo::dibujar() es una
función virtual pura. Sin embargo, Circulo::rotar() no lo es
(suponemos que definición se efectúa off-line). En consecuencia,
Circulo es también una clase abstracta. En cambio si hacemos:
class Circulo : public Figura { // Circulo deriva de
una C.A.
int radio;
public:
void rotar(int);
// convierte rotar en función
no virtual pura
void dibujar();
// convierte dibujar en función
no virtual pura
};
la clase Circulo deja de ser abstracta.
§6 Las funciones-miembro pueden ser llamadas desde el
constructor de una clase abstracta, pero la llamada directa o
indirecta de una función virtual pura desde tal constructor puede
provocar un error en tiempo de ejecución. Sin embargo, son
permitidas disposiciones como la siguiente:
class CA {
// clase abstracta
public:
virtual void foo() = 0; // foo virtual pura
CA() {
// constructor
CA::foo();
// Ok.
};
...
void CA::foo() {
// definición en algún sitio
...
}
La razón es la ya señalada ( 4.11.8a) de que la utilización del
operador :: de acceso a ámbito anula el mecanismo de funciones
virtuales.
[1] Precisamente, la invocación de métodos de clases derivadas mediante
punteros a la superclase, es una de las características esenciales de la
tecnología COM de Microsoft.
[2]
Stroustrup & Ellis: ACRM §10.3.
http://www.zator.com/Cpp/E4_11_8c.htm
Clases Abstractas
A diferencia de lo visto con las clases visuales y las clases no visuales, las clases abstractas son
clases que sólo pueden tener subclases (no pueden ser instanciadas directamente).
Una clase abstracta puede contener métodos abstractos, esto es, métodos que no tienen
implementación. De esta forma, una clase abstracta puede definir una interfaz de programación
completa, incluso proporciona a sus subclases la declaración de todos los métodos necesarios
para implementar el interfaz de programación. Sin embargo, las clases abstractas pueden dejar
algunos detalles o toda la implementación de aquellos métodos a sus subclases.
http://www.microsoft.com/spanish/msdn/comunidad/dce/1/entrenamiento/foxpro/2b.asp
Clases abstractas
Una clase que declara la existencia de métodos pero no la implementación de dichos
métodos (o sea, las llaves { } y las sentencias entre ellas), se considera una clase
abstracta.
Una clase abstracta puede contener métodos no-abstractos pero al menos uno de los
métodos debe ser declarado abstracto.
Para declarar una clase o un metodo como abstractos, se utiliza la palabra reservada
abstract.
abstract class Drawing
{
abstract void miMetodo(int var1, int var2);
String miOtroMetodo( ){ ... }
}
Una clase abstracta no se puede instanciar pero si se puede heredar y las clases hijas
serán las encargadas de agregar la funcionalidad a los métodos abstractos. Si no lo
hacen así, las clases hijas deben ser también abstractas.
http://www.fib.unam.mx/pp/profesores/carlos/java/java_basico4_8.html
6.10.2 Redefinición.
6.11
Definición de una interfaz.
Interfaces
Son sintácticamente como las clases, pero no tienen variables de instancia y los métodos declarados no contienen cuerpo.


Se utilizan para especificar lo que debe hacer una clase, pero no cómo
lo hace.
Una clase puede implementar cualquier número de interfaces.
1 Definición de una interfaz
Una interfaz se define casi igual que una clase:
acceso interface nombre {
tipo_devuelto m鴯do1(lista_de_par᭥tros);
tipo_devuelto m鴯do2(lista_de_par᭥tros);
tipo var_final1=valor;
tipo var_final2=valor;
// ...
tipo_devuelto m鴯doN(lista_de_par᭥tros);
tipo var_finalN=valor;
}


acceso puede ser public o no usarse.
o Si no se utiliza (acceso por defecto) la interfaz está sólo
disponible para otros miembros del paquete en el que ha sido
declarada.
o Si se usa public, puede ser usada por cualquier código (todos los
métodos y variables son implícitamente públicos).
Los métodos de una interfaz son básicamente métodos abstractos (no
tienen cuerpo de implementación).

Un interfaz puede tener variables pero serán implícitamente final y
static.
Ejemplo definición de interfaz
interface Callback {
void callback(int param);
}
http://leo.ugr.es/~fjgc/CLDC/transjava/node9.html#SECTION000940000000000000000
6.12
Implementación de la definición de una
interfaz.
Implementación de una interfaz




Para implementar una interfaz, la clase debe implementar todos los
métodos de la interfaz. Se usa la palabra reservada implements.
Una vez declarada la interfaz, puede ser implementada por varias
clases.
Cuando implementemos un método de una interfaz, tiene que
declararse como public.
Si una clase implementa dos interfaces que declaran el mismo método,
entonces los clientes de cada interfaz usarán el mismo método.
Ejemplo de uso de interfaces: P28
class Client implements Callback {
public void callback(int p) {
System.out.println("callback llamado con " + p);
}
void nonIfaceMeth() {
System.out.println("Las clases que implementan interfaces " +
"adem᭥pueden definir otros miembros.");
}
}
class TestIface {
public static void main(String args[]) {
Callback c = new Client();
c.callback(42);
}
}
3 Acceso a implementaciones a través de referencias de la interfaz


Se pueden declarar variables usando como tipo un interfaz, para
referenciar objetos de clases que implementan ese interfaz.
El método al que se llamará con una variable así, se determina en
tiempo de ejecución.
Ejemplo
class TestIface {
public static void main(String args[]) {
Callback c = new Client();
c.callback(42);
}
}

Con estas variables sólo se pueden usar los métodos que hay en la
interfaz.
Otro ejemplo: P29
// Otra implementaci᭥e Callback.
class AnotherClient implements Callback {
public void callback(int p) {
System.out.println("Otra versi᭥e callback");
System.out.println("El cuadrado de p es " + (p*p));
}
}
class TestIface2 {
public static void main(String args[]) {
Callback c = new Client();
AnotherClient ob = new AnotherClient();
c.callback(42);
c = ob; // c hace referencia a un objeto AnotherClient
c.callback(42);
}
}
4 Implementación parcial

Si una clase incluye una interfaz, pero no implementa todos sus
métodos, entonces debe ser declarada como abstract.
5 Variables en interfaces


Las variables se usan para importar constantes compartidas en
múltiples clases.
Si una interfaz no tiene ningún método, entonces cualquier clase que
incluya esta interfaz no tendrá que implementar nada.
Ejemplo: P30/AskMe.java
import java.util.Random;
interface SharedConstants {
int NO = 0;
int YES = 1;
int MAYBE = 2;
int LATER = 3;
int SOON = 4;
int NEVER = 5;
}
class Question implements SharedConstants {
Random rand = new Random();
int ask() {
int prob = (int) (100 * rand.nextDouble());
if (prob < 30) return NO;
// 30%
else if (prob < 60) return YES;
// 30%
else if (prob < 75) return LATER; // 15%
else if (prob < 98) return SOON; // 13%
else return NEVER;
// 2%
}
}
class AskMe implements SharedConstants {
static void answer(int result) {
switch(result) {
case NO:
System.out.println("No"); break;
case YES:
System.out.println("Si"); break;
case MAYBE:
System.out.println("Puede ser"); break;
case LATER:
System.out.println("Mas tarde"); break;
case SOON:
System.out.println("Pronto"); break;
case NEVER:
System.out.println("Nunca"); break;
}
}
public static void main(String args[]) {
Question q = new Question();
answer(q.ask());
answer(q.ask());
answer(q.ask());
answer(q.ask());
}
}
6 Las interfaces se pueden extender


Una interfaz puede heredar otra utilizando la palabra reservada
extends.
Una clase que implemente una interfaz que herede de otra, debe
implementar todos los métodos de la cadena de herencia.
Ejemplo: P31/IFExtend.java
interface A {
void meth1();
void meth2();
}
interface B extends A {
void meth3();
}
class MyClass implements B {
public void meth1() {
System.out.println("Implemento meth1().");
}
public void meth2() {
System.out.println("Implemento meth2().");
}
public void meth3() {
System.out.println("Implemento meth3().");
}
}
class IFExtend {
public static void main(String arg[]) {
MyClass ob = new MyClass();
ob.meth1();
ob.meth2();
ob.meth3();
}
}
6.13
Reutilización de la definición de una
interfaz.
6.14
Definición y creación de paquetes / librería.
Paquetes
Un paquete es un contenedor de clases, que se usa para mantener el espacio de nombres de clase, dividido en
compartimentos.

Se almacenan de manera jerárquica: Java usa los directorios del
sistema de archivos para almacenar los paquetes
Ejemplo: Las clases del paquete MiPaquete (ficheros .class y .java)
se almacenarán en directorio MiPaquete



Se importan explícitamente en las definiciones de nuevas clases con la
sentencia
import nombre-paquete;
Permiten restringir la visibilidad de las clases que contiene:
Se pueden definir clases en un paquete sin que el mundo exterior sepa
que están allí.

Se pueden definir miembros de una clase, que sean sólo accesibles por
miembros del mismo paquete
1 Definición de un paquete
Incluiremos la siguiente sentencia como primera sentencia del archivo fuente
package nombre-paquete
.java




Todas las clases de ese archivo serán de ese paquete
Si no ponemos esta sentencia, las clases pertenecen al paquete por
defecto
Una misma sentencia package puede incluirse en varios archivos fuente
Se puede crear una jerarquía de paquetes: package paq1[.paq2[.paq3]];
Ejemplo: java.awt.image
2 La variable de entorno CLASSPATH
Supongamos que construimos la clase PaquetePrueba perteneciente al paquete prueba, dentro del fichero
PaquetePrueba.java (directorio prueba)






Compilación: Situarse en directorio prueba y ejecutar
javac -bootclasspath $CLDC_PATH/common/api/classes
-d tmpclasses prueba/PaquetePrueba.java
Preverificación
preverify -classpath $CLDC_PATH/common/api/classes:tmpclasses
-d . prueba/PaquetePrueba
o bien
preverify -classpath $CLDC_PATH/common/api/classes:tmpclasses
-d . tmpclasses


Ejecución: Situarse en directorio padre de prueba y ejecutar
kvm -classpath . prueba.PaquetePrueba
o bien añadir directorio padre de prueba a CLASSPATH y ejecutar
kvm prueba.PaquetePrueba
3 Ejemplo de paquete: P25/MyPack
package MyPack;
class Balance {
String name;
double bal;
Balance(String n, double b) {
name = n;
bal = b;
}
void show() {
if(bal<0)
System.out.print("-->> ");
System.out.println(name + ": $" + bal);
}
}
class AccountBalance {
public static void main(String args[]) {
Balance current[] = new Balance[3];
current[0] =
current[1] =
current[2] =
for(int i=0;
new Balance("K. J. Fielding", 123.23);
new Balance("Will Tell", 157.02);
new Balance("Tom Jackson", -12.33);
i<3; i++) current[i].show();
}
}
2 Protección de acceso
Las clases y los paquetes son dos medios de encapsular y contener el espacio de nombres y el ámbito de las variables y
métodos.


Paquetes: Actúan como recipientes de clases y otros paquetes
subordinados.
Clases: Actúan como recipientes de datos y código.
1 Tipos de acceso a miembros de una clase
Desde método en ...
private sin modif. protected public
misma clase
sí
sí
sí
sí
subclase del mismo paquete
no
sí
sí
sí
no subclase del mismo paquete
no
sí
sí
sí
subclase de diferente paquete
no
no
sí
sí
no subclase de diferente paquete
no
no
no
sí
2 Tipos de acceso para una clase


Acceso por defecto: Accesible sólo por código del mismo paquete
Acceso public: Accesible por cualquier código
Ejemplo: P26 Ejemplo: P26
package p1;
public class Protection {
int n = 1;
private int n_pri = 2;
protected int n_pro = 3;
public int n_pub = 4;
public Protection() {
System.out.println("constructor
System.out.println("n = " + n);
System.out.println("n_pri = " +
System.out.println("n_pro = " +
System.out.println("n_pub = " +
}
}
class Derived extends Protection {
Derived() {
System.out.println("constructor
System.out.println("n = " + n);
base ");
n_pri);
n_pro);
n_pub);
de Derived");
//
System.out.println("n_pri = " + n_pri);
System.out.println("n_pro = " + n_pro);
System.out.println("n_pub = " + n_pub);
}
}
class SamePackage {
SamePackage() {
Protection p = new Protection();
System.out.println("constructor de SamePackage");
System.out.println("n = " + p.n);
// System.out.println("n_pri = " + p.n_pri);
System.out.println("n_pro = " + p.n_pro);
System.out.println("n_pub = " + p.n_pub);
}
}
package p2;
class Protection2 extends p1.Protection {
Protection2() {
System.out.println("constructor de Protection2");
// System.out.println("n = " + n);
// System.out.println("n_pri = " + n_pri);
System.out.println("n_pro = " + n_pro);
System.out.println("n_pub = " + n_pub);
}
}
class OtherPackage {
OtherPackage() {
p1.Protection p = new p1.Protection();
System.out.println("other package constructor");
// System.out.println("n = " + p.n);
// System.out.println("n_pri = " + p.n_pri);
// System.out.println("n_pro = " + p.n_pro);
System.out.println("n_pub = " + p.n_pub);
}
}
3 Importar paquetes
Todas las clases estándar de Java están almacenadas en algún paquete con nombre. Para usar una de esas clases debemos
usar su nombre completo. Por ejemplo para la clase Date usaríamos java.util.Date. Otra posibilidad es usar la sentencia
import.






import: Permite que se puedan ver ciertas clases o paquetes enteros
sin tener que introducir la estructura de paquetes en que están
incluidos, separados por puntos.
Debe ir tras la sentencia package:
import paquete[.paquete2].(nombre_clase|*);
Al usar * especificamos que se importa el paquete completo. Esto
incrementa el tiempo de compilación, pero no el de ejecución.
Las clases estándar de Java están dentro del paquete java.
Las funciones básicas del lenguaje se almacenan en el paquete.
java.lang, el cual es importado por defecto.

Al importar un paquete, sólo están disponibles los elementos públicos
de ese paquete para clases que no sean subclases del código
importado.
Ejemplo: P27
MyPack/Balance.java
package MyPack;
/* La clase Balance, su constructor, y su metodo show()
deben ser p?os. Esto significa que pueden ser utilizados por
c᭥o que no sea una subclase y est頦uera de su paquete.
*/
public class Balance {
String name;
double bal;
public Balance(String n, double b) {
name = n;
bal = b;
}
public void show() {
if(bal<0)
System.out.print("-->> ");
System.out.println(name + ": $" + bal);
}
}
TestBalance.java
import MyPack.*;
class TestBalance {
public static void main(String args[]) {
/* Como Balance es p?a, se puede utilizar la
clase Balance y llamar a su constructor. */
Balance test = new Balance("J. J. Jaspers", 99.88);
test.show(); // tambi鮠se puede llamar al metodo show()
}
}
6.15
Reutilización de las clases de un paquete /
librería.
6.16
Clases genéricas (Plantillas).
Clases genéricas
§1 Sinopsis
Hemos indicado ( 1.12) que las clases-plantilla, clases
genéricas o generadores de clases, son un artificio C++ que
permite definir una clase mediante uno o varios parámetros.
Este mecanismo es capaz de generar la definición de clases
(instancias o especializaciones de la plantilla) distintas, pero
compartiendo un diseño común. Podemos imaginar que una
clase genérica es un constructor de clases, que como tal acepta
determinados argumentos (no confundir con el constructor deuna-clase, que genera objetos).
Para ilustrarlo recordemos la clase mVector que utilizamos al
tratar la sobrecarga de operadores ( 4.9.18d). En aquella
ocasión los objetos mVector eran matrices cuyos elementos
eran objetos de la clase Vector; que a su vez representaban
vectores de un espacio de dos dimensiones. El diseño básico de
la clase es como sigue:
class mVector {
// definición de
la clase mVector
int dimension;
public:
Vector* mVptr;
mVector(int n = 1) {
// constructor
por defecto
dimension = n;
mVptr = new Vector[dimension];
}
~mVector() { delete [] mVptr; }
// destructor
Vector& operator[](int i) {
// operador
subíndice
return mVptr[i];
}
void showmem (int);
// función
auxiliar
};
void mVector::showmem (int i) {
if((i >= 0) && (i <= dimension)) mVptr[i].showV();
else cout << "Argumento incorrecto! pruebe otra vez"
<< endl;
}
El sistema de plantillas permite definir una clase genérica que
instancie versiones de mVector para matrices de cualquier tipo
especificado por un parámetro. La ventaja de este diseño
parametrizado, es que cualquiera que sea el tipo de objetos
utilizados por las especializaciones de la plantilla, las
operaciones básicas son siempre las mismas (inserción, borrado,
selección de un elemento, etc).
§2 Definición
La definición de una clase genérica tiene el siguiente aspecto:
template<lista-de-parametros> class nombreClase
{ // Definición
...
};
Una clase genérica puede tener una declaración adelantada para
ser declarada después:
template<lista-de-parametros> class nombreClase;
// Declaración
...
template<lista-de-parametros> class nombreClase {
// Definición
...
};
pero recuerde que debe ser definida antes de su utilización [5], y
la regla de una sola definición ( 4.1.2).
Observe que la definición de una plantilla comienza siempre con
template<...>, y que los parámetros de la lista <...> no son
valores, sino tipos de datos . En la página adjunta se muestra
la gramática C++ para el especificador template ( Gramática).
La definición de la clase genérica correspondiente al caso
anterior es la siguiente:
template<class T> class mVector {
// L.1
Declaración de plantilla
int dimension;
public:
T* mVptr;
mVector(int n = 1) {
// constructor
por defecto
dimension = n;
mVptr = new T[dimension];
}
~mVector() { delete [] mVptr; }
// destructor
T& operator[](int i) { return mVptr[i]; }
void showmem (int);
};
template<class T> void mVector<T>::showmem (int i) {
// L.16:
if((i >= 0) && (i <= dimension)) mVptr[i].showV();
else cout << "Argumento incorrecto! pruebe otra vez"
<< endl;
}
Observe que aparte del cambio de la declaración (L.1), se han
sustituido las ocurrencias de Vector (un tipo concreto) por el
parámetro T. Observe también la definición de showmem() en
L.16, que se realiza off-line con la sintaxis de una función
genérica ( 4.12.1).
§2.1 Recordemos que en estas expresiones, el especificador
class puede ser sustituido por typename ( 3.2.1e), de forma
que la expresión L.1 puede ser sustituida por:
tamplate<typename T> class mVector {
//
L.1bis
...
};
También que la definición puede corresponder a una estructura
(recordemos que son un tipo de clase en las que todos sus
miembros son públicos). Por ejemplo:
template<class Arg> struct all_true : public
unary_function<Arg, bool> {
bool operator()(const Arg& x){ return 1; }
};
§2.2 Ejemplo-2
En la página adjunta se incluye un ejemplo operativo. Tomando
como punto de partida la versión definitiva de mVector (
4.9.18d1), se ha reproducido el mismo programa, pero
cambiando el diseño, de forma que mVector es ahora una clase
genérica en vez de una clase específica ( Ejemplo-2).
§2.3 Miembros de clases genéricas
Los miembros de las clases genéricas se definen y declaran
exactamente igual que los de clases concretas.
Pero debemos
señalar que las funciones-miembro son a su vez plantillas
parametrizadas (funciones genéricas) con los mismos parámetros
que la clase genérica a que pertenecen.
Consecuencia de lo anterior es que si las funciones-miembro se
definen fuera de la plantilla, sus prototipos deberían presentar el
siguiente aspecto:
emplate<class T> class mVector {
// Clase
genérica
int dimension;
public:
T* mVptr;
template<class T> mVector<T>& operator=(const
mVector<T>&);
template<class T> mVector<T>(int);
//
constructor por defecto
template<class T> ~mVector<T>();
//
destructor
template<class T> mVector<T>(const mVector<T>& mv);
// constructor-copia
T& operator[](int i) { return mVptr[i]; }
template <class T> void showmem (int);
// función
auxiliar
template <class T> void show ();
// función
auxiliar
};
Sin embargo, no es exactamente así por diversas razones: La
primera es que, por ejemplo, se estaría definiendo la plantilla
showmem sin utilizar el parámetro T en su lista de argumentos (lo
que no está permitido 4.12.1). Otra es que no está permitido
declarar los destructores como funciones genéricas. Además los
especificadores <T> referidos a mVector dentro de la propia
definición son redundantes.
Estas consideraciones hacen que los prototipos puedan ser
dejados como sigue (los datos faltantes pueden deducirse del
contexto):
template<class T> class mVector {
// Clase
genérica
int dimension;
public:
T* mVptr;
mVector& operator= (const mVector&);
mVector(int);
// constructor
por defecto
~mVector();
// destructor
mVector(const mVector& mv);
//
constructor-copia
T& operator[](int i) { return mVptr[i]; }
void showmem (int);
// función
auxiliar
void show ();
// función
auxiliar
};
§2.3.1 Las definiciones de métodos realizadas off-line (fuera del
cuerpo de una plantilla) deben ser declaradas explícitamente
como funciones genéricas ( 4.12.1). Por ejemplo:
template <class T> void mVector<T>::showmem (int i) {
...
}
§2.3.2 Ejemplo-3: Observe la sintaxis del siguiente ejemplo.
Es igual que el anterior (Ejemplo-2 ), con la diferencia de que
en este, las funciones-miembro se definen off-line ( Ejemplo3).
§2.3.3 Miembros estáticos
Las clases genéricas pueden tener miembros estáticos
(propiedades y métodos). Posteriormente cada especialización
dispondrá de su propio conjunto de estos miembros. Estos
miembros estáticos deben ser definidos fuera del cuerpo de la
plantilla, exactamente igual que si fuesen miembros estáticos de
clases concretas ( 4.11.7).
Ejemplo:
template<class T> class mVector {
// Clase
genérica
...
static T* mVptr;
static void showmem (int);
...
};
template<class T> T* mVector<T>::nVptr; // no es
necesario poner static
template<class T> void mVector<T>::showmem(int x){ ...
};
§2.3.4 Métodos genéricos
Hemos señalado que, por su propia naturaleza, los métodos de
clases genéricas son a su vez (implícitamente) funciones
genéricas con los mismos parámetros que la clase, pero pueden
ser además funciones genéricas explícitas (que dependan de
parámetros distintos de la plantilla a que pertenecen):
template<class X> class A {
// clase genérica
template<class T> void func(T& t); // método
genérico de clase genérica
...
}
Según es usual, la definición del miembro genérico puede
efectuarse de dos formas: on-line (en el interior de la clase
genérica Ejemplo), u off-line (en el exterior Ejemplo).
§3 Observaciones:
La definición de una clase genérica puede suponer un avance
importante en la definición de clases relacionadas, sin embargo
son pertinentes algunas advertencias:
§3.1 Las clases genéricas son entes de nivel superior a las
clases concretas. Representan para las clases normales (en este
contexto preferimos llamarlas clases explícitas) lo mismo que las
funciones genéricas a las funciones concretas. Como aquellas,
solo tienen existencia en el código. Como el mecanismo de
plantillas C++ se resuelve en tiempo de compilación, ni en el
fichero ejecutable ni durante la ejecución existe nada parecido a
una clase genérica, solo existen especializaciones (ver a
continuación §4 ). En realidad la clase genérica que se ve en el
código, actúa como una especie de "macro" que una vez
ejecutado su trabajo en la fase de compilación, desaparece de
escena.
Como ha señalado algún autor, el mecanismo de plantillas
representa una especie de polimorfismo en tiempo de
compilación, similar al que proporciona la herencia de métodos
virtuales en tiempo de ejecución.
§3.2 Aconsejamos realizar el diseño y una primera depuración
con una clase explícita antes de convertirla en una clase
genérica. Es más fácil imaginarse el funcionamiento referido a
un tipo concreto que a entes abstractos. Además es más fácil
entender los problemas que pueden presentarse si se maneja
una imagen mental concreta [1]. En estos casos es más sencillo
ir de lo particular a lo general.
§3.3 Contra lo que ocurre con las funciones genéricas, en la
instanciación de clases genéricas el compilador no realiza
ninguna suposición sobre la naturaleza de los argumentos a
utilizar, de modo que se exige que sean declarados siempre de
forma explícita. Por ejemplo:
mVector<char> mv1;
mVector mv2 = mv1;
// Error !!
mVector<char> mv2 = mv1; // Ok.
Nota: Como veremos a continuación , las clases genéricas
pueden tener argumentos por defecto, por lo que en estos
casos la declaración puede no ser explícita sino implícita
(referida a los valores por defecto de los argumentos). La
consecuencia es que en estos casos el compilador tampoco
realiza ninguna suposición sobre los argumentos a utilizar.
§3.4 Las clases genéricas pueden ser utilizadas en los
mecanismos de herencia. Por ejemplo:
template <class T> class Base { ... };
template <class T> class Deriv : public Base<T> {...};
§3.5 Los typedef ( 3.2.1a) son muy adecuados para acortar
la notación de objetos de clases genéricas cuando se trata de
declaraciones muy largas o no interesan los detalles. Por
ejemplo :
typedef basic_string <char> string;
más tarde, para obtener un objeto puede escribirse:
string st1;
// equivale a: basic_string <char> st1;
§4 Instanciación (obtener especializaciones)
Al tratar las funciones genéricas vimos que el compilador
genera el código apropiado en cuanto aparece una invocación a
la función. Por ejemplo si max(a, b) es una función genérica:
UnaClase a, b, m;
m = max(a, b);
// invoca especialización para
objetos UnaClase
Para utilizar un objeto de una clase genérica, es necesario
previamente instanciar la especialización de la clase que
instanciará a su vez el objeto. La primera instanciación (de la
clase concreta) se realiza mediante la invocación de la clase
genérica utilizando argumentos [4]. Por ejemplo:
mVector<Vector> mV1, mV2;
// Ok. matrices de
Vectores
mVector<Complejo> mC1, mC2;
// Ok. matrices de
Complejos
Durante la compilación, la primera sentencia crea una instancia
(función-clase 4.11.5) de mVector específica para miembros
Vector; que a su vez creará un objeto-clase en tiempo de
ejecución que será el encargado de instanciar los objetos mV1 y
mV2. En este contexto mVector<Vector> representa la clase que
genera los objetos mV1 y mV2. La segunda sentencia es análoga
aunque para complejos.
Observe que si mVector es una clase genérica, el identificador
mVector debe ir acompañado siempre por un tipo T entre
ángulos ( <T> ), ya que los ángulos vacíos ( <> ) no pueden
aparecer solos excepto en algunos casos de la definición de la
plantilla o cuando tenga valores por defecto.
Nota: Como veremos a continuación , las clases genéricas
pueden tener argumentos por defecto, en cuyo caso, el tipo T
puede omitirse, pero no los ángulos <>. Por ejemplo:
template<class T = int> class mVector {/* ...
*/};
...
mVector<char> mv1;
// Ok. argumento char
explícito
mVector<> mv2;
// Ok. argumento int
implícito (valor por defecto)
Cada instancia de una clase genérica es realmente una clase, y
sigue las reglas generales de las clases. Por ejemplo, como se
verá a continuación , pueden recibir referencias y punteros, y
dispone de su propia versión de todos los miembros estáticos si
los hubiere ( 4.11.7). Estas clases son denominadas
implícitas, para distinguirlas de las definidas "manualmente",
que se denominan explícitas.
La primera vez que el compilador encuentra una sentencia del
tipo mVector<Vector> crea la función-clase para dicho tipo; es
el punto de instanciación. Con objeto de que solo exista una
definición de la clase, si existen más ocurrencias de este mismo
tipo, las funciones-clase redundantes son eliminados por el
enlazador. Por la razón inversa, si el compilador no encuentra
ninguna razón para instanciar una clase (generar la funciónclase), esta generación no se producirá y no existirá en el código
ninguna instancia de la plantilla.
§5 Evitar la generación automática de especializaciones
(especializaciones explícitas)
Lo mismo que ocurre con las funciones genéricas ( 4.12.1a), en
las clases genéricas también puede evitarse la generación de
versiones implícitas para tipos concretos proporcionando una
especialización explícita. Por ejemplo:
class mVector<T*> { ... };
// L.1: definición
genérica
...
class mVector<char*> { ... };
// L.2: definición
específica
más tarde, las declaraciones del tipo
mVector<char*> mv1, mv2;
generará objetos utilizando la definición específica proporcionada
por L.2. En este caso mv1 y mv2 serán matrices alfanuméricas
(cadenas de caracteres).
Observe que la definición explícita comporta dos requisitos:

Aunque es una versión específica (para un tipo concreto), se utiliza la
sintaxis de plantilla: class mVector<...> {...};
 Se ha sustituido el tipo genérico <T*> por un tipo concreto <char*>.
Resulta evidente que una definición específica, como la incluída
en L.2, solo tiene sentido si se necesitan algunas modificaciones
en el diseño L.1 cuando la clase se refiera a tipos char*
(punteros-a-char)
§6 Argumentos de la plantilla
La declaración de clases genéricas puede incluir una lista con
varios parámetros. Estos pueden ser casi de cualquier tipo:
complejos, fundamentales ( 2.2), por ejemplo un int, o incluso
otra clase genérica (plantilla). Además en todos los casos
pueden presentar valores por defecto. Ejemplo:
template<class T, int dimension = 128> class mVector {
... };
§6.1 En la instanciación de clases genéricas, los valores de los
parámetros que no sean tipos complejos deben ser constantes o
expresiones constantes ( 3.2.3). Por ejemplo:
const int K = 128;
int i = 256;
mVector<int, 2*K> mV1;
// OK
mVector<Vector, i> mV2;
// Error: i no es
constante
Este tipo de parámetros constantes son adecuados para
establecer tamaños y límites. Por ejemplo:
template <class T, int max = 128 > class Matriz {
...
int dimension;
T* ptr;
Matriz (int d = 0) {
// constructor
dimension = d > max ? max : d;
ptr = new T [dimension];
}
...
};
Sin embargo, por su propia naturaleza de constantes, cualquier
intento posterior de alterar su valor genera un error.
Cuando es necesario pasar valores numéricos, enteros o
fraccionarios, para los que pueda existir ambiguedad en el tipo,
se aconseja incluir sufijos ( 3.2.3b y 3.2.3c). Por ejemplo,
en el caso de instanciar un objeto para la plantilla anterior:
Matriz<Vector, 256U> mV1;
§6.2 Los argumentos pueden ser otras plantillas, pero solo
clases genéricas. Las funciones genéricas no están permitidas:
template <class T, template<class X> class C> class
MatrizC { // Ok.
...
};
template <class T, template<class X> void Func(X a)>
class MatrizF { // Error!!
...
};
§6.3 Tenga en cuenta que no existe algo parecido a un
mecanismo de "sobrecarga" de las clases genéricas paralelo al de
las funciones genéricas. Por ejemplo las siguientes declaraciones
producen un error de compilación.
template<class T> class mVector { ... };
template<class T, int dimension> class mVector { ... };
Error: Number of template parameters does not match in
redeclaration of 'Matriz<T>'.
template<class T, class D> class mVector { ... };
template<class T, int dimension> class mVector { ... };
Error: Argument kind mismatch in redeclaration of
template parameter 'dimension'.
§7 La cuestión de los "Tipos" en las plantillas
La "Plantilla" que ve el programador en el código no tiene
existencia cuando termina la compilación. En el ejecutable solo
existen funciones-clase específicas ( 4.11.5) que darán lugar a
objetos-clase durante la ejecución. Esto hace que no sea posible
obtener el "Tipo" de una plantilla con el operador typeid (
4.9.14); en cualquier caso hay que aplicarlo sobre una instancia
concreta. Por ejemplo:
template <class T, int size = 0 > class Matriz { ... };
...
const type_info & ref1 = typeid(Matriz);
// Error!!
Con este intento compilador muestra un mensaje muy
esclarecedor: Error ... Cannot use template 'Matriz<T,
max>' without specifying specialization parameters. En
cambio el código:
const type_info & ref = typeid(Matriz<int, 5>);
//
Ok.
cout << "El tipo es: " << ref.name() << endl;
Produce la siguiente salida:
El tipo es: Matriz<int,5>
Es el mismo resultado que para el objeto m1:
Matriz<int, 5> m1;
Matriz<int, 2> m2;
Matriz<char, 5> m3;
Tenga en cuenta que m2 es tipo Matriz<int, 2> y que m3 es tipo
Matriz<char, 5>. Para el compilador todos ellos son tipos
distintos. Justamente debido a esto, para conseguir clases lo
más genéricas posibles conviene circunscribir al mínimo los
argumentos de la plantilla, ya que el lenguaje C++ es
fuertemente tipado ( 2.2), y cada nuevo argumento aumenta la
posibilidad de que los tipos sean distintos entre si, lo que
conduce a una cierta rigidez posterior [3]. Por ejemplo, en el
caso anterior m1 y m2 son tipos distintos, en consecuencia quizás
no se puedan efectuar determinadas operaciones entre ellos (m1
+ m2) a pesar que el operador suma + esté definido en la
plantilla. En cambio si la definición de esta es del tipo:
template < T > class Matriz { ...... };
Y dejamos el argumento size para el constructor, las matrices
m1 y m2:
Matriz<int> m1(5);
Matriz<int> m2(2);
Son del mismo tipo y seguramente se podrá efectuar la
operación m1 + m2.
§7.1 Ejemplo-4: El ejemplo adjunto muestra claramente esta
influencia, así como las diferencias obtenidas con varias formas
del programa. En la primera, se utiliza una clase explícita; en la
segunda esta clase se transforma en una clase genérica, y en la
tercera se incluye un segundo argumento que evita incluirlo en el
constructor ( Ejemplo-4).
§8 Punteros y referencias a clases implícitas
Como hemos señalado, las clases implícitas gozan de todas las
prerrogativas de las explícitas, incluyendo por supuesto la
capacidad de definir punteros y referencias. En este sentido no
se diferencian en nada de aquellas; la única precaución es tener
presente la cuestión de los tipos a la hora de efectuar
asignaciones, y no perder de vista que la plantilla es una
abstracción que representa múltiples clases (tipos), cada una
representada por argumentos concretos.
§8.1 Consideremos la clase genérica Matriz ya utilizada
anteriormente ( Ejemplo-4) cuya declaración es:
template <class T, int dim =1> class Matriz { /* ...
*/};
La definición de punteros y referencias sería como sigue:
Matriz<int,5> m1;
Matriz<char,5> m2;
Matriz<char> m3;
...
Matriz<int,5>* ptrMi5 = &m2
distintos
Matriz<char,5>* ptrMch5 = &m2;
Matriz<char,1>* ptrMch1 = &m2;
distintos
Matriz<char,1>* ptrMch1 = &m4;
ptrMch5->show();
método
Matriz<char> m4 = *ptrMch1;
asignación mediante puntero
// Ok. Tres objetos
// de tipos
// distintos.
void (Matriz<char,5>::* fptr1)();
puntero a método
fptr1 = &Matriz<char,5>::show;
(m3.*fptr1)();
incorrecto
(m2.*fptr1)();
método
// L.11: Ok. declara
Matriz<char,5>& refMch5 = m2;
Matriz<char>& refMch1 = m4;
refMch5.show();
método
// Ok. referencia
// Ok.
// Ok. invocación de
// Error!! tipos
// Ok.
// Error!! tipos
// Ok.
// Ok. invocación de
// L.10: Ok
// Ok. asignación
// Error!! tipo de m3
// Ok. invocación de
Comentario:
Observe que la asignación L.10 exige que la clase genérica
Matriz tenga definido el constructor-copia y que esté
sobrecargado el operador de indirección * para los miembros de
la clase ( 4.9.11).
Merecen especial atención las sentencias L.11/14. En este caso
se trata de punteros-a-miembros de clases implícitas. El tipo de
clase está definido en los parámetros. Observe que fptr1 es
puntero-a-método-de-clase-Matriz<char, 5> [2], y no puede
referenciar un método de m3, que es un objeto de tipo
Matriz<char,1>.
Nota: Para comprender cabalmente las sentencias
anteriores, puede ser de gran ayuda un repaso previo a los
capítulos: Punteros a Clases ( 4.2.1f) y Punteros a
Miembros ( 4.2.1g).
§8.2 Ejemplo-5:
En este ejemplo se muestra la utilización de
punteros a clases implícitas así como de la sobrecarga del
operador de indirección ( Ejemplo-5).
[1] Esto es solo una generalización de un principio universal de
programación: Realizar pruebas con casos lo más simples posibles e ir
complicándolos paulatinamente.
[2] Más formalmente: Puntero a método que no acepta argumentos y
devuelve void de la clase Matriz<char, 5>.
[3] Suponiendo naturalmente que esta mayor "rigidez" no sea justamente el
efecto que se pretende al definir nuevos argumentos.
[4] Esta notación es análoga a la utilizada con las funciones genéricas que
hemos denominado instanciación implícita específica ( 4.12.1)
[5] En este sentido no son como las funciones genéricas que pueden ser
declaradas antes de su utilización y definidas en cualquier otro sitio (incluso
después de su uso -en el código-).
http://www.zator.com/Cpp/E4_12_2.htm
Plantillas (INF-121 Lic. Menfy Morales – Apuntes del
paralelo)
La generacidad es la propiedad que permite definir una clase (o una función)
sin especificar el tipo de datos de uno o más de sus miembros (parámetros).
De esta forma la clase escrita se adaptará al uso con diferentes tipos de
datos, sin tener que rescribirla.
Una plantilla es un patrón para crear funciones y clases.
Las plantillas permiten la construcción de procesos no dependientes de tipos
de datos, siendo estos procesos genéricos.
Tipos de plantillas
1) plantillas de función (permiten construir funciones generales)
2) plantillas de clase (permiten construir clase generales)
Plantillas de función
Supongamos que se desea escribir una función min(a, b) que devuelva el
valor más pequeño entre a y b.
int método min(int a, int
b)
if (a <= b) then
return (a);
else return ( b);
endmin;
real método min(real a, real
b)
if (a <= b) then
return (a);
else return ( b);
endmin;
Este proceso puede diseñarse para datos de tipo largo int, o string, en todos
los casos el proceso en si es el mismo, varían los tipos de datos.
El propósito de usar plantillas es el de liberar al programador de escribir
múltiples versiones de la misma función para llevar a cabo la misma
operación sobre datos de distinto tipo.
Una plantilla de función especifica un conjunto infinito de funciones
sobrecargadas y describe las propiedades genéricas de una función.
Comienza con la palabra reservada Template seguida de una lista de
parámetros formales para la plantilla de función.
Estructura
Template <lista de tipo parametrizado>
Tipo Nombre(Conjunto de parámetros)
{ .... sentencias;
}
Ejemplo. Escribir una plantilla para hallar el menor de dos números
template <class T>
T Menor ( T a, T b)
{ if (a <= b) then return ( a );
else return ( b );
}
? 7, 3, 5.2, 6.7, mano,
limón
3
5.2
limón
main()
{ int a, b;
real c, d;
string e, f;
read(a, b, c, d, e, f );
print (Menor (a, b));
print (Menor (c, d));
print (Menor (e, f));
}
Ejemplo 2. Escribir una plantilla para cambiar 2 valores
template <class T>
método Cambia ( T a, T b)
{ T aux;
aux  a; a  b; b  aux;
}
main()
{ int a, b;
real c, d;
string e, f;
read(a, b, c, d, e, f );
Cambia(a, b);
print (a, b);
Cambia (c, d);
print (c, d);
Cambia ( e, f );
print (e, f );
}
? 8, 4, 2.5, 3.6, taza,
plato
4, 8
3.6, 2.5
plato, taza
Ejemplo. Escribir una plantilla para ordenar 3 valores.
Plantillas de clases
Clases genéricas que trabajan sobre tipos de datos parametrizados.
Permiten definir clases genéricas que pueden manipular diferentes tipos de
datos.
Una aplicación importante es la creación de contenedores, clases que
contienen objetos de un tipo de dato, como vectores, matrices, listas, tablas,
etc. Los contenedores manejan estructuras de datos.
Se usan plantillas de clases para no crear múltiples clases con los mismos
atributos y operaciones.
Estructura
template <lista de tipos parametrizados>
class Nombre
{
//miembro dato (parametrizado)
public
//miembro función (parametrizada)
}
main()
{}
Ejemplo. Escribir una plantilla de clase para la clase fracción
template <class X, class Y>
class Fracción
{
X num;
Y den;
public
método leer()
read(num, den);
endleer;
método mostrar()
print(num, den);
endmostrar;
Fracción ()
num  7;
den  3;
endFracción;
Fracción (X n, Y d)
num  n;
den  d;
endFracción;
método operator ++ ()
num ++;
den++;
end++;
Fracción método suma (Fracción F)
F.num  F.num * den + F.den * num;
F.den  den * F.den + num * F.num;
return ( F );
endsuma;
¿leer? 7, 4
7, 4
¿leer? 3, 6
54, 45
}
main()
{ Fracción <int, int> F1, F4;
Fracción <char, char > F2(“x”,”y”);
Fracción <char, int > F3(“*”, 7 );
F1.leer();
F1.mostrar();
F4.leer();
F1.suma( F4 ).mostrar();
}
Ejemplo. Escribir una plantilla de clase para buscar un elemento X en una
matriz a(n,m) y contar cuantas veces se repite.
template <class T >
class Matriz
{
T a(50, 50);
int n;
int m;
public
Matriz (int x, int y)
n  x; m  y;
endMatriz;
método leer();
método mostrar();
int método cuenta( T x );
}
template <class T>
int Matriz <class T>:: cuenta(T x)
int c 0;
for ( i  1 to n) do
for ( j  1 to m) do
if ( a(i, j)) = x then c  c + 1;
endif;
endfor;
endfor;
return (c );
endcuenta;
template <class T >
Matriz<class T >:: leer()
for (i  1 to n ) do
for (j  1 to m) do
read(a ( i, j));
endfor;
endfor;
endleer;
template <class T >
Matriz<class T >:: mostrar()
for (i  1 to n ) do
for (j  1 to m) do
print(a ( i, j));
endfor;
endfor;
endmostrar;
main()
{
}
Matriz <int > A (2, 3);
Matriz <char > B ( 3, 3 );
A.leer();
A.mostrar();
print(A.cuenta(8));
B.leer();
B.mostrar();
print(B.cuenta(“m”));
¿?
5
9
2
¿?
m
f
w
s
3
5, 6, 8, 9, 8, 2
6 8
8 2
f, d, m, w, m, q, s, a,
d m
m q
a m
http://www.umsanet.edu.bo/docentes/menfy/Plantillas.html
Una clase genérica de C++ es una clase que se define o se deriva de una clase definida por el usuario. Puede
agregar una clase genérica de C++ desde la Vista de clases.
Para agregar una clase genérica de C++ a un proyecto
1.
En la Vista de clases, haga clic con el botón secundario en el nombre del proyecto al que desee agregar
una clase nueva.
2.
En el menú contextual, haga clic en Agregar y, después, en Agregar clase.
3.
En el cuadro de diálogo Agregar clase, haga clic en Clase genérica de C++ del panel Plantillas. Haga clic
en Abrir para mostrar el Asistente para clases genéricas de C++.
4.
Defina la configuración en el Asistente para clases genéricas de C++. Debe proporcionar al menos el
nombre de una clase.
5.
Haga clic en Finalizar para cerrar el asistente y ver la nueva clase genérica de C++ en el proyecto.
Fuente:
http://msdn.microsoft.com/library/spa/default.asp?url=/library/SPA/vccore/html/_core_Adding_
a_Generic_Class.asp
Unidad 7. Excepciones.
7.4 Definición.
7.4.1 Que son las excepciones.
Definición: Excepciones ``Alternativa más elaborada para la gestión de
errores y situaciones anormales: se invoca directamente un
procedimiento corrector''
http://gsyc.escet.urjc.es/docencia/cursos/fse-distribuidos/transpas/node5.html
Manejo de Errores Usando Excepciones Java
Autor:
Traductor: Juan Antonio Palos (Ozito)

¿Qué es un Excepción y Por Qué Debo Tener Cuidado?
o Ventaja 1: Separar el Manejo de Errores del Código "Normal"
o Ventaja 2: Propagar los Errores sobre la Pila de Llamadas
Sun
o
o
Ventaja 3: Agrupar Errores y Diferenciación
¿ Y ahora qué?
¿Qué es un Excepción y Por Qué Debo Tener Cuidado?
El término excepción es un forma corta da la frase "suceso excepcional" y puede definirse de la siguiente forma.
Definición:
Una excepción es un evento que ocurre durante la ejecución del programa que
interrumpe el flujo normal de las sentencias.
Muchas clases de errores pueden utilizar excepciones -- desde serios problemas de hardware, como la avería de un
disco duro, a los simples errores de programación, como tratar de acceder a un elemento de un array fuera de sus
límites. Cuando dicho error ocurre dentro de un método Java, el método crea un objeto 'exception' y lo maneja fuera,
en el sistema de ejecución. Este objeto contiene información sobre la excepción, incluyendo su tipo y el estado del
programa cuando ocurrió el error. El sistema de ejecución es el responsable de buscar algún código para manejar el
error. En terminología java, crear una objeto exception y manejarlo por el sistema de ejecución se llama lanzar una
excepción.
Después de que un método lance una excepción, el sistema de ejecución entra en acción para buscar el manejador
de la excepción. El conjunto de "algunos" métodos posibles para manejar la excepción es el conjunto de métodos de
la pila de llamadas del método donde ocurrió el error. El sistema de ejecución busca hacia atrás en la pila de
llamadas, empezando por el método en el que ocurrió el error, hasta que encuentra un método que contiene el
"manejador de excepción" adecuado.
Un manejador de excepción es considerado adecuado si el tipo de la excepción lanzada es el mismo que el de la
excepción manejada por el manejador. Así la excepción sube sobre la pila de llamadas hasta que encuentra el
manejador apropiado y una de las llamadas a métodos maneja la excepción, se dice que el manejador de excepción
elegido captura la excepción.
Si el sistema de ejecución busca exhaustivamente por todos los métodos de la pila de llamadas sin encontrar el
manejador de excepción adecuado, el sistema de ejecución finaliza (y consecuentemente y el programa Java
también).
Mediante el uso de excepciones para manejar errores, los programas Java tienen las siguientes ventajas frente a las
técnicas de manejo de errores tradicionales.



Ventaja 1: Separar el Manejo de Errores del Código "Normal"
Ventaja 2: Propagar los Errores sobre la Pila de Llamadas
Ventaja 3: Agrupar los Tipos de Errores y la Diferenciación de éstos
Ventaja 1: Separar el Manejo de Errores del Código "Normal"
En la programación tradicional, la detección, el informe y el manejo de errores se convierte en un código muy liado.
Por ejemplo, supongamos que tenemos una función que lee un fichero completo dentro de la memeoria. En pseudocódigo, la función se podría parecer a esto.
leerFichero {
abrir el fichero;
determinar su tamaño;
asignar suficiente memoria;
leer el fichero a la memoria;
cerrar el fichero;
}
A primera vista esta función parece bastante sencilla, pero ignora todos aquello errores potenciales.





¿Qué sucede si no se puede abrir el fichero?
¿Qué sucede si no se puede determinar la longitud del fichero?
¿Qué sucede si no hay suficiente memoria libre?
¿Qué sucede si la lectura falla?
¿Qué sucede si no se puede cerrar el fichero?
Para responder a estas cuestiones dentro de la función, tendríamos que añadir mucho código para la detección y el
manejo de errores. El aspecto final de la función se parecería esto.
codigodeError leerFichero {
inicializar codigodeError = 0;
abrir el fichero;
if (ficheroAbierto) {
determinar la longitud del fichero;
if (obtenerLongitudDelFichero) {
asignar suficiente memoria;
if (obtenerSuficienteMemoria) {
leer el fichero a memoria;
if (falloDeLectura) {
codigodeError = -1;
}
} else {
codigodeError = -2;
}
} else {
codigodeError = -3;
}
cerrar el fichero;
if (ficheroNoCerrado && codigodeError == 0) {
codigodeError = -4;
} else {
codigodeError = codigodeError and -4;
}
} else {
codigodeError = -5;
}
return codigodeError;
}
Con la detección de errores, las 7 líneas originales (en negrita) se han covertido en 29 líneas de código-- a
aumentado casi un 400 %. Lo peor, existe tanta detección y manejo de errores y de retorno que en las 7 líneas
originales y el código está totalmente atestado. Y aún peor, el flujo lógico del código también se pierde, haciendo
díficil poder decir si el código hace lo correcto (si ¿se cierra el fichero realmente si falla la asignación de memoria?) e
incluso es díficil asegurar que el código continue haciendo las cosas correctas cuando se modifique la función tres
meses después de haberla escrito. Muchos programadores "resuelven" este problema ignorádolo-- se informa de los
errores cuando el programa no funciona.
Java proporciona una solución elegante al problema del tratamiento de errores: las excepciones. Las excepciones le
permiten escribir el flujo principal de su código y tratar los casos excepcionales en otro lugar. Si la función
leerFcihero utilizara excepciones en lugar de las técnicas de manejo de errores tradicionales se podría parecer a
esto.
leerFichero {
try {
abrir el fichero;
determinar su tamaño;
asignar suficiente memoria;
leer el fichero a la memoria;
cerrar el fichero;
} catch (falloAbrirFichero) {
hacerAlgo;
} catch (falloDeterminacionTamaño) {
hacerAlgo;
} catch (falloAsignaciondeMemoria) {
hacerAlgo;
} catch (falloLectura) {
hacerAlgo;
} catch (falloCerrarFichero) {
hacerAlgo;
}
}
Observa que las excepciones no evitan el esfuerzo de hacer el trabajo de detectar, informar y manejar errores. Lo
que proporcionan las excepciones es la posibilidad de separar los detalles oscuros de qué hacer cuando ocurre algo
fuera de la normal.
Además, el factor de aumento de cáodigo de este es programa es de un 250% -- comparado con el 400% del
ejemplo anterior.
Ventaja 2: Propagar los Errores sobre la Pila de Llamadas
Una segunda ventaja de las exepciones es la posibilidad del propagar el error encontrado sobre la pila de llamadas a
métodos. Supongamos que el método leerFichero es el cuarto método en una serie de llamadas a métodos
anidadas realizadas por un programa principal: metodo1 llama a metodo2, que llama a metodo3, que finalmente
llama a leerFichero.
metodo1 {
call metodo2;
}
metodo2 {
call metodo3;
}
metodo3 {
call leerFichero;
}
Supongamos también que metodo1 es el único método interesado en el error que ocurre dentro de leerFichero.
Tradicionalmente las técnicas de notificación del error forzarían a metodo2 y metodo3 a propagar el código de error
devuelto por leerFichero sobre la pila de llamadas hasta que el código de error llegue finalmente a metodo1 -- el
único método que está interesado en él.
metodo1 {
codigodeErrorType error;
error = call metodo2;
if (error)
procesodelError;
else
proceder;
}
codigodeErrorType metodo2 {
codigodeErrorType error;
error = call metodo3;
if (error)
return error;
else
proceder;
}
codigodeErrorType metodo3 {
codigodeErrorType error;
error = call leerFichero;
if (error)
return error;
else
proceder;
}
Como se aprendió anteriormente, el sistema de ejecución Java busca hacia atrás en la pila de llamadas para
encontrar cualquier método que esté interesado en manejar una excepción particular. Un método Java puede
"esquivar" cualquier excepción lanzada dentro de él, por lo tanto permite a los métodos que están por encima de él
en la pila de llamadas poder capturarlo. Sólo los métodos interesados en el error deben preocuparse de detectarlo.
metodo1 {
try {
call metodo2;
} catch (excepcion) {
procesodelError;
}
}
metodo2 throws excepcion {
call metodo3;
}
metodo3 throws excepcion {
call leerFichero;
}
Sin embargo, como se puede ver desde este pseudo-código, requiere cierto esfuerzo por parte de los métodos
centrales. Cualquier excepción chequeada que pueda ser lanzada dentro de un método forma parte del interface de
programación público del método y debe ser especificado en la clausula throws del método. Así el método informa a
su llamador sobre las excepciones que puede lanzar, para que el llamador pueda decidir concienzuda e
inteligentemente qué hacer con esa excepción.
Observa de nuevo la diferencia del factor de aumento de código y el factor de ofuscación entre las dos técnicas de
manejo de errores. El código que utiliza excepciones es más compacto y más fácil de entender.
Ventaja 3: Agrupar Errores y Diferenciación
Frecuentemente las excepciones se dividen en categorias o grupos. Por ejemplo, podríamos imaginar un grupo de
excepciones, cada una de las cuales representara un tipo de error específico que pudiera ocurrir durante la
manipulación de un array: el índice está fuera del rango del tamaño del array, el elemento que se quiere insertar en el
array no es del tipo correcto, o el elemento que se está buscando no está en el array. Además, podemos imaginar
que algunos métodos querrían manejar todas las excepciones de esa categoria (todas las excepciones de array), y
otros métodos podría manejar sólo algunas excepciones específicas (como la excepción de índice no válido).
Como todas las excepciones lanzadas dentro de los programas Java son objetos de primera clase, agrupar o
categorizar las excepciones es una salida natural de las clases y las superclases. Las excepciones Java deben ser
ejemplares de la clase Throwable, o de cualquier descendiente de ésta. Como de las otras clases Java, se pueden
crear subclases de la clase Throwable y subclases de estas subclases. Cada clase 'hoja' (una clase sin subclases)
representa un tipo específico de excepción y cada clase 'nodo' (una clase con una o más subclases) representa un
grupo de excepciones relacionadas.
InvalidIndexException, ElementTypeException, y NoSuchElementException son todas clases hojas. Cada una
representa un tipo específico de error que puede ocurrir cuando se manipula un array. Un método puede capturar
una excepción basada en su tipo específico (su clase inmediata o interface). Por ejemplo, una manejador de
excepción que sólo controle la excepción de índice no válido, tiene una sentencia catch como esta.
catch (InvalidIndexException e) {
. . .
}
ArrayException es una clase nodo y representa cualquier error que pueda ocurrir durante la manipulación de un
objeto array, incluyendo aquellos errores representados específicamente por una de sus subclases. Un método
puede capturar una excepción basada en este grupo o tipo general especificando cualquiera de las superclases de la
excepción en la sentencia catch. Por ejemplo, para capturar todas las excepciones de array, sin importar sus tipos
específicos, un manejador de excepción especificaría un argumento ArrayException.
catch (ArrayException e) {
. . .
}
Este manejador podría capturar todas las excepciones de array, incluyendo InvalidIndexException,
ElementTypeException, y NoSuchElementException. Se puede descubrir el tipo de excepción preciso que ha
ocurrido comprobando el parámtero del manejador e. Incluso podríamos seleccionar un manejador de excepciones
que controlara cualquier excepción con este manejador.
catch (Exception e) {
. . .
}
Los manejadores de excepciones que son demasiado generales, como el mostrado aquí, pueden hacer que el código
sea propenso a errores mediante la captura y manejo de excepciones que no se hubieran anticipado y por lo tanto no
son manejadas correctamente dentro de manejador. Como regla no se recomienda escribir manejadores de
excepciones generales.
Como has visto, se pueden crear grupos de excepciones y manejarlas de una forma general, o se puede especificar
un tipo de excepción específico para diferenciar excepciones y manejarlas de un modo exacto.
http://www.programacion.net/tutorial/excepciones/2/
Excepciones
Excepcion es, o sencillamente problemas. En la programación siempre se producen errores, más
o menos graves, pero que hay que gestionar y tratar correctamente. Por ello en java disponemos
de un mecanismo consistente en el uso de bloques try/catch/finally . La técnica básica consiste en
colocar las instrucciones que podrían provocar problemas dentro de un bloque try, y colocar a
continuación uno o más bloques catch, de tal forma que si se provoca un error de un determinado
tipo, lo que haremos será saltar al bloque catch capaz de gestionar ese tipo de error específico. El
bloque catch contendrá el codigo necesario para gestionar ese tipo específico de error.
Suponiendo que no se hubiesen provocado errores en el bloque try, nunca se ejecutarían los
bloques catch.
Veamos ahora la estructura del bloque try/catch/finally:
try
{
//Código que puede provocar errores
}
catch(Tipo1 var1)
{
//Gestión del error var1, de tipo Tipo1
}
[ ...
catch(TipoN varN)
{
//Gestión del error varN, de tipo TipoN
}]
[
finally
{
//Código de finally
}
]
Como podemos ver es obligatorio que exista la zona try, o zona de pruebas, donde pondremos las
instrucciones problemáticas. Después vienen una o más zonas catch, cada una especializada en un
tipo de error o excepción. Por último está la zona finally, encargada de tener un código que se
ejecutará siempre, independientemente de si se produjeron o no errores.
Se puede apreciar que cada catch se parece a una función en la cuál sólo recibimos un objeto de
un determinado tipo, precisamente el tipo del error. Es decir sólo se llamará al catch cuyo
argumento sea coincidente en tipo con el tipo del error generado.
http://www.mailxmail.com/curso/informatica/java/capitulo57
.htm
La instrucción try (try-statement) proporciona un mecanismo para capturar las excepciones que ocurren durante la
ejecución de un bloque. La instrucción try permite además especificar un bloque de código que siempre se ejecuta
cuando el control abandona la instrucción try.
try-statement:
try block
try block
try block
catch-clauses
finally-clause
catch-clauses
finally-clause
catch-clauses:
specific-catch-clauses general-catch-clauseopt
specific-catch-clausesopt general-catch-clause
specific-catch-clauses:
specific-catch-clause
specific-catch-clauses specific-catch-clause
specific-catch-clause:
catch ( class-type identifieropt )
catch
finally
block
general-catch-clause:
block
block
finally-clause:
Existen tres formas posibles de instrucciones try:

Un bloque try seguido de uno o varios bloques catch.

Un bloque try seguido de un bloque finally.

Un bloque try seguido de uno o varios bloques catch y seguidos de un bloque
finally.
Cuando una cláusula catch especifica un tipo-de-clase (class-type), el tipo debe ser
System.Exception o un tipo que derive de System.Exception.
Cuando una cláusula catch especifica tanto un tipo-de-clase (class-type) como un
identificador (identifier) se declara una variable de excepción con el nombre y tipo dados.
La variable de excepción es una variable local con un ámbito que se extiende a lo largo
de todo el bloque catch. Durante la ejecución de un bloque catch, la variable de
excepción representa la excepción que se está controlando en ese momento. Desde el
punto de vista de comprobación de asignación definitiva, la variable de excepción se
considera asignada definitivamente en todo su ámbito.
Si la cláusula catch no incluye el nombre de una variable de excepción, no es posible
tener acceso al objeto de excepción en el bloque catch.
Una cláusula catch general es una cláusula catch que no especifica el tipo ni la variable
de excepción. Una instrucción try sólo puede tener una cláusula catch general y, si
existe, debe ser la última cláusula catch.
Algunos lenguajes de programación pueden aceptar excepciones que no son
representables como un objeto derivado de System.Exception, aunque estas
excepciones nunca pueden ser generadas por código C#. Una cláusula catch general
puede utilizarse para detectar este tipo de excepciones. Por lo tanto, una cláusula catch
general es semánticamente diferente de otra que especifica el tipo System.Exception,
ya que la primera también puede detectar excepciones de otros lenguajes.
Las cláusulas catch se examinan en orden léxico para encontrar un controlador para la
excepción. Si una cláusula catch especifica un tipo que es igual o derivado de un tipo
especificado en una cláusula catch anterior de la misma instrucción try, se produce un
error en tiempo de compilación. Si no existiera esta restricción, sería posible escribir
cláusulas catch inalcanzables.
Dentro de un bloque catch, puede utilizar una instrucción throw (Sección 8.9.5) sin
expresión para volver a iniciar la excepción que capturó el bloque catch. Las
asignaciones a una variable de excepción no modifican la excepción que vuelve a
iniciarse.
En el ejemplo
using System;
class Test
{
static void F() {
try {
G();
}
catch (Exception e) {
Console.WriteLine("Exception in F: " + e.Message);
e = new Exception("F");
throw;
// re-throw
}
}
static void G() {
throw new Exception("G");
}
static void Main() {
try {
F();
}
catch (Exception e) {
Console.WriteLine("Exception in Main: " + e.Message);
}
}
}
el método F captura una excepción, escribe información de diagnóstico en la consola,
modifica la variable de excepción y vuelve a iniciar la excepción. La excepción que se
vuelve a iniciar es la excepción original, de modo que el resultado producido es el
siguiente:
Exception in F: G
Exception in Main: G
Si el primer bloque catch iniciara e en lugar de volver a iniciar la excepción actual, el
resultado producido sería el siguiente:
Exception in F: G
Exception in Main: F
Se produce un error en tiempo de compilación cuando una instrucción break, continue o
goto transfiere el control fuera del bloque finally. Cuando ocurre una instrucción
break, continue o goto dentro de un bloque finally, el destino de la instrucción debe
encontrarse dentro del propio bloque finally. En caso contrario, se producirá un error
en tiempo de compilación.
También se produce un error en tiempo de compilación si una instrucción return ocurre
dentro de un bloque finally.
Una instrucción try se ejecuta de la siguiente forma:

El control se transfiere al bloque try.

Cuando el control alcanza el punto final del bloque try:

Si la instrucción try tiene un bloque finally, se ejecuta éste.

El control se transfiere al punto final de la instrucción try.

Si se propaga una excepción a la instrucción try durante la ejecución del bloque
try:

Si existen cláusulas catch, se examinan en orden de aparición para buscar un
controlador adecuado para la excepción. La primera cláusula catch que especifique
el tipo de excepción o un tipo base del tipo de excepción se considera una
coincidencia. Una cláusula catch general es una coincidencia para cualquier tipo de
excepción. Si se encuentra una cláusula catch coincidente:

Si la cláusula catch coincidente declara una variable de excepción, se asigna el
objeto de excepción a dicha variable.

El control se transfiere al bloque catch coincidente.

Cuando el control alcanza el punto final del bloque catch:

Si la instrucción try tiene un bloque finally, se ejecuta éste.

El control se transfiere al punto final de la instrucción try.

Si se propaga una excepción a la instrucción catch durante la ejecución del bloque
try:

Si la instrucción try tiene un bloque finally, se ejecuta éste.

La excepción se propaga a la siguiente instrucción try envolvente.

Si la instrucción try no tiene cláusulas catch o si ninguna cláusula catch coincide
con la excepción:

Si la instrucción try tiene un bloque finally, se ejecuta éste.

La excepción se propaga a la siguiente instrucción try envolvente.
Las instrucciones de un bloque finally siempre se ejecutan cuando el control abandona
la instrucción try. Esto se cumple cuando el control se transfiere como resultado de una
ejecución normal, de la ejecución de una instrucción break, continue, goto o return, o
de la propagación de una excepción fuera de la instrucción try.
Si se vuelve a iniciar una excepción durante la ejecución de un bloque finally, la
excepción se propaga a la siguiente instrucción try envolvente. Si se estaba propagando
otra excepción anterior, ésta se pierde. El proceso de propagación de una excepción se
explica con mayor detalle en la descripción de la instrucción throw (Sección 8.9.5).
El bloque try de una instrucción try es alcanzable si la instrucción try es alcanzable.
El bloque catch de una instrucción try es alcanzable si la instrucción try es alcanzable.
El bloque finally de una instrucción try es alcanzable si la instrucción try es
alcanzable.
El punto final de una instrucción try es alcanzable si se cumplen las dos condiciones
siguientes:

El punto final de un bloque try es alcanzable o el punto final de al menos uno de
sus bloques catch es alcanzable.

Si existe un bloque finally, el punto final del bloque finally es alcanzable.
http://msdn.microsoft.com/library/SPA/csspec/html/vclrfcsh
arpspec_8_10.asp
7.4.2 Clases de excepciones, excepciones
predefinidas por el lenguaje.
Generar Excepciones en Java
Cuando se produce una condición excepcional en el transcurso de la ejecución de un
programa, se debería generar, o lanzar, una excepción. Esta excepción es un objeto
derivado directa, o indirectamente, de la clase Throwable. Tanto el intérprete Java como
muchos métodos de las múltiples clases de Java pueden lanzar excepciones y errores.
La clase Throwable tiene dos subclases: Error y Exception. Un Error indica que se ha
producido un fallo no recuperable, del que no se puede recuperar la ejecución normal del
programa, por lo tanto, en este caso no hay nada que hacer. Los errores, normalmente,
hacen que el intérprete Java presente un mensaje en el dispositivo estándar de salida y
concluya la ejecución del programa. El único caso en que esto no es así, es cuando se
produce la muerte de un thread, en cuyo caso se genera el error ThreadDead, que lo que
hace es concluir la ejecución de ese hilo, pero ni presenta mensajes en pantalla ni afecto a
otros hilos que se estén ejecutando.
Una Exception indicará una condición anormal que puede ser subsanada para evitar la
terminación de la ejecución del programa. Hay nueve subclases de la clase Exception ya
predefinidas, y cada una de ellas, a su vez, tiene numerosas subclases.
Para que un método en Java, pueda lanzar excepciones, hay que indicarlo expresamente.
void MetodoAsesino() throws NullPointerException,CaidaException
Se pueden definir excepciones propias, no hay por qué limitarse a las nueve predefinidas y a
sus subclases; bastará con extender la clase Exception y proporcionar la funcionalidad
extra que requiera el tratamiento de esa excepción.
También pueden producirse excepciones no de forma explícita como en el caso anterior, sino
de forma implícita cuando se realiza alguna acción ilegal o no válida.
Las excepciones, pues, pueden originarse de dos modos: el programa hace algo ilegal (caso
normal), o el programa explícitamente genera una excepción ejecutando la sentencia throw
(caso menos normal). La sentencia throw tiene la siguiente forma:
throw ObtejoExcepction;
El objeto ObjetoException es un objeto de una clase que extiende la clase Exception.
El siguiente código de ejemplo, java901.java, origina una excepción de división por cero:
class java901 {
public static void main( String[] a ) {
int i=0, j=0, k;
k = i/j;
}
// Origina un error de division-by-zero
}
Si compilamos y ejecutamos esta aplicación Java, obtendremos la siguiente salida por
pantalla:
% javac java901.java
% java java901
java.lang.ArithmeticException: / by zero
at java901.main(java901.java:25)
Las excepciones predefinidas, como ArithmeticException, se conocen como excepciones
runtime. Actualmente, como todas las excepciones son eventos runtime, sería mejor
llamarlas excepciones irrecuperables. Esto contrasta con las excepciones que se generan
explícitamente, a petición del programador, que suelen ser mucho menos severas y en la
mayoría de los casos no resulta complicado recuperarse de ellas. Por ejemplo, si un fichero
no puede abrirse, se puede preguntar al usuario que indique otro fichero; o si una estructura
de datos se encuentra completa, siempre se podrá sobreescribir algún elemento que ya no
se necesite.
Todas las excepciones deben llevar un mensaje asociado a ellas al que se puede acceder
utilizando el método getMessage(), que presentará un mensaj describiendo el error o la
excepción que se ha producido.
Si se desea, se pueden invocar otros métodos de la clase Throwable que presentan un
traceado de la pila en donde se ha producido la excepción, o también se pueden invocar
para convertir el objeto Exception en una cadena, que siempre es más intelegible y
agradable a la vista.
Excepciones Predefinidas
Las excepciones predefinidas por la implementación actual del lenguaje Java y su jerarquía
interna de clases son las que se representan en el esquema de la figura que aparece a
continuación:
Los nombres de las excepciones indican la condición de error que representan. Las
siguientes son las excepciones predefinidas más frecuentes que se pueden encontrar:
ArithmeticException
Las excepciones aritméticas son típicamente el resultado de división por 0:
int i = 12 / 0;
NullPointerException
Se produce cuando se intenta acceder a una variable o método antes de ser definido:
class Hola extends Applet {
Image img;
paint( Graphics g ) {
g.drawImage( img,25,25,this );
}
}
IncompatibleClassChangeException
El intento de cambiar una clase afectada por referencias en otros objetos, específicamente
cuando esos objetos todavía no han sido recompilados.
ClassCastException
El intento de convertir un objeto a otra clase que no es válida.
y = (Prueba)x;
// donde x no es de tipo Prueba
NegativeArraySizeException
Puede ocurrir si hay un error aritmético al cambiar el tamaño de un array.
OutOfMemoryException
¡No debería producirse nunca! El intento de crear un objeto con el operador new ha fallado
por falta de memoria. Y siempre tendría que haber memoria suficiente porque el garbage
collector se encarga de proporcionarla al ir liberando objetos que no se usan y devolviendo
memoria al sistema.
NoClassDefFoundException
Se referenció una clase que el sistema es incapaz de encontrar.
ArrayIndexOutOfBoundsException <
Es la excepción que más frecuentemente se produce. Se genera al intentar acceder a un
elemento de un array más allá de los límites definidos inicialmente para ese array.
UnsatisfiedLinkException
Se hizo el intento de acceder a un método nativo que no existe. Aquí no existe un método
a.kk()
class A {
native void kk();
}
y se llama a a.kk(), cuando debería llamar a A.kk().
InternalException
Este error se reserva para eventos que no deberían ocurrir. Por definición, el usuario nunca
debería ver este error y esta excepción no debería lanzarse.
El compilador Java obliga al programador a proporcionar el código de manejo o control de
algunas de las excepciones predefinidas por el lenguaje. Por ejemplo, el siguiente programa
java902.java, no compilará porque no se captura la excepción InterruptedException que
puede lanzar el método sleep().
import java.lang.Thread;
class java902 {
public static void main( String args[] ) {
java902 obj = new java902();
obj.miMetodo();
}
void miMetodo() {
// Aqui se produce el error de compilacion, porque no se esta
// declarando la excepcion que genera este metodo
Thread.currentThread().sleep( 1000 ); // currentThread() genera
// una excepcion
}
}
Este es un programa muy simple, que al intentar compilar, producirá el siguiente error de
compilación que se visualizará en la pantalla tal como se reproduce a continuación:
% javac java902.java
java902.java:41: Exception java.lang.InterruptedException must be caught,
or it must be declared in the throws clause of this method.
Thread.currentThread().sleep( 1000 ); // currentThread() genera
^
Como no se ha previsto la captura de la excepción, el programa no compila. El error
identifica la llamada al método sleep() como origen del problema. Así que, la siguiente
versión del programa, java903.java, soluciona el problema generado por esta llamada.
import java.lang.Thread;
class java903 {
public static void main( String args[] ) {
// Se instancia un objeto
java903 obj = new java903();
// Se crea la secuencia try/catch que llamara al metodo que
// lanza la excepcion
try {
// Llamada al metodo que genera la excepcion
obj.miMetodo();
}catch(InterruptedException e){} // Procesa la excepcion
}
// Este es el metodo que va a lanzar la excepcion
void miMetodo() throws InterruptedException {
Thread.currentThread().sleep( 1000 ); // currentThread() genera
// una excepcion
}
}
Lo único que se ha hecho es indicar al compilador que el método miMetodo() puede lanzar
excepciones de tipo InterruptedException. Con ello conseguimos propagar las excepción que
genera el método sleep() al nivel siguiente de la jerarquía de clases. Es decir, en realidad no
se resuelve el problema sino que se está pasando a otro método para que lo resuelva él.
En el método main() se proporciona la estructura que resuelve el problema de compilación,
aunque no haga nada, por el momento. Esta estructura consta de un bloque try y un bloque
catch, que se puede interpretar como que intentará ejecutar el código del bloque try y si
hubiese una nueva excepción del tipo que indica el bloque catch, se ejecutaría el código de
este bloque, si ejecutar nada del try.
La transferencia de control al bloque catch no es una llamada a un método, es una
transferencia incondicional, es decir, no hay un retorno de un bloque catch.
Crear Excepciones Propias
También el programador puede lanzar sus propias excepciones, extendiendo la clase
System.exception. Por ejemplo, considérese un programa cliente/servidor. El código
cliente se intenta conectar al servidor, y durante 5 segundos se espera a que conteste el
servidor. Si el servidor no responde, el servidor lanzaría la excepción de time-out:
class ServerTimeOutException extends Exception {}
public void conectame( String nombreServidor ) throws Exception {
int exito;
int puerto = 80;
exito = open( nombreServidor,puerto );
if( exito == -1 )
throw ServerTimeOutException;
}
Si se quieren capturar las propias excepciones, se deberá utilizar la sentencia try:
public void encuentraServidor() {
...
try {
conectame( servidorDefecto );
catch( ServerTimeOutException e ) {
g.drawString(
"Time-out del Servidor, intentando alternativa",5,5 );
conectame( servidorAlterno );
}
...
}
Cualquier método que lance una excepción también debe capturarla, o declararla como parte
del interfaz del método. Cabe preguntarse entonces, el porqué de lanzar una excepción si
hay que capturarla en el mismo método. La respuesta es que las excepciones no simplifican
el trabajo del control de errores. Tienen la ventaja de que se puede tener muy localizado el
control de errores y no hay que controlar millones de valores de retorno, pero no van más
allá.
Y todavía se puede plantear una pregunta más, al respecto de cuándo crear excepciones
propias y no utilizar las múltiples que ya proporciona Java. Como guía, se pueden plantear
las siguientes cuestiones, y si la respuesta es afirmativa, lo más adecuado será implementar
una clase Exception nueva y, en caso contrario, utilizar una del sistema.




¿Se necesita un tipo de excepción no representado en las que proporciona el entorno
de desarrollo Java?
¿Ayudaría a los usuarios si pudiesen diferenciar las excepciones propias de las que
lanzan las clases de otros desarrolladores?
¿Si se lanzan las excepciones propias, los usuarios tendrán acceso a esas
excepciones?
¿El package propio debe ser independiente y auto-contenido?
http://www.itapizaco.edu.mx/paginas/JavaTut/froufe/parte9/cap9-2.html
7.4.3 Propagación.
PROPAGACION DE EXCEPCIONES
La cláusula catch comprueba los argumentos en el mismo orden en que aparezcan en el
programa. Si hay alguno que coincida, se ejecuta el bloque y sigue el flujo de control por el
bloque finally (si lo hay) y concluye el control de la excepción.
Si ninguna de las cláusulas catch coincide con la excepción que se ha producido, entonces se
ejecutará el código de la cláusula finally (en caso de que la haya). Lo que ocurre en este caso, es
exactamente lo mismo que si la sentencia que lanza la excepción no se encontrase encerrada en el
bloque try.
El flujo de control abandona este método y retorna prematuramente al método que lo llamó. Si la
llamada estaba dentro del ámbito de una sentencia try, entonces se vuelve a intentar el control de
la excepción, y así continuamente.
Veamos lo que sucede cuando una excepción no es tratada en la rutina en donde se produce. El
sistema Java busca un bloque try..catch más allá de la llamada, pero dentro del método que lo
trajo aquí. Si la excepción se propaga de todas formas hasta lo alto de la pila de llamadas sin
encontrar un controlador específico para la excepción, entonces la ejecución se detendrá dando
un mensaje. Es decir, podemos suponer que Java nos está proporcionando un bloque catch por
defecto, que imprime un mensaje de error y sale.
No hay ninguna sobrecarga en el sistema por incorporar sentencias try al código. La sobrecarga
se produce cuando se genera la excepción.
Hemos dicho ya que un método debe capturar las excepciones que genera, o en todo caso,
declararlas como parte de su llamada, indicando a todo el mundo que es capaz de generar
excepciones. Esto debe ser así para que cualquiera que escriba una llamada a ese método esté
avisado de que le puede llegar una excepción, en lugar del valor de retorno normal. Esto permite
al programador que llama a ese método, elegir entre controlar la excepción o propagarla hacia
arriba en la pila de llamadas. La siguiente línea de código muestra la forma general en que un
método declara excepciones que se pueden propagar fuera de él:
tipo_de_retorno( parametros ) throws e1,e2,e3 { }
Los nombres e1,e2,... deben ser nombres de excepciones, es decir, cualquier tipo que sea
asignable al tipo predefinido Throwable. Observar que, como en la llamada al método se
especifica el tipo de retorno, se está especificando el tipo de excepción que puede generar (en
lugar de un objeto exception).
He aquí un ejemplo, tomado del sistema Java de entrada/salida:
byte readByte() throws IOException;
short readShort() throws IOException;
char readChar() throws IOException;
void writeByte( int v ) throws IOException;
void writeShort( int v ) throws IOException;
void writeChar( int v ) throws IOException;
Lo más interesante aquí es que la rutina que lee un char, puede devolver un
char; no el entero que se requiere en C. C necesita que se devuelva un int,
para poder pasar cualquier valor a un char, y además un valor extra (-1)
para indicar que se ha alcanzado el final del fichero. Algunas de las rutinas
Java lanzan una excepción cuando se alcanza el fin del fichero.
En el siguiente diagrama se muestra gráficamente cómo se propaga la excepción que se genera en
el código, a través de la pila de llamadas durante la ejecución del código:
Cuando se crea una nueva excepción, derivando de una clase Exception ya existente, se puede
cambiar el mensaje que lleva asociado. La cadena de texto puede ser recuperada a través de un
método. Normalmente, el texto del mensaje proporcionará información para resolver el problema
o sugerirá una acción alternativa. Por ejemplo:
class SinGasolina extends Exception {
SinGasolina( String s ) {
// constructor
super( s );
}
....
// Cuando se use, aparecerá algo como esto
try {
if( j < 1 )
throw new SinGasolina( "Usando deposito de reserva" );
} catch( SinGasolina e ) {
System.out.println( o.getMessage() );
}
Esto, en tiempo de ejecución originaría la siguiente salida por pantalla:
> Usando deposito de reserva
Otro método que es heredado de la superclase Throwable es printStackTrace(). Invocando a este
método sobre una excepción se volcará a pantalla todas las llamadas hasta el momento en donde
se generó la excepción (no donde se maneje la excepción). Por ejemplo:
// Capturando una excepción en un método
class testcap {
static int slice0[] = { 0,1,2,3,4 };
public static void main( String a[] ) {
try {
uno();
} catch( Exception e ) {
System.out.println( "Captura de la excepcion en main()" );
e.printStackTrace();
}
}
static void uno() {
try {
slice0[-1] = 4;
} catch( NullPointerException e ) {
System.out.println( "Captura una excepcion diferente" );
}
}
}
Cuando se ejecute ese código, en pantalla observaremos la siguiente salida:
> Captura de la excepcion en main()
> java.lang.ArrayIndexOutOfBoundsException: -1
at testcap.uno(test5p.java:19)
at testcap.main(test5p.java:9)
Con todo el manejo de excepciones podemos concluir que proporciona un método más seguro
para el control de errores, además de representar una excelente herramienta para organizar en
sitios concretos todo el manejo de los errores y, además, que podemos proporcionar mensajes de
error más decentes al usuario indicando qué es lo que ha fallado y por qué, e incluso podemos, a
veces, recuperarnos de los errores.
La degradación que se produce en la ejecución de programas con manejo de excepciones está
ampliamente compensada por las ventajas que representa en cuanto a seguridad de
funcionamiento de esos mismos programas.
http://www.cica.es/formacion/JavaTut/Cap6/propaga.html
Propagación de excepciones
Cuando ocurre se lanza una excepción dentro de la sección ejecutable de un
bloque ocurre lo siguiente:
1. Si el bloque actual tiene un manejador para la excepción, se ejecutan las
instrucciones asociadas al manejador y control pasa al bloque que
engloba a éste.
2. Si no hay un manejador para la excepción, éste se propaga lanzándola
en el bloque que engloba al actual. El primer paso se repite para el
bloque que engloba.
Ejemplos
DECLARE
A EXCEPTION;
BEGIN
BEGIN
RAISE A;
EXCEPTION
WHEN A THEN
...
END;
END;
Se aplica la regla 1 para
manejar la excepción
DECLARE
A EXCEPTION;
B EXCEPTION;
BEGIN
RAISE A;
EXCEPTION
WHEN A THEN
RAISE B;
WHEN B THEN
...
END;
La excepción se propaga y el
bloque no concluye
exitósamente.
BEGIN
DECLARE
v1
NUMBER(3):='abc';
BEGIN
...
EXCEPTION
WHEN OTHERS THEN
...
END;
EXCEPTION
WHEN OTHERS THEN
...
END;
DECLARE
A EXCEPTION;
B EXCEPTION;
BEGIN
BEGIN
RAISE B;
EXCEPTION
WHEN A THEN
...
END;
EXCEPTION
WHEN B THEN
...
END;
Se aplica la regla 2 al bloque interno
DECLARE
v1 NUMBER(3) := 'abc';
BEGIN
...
EXCEPTION
WHEN OTHERS THEN
...
END;
Esta excepción es inmediatamente
propagada, sin ejecutar las instrucciones
asociadas al manejador WHEN OTHERS
DECLARE
A EXCEPTION;
B EXCEPTION;
BEGIN
RAISE A;
EXCEPTION
WHEN A THEN
RAISE B;
WHEN B THEN
...
END;
En este caso, el bloque interno
lanza la excepción, y el bloque
externo la maneja.
BEGIN
DECLARE
A EXCEPTION;
B EXCEPTION;
BEGIN
RAISE A;
EXCEPTION
WHEN A THEN
RAISE B;
WHEN B THEN
...
END;
EXCEPTION
WHEN B THEN
...
END;
En este caso, si se tiene una
conclusión exitósa.
La excepción A es lanzada y manejada, pero
su manejador lanza B y esta es propagada
hacia un bloque exterior, por lo que este
bloque no termina exitósamente
DECLARE
A EXCEPTION;
BEGIN
RAISE A;
EXCEPTION
WHEN A THEN
...
RAISE;
END;
Cuando RAISE no tiene argumento, la
excepción actual es propagada a un bloque
externo.
http://www.lania.mx/biblioteca/seminarios/basedatos/plsql/errores/propagac
ion.html
7.5 Gestión de excepciones.
Gestión de excepciones
Es posible escribir programas que gestionen ciertas excepciones. Mira el siguiente ejemplo, que
pide datos al usuario hasta que se introduce un entero válido, pero permite al usuario interrumpir
la ejecución del programa (con Control-C u otra adecuada para el sistema operativo en
cuestion); Observa que una interrupción generada por el usuario se señaliza haciendo saltar la
excepcion KeyboardInterrupt.
>>> while 1:
...
try:
...
x = int(raw_input("Introduce un número: "))
...
break
...
except ValueError:
...
print "¡Huy! No es un número. Prueba de nuevo..."
...
La sentencia try funciona de la siguiente manera:




Primero se ejecuta la cláusula try (se ejecutan las sentencias entre try
y except).
Si no salta ninguna excepción, se omite la cláusula except y termina la
ejecución de la sentencia try.
Si salta una excepción durante la ejecución de la cláusula try, el resto
de la cláusula se salta. Seguidamente, si su tipo concuerda con la
excepción nombrada tras la palabra clave except, se ejecuta la cláusula
except y la ejecución continúa tras la sentencia try.
Si salta una excepción que no concuerda con la excepción nombrada
en la cláusula except, se transmite a sentencias try anidadas
exteriormente. Si no se encuentra un gestor de excepciones, se
convierte en una excepción imprevista y termina la ejecución con un
mensaje como el mostrado anteriormente.
Una sentencia try puede contener más de una cláusula except, para capturar diferentes
excepciones. Nunca se ejecuta más de un gestor para una sola excepción. Los gestores sólo
capturan excepciones que saltan en la cláusula try correspondiente, no en otros gestores de la
misma sentencia try. Una cláusula try puede capturar más de una excepción, nombrándolas
dentro de una lista:
... except (RuntimeError, TypeError, NameError):
...
pass
La última cláusula except puede no nombrar ninguna excepción, en cuyo caso hace de comodín y
captura cualquier excepción. Se debe utilizar esto con mucha precaución, pues es muy fácil
enmascarar un error de programación real de este modo. También se puede utilizar para mostrar
un mensaje de error y relanzar la excepción (permitiendo de este modo que uno de los llamantes
gestione la excepción a su vez):
import string, sys
try:
f = open('mifichero.txt')
s = f.readline()
i = int(string.strip(s))
except IOError, (errno, strerror):
print "Error de E/S(%s): %s" % (errno, strerror)
except ValueError:
print "No ha sido posible covertir los datos a entero."
except:
print "Error no contemplado:", sys.exc_info()[0]
raise
La sentencia try ... except tiene una cláusula else opcional, que aparece tras las cláusulas
except. Se utiliza para colocar código que se ejecuta si la cláusula try no hace saltar ninguna
excepción. Por ejemplo:
for arg in sys.argv[1:]:
try:
f = open(arg, 'r')
except IOError:
print 'no se puede abrir', arg
else:
print arg, 'contiene', len(f.readlines()), 'líneas'
f.close()
El uso de la cláusula else es mejor que añadir código adicional a la cláusula try porque evita
que se capture accidentalemente una excepción que no fue lanzada por el código protegido por la
sentencia try ... except.
Cuando salta una excepción, puede tener un valor asociado, también conocido como el/los
argumento/s de la excepción. Que el argumento aparezca o no y su tipo dependen del tipo de
excepción. En los tipos de excepción que tienen argumento, la cláusula except puede especificar
una variable tras el nombre de la excepción (o tras la lista) que recibirá el valor del argumento,
del siguiente modo:
>>> try:
...
fiambre()
... except NameError, x:
...
print 'nombre', x, 'sin definir'
...
nombre fiambre sin definir
Si una excepción no capturada tiene argumento, se muestra como última parte (detalle) del
mensaje de error.
Los gestores de excepciones no las gestionan sólo si saltan inmediatamente en la cláusula try,
también si saltan en funciones llamadas (directa o indirectamente) dentro de la cláusula try. Por
ejemplo:
>>> def esto_casca():
...
x = 1/0
...
>>> try:
...
esto_casca()
... except ZeroDivisionError, detalle:
...
print 'Gestión de errores:', detalle
...
Gestión de errores: integer division or modulo
7.5.1 Manejo de excepciones.
Manejo de
excepciones en
VB.net
Fecha: 24/Feb/2005 (16 de Febrero del 2005)
Autor: Jesús Enrique Gonzáles Azcarate
Email: [email protected]
Lo siguiente fue extraído de la ayuda, que otra mejor explicación que esa.
Visual Basic admite un control de excepciones (errores) tanto estructurado
como no estructurado.
El control de errores estructurado y no estructurado permite establecer un
plan para detectar posibles errores, y así impedir que éstos interfieran en los
supuestos objetivos de la aplicación.
Si se produce una excepción en un método que no esté preparado para
controlarla, la excepción se propagará de vuelta al método de llamada o al
método anterior. Si el método anterior tampoco tiene controlador de
excepciones, la excepción se propagará de vuelta al llamador del método, y
así sucesivamente. La búsqueda de un controlador continuará hasta la pila de
llamadas, que es la serie de procedimientos a los que se llama dentro de la
aplicación. Si ésta tampoco encuentra un controlador para la excepción, se
mostrará un mensaje de error y la aplicación finalizará.
Nota Un método puede contener un método de control de excepciones
estructurado o uno no estructurado, pero no ambos.
Control estructurado de excepciones
En el control estructurado de excepciones, los bloques de código se
encapsulan y cada uno de ellos tiene uno o varios controladores asociados.
Cada controlador especifica una forma de condición de filtro para el tipo de
excepción que controla. Cuando el código de un bloque protegido genera una
excepción, se busca por orden en el conjunto de controladores
correspondientes y se ejecuta el primero que contenga una condición de filtro
coincidente. Un método puede tener varios bloques de control estructurado de
excepciones y dichos bloques pueden además estar anidados.
La instrucción Try...Catch...Finally se utiliza específicamente para el control
estructurado de excepciones.
Aquí en este manualillo solo se explicará este método, el control estructurado
de excepciones.
Visual Basic admite el control estructurado de excepciones, que facilita la
tarea de crear y mantener programas mediante controladores de errores
consistentes y exhaustivos. El control estructurado de excepciones es un
código diseñado para detectar y dar respuesta a los errores que se producen
durante la ejecución, mediante la combinación de una estructura de control
(similar a Select Case o While) con excepciones, bloques de código protegidos
y filtros.
El uso de la instrucción Try…Catch…Finally permite proteger bloques de código
con posibilidades de generar errores. Los controladores de excepciones
pueden anidarse, y las variables que se declaren en cada bloque tendrán un
ámbito local.
Try
‘El código que puede producir el error
Catch [filtros opcionales o tipos de errores a capturar]
‘Código para cuando se produzca un error
[bloques catch adicionales]
Finally
‘Código para cuando aparezca o no un error
End Try
El bloque Try de un controlador de excepción Try...Catch...Finally contiene la
sección del código que el controlador de errores va a supervisar. Si se produce
un error durante la ejecución de cualquier parte del código de esta sección,
Visual Basic examinará cada instrucción Catch de Try...Catch...Finally hasta
que encuentre una cuya condición coincida con el error. Si la encuentra, el
control se transferirá a la primera línea de código del bloque Catch. Si no se
encuentra una instrucción Catch coincidente, la búsqueda continuará en las
instrucciones Catch del bloque Try...Catch...Finally exterior que contiene el
bloque en el que ocurrió la excepción. Este proceso se prolongará a lo largo de
toda la pila hasta que se encuentre un bloque Catch coincidente en el
procedimiento actual. De no encontrarse, se produciría un error.
El código de la sección Finally siempre se ejecuta en último lugar,
inmediatamente antes que el bloque de control de errores pierda su ámbito,
con independencia de que se ejecute el código de los bloques Catch. Sitúe el
código de limpieza (el código que cierra los archivos y libera los objetos, por
ejemplo) en la sección Finally.
Filtrar errores en el bloque Catch
Los bloques Catch ofrecen tres opciones para filtrar errores específicos. En una
de ellas, los errores se filtran basándose en la clase de la excepción (en este
caso ClassLoadException), como se muestra en el siguiente código:
Try
' "Try" block.
Catch e as ClassLoadException
' "Catch" block.
Finally
' "Finally" block.
End Try
Si se produce un error ClassLoadException, se ejecuta el código del bloque
Catch especificado.
En la segunda opción para filtrar errores, la sección Catch puede filtrar
cualquier expresión condicional. Un uso común de este formato del filtro Catch
consiste en comprobar números de error específicos, como se muestra en el
código siguiente:
Try
' "Try" block.
Catch When ErrNum = 5 'Type mismatch.
' "Catch" block.
Finally
' "Finally" block.
End Try
Cuando Visual Basic encuentra el controlador de errores coincidente, ejecuta
el código de este controlador y pasa el control al bloque Finally.
Nota Al buscar un bloque Catch que controle una excepción, se evalúa el
controlador de cada bloque hasta encontrar uno que coincida. Puesto que
estos controladores pueden ser llamadas a funciones, es posible que se
produzcan efectos secundarios no esperados; por ejemplo, una llamada de
este tipo puede cambiar una variable pública y provocar que ésta se utilice en
el código de un bloque Catch distinto que termina controlando la excepción.
La tercera alternativa, consiste en combinar las primeras dos opciones y
utilizar ambas para el control de excepciones.
El bloque Finally siempre se ejecuta, con independencia de cualquier otra
acción que tenga lugar en los bloques Catch anteriores. No puede utilizar
Resume o Resume Next en el control estructurado de excepciones.
Ahora se realizarán una serie de ejemplos que aclararán lo expuesto
Ejemplo 1.
Module Module1
Sub Main()
Dim sValor As String
Dim iNumero As Integer
Try
'Aqui comienza el control de errores
Console.WriteLine("Introducir un número")
sValor = Console.ReadLine
'Si no hemos introducido ningún número
iNumero = sValor 'aquí se producira un error
Catch
'Si se produce un error, se generará una excepción
'que capturamos en este bloque de código
'manipulador de excepción, definido por Catch
Console.WriteLine("Error al introducir el número" & ControlChars.CrLf
& "el valor {0} es incorrecto", sValor)
End Try
Console.Read()
End Sub
End Module
Esta y las siguientes 2 líneas no pertenecen al código anterior
Observen que antes de module1 no están declaradas Option Explicit On o
Option Strict On.
Ejemplo 2. El clásico de la división por cero el que está en todos los libros
Module Module1
Sub Main()
Dim x As Integer = 5
Dim y As Integer = 0
Try
x /= y
Catch ex As Exception When y = 0
Console.WriteLine(ex.Message)
Finally
Console.WriteLine("Este código siempre se ejecuta")
End Try
Console.Read()
End Sub
End Module
Esta y las siguientes 2 líneas no pertenecen al código anterior
Observen que antes de module1 no están declaradas Option Explicit On o
Option Strict On.
Otra forma de generar la anterior excepción es:
Ejemplo 3
Module Module1
Sub Main()
Dim x As Integer = 0
Try
Dim y As Integer = 100 / x
Catch e As ArithmeticException
Console.WriteLine("ArithmeticException Handler: {0}", e.ToString())
Catch e As Exception
Console.WriteLine("Generic Exception Handler: {0}", e.ToString())
End Try
Console.Read()
End Sub
End Module
Ejemplo 4
El siguiente ejemplo es el mismo del ejemplo 1 pero se le adicionó al final más
código
Module Module1
Sub Main()
Dim sValor As String
Dim iNumero As Integer
Try
'Aqui comienza el control de errores
Console.WriteLine("Introducir un número")
sValor = Console.ReadLine
'Si no hemos introducido ningún número
iNumero = sValor 'aquí se producira un error
Catch
'Si se produce un error, se generará una excepción
'que capturamos en este bloque de código
'manipulador de excepción, definido por Catch
Console.WriteLine("Error al introducir el número" & ControlChars.CrLf
& "el valor {0} es incorrecto", sValor)
End Try
Dim dtFecha As Date
Console.WriteLine("Introducir una fecha")
'Si ahora se produce un error,
'al no disponer de una estructura para controlarlo
'se cancelará la ejecución
dtFecha = Console.ReadLine
Console.WriteLine("La fecha es {0}", dtFecha)
Console.ReadLine()
End Sub
End Module
Se debe tener en cuenta la forma de capturar los errores desde los más
específicos hasta los más generales de lo contrario los errores serán tomados
dentro del primer catch no permitiendo capturar el error específico que
necesitamos. Por ejemplo en el ejercicio anterior el catch es muy general esto
quiere decir que cualquier error que se produzca entrará a este bloque.
http://www.elguille.info/colabora/NET2005/cyber_Manejo_de_excepciones_en_VB.net.htm
Manejo de excepciones en Turbo Pascal
Revisión 4.0
Adolfo Di Mare
Resumen
Se presenta un paquete que implementa
las construcciones requeridas para
manejar excepciones en el ámbito del
Turbo Pascal, en las versiones v4.0 y
posteriores 5.0, 5.5 y 6.0. El paquete
sigue el modelo de terminación para
manejo de excepciones, y es relativamente
fácil de usar.
A software package for
exception handling in Turbo
Pascal version v4.0 and later is
discussed. This package
follows the exception
termination model, and it is
relatively easy to use.
Una excepción es un evento inesperado, generalmente con proporciones de calamidad. El
buen programador desea que su programa sea robusto, de forma que ante estos eventos reaccione
adecuadamente; si no es posible corregir los problemas que se producen después de una falla, es
por lo menos deseable que el programa tenga una degradación suave, evitando de esta manera un
estruendoso fin de ejecución. En lo posible se trata siempre de sobreponerse de la mejor manera a
la catástrofe.
Existen muchos tipos de excepciones. Por ejemplo, cuando en tiempo de ejecución se usa un
índice de vector que está fuera de rango se produce una excepción. También sucede cuando se
hace una división por cero, o cuando el resultado de una expresión es demasiado pequeño
(desbordamiento y bajo rebase). Los principales tipos de excepción son los siguientes:



Errores en tiempo de ejecución, como división por cero, sobre rebase
(overflow), bajo rebase (underflow), o intentar leer de un archivo que
no existe.
Excepciones generadas por las bibliotecas de programas, que
aprovechan el mecanismo de excepciones para simplificar el uso de la
biblioteca.
Excepciones generadas por el programador, porque mediante la
generación y manejo de excepciones es posible construir programas
más robustos o retornar de invocaciones que están profundamente
anidades [LG-86]. .
Un ejemplo práctico del uso de excepciones es el siguiente: supóngase que se está escribiendo
una hoja de cálculo, que tiene un largo y complicado proceso para recalcular el valor de todas las
celdas. Se desea permitirle al usuario interrumpir en cualquier momento el proceso, lo que puede
implementarse de dos formas.
La primera implementación, y más complicada, consiste en agregar un montón de parámetros
de retorno a cada rutina usada para recalcular la hoja. En el momento en que se detecta la
interrupción, se comenzaría una cadena de retornos de procedimientos, hasta llegar al nivel
superior. En cada procedimiento debe incluirse código para retornar adecuadamente después de
que se produce la interrupción, mientras realiza su trabajo.
La otra forma es usar excepciones. Inicialmente, se establece un contexto de excepción en el
nivel principal. Luego cualquier rutina (hasta las recursivas) pueden retornar abruptamente a este
nivel generando una excepción. De esta manera el programa que no estará plagado de códigos de
retorno para manejar esta situación.
El ejemplo anterior muestra que si no se usan excepciones, el programador que desea
implementar un programa muy robusto debe incluir en cada procedimiento muchos parámetros
que le permitan retornar códigos de error. Además, cada procedimiento debe también incluir
bastante código para tomar las acciones apropiadas, dependiendo de cada posible problema. De
esta forma quedan mezcladas en el mismo módulo la lógica que resuelve un problema con la que
maneja las situaciones insólitas que surgen cuando ocurren fallas. El resultado de todo esto es
programas que son tan difíciles de leer y como de mantener.
Una manera de resolver este problema es usar un manejador de excepciones. Cada lenguaje
de computación da soporte al manejo de excepciones de forma y grado diferente. En el lenguaje
PL/I se implementa mediante la cláusula ON; CLU tiene un mecanismo a este efecto (que es, por
cierto, muy completo) y en C se usan los famosos procedimientos de biblioteca setjump() y
longjump(). Lisp no podía ser la excepción, y cuenta con CATCH y THROW. Para el caso de Turbo
Pascal existen varias bibliotecas que implementan este mismo concepto, usando alguno de los
nombres anteriores. En ADA se usan los verbos "exception" y "raise"; la implementación que
aquí se describe es muy parecida al mecanismo de ADA, aumentado con el verbo Signal()
propuesto en [Lee-83].
Uso del manejador except.pas
La implementación del manejador de excepciones para Turbo Pascal está en la unidad
except.pas, la que cuenta con los siguientes procedimientos:
-
FUNCTION
FUNCTION
FUNCTION
PROCEDURE
PROCEDURE
PROCEDURE
PROCEDURE
PROCEDURE
Exception_Code
: WORD;
Catch
: WORD;
Exception
: BOOLEAN;
Throw
(code : WORD);
Signal
(code : WORD);
Re_Throw;
Leave_Exception_Context;
Release_Nested_Contexts;
Una buena parte de la implementación de estas operaciones está escrita
en lenguaje de máquina, pues para manejar las excepciones es necesario
modificar la pila de ejecución del programa. Su funcionamiento es realmente
interesante, pues aunque estos procedimientos son llamados como si fueran
procedimientos, al ejecutar un Throw(), Signal() o Re_Throw() se regresa a
donde se llamó al último Exception().
IF NOT Exception THEN BEGIN
{ <=== }
Proceso_Normal({...});
Leave_Exception_Context;
END
ELSE BEGIN
{ Manejadores de excepciones }
{ ==> Exception = TRUE
}
CASE Catch OF
1: Interrupcion_Operador;
2: Datos_Invalidos({...});
3: Error_Fatal;
6: Throw(7);
8: Re_Throw;
ELSE
Re_Throw;
END; { CASE }
END; { Exception }
Figura 1
Para atender excepciones, el programador debe establecer un Contexto de Excepción, que es
un bloque de código que tiene asociados varios manejadores de excepciones. En el caso de Turbo
Pascal, para establecer el contexto de excepción se usa el procedimientos Exception(), en la
forma ilustrada en la Figura 1.
En la Figura 1 se muestra cómo el código que normalmente se ejecuta para resolver un
problema dado, que se denota por medio del procedimiento Proceso_Normal(), está físicamente
separado de los manejadores de excepciones, los que se encuentran después de la instrucción
"CASE Catch OF".
Cuando el procedimiento Exception() se ejecuta el resultado es que guarda, en un variable
global definida dentro de la unidad except.pas, su propia dirección de retorno. Además,
Exception() siempre retorna FALSE como su valor. Como se ha guardado la dirección de retorno
de Exception(), las demás operaciones que implementan el manejo de excepciones pueden
luego simular un retorno de la función Exception() pero con el valor TRUE, lo que hace que se
ejecute el código envuelto en le ELSE del IF que envuelve al contexto de excepción [Col-91].
Esto quiere decir que cuando Exception() se ejecuta siempre retorna el valor FALSE,
mientras que cuando se intercepta una excepción la forma de procesarla es simular un retorno de
la función Exception(), pero con el valor de retorno TRUE. Este elegante truco es muy conocido
en ambientes C, en los que las funciones setjump() y longjump() soportan este tipo de
comportamiento [Vid-92].
Lo importante del manejo de excepciones es que en general no se puede saber de antemano
cuando se producirá una. El programador puede generar una en un módulo anidado
profundamente, o puede ser que un dato recién leido de un archivo tenga un formato incorrecto
que provoque una falla en el programa. Independientemente de cómo se genera una excepción, en
el ambiente de programación debe definirse como interceptarlas. Por eso el manejo de
excepciones depende mucho de la plataforma en que corra el programa. En el caso de los
sistemas Unix, el soporte para manejo de excepciones se da por medio de las funciones
setjump() y longjump(), aunque los lenguajes de programación para una plataforma a veces
incluyen un soporte más extenso, como es el caso de ADA y C++.
Lo más usual es identificar cada una de las posibles excepciones por medio de un Código de
Excepción. La siguiente es pequeña lista de algunas excepciones "famosas" de Turbo Pascal:
CONST
No_Exception
=
0; { ZERO }
{ DOS Errors: [1..99] }
File_Not_Found
File_Access_Denied
=
=
2;
5; {...}
{ I/O Errors: [100..149] }
Disk_Read_Error
= 100;
Disk_Write_Error
Disk_Full
File_Not_Open
= 101;
= Disk_Write_Error;
= 103;
{ Critical Errors: [150..199] }
Device_Write_Fault
= 160;
Device_Read_Fault
= 161;
{ Fatal Errors: [200..255] }
Division_by_Zero
= 200;
Range_Check_Error
= 201;
Floating_Point_Overflow
= 205;
Floating_Point_Underflow
= 206;
Invalid_Floating_Point_Opr = 207;
En el caso del Turbo Pascal, lo que sucede es que cuando un
procedimiento de biblioteca se encuentra con una falla, o cuando se recibe
un código que amerita la cancelación del programa, entonces el ambiente
Turbo Pascal invoca a la función apuntada por la variable global
System.ExitSave. Como este comportamiento está claramente documentado,
para implementar la unidad except.pas bastó usar a ExitSave para interceptar
todas las excepciones que se producen en tiempo de ejecución. El código de
excepción siempre se encuentra en la variable global System.ExitCode.
La programación con Excepciones
Una vez que el programador cuenta con la posibilidad de manejar
excepciones también necesita poder generarlas en sus programas. Para esto
debe utilizar a las rutinas Signal(n), Throw(n) y Re_Throw(). Estas operaciones
lo que hacen es simular que en tiempo de ejecución se ha producido la
excepción número "n". Por ejemplo, para simular que se ha dado un error
por división por cero, el programador puede ejecutar una de estas
operaciones:
Throw(Division_by_Zero);
Signal(Division_by_Zero);
Throw(200);
Signal(200);
La diferencia al regresar a Exception() desde Throw(), Re_Throw() o
Signal() es que en este caso el valor retornado por Exception() es TRUE. De
esta manera el programador puede depurar el código de manejo de
excepciones, o usarlas para cualquier otro propósito en sus programas.
Cuando se ejecuta alguna de las funciones Throw(), Signal() o Re_Throw(), lo que sucede
es que estas operaciones simulan un retorno a la última invocación de la función Exception(),
pero el valor retornado es TRUE. Esto implica que dentro de la unidad except.pas debe
manetenerse una pila de invocaciones a Exception(). Es muy importante mencionar que para
evitar retornar a un contexto de excepción que ya no existe, la última instrucción en todo contexto
de excepción debe ser una invocación a la operción Leave_Exception_Context(), la que se
encarga de resincronizar la pila interna que contiene except.pas con la pila de ejecución del
programa.
Desgraciadamete, en esta implementación si el programador olvida invocar a
Leave_Exception_Context() al final de cada bloque de excepción el resultado es un error que
es muy difícil de encontrar, pues el paquete de excepciones tratará de ejecutar procedimientos
que ya han terminado, por lo que su pila de ejecución contendrá basura. Encontrar un error de
estos es realmente muy difícil, y desgraciadamente no es posible dejar de obligar al programador
a invocar a Leave_Exception_Context(); la única salida de este problema es que el compilador
provea el manejo de excepciones, de forma que al compilar incluya que haga el trabajo de
Leave_Exception_Context().
En la Figura 1 se muestra cómo se usan los el manejadores de excepciones. La primera vez
que se invoque a Exception() el valor retornado será FALSE, con lo que el computador
continuará la ejecución en el bloque denotador por Proceso_Normal({...});. Cuando el
programador desea invocar al manejador de excepciones, posiblemente porque detecta una
situación anómala, entonces debe ejecutar el siguiente código:
Throw(3); { vuelve a Exception; Catch = 3 }
También podría ocurrir que un error de tiempo de ejecución produzca una excepción de
código número 3 (que significa "Path_Not_Found" en Turbo Pascal), con lo que el manejador de
excepciones sería invocado. El resultado será que se transferirá el control de ejecución a la última
(más reciente) invocación activa del procedimiento Exception(), y el valor que se retornará será
TRUE. Esto hará que el control se transfiera a la parte ELSE del IF, y luego el valor retornado por
Catch() será 3.
El argumento de Throw(3) es el código de error, o número de excepción, que sirve para
identificar a cada una de las excepciones para las que el programador ha implementado un
manejador de excepciones. En la Figura 1 se incluye código para procesar las excepciones 1, 2, 3,
6 y 8. Si el código enviado por Throw() no es uno de éstos, entonces la cláusula ELSE del CASE se
ejecutará. La utilidad de la pila interna de except.pas es precisamente recordar cuáles son todos
los contextos de excepción a los que se ha entrado.
El Re_Throw() actúa como un Throw(), pero envía el control al contexto asociado con el
Exception() inmediatamente anterior (si lo enviara al mismo contexto entonces el programa se
enciclaría). Re_Throw() simplemente ejecuta un Throw() con el último código de excepción, por
lo que debe usarse únicamente dentro del código de manejo de excepciones. Si se ejecuta un
Re_Throw() cuando no se ha producido una excepción, el resultado es, como mínimo, desastroso,
pues el paquete de excepciones no sabrá cual es el código de excepción que debe retorno.
Los nodos más viejos que están en la pila de except.pas corresponden a las primeras
invocaciones de Exception(). De esta manera los procedimientos de nivel superior puedan
atender excepciones que los de nivel inferior no manejan. La cadena de transferencias de control
terminaría si se llegara al primer contexto de excepción, pues simplemente se cancelaría el
programa para luego regresas el control al sistemas operativo. En este caso, el código de
excepción indicará la razón de cancelación del programa.
Excepciones y ADTs
Un Tipo Abstracto de Datos [ADT] es un módulo que encapsula el acceso
a una estructura de datos particular. Los ADTs más conocidos son el Arreglo,
la Lista y el Arbol. Además de la modularidad de programación que los ADTs
ayudan a alcanzar, también son muy importantes porque con ellos se
introdujeron los concepto de constructores y destructores, que son las
operaciones del ADT encargadas de inicializar y destruir los objetos que usa
un programa.
Un manejador de excepciones no está completo si no tiene soporte para ADTs. Para esto, es
necesario que el manejador de excepciones sea capaz de destruir los objetos que están en un
contexto de excepción que debe ser abandonado abruptamente cuando se produce una excepción.
Como el compilador de Pascal no tiene soporte para excepciones, entonces para implementar
el soporte para ADTs es necesario que el programador incluya en sus ADTs un campo que luego
le permita al manejador de excepciones invocar a los destructores de los ADTs. Para esto el
programador debe incluir dentro de su ADT un campo de tipo TException, que sirve para crear
una lista doblemente enlazada que une a todos los ADTs que deben ser destruidos. Este campo
tiene un puntero que apunta al objeto a destruir y otro que apunta al destructor del objeto. De esta
manera, cuando se produce una excepción, el manejador de excepciones puede invocar al
destructor del ADT.
Para enlazar un ADT a la lista de excepciones el programador debe invocar a la operación:
PROCEDURE Register_ADT(
VAR ADT;
VAR snode: TException;
done : POINTER);
{ Instancia del ADT }
{ campo dentro del ADT }
{ Puntero al destructor }
En la implementación del destructor del ADT, el programador debe
invocar a la operación:
PROCEDURE UnRegister_ADT(VAR snode: TException);
que se encarga de desligar al ADT de la lista de excepciones.
Throw()
vs Signal()
La diferencia entre Throw() y Signal() es que Signal() no vuelve a un
contexto que esté dentro del mismo procedimiento en que es invocado, sino
que siempre retorna al contexto más cercano que esté en algún
procedimiento que haya invocado, directa o indirectamente, al procedimiento
en donde se invoca a Signal(). Signal() ignora los contextos de excepción
que están a su mismo nivel, y siempre retorna a los de algún procedimiento
llamador.
Por el contrario, Throw() siempre retorna al contexto de excepción más inmediato, aunque
ese contexto se encuentre en el mismo procedimiento en que se invoca a Throw().
Las operaciones Throw(), Signal() y Re_Throw() tienen el mismo objetivo: transferir el
control al llamado de Exception(), pero devolviendo el valor TRUE. El Throw() transfiere
control al contexto de excepción más próximo, que puede estar en el mismo procedimiento en el
que la invocación Throw() aparece. Signal() actúa diferente, pues inmediatamente causa la
terminación del procedimiento en que aparece, y transfiere el control al siguiente contexto de
manejo de excepciones. Es perfectamente válido invocar a Throw(), Signal() o Re_Throw()
dentro del mismo manejador de excepciones.
La sutil diferencia entre Throw() y Signal() surge para darle apoyo a una técnica de
construcción de programas en la que los módulos se organizan en capas de diferente profundidad.
Como estas capas corresponden a la invocación de procedimientos, la diferencia entre Throw() y
Signal() puede expresarse de la siguiente manera: Signal() siempre transfiere el control a la
capa superior de procesamiento, mientras que Throw() no necesariamente lo hace. En aquellos
casos en que el programador no usa contextos de excepción anidados, por lo que en cada
momento cualquier procedimiento tiene sólo un contexto para manejo de excepciones activo, no
hay diferencia entre Throw() y Signal().
Esta implementación de Signal() difiere de la propuesta en [Lee-83], en la que invocar a
Signal() siempre es equivalente a invocar a Throw() seguido por Re_Throw(). Signal()
ignora todos los contextos de manejo de excepciones que están dentro del mismo procedimiento,
y bifurca a algún procedimiento llamador.
Programa de ejemplo useexcp.pas
Para mostrar las cualidades (y defectos) del uso de excepciones hay que
hecharle una ojeada al programa useexcp.pas, que realiza un trabajo
bastante reducido, aunque ejercita toda la funcionalidad de except.pas. Este
programa insistentemente invoca varios procedimientos para mostrar el
resultado de usar las excepciones. La forma de ver qué ocurre en el
programa es utilizar el depurador simbólico del ambiente de programación
para ejecutarlo paso por paso, instrucción por instrucción (con las tecla F7 y
F8). Es necesario establecer un punto de corte (breakpoint) para el
manejador de excepciones antes de entrar al contexto de excepción, pues
cuando se produce una excepción para interceptarla hay que modificar la pila
de ejecución del programa, y si de antemano el operador no ha establecido
el punto de corte entonces el programa continuará ejecutándose hasta
terminar. En cualquier momento es posible ver cuáles son los procedimientos
activos examinando la pila de ejecución del programa con la tecla Ctrl-F3.
VAR
clean : ARRAY[0..5] OF BYTE;
{...}
IF NOT Exception THEN BEGIN
FOR i := 0 TO 6 DO BEGIN
clean[i] := 0;
{ Range_Check_Error ==> => =>}
END;
{||}
{||}
Leave_Exception_Context;
{\/}
END
{ }
ELSE BEGIN
{||}
{ Exception handler }
{\/}
CASE Catch OF
{ <= <= <= <= <= <= <=}
Range_Check_Error : BEGIN
WriteLn('clean[',i:1,'] is out of bounce');
i := 0;
END;
ELSE
EXIT;
END { CASE }
END; { Exception }
Figura 2
Todo el trabajo se realiza dentro del procedimiento WORK(). Al principio se define el vector
clean[] de 6 componentes con rango [0..5]. La Figura 2 contiene el primer ejemplo de uso de
except.pas. Cuando en el contexto de excepción se trata de accesar el campo clean[6], que no
existe pues el rango máximo es 5, se produce la excepción de rango y el control pasa al contexto
de excepción que envuelve al ciclo FOR en que se produce la falla del programa. Para facilitarle al
lector ubicar los puntos de salto, cada punto de salto está marcado con flechas que indican adónde
será transferido el control una vez que se produzca la excepción (en este caso el lector debe poner
un punto de corte en la instrucción CASE Catch OF, que es adónde comienza el manejador de
excepciones).
Luego se muestra que es factible detectar excepciones de sobre rebase (overflow) cuando
dentro de un contexto de excepción se ejecuta la instrucción:
a := Exp(100000);
En este mismo ejemplo se muestra que la invocación a Re_Thorw() hace que el control pase al
contexto de excepción inmediatamente superior.
Más adelante al invocar al procedimiento Cicle() se muestra que es posible retornar de un
procedimiento recursivo usando Signal() o Throw(). Como el procedimiento Cicle() no
establece su propio contexto de excepción, el efecto de usar Signal() o Throw() en este caso es
el mismo, aunque más adelante, en el procedimiento Throw_vs_Signal(), se muestra la
diferencia. En todo momento es válido anidar contextos de excepción, ya sea directamente en el
programa, o indirectamente al invocar una rutina que tiene su propio contexto de excepción.
Algunas veces este paquete de excepciones puede detectar que el programador olvidó liberar
un contexto de excepción invocando a Leave_Exception_Context(). Un ejemplo se muestra en
la rutina Exception_without_Leave_Exception().
Otro ejemplo interesante del uso de excepciones es la invocación al procedimiento
Deep_Throat(0), que lo único que hace es llamarse recursivamente y de cuando en cuando
imprimir un puntico. Eventualmente la pila de ejecución se agota, y se produce una excepción por
rebase de la pila del programa. Este ejemplo muestra una de las fortalezas de except.pas, pues
pocos programas pueden soportar que se les rebase la pila de ejecución.
************
clean[6] is out of bounce
Overflow found a := 0.0
==> call Cicle(7)==> call Cicle(6)==> Cicle(6)::Throw(6)
==> call Cicle(9)==> call Cicle(8)==> Cicle(8)::Signal(8)
==> call Cicle(2)==> call Cicle(1)==> call Cicle(0)==>
Cicle(0)::Throw(0)
0 1 2 3 Level #3
Throw(25) received ==> Signal(NINE)
a := 246913578.0
i := 0
I will create some exception contexts
==> I will NOT release them!!! ++++++++++++++++++----- (40)
As except.pas is dumb, this gets executed
====> I cleaned up the mess!!! [but I should fix the program]
Couldn't fix the exception stack
Exception stack cleared!
Ctrl-Break cannot be detected within the IDE
(0)... Program_Stack_Overflow
clean[6] is out of bounce
ExitProcedure [System.ExitCode = 0]
Figura 3
A fin de cuentas, el resultado de ejecutar useexcp.pas se muestra en la Figura 3. Algunos de
los mensajes fueron producidos por la rutina ExitProcedure() que se ejecuta cuando el
programa termina; los otros sirven para ver cómo se desarrolla la ejecución cada vez que se
produce una excepción. Al ejecutar a useexcp.pas también se puede observar cómo funciona
except.pas, entrando con F7 a ver qué hace cada uno de las rutinas que exporta esa unidad.
Restricciones de except.pas
Ya se mencionó que esta implementación de excepciones requiere que el
programador "recuerde" siempre cerrar cada contexto de excepción
invocando a Leave_Exception_Context(). Esto es muy incómodo, pero necesario
porque no siempre es posible saber cuál contexto de excepción está todavía
activo. Si except.pas no fuera un módulo externo al lenguaje Turbo Pascal,
entonces el compilador podría insertar la invocación a
Leave_Exception_Context() automáticamente; pero no parece que este tipo de
soporte se vaya a incluir para el lenguaje Turbo Pascal.
El otro problema que debe enfrentar el programador cuando usa el paquete de excepciones es
que en algunos casos pueden quedar variables asignadas en memoria dinámica que no pueden
destruirse porque para manejar excepción se ha debido abandonar abruptamente un
procedimiento. Por ejemplo, si el procedimiento Proc() llama a Explota(), y Explota() crea
algunas variables en memoria dinámica, como los punteros a esas variables son valores locales a
Explota(), cuando el manejador de excepciones transfiere el control de ejecución a Proc() a
causa de una excepción, los punteros a las variables creadas en memoria dinámica por Explota()
se pierden porque ya no existen las variables locales de Explota(). El resultado es que queda un
poco de memoria dinámica asignada que no puede ser recuperada. Una solución paracial para
este problema es que el programador use ADTs, y nuevamente recuerde registrarlos al contexto
de excepción al implementar los constructores y destructores, para que el manejador de
excepciones se encarga de invocar a sus destructores si han sido debidamente registrados en su
contexto de excepción.
Otra solución al problema de la memoria dinámica es que el programador se preocupe de
desasignar la memoria dinámica utilizado en antes de invocar a Throw() o Signal().
Nuevamente, la responsabilidad de restaurar el estado correcto del programa recae sobre el
programador, quien para hacerlo estaría obligado a incluir código en el ELSE del IF en que se
invoca a Exception(), que es donde se establece el contexto de excepción: este código se
encargaría de desasignar la memoria dinámica que haya sido asignada en el contexto de
excepción, para luego ejecutar un Re_Throw() que pase la excepción sea al contexto de
excepción inmediatamente superior. En [Ich-79] se discute cómo lograr esto.
Como el código del manejador excepciones tiene acceso a las variables del programa, pues el
manejador aparece en el ELSE del IF que implementa el contexto de excepción, el programdor
puede encargarse de destruir manualmente las variables de memoria dinámica que haya usado. A
veces da pereza destruir esas variables, principalmente si no es posible utilizar el código que las
destruye cuando no ha ocurrido una excepción.
Cuando se produce una excepción el manejador de excepciones la intercepta, y retorna al
contexto de excepción adecuado indicando un código de error. Sin embargo, en muchas
aplicaciones este código de error no da suficiente información como para resolver adecuadamente
el problema que produjo la excepción. Por eso algunos autores exigen que en lugar de un
"código", el manejador de excepciones pueda producir un "mensaje" de excepción. Este es el
enfoque que se ha usado en C++ [Str-88b]. A todas luces es mejor retornar mensajes que códigos,
pero para eso se necesita asistencia del compilador. La solución que aquí se presenta es parcial,
pero tiene la ventaja de que es suficiente para muchas como aplicaciones, y es adecuada para que
los estudiantes aprendan a escribir programas usando contextos de excepción.
El problema de como mezclar bien un paquete de excepciones con un eficiente manejador de
memoria dinámica es muy difícil de resolver. Por ejemplo, el manejo de excepciones que se
incluyó en el lenguaje C++ debió esperar casi dos años antes de ser aceptado, pues el uso
excepciones complica la administración de la memoria dinámica.
Para implementar except.pas ha sido necesario manipular los registros de estado del programa
en tiempo de ejecución. El código ha sido probado con las versiones más importantes del
compilador Turbo Pascal (v4.0, v5.0, v5.5, v6.0, v7.0) pero podría darse el caso de que cambiara
la forma de invocar procedimientos en una versión posterior: entonces debería cambiarse esta
implementación. Sin embargo, lo más posible es que no haya que hacer esto cambios para
soportar futuras versiones de Turbo Pascal.
Este paquete sólo da soporte a ambientes MS-DOS monousuario. Si se desea escribir
programas que puedan crear tareas concurrentes, será necesario modificar significativamente la
implementación de esta biblioteca, pues en estos momentos además de que ha sido implementada
usando lenguaje de máquina x86, la pila para anotar cuáles son los contextos de excepción que
está dentro de la unidad except.pas es una variable global. Esto impide compartirla entre varias
tareas que nacen del mismo programa.
Conclusiones
Esta implementación obliga al programador a ser cuidadoso al usar
excepciones, pues por un pequeño descuido pueden quedar variables
asignadas en memoria dinámica "guindando". Pese al cúmulo de
restricciones que presenta, esta paquete para el manejo de excepciones en
Pascal es muy útil por lo menos en un ambiente académico, pues los
estudiantes pueden usarlo para aprender a usar esta importante herramienta
de programación. Como Turbo Pascal corre en máquinas muy pequeñas, este
paquete puede ser usado prácticamente en cualquier ambiente.
Agradecimientos
Cuatro personas han trabajado para lograr implementar except.pas.
David Chaves fue quien programó inicialmente la mayor parte del código
ensamblador de la unidad Except. Luego William Martínez tuvo la paciencia de
buscarle errores a la primera implementación. Por último, Gustavo Alonso
Sanabria quien tuvo la pacienca de depurar el código. Además, William y
Gustavo le hicieron algunas modificaciones al paquete original definido por
Adolfo, lo que ha resultado en un sistema más fácil de usar.
Reconocimientos
Esta investigación se realizó dentro del proyecto de investigación 32989-019 "Estudios en la tecnología de programación por objetos y C++",
inscrito ante la Vicerrectoría de Investigación de la Universidad de Costa
Rica. La Escuela de Ciencias de la Computación e Informática también ha
aportado fondos para este trabajo.
Bibliografía
[DiM- Di Mare, Adolfo: Convenciones de Programación para Pascal,
88]
Reporte Técnico ECCI-01-88, Proyecto 326-86-053, Escuela de
Ciencias de la Computación e Informática, Universidad de Costa Rica,
1988.
[BIBorland International: Turbo Pascal Reference Manual version
88]
5.5, Borland International, California (U.S.A.), 1988.
[Col- Colvin, Gegory: Exception Handling in ANSI C, C/C++ Users
91]
Journal, Vol.9 No.8, pp [77-78,83-88], Agosto 1991.
[Ich- Ichbiah, J.D et al: Rationale for the Design of the ADA
79]
Programming Language, SigPlan Notices, Vol.14 No.6, Junio 1979.
[Ker- Kernighan, Brian: El lenguaje de programación C, Prentice Hall;
86]
1986.
[Lee- Lee, P. A.: Exception Handling in C Programs, Software Practice
83]
and Experience, Vol.13, pp 389-405.
[LG- Liskov, Barbara & Gutag, John: Abstraction and Specification in
86]
Program Development, McGraw-Hill, 1986.
[Str- Stroustrup, Bjarne: The C++ Programming Language, Addison86]
Wesley, 1986.
[Str- Stroustrup, Bjarne: What is Object-Oriented Programming, IEEE
88a] Transactions on Software Engineering, Mayo 1988.
[Str- Stroustrup, Bjarne: C++ Exception Handling, IEEE Transactions on
88b] Software Engineering, Mayo 1988.
[Vid- Vidal, Carlos: Exception Handling, C/C++ Users Journal,
92]
Vol.10 No.9, pp [19-27], Septiembre 1992.
http://www.di-mare.com/adolfo/p/except.htm
7.5.2 Lanzamiento de excepciones.
Manejo de excepciones
Introducción
¿Qué es el Manejo de Excepciones?
 El manejo de excepciones es un mecanismo de algunos lenguajes de programación,


que permite definir acciones a realizar en caso de producirse una situaciones anomala
(excepción) producida durante la ejecución del programa.
La acción a realizar suele consistir en una acción correctiva, o la generación de un
informe acerca del error producido.
Ejemplos de lenguajes con gestión de excepciones son Java, Net, C++, Objective-C,
Ada, Eiffel, y Ocaml.
¿Como se implementa?
 Los lenguajes con gestión de excepciones incorporan en sus bibliotecas la capacidad
de detectar y notificar errores.
 Cuando un error es detectado se siguen estas acciones:

1. Se interrumpe la ejecución del código en curso.
2. Se crea un objeto excepción que contiene información del problema. Esta
acción es conocida como "lanzar una excepción".Existe un mecanismo
estandar de gestión de error
3. Si hay un manejador de excepciones en el contexto actual le transfiere el
control. En caso contrario, pasa la referencia del objeto excepción al contexto
anterior en la pila de llamadas.
4. Si no hay ningún manejador capaz de gestionar la excepción, el hilo que la
generó es terminado.
Por ejemplo, si intentamos leer un fichero inexistente usando la clase FileReader
del lenguaje Java, la implementación de la propia clase detectará el problema, y
lanzará una excepción de tipo FileNotFoundException.
¿Por qué es importante usar excepciones?
 El código de tratamiento del error está separado del resto del programa. Esto aumenta la
legibilidad y permite centrarse en cada tipo de código.
 Un mismo manejador puede gestionar las excepciones de varios ámbitos inferiores. Esto reduce la
cantidad de código necesaria para gestionar los errores.
 Existe un mecanismo estandar de gestión de error. Lenguajes anteriores como C empleaban el
retorno de valores especiales en cada método para señalar condiciones anomalas.
Esto implicaba que a cada llamada debía seguirle un código de gestión de errores que
enturbiaba la legibilidad del código.
Implementación en Java
La captura de excepciones se implementa en el lenguaje Java con el bloque try/catch/finally. El capítulo 9 de
Thinking in Java es una excelente introducción al tema (que vale la pena leer aunque seas un programador
senior).
1
2
3
4
5
6
7
try {
// código sospechoso de producir excepciones
} catch(Excepcion e){
// registra el error y/o relanza la excepción
} finally {
// libera recursos
}
 La excepción a capturar puede ser cualquier subclase de Throwable.
 El código del finally se ejecutará siempre. Una excepción en el try, o en el bloque catch
no impedirán su ejecución.
 Si el bloque finally lanza una excepción, quedarán descartadas excepciones anteriores.
Es decir, una excepción en el finally oculta excepciones anteriores.
Throwable
Throwable es la superclase de todos los objetos excepción:
java.lang.Object
java.lang.Throwable
java.lang.Exception
java.lang.RuntimeException
Según su superclase, hay dos tipos de excepciones:
 Checked: el programa no compila si no existe un manejador de excepciones. Son las
subclases de Exception que no son subclases de RuntimeException.
 Unchecked: su captura no es obligatoria. Son las subclases de RuntimeException.
Nota
Lo lógico sería que RuntimeException fuera subclase de Throwable, pero por
algún motivo historico (?) no es así.
En el constructor de Throwable se puede especificar:
 Un mensaje indicando el motivo de la excepción.
 Una referencia a otro objeto Throwable asociado (solo desde 1.4). Esto permite
indicar que la causa de esta excepción fue otra.
Throwable()
Throwable(String message)
Throwable(Throwable cause) // 1.4
Throwable(String message, Throwable cause)
// 1.4
Nota
En JDK 1.3. puedes usar NestableException para implementar excepciones
anidadas.
El método getStackTrace() devuelve un array de elementos StackTraceElement, que representan
cada uno (excepto el primero en la pila) una llamada a un método. Cuando ejecutamos
Throwable.printStacktrace() obtenemos una representación como cadena de estos objetos.
Observa que según esto, todos los objetos derivados de Throwable proporcionan la información siguiente:
 El tipo de excepción (la clase).
 Donde ocurrió la excepción (el stacktrace).
 Un mensaje de error.
Un ejemplo completo
Un ejemplo completo:
 El siguiente programa realiza la división entera de dos números pasados por consola:






1
import java.math.BigInteger;
3
public class Main {
5
private static void usage(){
6
System.out.println("USAGE: java Main number1
number2");
 7
}

 9
private static void divide(String a, String b){
 10
try {
 11
BigInteger bi1 = new BigInteger(a);
 12
BigInteger bi2 = new BigInteger(b);
 13
System.out.println( bi1.divide(bi2) );
 14
} catch (NumberFormatException e2) {
 15
System.err.println("Not a number! " +
e2.getMessage());
 16
usage();
 17
}
 18
}

 20
public static void main(String[] args) {
 21
divide(args[0],args[1]);
 22
}

24 }
http://www.1x4x9.info/files/excepciones/html/online-chunked/
Tratamiento de excepciones
§1 Introducción
El problema de la seguridad es uno de los clásicos quebraderos
de cabeza de la programación. Los diversos lenguajes han tenido
siempre que lidiar con el mismo problema: ¿Que hacer cuando se
presenta una circunstancia verdaderamente imprevista? (por
ejemplo un error). El asunto es especialmente importante si se
trata de lenguajes para escribir programas de "Misión crítica";
digamos por ejemplo controlar los ordenadores de una central
nuclear o de un sistema de control de tráfico aéreo.
Antes que nada, digamos que en el lenguaje de los
programadores C++ estas "circunstancias imprevistas" reciben el
nombre de excepciones, por lo que el sistema que implementa
C++ para resolver estos problemas recibe el nombre de
manejador de excepciones. Así pues, las excepciones son
condiciones excepcionales que pueden ocurrir dentro del
programa durante su ejecución (por ejemplo, que ocurra una
división por cero, se agote la memoria disponible, etc.) que
requieren recursos especiales para su control.
En este capítulo trataremos del manejador de excepciones C++;
una serie de técnicas que permiten formas normalizadas de
manejar los errores, intentando anticiparse a los problemas
potenciales previstos e imprevistos. Así como permitir al
programador reconocerlos, fijar su ubicación y corregirlos.
§2 Manejo de excepciones en C++
El manejo de excepciones C++ se basa en un mecanismo cuyo
funcionamiento tiene tres etapas básicas:
1:
Se intenta ejecutar un bloque de código y se decide que
hacer si se produce una circunstancia excepcional durante
su ejecución.
2:
Se produce la circunstancia: se "lanza" una excepción
(en caso contrario el programa sigue su curso normal).
3:
La ejecución del programa es desviada a un sitio
específico donde la excepción es "capturada" y se decide
que hacer al respecto.
¿Pero que es eso de "lanzar" y "capturar" una excepción"? En
general la frase se usa con un doble sentido: Por un lado es un
mecanismo de salto que transfiere la ejecución desde un punto
(el que "lanza" la excepción) a otro dispuesto de antemano para
tal fin (el que "captura" la excepción). A este último se le
denomina manejador o "handler" de la excepción. Además del
salto (como un goto), en el punto de lanzamiento de la
excepción se crea un objeto, a modo de mensajero, que es
capturado por el "handler" (como una función que recibe un
argumento). El objeto puede ser cualquiera, pero lo normal es
que pertenezca a una clase especial definida al efecto que
contiene la información necesaria para que el receptor sepa que
ha pasado; cual es la naturaleza de la circunstancia excepcional
que ha "lanzado" la excepción [6].
Para las tres etapas anteriores existen tres palabras clave
específicas: try, throw y catch. El detalle del proceso es como
sigue.
§2.1 Intento ( try ).
En síntesis podemos decir que el programa se prepara para
cierta acción, decimos que "lo intenta". Para ello se especifica
un bloque de código cuya ejecución se va a intentar ("Tryblock") utilizando la palabra clave try.
try {
// bloque de código-intento
...
}
El juego consiste en indicar al programa que si existe un
error durante el "intento", entonces debe lanzar una
excepción y transferir el control de ejecución al punto donde
exista un manejador de excepciones ("Handler") que coincida
con el tipo lanzado. Si no se produce ninguna excepción, el
programa sigue su curso normal.
De lo dicho se deduce inmediatamente que se pueden lanzar
excepciones de varios tipos y que pueden existir también
receptores (manejadores) de varios tipos; incluso
manejadores "universales", capaces de hacerse cargo de
cualquier tipo de excepción. A la inversa, puede ocurrir que
se lance una excepción para la que no existe manejador
adecuado, en cuyo caso... (la solución más adelante).
Así pues, try es una sentencia que en cierta forma es capaz
de especificar el flujo de ejecución del programa. Un bloqueintento debe ser seguido inmediatamente por el bloque
manejador de la excepción.
§2.2 Se lanza una excepción ( throw ).
Si se detecta una circunstancia excepcional dentro del
bloque-intento, se lanza una excepción mediante la
ejecución de una sentencia throw. Por ejemplo:
if (condicion) throw "overflow";
Es importante advertir que, salvo los casos en que la
excepción es lanzada por las propias librerías C++ (como
consecuencia de un error 1.6.1a), estas no se lanzan
espontáneamente. Es el programador el que debe utilizar
una sentencia (generalmente condicional) para en su caso,
lanzar la excepción.
El lenguaje C++ especifica que todas las excepciones deben
ser lanzadas desde el interior de un bloque-intento y permite
que sean de cualquier tipo. Como se ha apuntado antes,
generalmente son un objeto (instancia de una clase) que
contiene información. Este objeto es creado y lanzado en el
punto de la sentencia throw y capturado donde está la
sentencia catch. El tipo de información contenido en el
objeto es justamente el que nos gustaría tener para saber
que tipo de error se ha producido. En este sentido puede
pensarse en las excepciones como en una especie de correos
que transportan información desde el punto del error hasta el
sitio donde esta información puede ser analizada.
§2.3 La excepción es capturada en un punto específico del
programa ( catch ).
Esta parte del programa se denomina manejador
("handler"); se dice que el "handler" captura la excepción.
El handler es un bloque de código diseñado para manejar la
excepción precedido por la palabra catch. El lenguaje C++
requiere que exista al menos un manejador inmediatamente
después de un bloque try. Es decir, se requiere el siguiente
esquema:
try {
// bloque de código que se
intenta
...
}
catch (...) { // bloque manejador de
posibles excepciones
...
}
...
// continua la ejecución
normal
El "handler" es el sitio donde continua el programa en caso
de que ocurra la circunstancia excepcional (generalmente un
error) y donde se decide que hacer. A este respecto, las
estrategias pueden ser muy variadas (no es lo mismo el
programa de control de un reactor nuclear que un humilde
programa de contabilidad). En último extremo en caso de
errores absolutamente irrecuperables, la opción adoptada
suele consistir en mostrar un mensaje explicando el error.
Puede incluir el consabido "Avise al proveedor del programa"
o bien generar un fichero texto (por ejemplo: error.txt) con
la información pertinente, que se guarda en disco con objeto
de que pueda ser posteriormente analizado y corregido en
sucesivas versiones de la aplicación [2].
Llegados a este punto debemos recordar que, como veremos
en los ejemplos, las excepciones generadas pueden ser de
diverso tipo (según el tipo de error), y que también pueden
existir doversos manejadores. De hecho se debe incluir el
manejador correspondiente a cada excepción que se pueda
generar.
§3 Resumen
Hemos dicho que try es una sentencia que en cierta forma es capaz de
especificar el flujo de ejecución del programa; en el fondo el mecanismo
de excepciones de C++ funciona como una especie de sentencia if ...
then .... else, que tendría la forma:
If { este bloque se ejecuta correctamente }
then
seguir la ejecución normal de programa
else
// tres acciones sucesivas.
a. Crear un objeto con información del suceso
(excepción)
b. Transferir el control de ejecución al
"handler" correspondiente
c. Recibir el objeto para su análisis y decisión
de la acción a seguir
en este caso la sintaxis utilizada es la siguiente:
try {
// bloque de código que se intenta
...
}
catch (...) { // captura de excepciones
...
}
...
// continua la ejecución normal
El diseño del mecanismo de excepciones C++, someramente
expuesto, tiene la ventaja de permitir resolver una situación muy
frecuente: el bloque en que se detecta el error no sabe que hacer
en tal caso (cuando se presenta el error o excepción); la acción
depende en realidad de un nivel anterior, el módulo que invocó la
operación. Como decimos, esta situación es muy frecuente ( ),
entre otras razones porque si un módulo pudiera anticipar un
error por si mismo, también podría evitarlo, con lo que no habría
necesidad de mecanismo de excepciones. Esta circunstancia es
especialmente patente en el caso de librerías, en las que el autor
generalmente no sabe ni puede hacer nada al respecto de ciertos
errores a excepción de informar al usuario.
Lo anterior no es óbice para que, como buena práctica de
programación, se intente la captura sistemática de errores lo
más próximo posible a su identificación, dejando el
mecanismo de excepciones para las situaciones realmente
imprevisibles. Por ejemplo, siempre que sea posible:
if (b == 0) { /* alguna acción... */; }
else x = a/b;
§4 Precauciones
Cuando se plantean este tipos de cuestiones de seguridad surge
inevitablemente una pregunta: ¿Que sucede si se producen
errores (circunstancias excepcionales) durante el proceso de
control de excepciones?.
La respuesta más honesta es que el sistema perfecto e
invulnerable no existe. Aunque el sistema de excepciones de
C++ es una formidable herramienta para controlar imprevistos,
que permite hasta cierto punto controlar imprevistos dentro de
imprevistos. A pesar de ello, nada puede sustituir a una
programación cuidadosa. El propio Stroustrup advierte: "Aunque
las excepciones se pueden usar para sistematizar el manejo de
errores, cuando se adopta este esquema, debe prestarse
atención para que cuando se lance una excepción no cause más
problemas de los que pretende resolver. Es decir, se debe
prestar atención a la seguridad de las excepciones.
Curiosamente, las consideraciones sobre seguridad del
mecanismo de excepciones conducen frecuentemente a un
código más simple y manejable".
Comentarios sobre la idoneidad del mecanismo de
excepciones ( 1.6w2).
§5 Tipos de excepciones
Durante la ejecución de un programa pueden existir dos tipos de
circunstancias excepcionales: síncronas y asíncronas. Las
primeras son las que ocurren dentro del programa. Por ejemplo,
que se agote la memoria o cualquier otro tipo de error. Son a
estas a las que nos hemos estado refiriendo. En la introducción
(§1 ) hemos indicado: las excepciones son condiciones
excepcionales que pueden ocurrir dentro del programa..." y las
únicas que se consideran. Las excepciones asíncronas son las
que tienen su origen fuera del programa, a nivel del Sistema
Operativo. Por ejemplo que se pulsen las teclas Ctrl+C.
Generalmente las implementaciones C++ solo consideran las
excepciones síncronas, de forma que no se pueden capturar con
ellas excepciones tales como la pulsación de una tecla. Dicho
con otras palabras: solo pueden manejar las excepciones
lanzadas con la sentencia throw. Siguen un modelo
denominado de excepciones síncronas con terminación, lo que
significa que una vez que se ha lanzado una excepción, el control
no puede volver al punto que la lanzó.
Nota: El "Handler" no puede devolver el control al punto de
origen del error mediante una sentencia return. En este
contexto, un return en el bloque catch supone salir de la
función que contiene dicho bloque.
El sistema Estándar C++ de manejo de excepciones no está
diseñado para manejar directamente excepciones asíncronas,
como las interrupciones de teclado, aunque pueden
implementarse medidas para su control. Además las
implementaciones más usuales disponen de recursos para
menejar las excepciones del Sistema Operativo. Por ejemplo,
C++ Builder dispone de los mecanismos adecuados para manejar
excepciones de Windows-32 (asíncronas) a través de su librería
VCL [1] ( 1.6w1).
§6 Secuencia de ejecución
Como puede verse, la filosofía C++ respecto al manejo de
excepciones no consiste en corregir el error y volver al punto de
partida. Por el contrario, cuando se genera una excepción el
control sale del bloque-intento try que lanzó la excepción
(incluso de la función), y pasa al bloque catch cuyo manejador
corresponde con la excepción lanzada (si es que existe).
A su vez el bloque catch puede hacer varias cosas:

Relanzar la misma excepción ( 1.6.1).
 Saltar a una etiqueta (
1.6.2)
 Terminar su ejecución normalmente (alcanzar la llave } de cierre).
Si el bloque-catch termina normalmente sin lanzar una nueva
excepción, el control se salta todos los bloques-catch que
hubiese a continuación y sigue después del último.
Puede ocurrir que el bloque-catch lance a su vez una excepción.
Lo que nos conduce a excepciones anidadas. Esto puede ocurrir,
por ejemplo, cuando en el proceso de limpieza de pila ("Stack
unwinding") que tienen lugar tras una excepción, un destructor
lanza una excepción .
Como se verá en los ejemplos, además del manejo y control
de errores, las excepciones C++ pueden utilizarse como un
mecanismo de return o break multinivel, controlado no por una
circunstancia excepcional, sino como un acto deliberado del
programador para controlar el flujo de ejecución [5]. Ejemplos:
( 4.5.8).
§7 Constructores y destructores en el manejo de
excepciones
Cuando en el lanzamiento de excepciones se utilizan objetos por
valor, throw llama al constructor copia ( 4.11.2d4). Este
constructor inicializa un objeto temporal en el punto de
lanzamiento.
Si ocurren errores durante la construcción de un objeto, los
constructores pueden lanzar excepciones [3]. Si un constructor
lanza una excepción, el destructor del objeto no es llamado
necesariamente.
Cada vez que desde dentro de un bloque-try se lanza una
excepción y el control sale fuera de bloque, tiene lugar un
proceso de búsqueda y desmontaje descendente en la pila hasta
encontrar el manejador ("Catcher") correspondiente. Durante
este proceso, denominado "Stack unwinding", todos los objetos
de duración automática que se crearon hasta el momento de
ocurrir la excepción, son destruidos de forma controlada
mediante llamadas a sus destructores. Si uno de los
destructores invocados provoca a su vez una excepción que no
tiene un "handler" adecuado, se produce una llamada a la
función terminate ( 1.6.3).
Nota: Observe que no se menciona para nada la destrucción
de objetos persistentes que se hubiesen creado entre el inicio
del bloque try y el punto de lanzamiento de la excepción.
Esto origina una difícil convivencia entre el mecanismo de
excepciones y el operador new. Para resolver los problemas
potenciales deben adoptarse precauciones especiales (
4.9.20).
La invocación a los destructores de los objetos automáticos, se
realiza solo para aquellos objetos que hubiesen sido construidos
totalmente a partir de la entrada en el bloque-intento (objetos
cuyos constructores hubiesen finalizado satisfactoriamente). Si
los objetos tienen sub-objetos, la invocación solo se realiza para
los destructores de la clase-base.
Tema relacionado: Control de recursos ( 4.1.5a)
Nota: En el caso del C++Builder, los destructores son llamados
por defecto, pero puede evitarse mediante la opción -xd- del
compilador como se indica a continuación. Esta opción, como
otras de este tipo, está gobernada por el valor de una constante
global ( 1.4.1a).
§8 Establecer opciones de manejo de excepciones
En C++Builder se pueden establecer comandos de compilación ( 1.4.3) para
determinar el tratamiento que seguirá el manejo de excepciones; son los
siguientes:
Opción Descripción
-x
Habilita manejo de excepciones C++ (activo por
defecto).
-xd
Habilita limpieza total. Llamada a los destructores
para todos los objetos declarados automáticos
(locales) entre el ámbito del capturador (catch) y el
lanzador (throw) de la excepción cuando es lanzada la
excepción (activo por defecto). Si se activa esta
opción, también hay que activar la opción –RT (
4.9.14).
-xp
Habilita información sobre las excepciones.
Posibilita identificación de excepciones en tiempo de
ejecución mediante la inclusión en el objeto del
número de líneas del código fuente. Esto permite al
programa interrogar el fichero y número de línea
donde ha ocurrido una excepción utilizando los
identificadores globales __ThrowFileName y
__ThrowLineNumber.
http://www.zator.com/Cpp/E1_6.htm
Excepciones
Durante la ejecución, las clases pueden provocarse errores de diferentes tipos y diversos grados
de gravedad..
Cuando se invocan métodos sobre un objeto, se puede encontrar con problemas internos de
estado(valores incongruentes ), detectar errores con los objetos o datos que manipula ( como la
dirección a un archivo o red), querer accesar sobre un archivo ya cerrado u otros problemas.
Proporcionan una manera de verificar los errores y poder controlarlos si fuera el caso si abortar el
código.
Las excepciones hacen que las situaciones de error que puede señalar un método sean parte
explícita del contrato del mismo.
Proceso de excepción:
- se lanza una excepción cuando detecta un error
- la excepción es capturada por una cláusula que delimita al método
Para Java las excepciones son objetos.
try, catch y finally
Las excepciones se capturan encerrando código en bloques try:
try
bloque
catch (excepción_tipo identificador)
bloque
catch (excepción_tipo identificador)
bloque
...
finally
bloque
El cuerpo de la sentencia try se ejecuta hasta que se lanza una excepción o se acaba con éxito.
Si se lanza una excepción se examinan las cláusulas catch con el fin de encontrar la excepción
que se quiere controlar.
Si en un try también se encuentra un finally, su código se ejecuta una vez que se haya completado
todas las sentencias del try. Se ejecutan todas las sentencias del bloque finally se lance o no una
excepción.
public boolean searchFor(String file, String world) throws StreamException
{
Stream input =null;
try{
input= new Stream(file);
while(!input.eof())
{
if (input.next() == world)
return true;
return false;
}
}
finally {
if (input !=null)
input.close();
}
}
El contrato definido por la cláusula throws se aplica estrictamente: sólo se puede lanzar un tipo
de excepción que haya sido declarado en la cláusula throws.
En el siguiente bloque se atrapa la excepción que se alcanza el final de la entrada:
try {
while(( token = stream.next() ) !=Stream.END)
process(token);
}
catch (StreamEndException e) {stream.close(); }
La clase Throwable es la superclases de todos los errores y excepciones en Java.
Existen excepciones implícitas y
explícitas:
Un ejemplo de atrapar una excepción(explícita):
try {
int a[] = new int[2];
a[4]=10;
} catch (ArrayIndexOutOfBoundsException e) {
System.out.println("exception: " + e.getMessage());
e.printStackTrace();
}
(excepción implícita)
int a[]=new int[2];
a[4]=10;
public class Prueba{
public static void main(String args[])
{
int dato1=0, dato2=0, dato3=0;
try{
dato1++;
dato3=dato1/dato2;
dato2++;
}
catch (ArithmeticException t)
{
System.out.println("Error de division "+t.getMessage());
dato3=0;
}
System.out.println(dato1 + " "+dato2+" "+dato3);
}
}
Las excepciones en Java son objetos de clases derivas de la clase Throwable definida en el
paquete java.lang
Throwable


Error
Exception
o
o
o
RuntimeException
ClassNotFoundException
IOException
 EOFException
La clase Exception cubre las excepciones que puede ejecutar una aplicación.
RuntimeException controla las excepciones ocurridas al ejecutar operaciones sobre datos que
maneja una aplicación y que se encuentran en memoria.
IOException controla las excepciones sobre operaciones de entrada y salida.
Lanzar una excepción equivale a lanzar un objeto de la clase de la excepción que se va a
manipular.
if (t== null)
throw new NullPointerException();
Creación de propias Excepciones
Para denotar un error en particular de las clases que están en un paquete. En donde también la
herencia es importante.
class SimpleException extends Exception {}
public class SimpleExeptionDemo
{
public void f() throws SimpleException
{
System.out.println(" Throwing SimpleException form f() ");
trow new SimpleException();
}
public static void main(String[] args)
{
SimpleExceptionDemo sed=new SimpleExceptionDemo();
try{
sed.f();
}catch(SimpleException e) {
System.out.println("Caught it!");}
}
}
http://mail.udlap.mx/~sainzmar/is117/excepciones.html
Relanzar una Exception
Existen algunos casos en los cuales el código de un método puede generar
una Exception y no se desea incluir en dicho método la manejo del error.
Java permite que este método pase o relance (throws) la Exception al
método desde el que ha sido llamado, sin incluir en el método los bucles
try/catch correspondientes. Esto se consigue mediante la adición de
throws más el nombre de la Exception concreta después de la lista de
argumentos del método. A su vez el método superior deberá incluir los
bloques try/catch o volver a pasar la Exception. De esta forma se puede ir
pasando la Exception de un método a otro hasta llegar al último método del
programa, el método main().
El ejemplo anterior (metodo1) realizaba la manejo de las excepciones
dentro del propio método.
Ahora se presenta un nuevo ejemplo (metodo2) que relanza las excepciones
al siguiente método:
void metodo2() throws IOException, MyException {
...
// Código que puede lanzar las excepciones IOException y MyException
...
} // Fin del metodo2
Según lo anterior, si un método llama a otros métodos que pueden lanzar
excepciones (por ejemplo de un paquete de Java), tiene 2 posibilidades:
1. 1.Capturar las posibles excepciones y manejarlas.
2. 2.Desentenderse de las excepciones y remitirlas hacia otro método
anterior en el stack para éste se encargue de manejarlas.
Si no hace ninguna de las dos cosas anteriores el compilador da un error,
salvo que se trate de una RuntimeException.
http://sai.azc.uam.mx/apoyodidactico/edoo/Unidad5/edoo5.h
tml
7.6 Excepciones definidas por el usuarios.
Excepciones definidas por el usuario
Los programas pueden nombrar sus propias excepciones asignando una cadena a una variable o
creando una nueva clase de excepción. Por ejemplo:
>>> class MiError:
...
def __init__(self, valor):
...
self.valor = valor
...
def __str__(self):
...
return `self.valor`
...
>>> try:
...
raise raise MiError(2*2)
... except MiError, e:
...
print 'Ha saltado mi excepción, valor:', e.valor
...
Ha saltado mi excepción, valor: 4
>>> raise mi_exc, 1
Traceback (innermost last):
File "<stdin>", line 1
mi_exc: 1
Muchos módulos estándar utilizan esto para informar de errores que pueden ocurrir dentro de las
funciones que definen.
Hay más información sobre las clases en el capítulo , ``Clases''.
http://pyspanishdoc.sourceforge.net/tut/node10.html#SECTION0010500000000000000000
Hay dos tipos de excepciones: las predefinidas y las definidas por el usuario.
Excepciones definidas por el usuario
Son errores que están definidas en el programa, el cual no necesariamente es un error
de Oracle. Las excepciones se declaran en la sección declarativa de un bloque PL/SQL.
Las excepciones tiene un tipo (EXCEPTION) y un ambiente.
DECLARE
e_error1
BEGIN
.
.
.
END;
EXCEPTION
Excepciones predefinidas
Las excepciones predefinidas corresponden a errores comunes en SQL.
Error de
Oracle
ORA-0001
ORA-0051
ORA-0061
ORA-1001
ORA_1012
ORA-1017
ORA-1403
ORA-1422
ORA-1476
ORA-1722
ORA-6500
ORA-6501
ORA-6502
ORA-6511
Excepción equivalente
Descripción
DUP_VAL_ON_INDEX
Restricción de unicidad violada
Tiempo fuera ocurrido mientras
TIMEOUT_ON_RESOURCE
esperaba un recurso
La transacció fue desecha por un
TRANSACTION_BACKED_OUT
"bloqueo mortal"
INVALID_CURSOR
Operación ilegal con un cursor
NOT_LOGGED_ON
Sin conexión a Oracle
LOGIN_DENIED
Nombre de usuario o password inválido
NO_DATA_FOUND
No se encontraron datos
La instrucción SELECT ... INTO
TOO_MANY_ROWS
devuelve más de un registro
ZERO_DIVIDED
División por cero
INVALID_NUMBER
Conversión inválida a un número
Error PL/SQL interno lanzado al
STORAGE_ERROR
exceder la memoria
PROGRAM_ERROR
Error PL/SQL interno
Error al trucar o convertir valores, o en
VALUE_ERROR
una operación aritmética
Al intentar abrir un cursor que ya está
CURSOR_ALREADY_OPEN
abierto
http://www.lania.mx/biblioteca/seminarios/basedatos/plsql/errores/declaracion.html
7.6.1 Clase base de las excepciones.
Excepciones internas
Las excepciones pueden ser objetos de una clase u objetos cadena. Aunque la mayoría de las
excepciones eran objetos cadena en las anteriores versiones de Python, en Python 1.5 y versiones
posteriores, todas las excepciones estándar han sido convertidas en objetos de clase y se anima a
los usuarios a que hagan lo propio. Las excepciones están definidas en el módulo exceptions.
Nunca es necesario importar este módulo explícitamente, pues las excepciones vienen
proporcionadas por el espacio nominal interno.
Dos objetos de cadena distintos con el mismo valor se consideran diferentes excepciones. Esto es
así para forzar a los programadores a usar nombres de excepción en lugar de su valor textual al
especificar gestores de excepciones. El valor de cadena de todas las excepciones internas es su
nombre, pero no es un requisito para las excepciones definidas por el usuario u otras excepciones
definidas por módulos de biblioteca.
En el caso de las clases de excepción, en una sentencia try con una cláusula except que
mencione una clase particular, esta cláusula también gestionará cualquier excepción derivada de
dicha clase (pero no las clases de excepción de las que deriva ella). Dos clases de excepción no
emparentadas mediante subclasificación nunca son equivalentes, aunque tengan el mismo
nombre.
Las excepciones internas enumeradas a continuación pueden ser generadas por el intérprete o por
funciones internas. Excepto en los casos mencionados, tienen un ``valor asociado'' indicando en
detalle la causa del error. Este valor puede ser una cadena o tupla de varios elementos
informativos (es decir, un código de error y una cadena que explica el código). El valor asociado
es el segundo argumento a la sentencia raise. En las cadenas de excepción, el propio valor
asociado se almacenará en la variable nombrada como el segundo argumento de la cláusula
except (si la hay). En las clases de excepción, dicha variable recoge la instancia de la excepción.
Si la clase de excepción deriva de la clase raíz estándar Exception, el valor asociado está
disponible en el atributo args de la instancia de excepción y es probable que aparezca en otros
atributos.
El código de usuario puede lanzar excepciones internas. Se puede usar para comprobar un gestor
de excepciones o para informar de una condición de error del mismo modo que el intérprete lanza
la misma excepción. Hay que ser precavido, pues nada incluye que el código de usuario lance una
excepción inadecuada.
Las siguientes excepciones sólo se usan como clase base de otras excepciones.
Exception
La clase base de las excepciones. Todas las excepciones internas
derivan de esta clase. Todas las excepciones definidas por usuario
deberían derivarse de esta clase, aunque no es obligatorio (todavía).
La función str(), aplicada a una instancia de una clase (o la mayoría de
sus clases derivadas) devuelve un valor cadena a partir de sus
argumentos o una cadena vacía si no se proporcionaron argumentos al
constructor. Si se usa como secuencia, accede a los argumentos
proporcionados al constructor (útil para compatibilidad con código
antiguo). Los argumentos también están disponibles en el atributo args
de la instancia, como tupla.
StandardError
La clase base para todas las excepciones internas excepto SystemExit.
StandardError deriva de la clase raíz Exception.
ArithmeticError
La clase base de las excepciones lanzadas por diversos errores
aritméticos: OverflowError, ZeroDivisionError, FloatingPointError.
LookupError
La clase base de las excepciones lanzadas cunado una clave o índice
utilizado en una correspondencia (diccionario) o secuencia son
incorrectos: IndexError, KeyError.
EnvironmentError
La clase base de las excepciones que pueden ocurrir fuera del sistema
Python: IOError, OSError. Cuando se crean excepciones de este tipo con
una tupla de dos valores, el primer elemento queda disponible en el
atributo errno de la instancia (se supone que es un número de error) y
el segundo en el atributo strerror (suele ser el mensaje de error
asociado). La propia tubpla está disponible en el atributo args. Nuevo
en la versión 1.5.2.
Cuando se instancia una excepción EnvironmentError con una tupla de tres elementos,
los primeros dos quedan disponibles como en el caso de dos elementos y el tercero queda
en el atributo filename. Sin embargo, por compatibilidad con sistemas anteriores, el
atributo args contiene sólo una tupla de dos elementos de los dos primeros argumentos
del constructor.
El atributo filename es None cuando se cree la excepción con una cantidad de
argumentos diferente de 3. Los atributos errno y strerror son también None cuando la
instancia no se cree con 2 ó 3 argumentos. En este último caso, args contiene los
argumentos del constructor tal cual, en forma de tupla.
Las siguientes excepciones son las realmente lanzadas.
AssertionError
Se lanza cuando una sentencia assert es falsa.
AttributeError
Se lanza cuando una referencia o asignación a atributo fracasa (cuando
un objeto no tenga referencias o asignaciones a atributos en absoluto,
se lanza, TypeError.)
EOFError
Se lanza cuando las funciones internas (input() o raw_input()) alcanzan
un final de fichero (EOF) sin leer datos. N.B.: Los métodos read() y
readline() de los objetos fichero devuelven una cadena vacía al
alcanzar EOF.
FloatingPointError
Se lanza cuando falla una operación de coma flotante. Esta excepción
siempre está definida, pero sólo se puede lanzar cuando Python esta
configurado con la opción --with-fpectl o se ha definido el símbolo
WANT_SIGFPE_HANDLER en el fichero config.h.
IOError
Se lanza cuando una operación de E/S (tal como una sentencia print,
la función interna open() o un método de un objeto fichero) fracasa por
motivos relativos a E/S, por ejemplo, por no encontrarse un fichero o
llenarse el disco.
Esta clase se deriva de EnvironmentError. En la explicación anterior se proporciona
información adicional sobre los atributos de instancias de excepción.
ImportError
Se lanza cuando una sentencia import no encuentra la definición del
módulo o cuando from ... import no encuentra un nombre a importar.
IndexError
Se lanza cuando un subíndice de una secuencia se sale del rango .Los
índices de corte se truncan silenciosamente al rango disponible. Si un
índice no es un entero simple, se lanza TypeError.
KeyError
Se lanza cuando no se encuentra una clave de una correspondencia
(diccionario) en el conjunto de claves existentes.
KeyboardInterrupt
Se lanza cuando el usuario pulsa la tecla de interrupción (normalmente
Control-C o DEL2.7). A lo largo de la ejecución se comprueba si se ha
interrumpido regularmente. Las interrupciones ocurridas cuando una
función input() o raw_input()) espera datos también lanzan esta
excepción.
MemoryError
Se lanza cuando una operación agota la memoria pero aún se puede
salvar la situación (borrando objetos). El valor asociado es una cadena
que indica qué tipo de operación (interna) agotó la memoria.
Obsérvese que por la arquitectura de gestión de memoria subyacente
(la función de C malloc()), puede que el intérprete no siempre sea
capaz de recuperarse completamente de esta situación. De cualquier
modo, se lanza una excepción para que se pueda imprimir una traza,
por si la causa fue un programa desbocado.
NameError
Se lanza cuando no se encuentra un nombre local o global. Sólo se
aplica a nombre no calificados. El valor asociado es el nombre no
encontrado.
NotImplementedError
Esta excepción se deriva de RuntimeError. En clases base definidas por
el usuario, los métodos abstractos deberían lanzar esta excepción
cuando se desea que las clases derivadas redefinan este método.
Nuevo en la versión 1.5.2.
OSError
Esta clase se deriva de EnvironmentError y se usa principalmente como
excepción os.error de os. En EnvironmentError hay una descripción de
los posibles valores asociados. Nuevo en la versión 1.5.2.
OverflowError
Se lanza cuando el resultado de una operación aritmética es demasiado
grande para representarse (desbordamiento). Esto no es posible en los
enteros largos (que antes que rendirse lanzarían MemoryError). Por la
falta de normalización de la gestión de excepciones de coma flotante
en C, la mayoría de las operaciones de coma flotante, tampoco se
comprueban. En el caso de enteros normales, se comprueban todas las
operaciones que pueden desbordar excepto el desplazamiento a la
izquierda, en el que las aplicaciones típicas prefieren perder bits que
lanzar una excepción.
RuntimeError
Se lanza cuando se detecta un error que no cuadra en ninguna de las
otras categorías. El valor asociado es una cadena que indica qué fue
mal concretamente. Esta excepción es mayormente una reliquia de
versiones anteriores del intérprete; ya casi no se usa.
SyntaxError
Se lanza cuando el analizador encuentra un error en la sintaxis. Esto
puede ocurrir en una sentencia import, en una sentencia exec, en una
llamada a la función interna eval() o input(), o al leer el guion inicial o
la entrada estándar (por ejemplo, la entrada interactiva).
Si se usan excepciones de clase, las instancias de esta clase tienen disponibles los
atributos filename (nombre del fichero), lineno (nº de línea), offset (nº de columna) y
text (texto), que ofrecen un acceso más fácil a los detalles. En las excepciones de cadena,
el valor asociado suele ser una tupla de la forma (mensaje, (nombreFichero,
numLinea, columna, texto)). En las excepciones de clase, str() sólo devuelve el
mensaje.
SystemError
Se lanza cuando el intérprete encuentra un error interno, pero la
situación no parece tan grave como para perder la esperanza. El valor
asociado es una cadena que indica qué ha ido mal (en términos de
bajo nivel).
Se debería dar parte de este error al autor o mantenedor del intérprete Python en cuestión.
Se debe incluir en el informe la cadena de versión del intérprete Python (sys.version,
que también se muestra al inicio de una sesión interactiva), la causa exacta del error y, si
es posible, el código fuente del programa que provocó el error.
SystemExit
Lanzada por la función sys.exit(). Si no se captura, el intérprete de
Python finaliza la ejecución sin presentar una pila de llamadas. Si el
valor asociado es un entero normal, especifica el estado de salida al
sistema (se pasa a la función de C exit()), Si es None, el estado de
salida es cero (que indica una salida normal sin errores). En el caso de
ser de otro tipo, se presenta el valor del objeto y el estado de salida
será 1.
Las instancias tienen un atributo code cuyo valor se establece al estado de salida o
mensaje de error propuesto (inicialmente None). Además, esta excepción deriva
directamente de Exception y no de StandardError, ya que técnicamente no es un error.
Una llamada a sys.exit() se traduce a un error para que los gestores de limpieza final
(las cláusulas finally de las sentencias try) se puedan ejecutar y para que un depurador
pueda ejecutar un guion sin riesgo de perder el control. Se puede usar la función
os._exit() si es total y absolutamente necesario salir inmediatamente (por ejemplo, tras
un fork() en el proceso hijo).
TypeError
Se lanza cuando una operación o función interna se aplica a un objeto
de tipo inadecuado. El valor asociado es una cadena con detalles de la
incoherencia de tipos.
UnboundLocalError
Se lanza cuando se hace referencia a una variable local en una función
o método, pero no se ha asignado un valor a dicha variable. Deriva de
NameError. Nuevo en la versión 2.0.
UnicodeError
Se lanza cuando se da un error relativo a codificación/descodificación
Unicode. Deriva de ValueError. Nuevo en la versión 2.0.
ValueError
Se lanza cuando una operación o función interna recibe un argumento
del tipo correcto, pero con un valor inapropiado y no es posible
describir la situación con una excepción más precisa, como IndexError.
WindowsError
Se lanza cuando se da un error específico de Windows o el número de
error no corresponde a un valor errno. Los valores errno y strerror se
crean a partir de los valores devueltos por las funciones GetLastError()
y FormatMessage() del API de plataforma de Windows. Deriva de OSError.
Nuevo en la versión 2.0.
ZeroDivisionError
Se lanza cuando el segundo argumento de una operación de división o
módulo es cero. El valor asociado es una cadena que indica el tipo de
operandos y la operación.
http://pyspanishdoc.sourceforge.net/lib/module-exceptions.html
7.6.2 Creación de un clase derivada del tipo
excepción.
7.6.3 Manejo de una excepción definida por el
usuario.
Unidad 8. Flujos y archivos.
8.4 Definición de Archivos de texto y archivos
binarios.
El Concepto de Datos
Datos son los hechos que describen sucesos y entidades."Datos" es una palabra en
plural que se refiere a más de un hecho. A un hecho simple se le denomina "data-ítem"
o elemento de dato.
Los datos son comunicados por varios tipos de símbolos tales como las letras del
alfabeto, números, movimientos de labios,
puntos y rayas, señales con la mano, dibujos, etc. Estos símbolos se pueden ordenar y
reordenar de forma utilizable y se les denomina información.
Los datos son símbolos que describen condiciones, hechos, situaciones o valores. Los
datos se caracterizan por no contener ninguna información. Un dato puede significar un
número, una letra, un signo ortográfico o cualquier símbolo que represente una
cantidad, una medida, una palabra o una descripción.
La importancia de los datos está en su capacidad de asociarse dentro de un contexto
para convertirse en información. Por si mismos los datos no tienen capacidad de
comunicar un significado y por tanto no pueden afectar el comportamiento de quien los
recibe. Para ser útiles, los datos deben convertirse en información para ofrecer un
significado, conocimiento, ideas o conclusiones.
2. El Concepto de Información
La información no es un dato conjunto cualquiera de ellos. Es más bien una colección
de hechos significativos y pertinentes, para el organismo u organización que los
percibe. La definición de información es la siguiente: Información es un conjunto de
datos significativos y pertinentes que describan sucesos o entidades.
DATOS SIGNIFICATIVOS. Para ser significativos, los datos deben constar de símbolos
reconocibles, estar completos y expresar una idea no ambigua.
Los símbolos de los datos son reconocibles cuando pueden ser correctamente
interpretados. Muchos tipos diferentes de símbolos comprensibles se usan para
transmitir datos.
La integridad significa que todos los datos requeridos para responder a una pregunta
específica están disponibles. Por ejemplo, un marcador de béisbol debe incluir el tanteo
de ambos equipos. Si se oye el tanteo "New York 6" y no oyes el del oponente, el
anuncio será incompleto y sin sentido.
Los datos son inequívocos cuando el contexto es claro. Por ejemplo, el grupo de signos
2-x puede parecer "la cantidad 2 menos la cantidad desconocida llamada x" para un
estudiante de álgebra, pero puede significar "2 barra x" a un vaquero que marca
ganado. Tenemos que conocer el contexto de estos símbolos antes de poder conocer
su significado.
Otro ejemplo de la necesidad del contexto es el uso de términos especiales en
diferentes campos especializados, tales como la contabilidad. Los contables utilizan
muchos términos de forma diferente al público en general, y una parte de un
aprendizaje de contabilidad es aprender el lenguaje de contabilidad. Así los términos
Debe y Haber pueden significar para un contable no más que "derecha" e "izquierda" en
una contabilidad en T, pero pueden sugerir muchos tipos de ideas diferentes a los no
contables.
DATOS PERTINENTES. Decimos que tenemos datos pertinentes (relevantes) cuando
pueden ser utilizados para responder a preguntas propuestas.
Disponemos de un considerable número de hechos en nuestro entorno. Solo los hechos
relacionados con las necesidades de información son pertinentes. Así la organización
selecciona hechos entre sucesos y entidades particulares para satisfacer sus
necesidades de información.
3. Diferencia entre Datos e información
1. Los Datos a diferencia de la información son utilizados como diversos métodos para comprimir
la información a fin de permitir una transmisión o almacenamiento más eficaces.
2. Aunque para el procesador de la computadora hace una distinción vital entre la información
entre los programas y los datos, la memoria y muchas otras partes de la computadora
no lo hace. Ambos son registradas temporalmente según la instrucción que se le de. Es como un
pedazo de papel no sabe ni le importa lo que se le escriba: un poema de amor, las cuentas
del banco o instrucciones para un amigo. Es lo mismo que la memoria de la computadora.
Sólo el procesador reconoce la diferencia entre datos e información de cualquier programa.
Para la memoria de la computadora, y también para los dispositivos de entrada y
salida (E/S) y almacenamiento en disco, un programa es solamente más datos, más
información que debe ser almacenada, movida o manipulada.
3. La cantidad de información de un mensaje puede ser entendida como el número de símbolos
posibles que representan el mensaje."los símbolos que representan el mensaje no son más que
datos significativos.
4. En su concepto más elemental, la información es un mensaje con un contenido determinado
emitido por una persona hacia otra y, como tal, representa un papel primordial en el proceso
de la comunicación, a la vez que posee una evidente función social. A diferencia de los
datos, la información tiene significado para quien la recibe, por eso, los seres humanos siempre
han tenido la necesidad de cambiar entre sí información que luego transforman en acciones.
"La información es, entonces, conocimientos basados en los datos a los cuales, mediante un
procesamiento, se les ha dado significado, propósito y utilidad"
4. El Concepto de Procesamiento de Datos
Hasta el momento hemos supuesto que los datos que maneja una aplicación no son tan
voluminosos y por lo tanto caben en memoria. Cuando recurrimos a archivos se debe a
la necesidad de conservar datos después de que termina un programa, por ejemplo
para apagar el computador.
Sin embargo, existen problemas en donde el volumen de datos es tan grande que es
imposible mantenerlos en memoria. Entonces, los datos se almacenan en un conjunto
de archivos, los que forman una base de datos. Una base de datos es por lo tanto un
conjunto de archivos que almacenan, por ejemplo, datos con respecto al negocio de
una empresa.
Cada archivo se forma en base a un conjunto de líneas y cada línea esta formada por
campos de información. Todas las líneas de un mismo archivo tienen la misma
estructura, es decir los mismos campos de información. Diferentes archivos poseen
estructuras distintas, i.e. campos de información.
Por ejemplo, el archivo de postulantes post.dat, visto en capítulos anteriores, tiene la
siguiente información:


ci: carnet de identidad de la persona.
nombre.
En lo que sigue supondremos que ambos archivos son lo suficientemente grandes
como para que no quepan en la memoria del computador. A continuación resolveremos
eficientemente el problema de generar un archivo con los tres campos de información,
sin colocar previamente el contenido de un archivo en un arreglo.
Algunas definiciones
Recolección de datos:
Provee un vínculo para obtener la información interoperacionables racional y las
parametrizaciones.
Almacenamiento de datos:
Las unidades de disco de la computadora y otros medios de almacenamiento externo
permiten almacenar los datos a más largo plazo, manteniéndolos disponibles pero
separados del circuito principal hasta que el microprocesador los necesita. Una
computadora dispone también de otros tipos de almacenamiento.
La memoria de sólo lectura (ROM) es un medio permanente de almacenamiento de
información básica, como las instrucciones de inicio y los procedimientos de
entrada/salida. Asimismo, una computadora utiliza varios buffers (áreas reservadas de
la memoria) como zonas de almacenamiento temporal de información específica, como
por ejemplo los caracteres a enviar a la impresora o los caracteres leídos desde el
teclado.
Procesamiento de datos:
a. El objetivo es graficar el Procesamiento de Datos, elaborando un Diagrama que permita
identificar las Entradas, Archivos, Programas y Salidas de cada uno de los Procesos.
b. Su antecedente es el Diagrama de Flujo.
c. Los elementos claves son los Programas.
d. Se confecciona el Diagrama de Procesamiento de Datos
e. Este Diagrama no se podrá elaborar por completo desde un primer momento ya que depende del
Flujo de Información.
f. En este primer paso sólo se identifican las Salidas y Programas. Los elementos restantes se
identifican en forma genérica.
Validación de datos:
Consiste en asegurar la veracidad e integridad de los datos que ingresan a un archivo.
Existen numerosas técnicas de validación tales como: Digito verificador, chequeo de
tipo, chequeo de rango.
5. Concepto de Procesamiento Distribuido y Centralizado
Procesamiento Centralizado:
En la década de los años 50’s las computadoras eran máquinas del tamaño de todo un
cuarto con las siguientes características:
• Un CPU
• Pequeña cantidad de RAM
• Dispositivos DC almacenamiento secundario (cintas)
• Dispositivos d salida (perforadoras de tarjetas)
• Dispositivos de entrada (lectores de tarjeta perforada)
Con el paso del tiempo, las computadoras fueron reduciendo su tamaño y creciendo en
sofisticación,
• Aunque la industria continuaba siendo dominada por las computadoras grandes
"mainframes". A medida que la computación evolucionaba, las computadoras, fueron
capaces de manejar aplicaciones múltiples simultáneamente, convirtiéndose en
procesadores centrales "hosts" a los que se les
Conectaban muchos periféricos y terminales tontas que consistían solamente de
dispositivos de entrada/salida (monitor y teclado) y quizá poco espacio de
almacenamiento, pero que no podían procesar por sí mismas. Las terminales locales se
conectaban con el procesador central a través de interfaces seriales ordinarias de baja
velocidad, mientras que las terminales remotas se enlazaban con
• El "host" usando módems y líneas telefónicas conmutadas. En este ambiente, se
ofrecían velocidades de transmisión de 1200, 2400, o 9600 bps. Un ambiente como el
descrito es lo que se conoce como procesamiento centralizado en su forma más pura
"host/terminal". Aplicaciones características de este tipo de ambiente son:
• Administración de grandes tuses de datos integradas
• Algoritmos científicos de alta velocidad
• Control de inventarios centralizado
Al continuar la evolución de los "mainframes", estos se comenzaron a conectar a
enlaces de alta velocidad donde algunas tareas relacionadas con las comunicaciones
se delegaban a otros dispositivos llamados procesadores comunicaciones "Front End
Procesos" (I7EP’s) y controladores de grupo "Cluster Controllers" (CC’s).
Procesamiento Distribuido:
El procesamiento centralizado tenía varios inconvenientes, entre los que podemos
mencionar que un número limitado de personas controlaba el acceso a la información y
a los reportes, se requería un grupo muy caro de desarrolladores de sistemas para
crear las aplicaciones, y los costos de mantenimiento y soporte eran extremadamente
altos. La evolución natural de la computación fue en el sentido del procesamiento
distribuido, así las minicomputadoras (a pesar de su nombre siguen siendo máquinas
potentes) empezaron a tomar parte del procesamiento que tenían los "mainframes".
Ventajas
Existen cuatro ventajas del procesamiento de bases de datos distribuidas. La primera,
puede dar como resultado un mejor rendimiento que el que se obtiene por un
procesamiento centralizado. Los datos pueden colocarse cerca del punto de su
utilización, de forma que el tiempo de comunicación sea mas corto. Varias
computadoras operando en forma simultánea pueden entregar más volumen de
procesamiento que una sola computadora.
Segundo, los datos duplicados aumentan su confiabilidad. Cuando falla una
computadora, se pueden obtener los datos extraídos de otras computadoras. Los
usuarios no dependen de la disponibilidad de una sola fuente para sus datos .Una
tercera ventaja, es que los sistemas distribuidos pueden variar su tamaño de un modo
más sencillo. Se pueden agregar computadoras adicionales a la red conforme
aumentan el número de usuarios y su carga de procesamiento. A menudo es más fácil y
más barato agregar una nueva computadora más pequeña que actualizar una
computadora única y centralizada. Después, si la carga de trabajo se reduce, el tamaño
de la red también puede reducirse.
Por último, los sistemas distribuidos se pueden adecuar de una manera más sencilla a
las estructuras de la organización de los usuarios.
Archivos:
Es una es estructura de datos que reside en memoria secundaria o almacenamiento
permanente (cinta magnética, disco
magnético, disco óptico, disco láser, etc.). La forma de clasificación más básica se
realiza de acuerdo al formato en que residen estos archivos, de esta forma hablamos de
archivos ASCII (de texto) y archivos binarios. En este capítulo nos centraremos en estos
últimos.
Definición archivo binario:
Estructura de datos permanente compuesto por registros (filas) y éstos a su vez por
campos (columnas). Se caracteriza por tener un tipo de dato asociado, el cual define su
estructura interna.
Definición archivo texto:
Estructura de datos permanente no estructurado formado por una secuencia de
caracteres ASCII.
Tipos de Acceso a los Archivos
a.)Secuencial:
Se accesan uno a uno los registros desde el primero hasta el último o hasta aquel que
cumpla con cierta condición de búsqueda. Se permite sobre archivos de Organización
secuencial y Secuencial Indexada.
b.)Random:
Se accesan en primera instancia la tabla de índices de manera de recuperar la dirección
de inicio de bloque en donde se encuentra el registro buscado. (dentro del rea primaria
o de overflow). Se permite para archivos con Organización Sec.Indexada.
c.)Dinámico:
Se accesan en primera instancia la tabla de índices de manera de recuperar la dirección
de inicio de bloque en donde se
encuentra el registro buscado. (dentro del rea primaria o de overflow). Se permite para
archivos con Organización Sec.Indexada.
d.)Directo:
Es aquel que utiliza la función de Hashing para recuperar los registros. Sólo se permite
para archivos con Organización Relativa.
Constantes
Las constantes son similares a una variable pero tienen un valor determinado que se
mantiene igual en toda la ejecución del programa. El contenido de una variable puede
cambiar tantas veces sea necesario. ¿Porque usar una constante si no puede cambiar
de valor?. Hacemos esto cuando deseamos usar un mismo número o una palabra
(string) varias veces.
Variables
Magnitud que puede tomar diferentes valores y se representa con una letra o letras. La
variable real es el conjunto de los números reales, y se puede representar por cualquier
letra o conjunto de letras y nos sirve para poder utilizar dicha letra para cálculos o para
obtener resultados.
http://www.monografias.com/trabajos14/datos/datos.shtml#
8.5 Operaciones básicas en archivos texto y binario.
Operaciones con archivos o carpetas.
Para trabajar con archivos o carpetas primero hay que seleccionarlos.
Existen dos modos de seleccionar:
Selección continua.
1.
2.
3.
4.
Pico el primer archivo que quiero seleccionar.
Pulso la tecla "Shift ( )" y la mantengo pulsada
Pico el último archivo a seleccionar
Suelto la tecla "Shift ( )"
Selección discontinua.
1.
2.
3.
4.
Pico un archivo
Pulso la tecla "CTRL" y la mantengo pulsada
Pico otro archivo, y otro, y otro y otro...
Suelto la tecla "CTRL"
Una vez seleccionados los archivos o carpetas sobre los que deseamos actuar, elegimos la operación a
realizar, tomándola de los botones o usando el botón derecho del ratón.
El método que aconsejo ya que no requiere memorizar, pasa por la utilización del botón derecho del ratón.
Copiar archivos.
1.
2.
Una vez seleccionados los archivos (pico con el botón derecho del ratón) y elijo "COPIAR".
Selecciono la nueva ubicación (pico con el botón derecho del ratón) y elijo "PEGAR".
Mover archivos.
1.
2.
Una vez seleccionados los archivos (pico con el botón derecho del ratón) y elijo "CORTAR".
Selecciono la nueva ubicación (pico con el botón derecho del ratón) y elijo "PEGAR".
Borrar archivos.
1.
Una vez seleccionados los archivos (pico con el botón derecho del ratón) y elijo "ELIMINAR". (o
presiono la tecla "Supr")
Observaciones.
Al borrar archivos o carpetas si estos se encuentran en el disco duro "C:" del ordenador no se borran
totalmente, sino que se envían a la papelera de reciclaje. En la papelera permanecen un tiempo hasta que
ésta se vacía. Durante ese tiempo se pueden recuperar.
Por el contrario si los archivos borrados se encuentran en un disquete serán eliminados directamente sin que
podamos posteriormente recuperarlos.
Recuperar archivos de la papelera de reciclaje.
1.
2.
3.
Activamos la papelera de reciclaje.
Seleccionamos los archivos que queremos recuperar.
Picamos con el botón derecho sobre los elementos y elegimos la opción RESTAURAR. De esta
manera el archivo se habrá colocado en su ubicación original igual que antes de borrarlo.
Abrir archivos.
Abrir un archivo consiste en recuperarlo con el programa que lo ha creado (si el archivo es un documento), o
en ejecutar un programa si el archivo es una aplicación (tienen extensión EXE).
1.
Una vez seleccionados los archivos (pico con el botón derecho del ratón) y elijo "ABRIR".
Observaciones.
Esta opción es interesante realizarla de uno en uno. Se realiza también haciendo doble Click sobre el archivo
que queremos abrir.
Crear carpetas
1.
2.
3.
Activar la unidad o carpeta en la que deseo crear una nueva carpeta
Ir a la opción del menú ARCHIVO / NUEVO / CARPETA (También se puede hacer con el botón
derecho)
Escribir el nombre de la nueva carpeta y pulsar INTRO.
Operaciones sobre disco.
Para operar con un disco picamos con el botón derecho sobre la unidad.
Copiar disco.
Duplica un disquete.
Formatear.
Prepara el disquete para trabajar. OJO (Se borra toda la información del disquete)
Propiedades.
Muestra la capacidad total de la unidad, el espacio libre, y el consumido.
http://www.adrformacion.com/guias/explorador.htm
Operaciones soportadas por el subsistema de archivos
Independientemente de los algoritmos de asignación de espacio, de los métodos de acceso y de la
forma de resolver las peticiones de lectura y escritura, el subsistema de archivos debe proveer un
conjunto de llamadas al sistema para operar con los datos y de proveer mecanismos de protección
y seguridad. Las operaciones básicas que la mayoría de los sistemas de archivos soportan son:




Crear ( create ) : Permite crear un archivo sin datos, con el propósito
de indicar que ese nombre ya está usado y se deben crear las
estructuras básicas para soportarlo.
Borrar ( delete ): Eliminar el archivo y liberar los bloques para su uso
posterior.
Abrir ( open ): Antes de usar un archivo se debe abrir para que el
sistema conozca sus atributos, tales como el dueño, la fecha de
modificación, etc. _ Cerrar ( close ): Después de realizar todas las
operaciones deseadas, el archivo debe cerrarse para asegurar su
integridad y para liberar recursos de su control en la memoria.
Leer o Escribir ( read, write ): Añadir información al archivo o leer el
caracter o una cadena de caracteres a partir de la posición actual. _
Concatenar ( append ): Es una forma restringida de la llamada `write',
en la cual sólo se permite añadir información al final del archivo. _
Localizar ( seek ): Para los archivos de acceso directo se permite
posicionar el apuntador de lectura o escritura en un registro aleatorio,
a veces a partir del inicio o final del archivo.



Leer atributos: Permite obtener una estructura con todos los atributos
del archivo especificado, tales como permisos de escritura, de borrado,
ejecución, etc.
Poner atributos: Permite cambiar los atributos de un archivo, por
ejemplo en UNIX, donde todos los dispositivos se manejan como si
fueran archivos, es posible cambiar el comportamiento de una terminal
con una de estas llamadas.
Renombrar ( rename ): Permite cambiarle el nombre e incluso a veces
la posición en la organización de directorios del archivo especificado.
Los subsistemas de archivos también proveen un conjunto de llamadas
para operar sobre directorios, las más comunies son crear, borrar,
abrir, cerrar, renombrar y leer. Sus funcionalidades son obvias, pero
existen también otras dos operaciones no tan comunes que son la de
`crear una liga' y la de `destruir la liga'. La operación de crear una liga
sirve para que desde diferentes puntos de la organización de
directorios se pueda accesar un mismo directorio sin necesidad de
copiarlo o duplicarlo. La llamada a `destruir nla liga' lo que hace es
eliminar esas referencias, siendo su efecto la de eliminar las ligas y no
el directorio real. El directorio real es eliminado hasta que la llmada a
`destruir liga' se realiza sobre él.
http://www.tau.org.ar/base/lara.pue.udlap.mx/sistoper/capit
ulo3.html
8.5.1 Crear.
8.5.2 Abrir.
8.5.3 Cerrar.
8.5.4 Lectura y escritura.
8.5.5 Recorrer.
8.6 Aplicaciones.
Descargar
Colecciones de estudio