Escolar Documentos
Profissional Documentos
Cultura Documentos
_______________________________________________________________________________________________________
UNIDAD II
ESTRUCTURAS Y CLASES
La característica más importante de C++ es su soporte para implementar clases. Así, mientras
que un ejemplar de un tipo predefinido lo hemos denominado siempre variable, un ejemplar de una
clase lo denominaremos objeto. Por otra parte, si la programación estructurada se interesaba primero
por los procedimientos y después por los datos, el diseño orientado a objetos se interesa en primer
lugar por los datos; esto es, ahora la idea principal es ¿de qué trata el programa? y no ¿qué debe
hacer el programa? Posteriormente se asociarán los procedimientos que
permitirán manipular esos datos.
ESTRUCTURA DE DATOS (struct).- Otro de los tipos de datos estructurado que existen en turbo C
son las estructuras (REGISTROS) las cuales se puede manejar al igual que los arreglos un conjunto de
datos pero a diferencia de estos pueden ser de distinto, la manera de declarar estas estructuras es
fuera de todo bloque del programa principal, se constituye igual que un registro el cual se compone
de un conjunto de datos, a continuación se muestra como debe declararse una estructura.
struct <nombre>
{
campo 1;
campo 2;
campo n;
} variables;
De lo anterior podemos decir que nombre se refiere al nombre que se le va a dar al tipo de
estructura a crear la cual variara de aplicación es decir cuantos campos empleara y de que tipo será
cada uno de estos por lo que el nombre equivale a declarar un tipo especifico de datos, las variables
se refiere a que una vez definida la estructura y sus campos, estos deberán manejarse mediante una
variable que corresponda a ese tipo, cada campo deberá declararse como se declaran la variables en
un programa, a continuación se da un ejemplo de una estructura.
struct datos_per
_______________________________________________________________________________________________________
PROGRAMACION ORIENTADA A OBJETOS ING. OSORNIO 14
CLASES UNIDAD II
_______________________________________________________________________________________________________
{
char nom[20];
int edad;
int estatura;
} d,x[5];
En el ejemplo anterior podemos observar que la estructura se nombra como datos_per que
contiene tres campos uno de ellos es una cadena de caracteres (un arreglo unidimensional de
caracteres), y dos campos de enteros para manejar datos numéricos, finalmente las variables a
emplear por esta estructura y que corresponden al tipo datos_per son dos, la primera “d”, solo puede
manejar un solo registro es decir nombre, una edad y una estatura, y la segunda equivale a un arreglo
de registros es decir pueden manejar un conjunto de registros, por lo que la manera de leer o
desplegar o asignar valores a los campos de un registro o estructura se muestra a continuación.
Par el caso en el cual la variable sea un arreglo es decir un arreglo de registros se deberá
asignar especificando el registro al que se refiere de la totalidad de los existentes en el arreglo
(localidad), si se manejan de otra manera se deberá de hacer usando estructuras de control de
repetición.
for (i=0;i<5;i++)
x[i ].edad=0
for (i=0;i<5;i++)
cout << x[ i ].estatura;
for (i=0;i<5;i++)
{
cout << “\n De ala edad del registro :”;
cin << x[i ].edad;
}
Una variante de uso de estas estructuras es que al declarar la estructura no se declaran las
variables y esta s se declaran posteriormente dentro del programa principal.
struct datos_per
{
char nom[20];
int edad;
int estatura;
} d,x[5];
#include <iostream.h>
#include <conio.h>
void main()
{
struct datos per d, x[5];
El siguiente ejemplo nos muestra como debe declararse, leerse y desplegar en pantalla una
estructura de datos primeramente solo se hará con una estructura sencilla.
#include <iostream.h>
_______________________________________________________________________________________________________
PROGRAMACION ORIENTADA A OBJETOS ING. OSORNIO 15
CLASES UNIDAD II
_______________________________________________________________________________________________________
#include <conio.h>
struct agenda
{
char nom[20];
char tel [15];
int edad;
} a;
void main()
{
clrscr();
cout <<"PROGRAMA que registra el nombre edad y teléfono de 1 persona";
cout << "\n\n De el nombre "; cin >> a.nom;
cout << " Da la edad "; cin >> a.edad;
cout << " De el teléfono "; cin >> a.tel;
cout << "\n Los datos dela persona son: " << a.nom << a.edad << a.tel;
getch();
}
El segundo ejemplo hará lo mismo que el programa anterior pero a diferencia del mismo es
que la variable de la estructura será un arreglo que corresponda al tipo de l estructura, también se
usara la variante de declararla dentro de la función.
#include <iostream.h>
#include <conio.h>
struct agenda
{
char nom[20];
char tel [15];
int edad;
}
void main()
{
int i;
struct agenda b[5];
clrscr();
for (i=0;i<=4;i++)
{
cout << "PROGRAMA que registra el nombre edad y teléfono de 5 personas";
cout << "\n\n De el nombre "; cin >> a[i].nom;
cout << " Da la edad "; cin >> a[i].edad;
cout << " De el teléfono "; cin >> a[i].tel;
}
getch();
clrscr();
for (i=0;i<=4;i++)
cout << "\n Los datos de la persona son: " << a[i].nom << a[i].edad << a[i].tel;
getch();
}
PUNTEROS A ESTRUCTURAS
Los punteros pueden también servir para el manejo de estructuras , y su alojamiento dinámico ,
pero tienen además la propiedad de poder direccionar a los miembros de las mismas utilizando un
operador particular , el -> , (escrito con los símbolos "menos" seguido por "mayor" ) .
_______________________________________________________________________________________________________
PROGRAMACION ORIENTADA A OBJETOS ING. OSORNIO 16
CLASES UNIDAD II
_______________________________________________________________________________________________________
Supongamos crear una estructura y luego asignar valores a sus miembros , por los métodos ya
descriptos anteriormente :
struct conjunto {
int a ;
double b ;
char c[5] ;
} stconj ;
stconj.a = 10 ;
stconj.b = 1.15 ;
stconj.c[0] = 'A' ;
struct conjunto {
int a ;
double b ;
char c[5] ;
} *ptrconj ;
ptrconj->a = 10 ;
ptrconj->b = 1.15 ;
ptrconj->c[0] = 'A' ;
En este caso vemos que antes de inicializar un elemento de la estructura es necesario alojarla
en la memoria mediante malloc(), observe atentamente la instrucción: primero se indica que el
puntero que devuelve la función sea del tipo de apuntador a conjunto (ésto es sólo formal), y luego
con sizeof se le da como argumento las dimensiones en bytes de la estructura.
Acá se puede notar la ventaja del uso del typedef , para ahorrar tediosas repeticiones de texto,
y mejorar la legilibilidad de los listados; podríamos escribir:
typedef struct {
int a ;
double b ;
char c[5] ;
} conj ;
conj *ptrconj ;
Es muy importante acá , repasar la TABLA 13 del final del capítulo 3 , donde se indican las
precedencias de los operadores , a fín de evitar comportamientos no deseados , cuando se usan
simultaneamente varios de ellos . Ya que c es un array podemos escribir :
x = *ptrconj -> c ;
la duda acá es, si nos referimos al contenido apuntado por ptrconj ó por c. Vemos en la tabla
que, el operador -> es de mayor precedencia que la de * (dereferenciación), por lo que, el resultado
de la expresión es asignar el valor apuntado por c, es decir el contenido de c[0] .
De la misma forma:
_______________________________________________________________________________________________________
PROGRAMACION ORIENTADA A OBJETOS ING. OSORNIO 17
CLASES UNIDAD II
_______________________________________________________________________________________________________
*ptrconj -> c++ ; incrementa el puntero c , haciendolo tener la direccion
de c[1] y luego extrae el valor de éste .
++ptrconj -> c ; incrementa el valor de c[0] .
En caso de duda , es conveniente el uso a discreción de paréntesis , para saltar por sobre las , a
veces complicadas , reglas que impone la precedencia así , si queremos por ejemplo el valor de c[3] ,
la forma más clara de escribir es:
*( ptrconj -> ( c + 4 ) ) ;
(Recuerde que c[3] es el CUARTO elemento del array ).
Una clase es un tipo definido por el usuario. La definición de una clase especifica cómo son los
objetos de esa clase; esto es, qué atributos definen el estado de cada objeto y qué operaciones
permiten variar su estado. Ambos, atributos y operaciones, se denominan miembros de la clase.
La definición de una clase consta de dos partes. La primera está formada por el nombre de la
clase precedido por la palabra reservada class. La segunda parte es el cuerpo de la clase encerrado
entre llaves y seguido por un punto y coma o por una lista de identificadores, objetos de la clase,
separados por comas. Esto es,
class nombreclase
{
cuerpo de la clase
} lista de declaraciones ;
_______________________________________________________________________________________________________
PROGRAMACION ORIENTADA A OBJETOS ING. OSORNIO 18
CLASES UNIDAD II
_______________________________________________________________________________________________________
Este ejemplo define un nuevo tipo de datos. Test, que puede ser utilizado dentro de un
programa fuente exactamente igual que cualquier otro tipo. Una clase que no contenga declaraciones
de funciones miembro, funcionalmente es equivalente a una estructura C, a excepción de que no
podemos acceder directamente a sus datos miembro, a no ser que estén declarados como public ya
que por omisión éstos son privados. Según lo dicho en el párrafo anterior, una estructura o una unión
es una d en la que todos sus miembros son public por omisión. Así mismo, una estruct o una unión,
igual que una clase, pueden contener funciones miembro (incluye constructores y destructores).
Los miembros de una clase pueden ser variables de cualquier tipo y funciones, programación
orientada a objetos con C++, a las variables generalmente se denomina datos miembro y a las
funciones, funciones miembro de la clase. Los datos miembro constituyen la estructura interna de los
objetos de la y las funciones miembro forman lo que se denomina interfaz o medio de acceso la
estructura de los objetos.
class Test
{
prívate:
int n_entero;
float n_rea1;
//…
};
En una clase, cada dato miembro debe tener un nombre único. En cambio se puede utilizar el
mismo nombre con miembros que pertenecen a diferentes el Un dato miembro de una clase no puede
ser inicializado. Esto es, la siguiente declaración daría lugar a un error:
class Test
{
private:
int n_entero = 0;
float n_real;
// ...
};
También podemos declarar como datos miembro de una clase, objetos, punteros a objetos y
referencias a objetos.
Para declarar un objeto de una clase como miembro de otra clase, es necesario que aquella
haya sido previamente definida. Por ejemplo:
class CHora
_______________________________________________________________________________________________________
PROGRAMACION ORIENTADA A OBJETOS ING. OSORNIO 19
CLASES UNIDAD II
_______________________________________________________________________________________________________
{
// ...
};
class Cfecha{
//...
Chora hora;
};
Un objeto de una clase no puede ser miembro de ella misma, pero sí puede serlo un puntero al
objeto. Por ejemplo:
class Cfecha
{
// ..
Cfecha ayer; // error: objeto de la misma clase
Cfecha *hoy; // correcto
};
Las funciones miembro de una clase definen las operaciones que se pueden realizar con sus
datos miembro. Desde el punto de vista de la POO, el conjunto de todas estas funciones se
corresponde con el conjunto de mensajes a los que los objetos de la clase pueden responder.
Para declarar una función miembro de una clase en C++, proceda exactamente igual que
para declarar cualquier otra función. Por ejemplo:
class Test
{
//..
int EsNegativo(void *, char);
public: // funciones miembro públicas
Test ( int i, float r); // constructor
void Asignar( char *c );
void Visualizar ( int a, float b, char *c);
};
Generalmente, el cuerpo de una clase contiene sólo los prototipos de sus funciones miembro,
realizando las definiciones de las mismas justamente a continuación en el propio fichero fuente o, lo
que es más común, en otro fichero fuente.
Para definir una función miembro fuera del cuerpo de la clase, hay que indicar a qué clase
pertenece dicha función. Para ello hay que especificar el nombre de la clase antes del nombre de la
función, separado del mismo por el operador de ámbito (::). Esto es así porque puede haber
diferentes clases que tengan funciones miembro con el mismo nombre. Por ejemplo,
class Test
_______________________________________________________________________________________________________
PROGRAMACION ORIENTADA A OBJETOS ING. OSORNIO 20
CLASES UNIDAD II
_______________________________________________________________________________________________________
{
void Test ::Asignar (char *c) // funcion de la clase test
{
// cuerpo de la funcion
}
};
Piense que si el objetivo uno no se cumpliera, cuando se diera el objetivo dos el usuario tendría
que reescribir el código que hubiera desarrollado basándose en la estructura interna de los datos.
Para controlar el acceso a los miembros de una clase, C++ provee las palabras clave prívate,
public y protected. Estas palabras clave, denominadas especificadores de acceso, son utilizadas en
el cuerpo de la clase para indicar el tipo de acceso de cada miembro de la misma. Por ejemplo,
Class Test
{
private:
int n_entero;
flota n_real;
protected:
char *p;
int EsNegativo(void *, char);
public:
Test ( int i, flota r);
void Asignar(char *c);
void Visua11zar( int a, flota b, char *c);
}
En una clase puede haber cero o más secciones públicas, privadas o protegidas. Cada sección
de una clase finaliza donde comienza la siguiente.
Los programas que utilicen objetos de una determinada clase sólo ti ene ceso a la interfaz
pública, quedando oculta la parte restante del objeto. ] ejemplo anterior, se tendría acceso a las
funciones miembro Test, Asignarsua.liz.ar.
La sintaxis para acceder a un miembro public de una clase es la misma q expuso para las
estructuras. Por lo tanto, las funciones miembro de una clase pueden ser llamadas para un objeto de
esa clase. Por ejemplo,
void main()
{
Test MiObjeto;
_______________________________________________________________________________________________________
PROGRAMACION ORIENTADA A OBJETOS ING. OSORNIO 21
CLASES UNIDAD II
_______________________________________________________________________________________________________
//…
Miobjeto.Asignar(cad) //se llama a l afuncion Asignar para el objeto Miobjeto
//…
}
En este ejemplo, la primera línea define MiObjeto como un objeto de la i Test y a continuación
se le envía el mensaje Asignar. La respuesta a este mei es ejecutar la función miembro Asignar de su
misma clase.
Como ejemplo, realizaremos el programa anterior, pero ahora plementando una clase.
Recuerde que el programa que solicitaba una fecha y veririficaba si era correcta; esto es, que dd esté
entre los límites 1 y días del mes, que mm esté entre los límites 1 y 12 y que aaaa sea mayor o igual
que 1582. Es evidente que el programa va operar sobre objetos que son fechas. Por lo tanto, parece
lógico que su estructura de datos esté formada por los miembros día, mes y año, y permanezca
oculta al usuario. Por otra parte, las operaciones sobre estos objetos tendrán que permitir asignar una
fecha, función AsignarFecha, obtener una fecha de un objeto existente, función ObtenerFecha, y
verificar si una fecha que se quiere asignar es correcta, función FechaCorrecta. Estas tres funciones
formarán la interfaz pública. Cuando el día corresponda al mes de febrero, la función FechaCorrecta
necesitará comprobar si el año es bisiesto para lo que añadiremos la función Bisiesto. Puesto que un
usuario no necesita acceder a esta función, la declararemos protegida con la intención de que, en un
futuro, sí pueda acceder una clase derivada. Según lo expuesto, podemos escribir una clase
denominada CFecha así:
class Cfecha
{
// Datos miembro de la clase CFecha
prívate:
int dia, mes, anyo;
// Furciones miembro de la clase
protected:
int Bisiesto();
public:
void AsignarFecha();
void ObtenerFechaC int *, int *, int * ) const;
int FechaCorrecta();
};
El paso siguiente es definir cada una de las funciones miembro. Al hablar de los especificadores
de acceso quedó claro que cada una de las funciones miembro de una clase tiene acceso directo al
resto de los miembros. Según esto, la definición de la función AsignarFecha puede escribirse así:
void CFecha::As1gnarFecha()
(
cout « "di a, ## : "; cin » dia;
cout « "mes, ## : "; cin » mes;
cout « "año, #### : " ; cin » anyo;
}
Observe que por ser AsignarFecha una función miembro de la clase CFecha, puede acceder
_______________________________________________________________________________________________________
PROGRAMACION ORIENTADA A OBJETOS ING. OSORNIO 22
CLASES UNIDAD II
_______________________________________________________________________________________________________
directamente a los datos miembro día, mes y anyo de su misma clase. Estos datos corresponderán en
cada caso al objeto que recibe el mensaje AsignarFecha (objeto para el que se invoca la función; vea
más adelante, fmismo capítulo, el puntero implícito this). Por ejemplo, si declaramos los c fechal y
fecha.2 de la clase CFecha, y enviamos a fechal el mensaje Asigí cha:
Fecha1. AsignarFecha();
como respuesta a este mensaje, se ejecuta la función miembro AsignarFecha asigna los datos
día, mes y año al objeto fechal; esto es, a fechal. dia,fechd y fechal. anyo; y si afecha2 le enviamos
también el mensaje AsignarFecha
fecha2.AsignarFecha();
como respuesta a este mensaje, se ejecuta la función miembro AsignarFed asigna los datos día,
mes y año al objeto fecha2; esto es, afecha2.dia,fecha yfecha2.anyo.
Siguiendo las reglas expuestas, escribiremos el resto de las funciones de forma análoga a
como lo hicimos en el programa realizado anteriormente
#include <iostream.h>
// ¿año correcto?
AnyoCorrecto = ( anyo >= 1582);
// ¿mes correcto?
MesCorrecto = ( mes >= 1 ) && ( mes <= 12 );
switch ( mes )
// ¿día correcto?
{
case 2:
if ( Bisiesto() )
DiaCorrecto = ( dia >= 1 && dia <= 29 );
else
DiaCorrecto = ( dia >= 1 && dia <= 28 );
_______________________________________________________________________________________________________
PROGRAMACION ORIENTADA A OBJETOS ING. OSORNIO 23
CLASES UNIDAD II
_______________________________________________________________________________________________________
break;
case 4: case 6: case 9: case 11:
DiaCorrecto = ( dia >= 1 && dia <= 30 );
break;
default:
DiaCorrecto = ( dia >= 1 && dia <= 31 );
}
if ( !( DiaCorrecto && MesCorrecto && AnyoCorrecto ) )
{
cout << "\ndatos no válidos\n\n";
return 0; // fecha incorrecta
}
else
return 1; // fecha correcta
}
do
fecha.AsignarFecha();
while (!fecha.FechaCorrecta());
VisualizarFecha( fecha );
}
Resumiendo: la funcionalidad de esta clase está soportada por los miembro privados día, mes y
anyo, y por las funciones miembro AsignarFecha ObtenerFecha, FechaCorrecta y Bisiesto.
La función miembro pública AsignarFecha, solicita tres enteros de la entra! estándar y los
almacena en los datos miembro día, mes y anyo del objeto quel cibe el mensaje AsignarFecha (objeto
para el que se invoca dicha función). |
_______________________________________________________________________________________________________
PROGRAMACION ORIENTADA A OBJETOS ING. OSORNIO 24
CLASES UNIDAD II
_______________________________________________________________________________________________________
La función miembro pública ObtenerFecha, permite el acceso a los da? día, mes y anyo del
objeto que recibe el mensaje ObtenerFecha. La función miembro pública FechaCorrecta, verifica si la
fecha que se di asignar al objeto que recibe este mensaje, es correcta. Esta función devuelven si la
fecha es correcta y un O en caso contrario.
La función miembro protegida Bisiesto, verifica si el año de la fecha que desea asignar al objeto
que recibe este mensaje, es bisiesto. Esta función retó un 1 si el año es bisiesto y un O en caso
contrario. La función global VisualizarFecha, presenta en la salida estándar la fecha almacenada en
el objeto que se le pasa como argumento. Observe que esta funci py¡ para acceder a los datos de un
objeto tiene que invocar a la función miembro, ObtenerFecha. Esto es así porque una función que no
es miembro de la clase del objeto, no tiene acceso a los datos privados, sólo tiene acceso a la interfaz
públicade la clase.
Los miembros privados de una clase, datos o funciones, son locales a la misma y sólo pueden
ser accedidos por las funciones miembro de su clase o por una función friend de la misma. Los
miembros públicos de una clase también son locales a la clase. Constituyen la interfaz pública de la
clase y pueden ser accedidos por las funciones miembro de su clase y por cualquier otra función del
programa que defina un objeto o un puntero a un objeto de esa clase. Dicho de otra forma, un
miembro público de una clase es local a su clase y sólo es accesible:
En el programa anterior, la clase CFecha declara privados los datos miembro día, mes y anyo.
Esto quiere decir que sólo son accesibles por las funciones miembro de su clase. Si otra función
intenta acceder a uno de estos datos privados, el compilador genera un error. Lo mismo ocurre con la
función miembro protegida Bisiesto. Por ejemplo,
void main()
{
CFecha fecha;
// ...
int dd = fecha.día: // error: día es un miembro privado
fecha.mes = 1; // error: mes es un miembro privado
}
En cambio, las funciones AsignarFecha, ObtenerFecha y Fecha Correcta son públicas. Por lo
tanto, son accesibles, además de por las funciones miembro de su clase, por cualquier otra función
global (ajena a la clase). Sirva como ejemplo la función main o VisualizarFecha del programa
anterior.
Operador Operación
:: Operación de resolucion de ambito de una variable o de una funcion
miembro de una clase
Acceso a un miembro de una clase:
nombre_clase :: nombre_miembro
Acceso a una variable global dentro del ambito de una local del mismo
_______________________________________________________________________________________________________
PROGRAMACION ORIENTADA A OBJETOS ING. OSORNIO 25
CLASES UNIDAD II
_______________________________________________________________________________________________________
nombre:
:: nombre_variable
Cada objeto de una determinada clase mantiene su propia copia de los datos miembro de la
clase, pero no de las funciones miembro, de las cuales sólo existen una copia para todos los objetos
de esa clase. Esto es, cada objeto tiene su propia estructura de datos, pero todos comparten el
mismo código, el correspondiente a la interfaz. Por lo tanto, para que una función miembro conozca la
identidad del objeto particular para el cual dicha función ha sido invocada, C++ proporcional un
puntero al objeto denominado this. Así, por ejemplo, si declaramos un objeto fechai y a continuación
le enviamos el mensaje AsignarFecha,
Fecha1.AsignarFecha();
C++ define un puntero this para permitir referirse al objeto fechal en el cuerpo de la función
que se ejecuta como respuesta al mensaje. La definición es así,
Según lo expuesto, la función AsignarFecha puede ser definida también como se muestra a
continuación:
void CFecha::AsignarFecha()
{
// this hace referencia al objeto para el que haya sido
// invocada la función.
cout « "dia, ## : "; cin » this->dia;
cout « "mes, ## : "; cin » this->mes;
cout « "año, #W : ": cin » this->anyo;
}
do
fecha.As1gnarFecha();
while( ! fecha. FechaCorrecta()
En este caso, igual que en el ejemplo anterior, la función miembro FechaCorrecta conoce con
exactitud el objeto sobre el que tiene que actuar, puesto que se ha expresado explícitamente. Pero
¿qué pasa con la función miembro Bisiesto que se encuentra sin referencia directa alguna en el
cuerpo de la función miembro FechaCorrecta?
int CFecha::FechaCorrecta()
// ....
if (Bisiesto())
//...
_______________________________________________________________________________________________________
PROGRAMACION ORIENTADA A OBJETOS ING. OSORNIO 26
CLASES UNIDAD II
_______________________________________________________________________________________________________
En este otro caso, la llamada no es explícita como en el caso anterior. Lo que ocurre en la
realidad es que todas las referencias a los datos y funciones miembro, son implícitamente realizadas
a través del puntero implícito this, que contiene, como ya hemos dicho, la dirección del objeto que
recibió el mensaje (objeto para el cual se invocó la función). Según esto, la sentencia if anterior
podría escribirse también así:
if ( this->Bisiesto() )
Normalmente, en una función miembro, no es necesario utilizar este puntero para acceder a los
miembros de su clase, pero es útil cuando se trabaja con estructuras dinámicas. Una expresión
equivalente es: (*this).miembro.
Operador Operación
this Es un puntero que hace referencia al objeto para el que ha sido invocada
una funcion miembro de una clase.
Declarar un objeto const hace que cualquier intento accidental de modificar dicho objeto sea
detectado durante la compilación, en vez, de causar errores durante la ejecución. Si declaramos
explícitamente un objeto constante, por ejemplo:
es un error que la función miembro invocada cuando se envía un mensaje a este objeto no sea
también constante. Así, por ejemplo, si cumpleanyos es un objeto constante de la clase CFecha y le
enviamos el mensaje FechaCorrecta,
cumpleanyos.FechaCorrecta();
class CFecha
{
// Datos y funciones miembro de la clase CFecha
// ...
int FechaCorrecta() const; // Fecha Correcta se declara constante
};
// cuerpo de la función
}
Por otra parte, sabemos que todas las referencias a datos y funciones miembro en el cuerpo de
una función miembro, son hechas a través del puntero implícito this. Si FechaCorrecta es constante
¿qué pasa con la función miembro Bisiesto que se invoca en el cuerpo de la función miembro
FechaCorrecta y no ha sido declarada como constante? El fuerte chequeo de tipos de C++ detectará
esta diferencia y se visualizará un error que sugiere se realice una conversión explícita del puntero
_______________________________________________________________________________________________________
PROGRAMACION ORIENTADA A OBJETOS ING. OSORNIO 27
CLASES UNIDAD II
_______________________________________________________________________________________________________
this para que apunte a un objeto no constante. Esto es,
La construcción cast del ejemplo anterior convierte el puntero this, declarado implícitamente
constante a un objeto constante, a un puntero constante a un objeto no constante:
(CFecha *)this
const CFecha *const this CFecha *const this
Si el objeto es no constante, la función invocada puede ser no constante, COMO ocurre con
Bisiesto, o constante. Una función miembro declarada constante no puede modificar la
representación interna de los objetos para los que es invocada. Esto es así, porque al ser invocada,
this queda implícitamente declarado constante a un objeto constante, y es evidente que un objeto
constante no puede ser modificado. Una forma de saltar este sistema de protección es, igual que
hemos indicado en el párrafo anterior, explicitar que this apunta a un objeto no constante:
Hemos dicho que una función miembro constante puede ser invocada por cualquier objeto,
constante o no. Según esto, una solución mejor que convertir el puntero this para poder invocar a la
función Bisiesto en el cuerpo de la función constante FechaCorrecta, es declarar a Bisiesto también
constante, ya que esta función no necesita modificar el objeto para el que es invocada. Esto es,
class CFecha
{
// ...
int Bisiesto() const;
// ...
};
INICIALIZACIÓN DE UN OBJETO
Sabemos que un objeto consta de una estructura interna (los datos) y de una interfaz que
permite acceder y manipular tal estructura (las funciones). Ahora, ¿cómo se construye un objeto de
un tipo definido por el usuario? La respuesta es: igual que cualquier otro objeto de un tipo
predefinido. Por ejemplo,
Este ejemplo define el objeto edad (variable edad) del tipo predefinido unsigned int. En este
caso, el compilador automáticamente reserva memoria para su ubicación, le asigna un valor (cero si
es global o un valor indeterminado si es local) y procederá a su destrucción, al salir del ámbito en el
_______________________________________________________________________________________________________
PROGRAMACION ORIENTADA A OBJETOS ING. OSORNIO 28
CLASES UNIDAD II
_______________________________________________________________________________________________________
que ha sido definido. Si en lugar de ser un objeto de un tipo predefinido es un objeto de un tipo
definido por el usuario, por ejemplo una estructura, la construcción lleva consigo la inicialización de
cada uno de sus miembros. Por ejemplo,
struct Complejo
{
doubie real, imag;
} c1;
Este ejemplo define la estructura de datos el (objeto c1) con dos miembros de tipo entero: real
e imag. En este caso, el compilador, secuencial y recursivamente (un miembro de una estructura
puede ser otra estructura), reserva memoria para cada uno de los miembros y, de la misma forma,
procederá a su destrucción, al salir del ámbito en el que ha sido definido,
En el caso de variables globales o estáticas, el compilador las inicializa a cero si son numéricas
o a nulos si son de caracteres. Si son locales, el compilador las inicializa a un valor indeterminado; se
dice entonces que contienen basura.
Esto nos hace pensar en la idea de que de alguna manera el compilador llama a una función de
inicialización, constructor, para inicializar cada una de las variables declaradas, y a una función de
eliminación, destructor, para liberar el espacio ocupado por dichas variables, justo al salir del ámbito
en el que han sido definídas. Pues bien, con un objeto de una clase ocurre exactamente lo mismo. Por
ejemplo,
class CComplejo
{
// miembros de la clase
} c1;
En este caso, el compilador proporciona un constructor público por omision para cada clase
definida. Este constructor, secuencial y recursivamente (un mientras bro de una clase puede ser un
objeto de otra clase) reserva memoria para cada uno de los miembros. Igualmente, el compilador
proporciona un destructor público por omisión para cada clase, que destruirá, liberando memoria, los
objetos consfruidos.
Además, sabemos que un dato miembro no puede ser inicializado explicitamente en una clase,
y tampoco el constructor por omisión permite hacerlo. solucionar este problema, C++ permite
inicializar un objeto de una determinada clase en el momento de su declaración o mediante un
constructor definido por el usuario.
Un objeto de una clase sin constructores definidos por el usuario, ni datos miembro privados o
protegidos, sin clases base y sin funciones virtuales, puede ser inicializado utilizando una lista de
inicializadores. Por ejemplo,
dass CComplejo
{
pub1ic:
doub1e real , imag ;
void AsignarCompíejo( double, double );
void ObtenerCompíejo( double *, double * );
};
Este ejemplo define el objeto el y asigna a los datos miembro real e imag los valores 1.5 y -2,
respectivamente. Esta forma de inicializar un objeto es muy restrictiva, ya que, como hemos indicado
anteriormente, exige que la clase no tenga constructores, ni miembros privados o protegidos, que la
clase no sea derivada o que no tenga funciones virtuales. En cambio, como veremos a continuación,
los objetos de una clase con constructores se pueden inicializar con una lista de valores entre
paréntesis y no tienen las restricciones a las que hemos aludido anteriormente.
_______________________________________________________________________________________________________
PROGRAMACION ORIENTADA A OBJETOS ING. OSORNIO 29
CLASES UNIDAD II
_______________________________________________________________________________________________________
2.2.2. Constructor
En C++, una forma de asegurar que los objetos siempre contengan valores válidos es escribir
un constructor. Un constructor es una función miembro especial de una clase que es llamada
automáticamente siempre que se declara un objeto de esa clase. Su función es crear e inicializar un
objeto de su clase. Se puede crear un objeto de cualquiera de las formas siguientes:
Dado que los constructores son funciones miembro, admiten argumentos igual que éstas.
Cuando en una clase no especificamos ningún constructor, el compilador crea uno por omisión sin
argumentos. Un constructor por omisión de una clase C es un constructor que se puede invocar sin
argumentos.
Si el objeto creado es global, el constructor por omisión inicializa a ceros los datos miembro
numéricos y a nulos los datos miembro alfanuméricos, y si el jeto creado es local, lo inicializa con
valores indeterminados (basura).
Un constructor se distingue fácilmente porque tiene el mismo nombre que clase a la que
pertenece. Por ejemplo, el constructor para la clase CFecha se de nomina también CFecha. Un
constructor no se hereda, no puede retomar un valor (incluyendo void) y no puede ser declarado
const, virtual o static. J
#include <iostream.h>
/////////////////////////////////////////////////////////////////// Definición de la clase CFecha
class CFecha
{
// Datos miembro de la clase CFecha
private:
int dia, mes, anyo;
// Funciones miembro de la clase
protected:
int Bisiesto() const;
public:
CFecha( int dd = 1, int mm = 1, int aa = 1980 ); // constructor
void AsignarFecha();
void ObtenerFecha( int *, int *, int * ) const;
_______________________________________________________________________________________________________
PROGRAMACION ORIENTADA A OBJETOS ING. OSORNIO 30
CLASES UNIDAD II
_______________________________________________________________________________________________________
int FechaCorrecta() const;
};
// Constructor
CFecha::CFecha(int dd, int mm, int aa)
{
dia = dd; mes = mm; anyo = aa;
}
}Cuando una clase tiene un constructor, éste será invocado automáticamente siempre que se
cree un nuevo objeto de esa clase. En el caso de que el constructor tenga argumentos, éstos deben
especificarse en una lista de valores entre paréntesis a continuación de la declaración del objeto. El
siguiente ejemplo muestra esto con claridad:
_______________________________________________________________________________________________________
PROGRAMACION ORIENTADA A OBJETOS ING. OSORNIO 32
CLASES UNIDAD II
_______________________________________________________________________________________________________
CFecha hoy(10, 2, 1997); // crear e inicializar el objeto hoy
El ejemplo anterior define un objeto hoy e inicializa sus datos miembro día, mes y anyo a los
valores 10, 2 y 1997, respectivamente. Para ello, invoca al constructor CFecha(int dd, int mrn, int aa),
le pasa los argumentos 10, 2 y 1997 y ejecuta el código que se especifica en el cuerpo del mismo.
Esta línea sería equivalente a
=
CFecha hoy CFecha( 10, 2, 1997 );
En este otro ejemplo, se observa una llamada explícita al constmctor Cfecha no olvide que un
constmctor es una función especial. El resultado, el objeto.c do, se asigna a hoy.
Así mismo, debemos de saber que la definición explícita del. constructor con argumentos
CFecha, ha sustituido al constructor implícito (constructor por omisión) de su clase. Por lo tanto,
cuando se ejecute una línea como la siguiente,
CFecha OtroDia;
se producirá un error, ya que esta línea invoca a un constructor sin argumenta constructor por
omisión, que no existe ya que ha sido reemplazado por el constructor con argumentos. Para
solucionar este problema, lo más fácil es añadir al clase un constructor por omisión. Esto es,
class CFecha
{
// Datos miembro de la clase CFecha
prívate:
int dia, mes, anyo;
// Funciones miembro de la clase
protected:
int BisiestoO const;
public:
Cfecha() {}; //constructor por omision
CFecha( int dd, int mm, int aa); // constructor
void AsignarFecha();
void ObtenerFecha( int *, int *, int * ) const;
int FechaCorrecta() const;
};
En el ejemplo anterior se observa que hemos definido un constructor sin gumentos con un
cuerpo sin código. Esto hace que una línea como
CFecha OtroDia;
invoque a los constructores por omisión para cada uno de los miembros del obj. OtroDia; esto
es, se invoca al constructor del tipo int para inicializar las variable día, mes y anyo, una vez por cada
variable, además de realizar otras operación! para crear la estructura básica del objeto OtroDia. Esto
es lo mismo que sucedei si se invocara al constructor implícito por omisión. |
Según lo expuesto, es evidente que podemos definir multiples constructores con el mismo
nombre y diferentes con el fin de poder inicializar un objeto de una clase de diferentes formas Una
forma de reducir el numero de constructores es utilizar constructores con argumentos por omision, la
clase Cfecha quedaria asi;
Class Cfecha
{ // Datos miembro de la clase Cfecha
private:
int dia, mes, anyo;
// funciones miembro de la clase
protected:
_______________________________________________________________________________________________________
PROGRAMACION ORIENTADA A OBJETOS ING. OSORNIO 33
CLASES UNIDAD II
_______________________________________________________________________________________________________
int Bisiesto() const;
public:
Cfecha (int d=1, int mm=1, int aa=1908); // constrcutor
void AsignarFecha( );
void ObtenerFecha( int *, int *, int * ) const;
int FechaCorrecta() const;
};
Ahora, el constructor definido con argumentos por omisión admite llamadas con 0, 1, 2 o 3
argumentos:
CFecha fecha;
CFecha fecha( 10 );
CFecha fecha( 10, 2 );
CFecha fecha( 10, 2, 1997 );
De esta forma, con un solo constructor quedan resueltos todos los problemas planteados
anteriormente. Pero ¿qué diferencia hay entre este constructor y el constructor por omisión implícito?
En ambos se invoca a los constructores por omisión para cada uno de los miembros; esto es, se
construyen las variables dia, mes y anyo de tipo int, inicializándolas como corresponda, dependiendo
de que el objeto sea local o global. Pero en el caso del constructor explícito, se ejecuta a continuación
el cuerpo del mismo que, según el ejemplo, asigna a cada variable un valor específico; esto supone
una segunda asignación que se traduce en más tiempo de ejecución. Lo ideal sería que el constructor
de cada uno de los miembros asignara él mismo los valores especificados y que después se ejecutara
el cuerpo del constructor CFecha para las operaciones adicionales, si las hay. esto, C++ recomienda
utilizar siempre que sea posible la siguiente sintaxis:
CFecha::CFecha(int dd, int mm, 1nt aa) : dia( dd ), mes( mm ), anyo( aa ){}
Los dos puntos a continuación de la lista de parámetros del constructor O cha indican que
sigue una lista de inicializadores; en este caso, de los dal miembro de la clase (ver en el capítulo
siguiente "Objetos miembro de una clase"). Observe también que el cuerpo del constructor aparece
vacío, puesto que se requiere ninguna operación adicional.
Asignación de objetos
Otra forma de inicializar un objeto es utilizando el operador de asignación (=)
Por ejemplo;
Este ejemplo define los objetos hoy y OtroDia y a continuación, asig contenido de hoy a
OtroDia. La asignación se hace miembro a miembro. " esta operación en el programa prog0303.cpp y
observe los resultados.
Cuando se realiza una asignación, no se construye ningún objeto, sinos ambos objetos existen.
Para realizar esta operación, el compilador proporciona operador de asignación por omisión público
para cada clase definida.
_______________________________________________________________________________________________________
PROGRAMACION ORIENTADA A OBJETOS ING. OSORNIO 34
CLASES UNIDAD II
_______________________________________________________________________________________________________
Como veremos mas adelante, cuando sea necesario codificaremos nuestra propia versión del
operador de asignación, añadiendo a la clase la funcion:
Como ejemplo observe como el operador por asignación por omision de la clase Cfecha:
class CFecha
{
// Datos miembro de la clase CFecha
private:
int dia, mes, anyo;
// Funciones miembro de la clase
protected:
int Bisiesto() const;
public:
CFecha( int dd = 1, int mm = 1, int aa = 1980 ); // constructor
CFecha &operator=(const CFecha &);
void AsignarFecha();
void ObtenerFecha( int *, int *, int * ) const;
int FechaCorrecta() const;
};
// Constructor
CFecha::CFecha(int dd, int mm, int aa)
{
dia = dd; mes = mm; anyo = aa;
}
// Operador de asignación
CFecha &CFecha::operator=(const CFecha &ObFecha)
{
dia = ObFecha.dia;
mes = ObFecha.mes;
anyo = ObFecha.anyo;
return *this;
}
// ¿año correcto?
AnyoCorrecto = ( anyo >= 1582);
// ¿mes correcto?
MesCorrecto = ( mes >= 1 ) && ( mes <= 12 );
switch ( mes )
// ¿día correcto?
{
case 2:
if ( Bisiesto() )
DiaCorrecto = ( dia >= 1 && dia <= 29 );
else
DiaCorrecto = ( dia >= 1 && dia <= 28 );
break;
case 4: case 6: case 9: case 11:
DiaCorrecto = ( dia >= 1 && dia <= 30 );
break;
default:
DiaCorrecto = ( dia >= 1 && dia <= 31 );
}
if ( !( DiaCorrecto && MesCorrecto && AnyoCorrecto ) )
{
cout << "\ndatos no válidos\n\n";
return 0; // fecha incorrecta
}
else
return 1; // fecha correcta
}
// Funciones prototipo
void VisualizarFecha( const CFecha &fecha );
Vemos que el operador de asignación acepta como argumento una referencil al objeto a copiar,
objeto hoy, el cuerpo de la función copia miembro a miembn ese objeto en el objeto para el cual ha
sido invocado el operador de asignación objeto OtroDia, y la función retoma una referencia al objeto
resultante lo permite realizar asignaciones múltiples (por ejemplo, a = b = c)
_______________________________________________________________________________________________________
PROGRAMACION ORIENTADA A OBJETOS ING. OSORNIO 37
CLASES UNIDAD II
_______________________________________________________________________________________________________
Ejecute este programa y observe que los resultados después de una asignaciffl son los mismos
que obtuvo cuando no había añadido el operador de asignación,!)que corrobora que el compilador
proporciona un operador de asignación por ODIÍ sión para cada clase definida.
Constructor copia
Otra forma de inicializar un objeto es asignándole otro objeto de la misma clase en el momento
de su declaración; esto es, cuando se crea. Por ejemplo,
En este ejemplo se crea y se inicializa el objeto hoy y a continuación se crea el objeto OtroDia
inicializándole con el objeto hoy. A diferencia de la operación de asignación, inicialmente aquí sólo
existe un objeto (hoy); después se crea otro objeto (OtroDia) y se inicializa con el primero. Pruebe
estas operaciones en el programa anterior. Lógicamente, si se crea un objeto tiene que intervenir un
constructor.
Un constructor que crea un nuevo objeto a partir de otro objeto existente es denominado
constructor copia. Un constructor de estas características tiene un solo argumento, el cual es una
referencia a un objeto constante de la misma clase. Por tratarse de un constructor no hay un valor
retomado. La función prototipo pa-ra este constructor es de la forma:
Como ejemplo, observe como es el constructor copia por omisión de la clase CFecha:
Añada esta función al programa realizado anteriormente. El resultado será el que se muestra a
continuación:
_______________________________________________________________________________________________________
PROGRAMACION ORIENTADA A OBJETOS ING. OSORNIO 38
CLASES UNIDAD II
_______________________________________________________________________________________________________
protected:
int Bisiesto() const;
public:
CFecha( int dd = 1, int mm = 1, int aa = 1980 ); // constructor
CFecha(const CFecha &); // constructor copia
~CFecha(); // destructor
CFecha &operator=(const CFecha &);
void AsignarFecha();
void ObtenerFecha( int *, int *, int * ) const;
int FechaCorrecta() const;
};
// Constructor
CFecha::CFecha(int dd, int mm, int aa)
{
dia = dd; mes = mm; anyo = aa;
}
// Constructor copia
CFecha::CFecha(const CFecha &ObFecha)
{
dia = ObFecha.dia;
mes = ObFecha.mes;
anyo = ObFecha.anyo;
}
// Destructor
CFecha::~CFecha()
{
cout << "Objeto destruido\n";
}
// Operador de asignación
CFecha &CFecha::operator=(const CFecha &ObFecha)
{
dia = ObFecha.dia;
mes = ObFecha.mes;
anyo = ObFecha.anyo;
return *this;
}
// Establecer una fecha
void CFecha::AsignarFecha()
{
cout << "día, ## : "; cin >> dia;
cout << "mes, ## : "; cin >> mes;
cout << "año, #### : "; cin >> anyo;
_______________________________________________________________________________________________________
PROGRAMACION ORIENTADA A OBJETOS ING. OSORNIO 39
CLASES UNIDAD II
_______________________________________________________________________________________________________
}
// Verificar si una fecha es correcta
int CFecha::FechaCorrecta() const
{
int DiaCorrecto, MesCorrecto, AnyoCorrecto;
// ¿año correcto?
AnyoCorrecto = ( anyo >= 1582);
// ¿mes correcto?
MesCorrecto = ( mes >= 1 ) && ( mes <= 12 );
switch ( mes )
// ¿día correcto?
{
case 2:
if ( Bisiesto() )
DiaCorrecto = ( dia >= 1 && dia <= 29 );
else
DiaCorrecto = ( dia >= 1 && dia <= 28 );
break;
case 4: case 6: case 9: case 11:
DiaCorrecto = ( dia >= 1 && dia <= 30 );
break;
default:
DiaCorrecto = ( dia >= 1 && dia <= 31 );
}
if ( !( DiaCorrecto && MesCorrecto && AnyoCorrecto ) )
{
cout << "\ndatos no válidos\n\n";
return 0; // fecha incorrecta
}
else
return 1; // fecha correcta
}
_______________________________________________________________________________________________________
PROGRAMACION ORIENTADA A OBJETOS ING. OSORNIO 40
CLASES UNIDAD II
_______________________________________________________________________________________________________
Vemos que el operador de asignación acepta como argumento una referencil al objeto a copiar,
objeto hoy, el cuerpo de la función copia miembro a miembn ese objeto en el objeto para el cual ha
sido invocado el operador de asignación objeto OtroDia, y la función retoma una referencia al objeto
resultante lo permite realizar asignaciones múltiples (por ejemplo, a = b = c).
Ejecute este programa y observe que los resultados después de una asignaciffl son los mismos
que obtuvo cuando no había añadido el operador de asignación,!)que corrobora que el compilador
proporciona un operador de asignación por OMIsión para cada clase definida}
2.2.3. Destructor
De la misma forma que existe una función para construir cada uno de los objetos que
declaramos, también existe una función que permite destruir cada objeto construido, liberando así la
memoria que ocupa. Esta función recibe el nombre de destructor. Un objeto es destruido
automáticamente al salir del ámbito en el que ha sido definido el objeto. Sin embargo, hay una
_______________________________________________________________________________________________________
PROGRAMACION ORIENTADA A OBJETOS ING. OSORNIO 41
CLASES UNIDAD II
_______________________________________________________________________________________________________
excepción: los objetos creados dinámicamente por el operador new tienen que ser destruidos
utilizando el operador delete, de lo contrario el sistema destruiría la variable puntero pero no
liberaría el espacio de memoria referenciado por ella. A diferencia de lo que ocurría con los
constructores, sólo es posible definir un destructor.
Destructor
Un destructor es una función miembro especial de una clase que se utiliza para eliminar un
objeto de esa clase, liberándose la memoria que ocupa. Un destructor se distingue fácilmente porque
tiene el mismo nombre que la clase a la que pertenece precedido por una tilde ~. Un destructor no es
heredado, no tiene argumentos, no puede retornar un valor (incluyendo void) y no puede ser
declarado const ni static, pero sí puede ser declarado virtual.
Utilizando destructores virtualespodremos destruir objetos sin conocer su tipo (más adelante
trataremos el mecanismo de las funciones virtuales). Cuando en una clase no especificamos un
destructor, el compilador crea uno por omisión, público.
-CFecha();
Cuando se define un destructor para una clase, éste es invocado automáticamente cuando sale
fuera del ámbito en el que el objeto es accesible, excepto cuando el objeto ha sido creado con el
operador new; en este caso, el destructor tiene que ser invocado explícitamente a través del
operador delete; lógicamente, si deseamos destruir el objeto.
Como ejemplo vamos a añadir a la clase CFecha del programa anterior, m destructor para que
simplemente nos muestre un mensaje de que se ha destruidí un objeto. Esto es,
class CFecha
{
// Datos miembro de la clase CFecha
prívate:
int día, mes, anyo;
// Funciones miembro de la clase
protected:
int Bisiesto() const;
public:
CFecha( 1nt dd = 1, int mm = 1, int aa = 1980
CFecha(const CFecha &); // constructor copia
~CFecha(); //destructor
CFecha &operator-(const CFecha &);
void AsignarFecha();
void ObtenerFecha( int *, int *, int * ) const;
int FechaCorrecta() const
};
_______________________________________________________________________________________________________
PROGRAMACION ORIENTADA A OBJETOS ING. OSORNIO 42
CLASES UNIDAD II
_______________________________________________________________________________________________________
CFecha::CFecha()
{
cout « "Objeto destru1do\n";
}
//...
void main()
{
CFecha Hoy (10, 2,1997); // crea e inicializa el objeto
CFecha*pOtroDia = new Cfecha(hoy); // llama al constructor copia
VisualizarFecha( *pOtroDia);
delete pOtroDia; // llama al destructor
}
Ejecute ahora el programa y observe los resultados. Observará que cuando finalice la
ejecución de la función main se visualizará el mensaje "Objeto destruido" tantas veces como objetos
haya. Observe también, cómo para destruir un objeto creado dinámicamente hay que utilizar el
operador delete. El operador delete libera la memoria asignada por new con el fin de destruir el
objeto, motivo por el cual antes invoca al destructor de la clase del objeto.
El cuerpo de un destructor se ejecuta antes que los destructores de los objetos miembro. En el
caso que nos ocupa, cuando se destruya un objeto CFecha, se visualizará el mensaje "Objeto
destruido" y a continuación el sistema invocará al destructor int::~int(), una vez por cada uno de los
datos miembro día, mes y anyo. En otras palabras, el orden de destrucción es inverso al orden de
construcción.
delete puntero
delete [] puntero
Operador new
El operador new permite asignar memoria para un objeto o para un array de objetos. El
operador new asigna memoria desde el área de memoria de almacenamiento libre (free store). En C,
el área de memoria de almacenamiento libre se denomina "montón o pila" (heap). En el caso de un
objeto, el tamaño viene definido por su tipo. En el caso de arrays, el tamaño de un elemento viene
dado por su tipo, pero el tamaño del array hay que especificarlo explícitamente. El tipo puede ser un
tipo predefinido o un tipo definido por el usuario. El operador new devuelve un puntero a un objeto
del tipo especificado, que referencia el espacio reservado. Si no hay espacio suficiente para crear un
objeto del tipo especificado, el operador new devuelve un puntero nulo (O o NULL). Por ejemplo,
_______________________________________________________________________________________________________
PROGRAMACION ORIENTADA A OBJETOS ING. OSORNIO 43
CLASES UNIDAD II
_______________________________________________________________________________________________________
long *p1 = new long; // asignación para un long notación sin paréntesis
f1oat *pf = new (float); // asignación para un flota notación funcional
struct complejo
(
float re, im;
};
complejo *pc = new complejo; // asignación para una estructura
int n_e1ementes;
cin » n_e1omentos;
int *a = new int[n_elementes]; // creación dinámica del array a
int di = 5;
int d2- 10;
double **a = new double *[d1 * 2]; // array de punteros a
for (int i = 0; i < d1 * 2; 1++)
a[i] = new doub1e[d2]; // array a[i]
Cuando new se utiliza para asignar memoria para un único objeto, devuelve un puntero a ese
objeto:
double *a = new double; // devuelve el tipo (*)
Si se utiliza para asignar memoria para un array de objetos unidimensional, devuelve un
puntero al elemento primero del array:
int d1 = 4;
const int d2 = 5;
new doub1e[d1][d2];
(*)[d2]
Por lo tanto, el siguiente código no sería correcto porque intentaría asignar un puntero a un
array de elementos de tipo double de dimensión d2 a un puntero de tipo double:
=
La expresión correcta es; double (*a)[d2] new double[d1][d2];
El ejemplo anterior asigna memoria para un array a de dos dimensiones de tamaño d1*d2. No
asigna un array de punteros. Para acceder a sus elementos podemos utilizar la notación a[0][0], a[0]
[l], a[0][2],...
Si la especificación del tipo del objeto para el que queremos asignar memoria es complicada,
se pueden utilizar paréntesis para forzar el orden en el que se debe interpretar lo especificado. En
este caso, es obligatorio utilizar la versión con paréntesis del operador new (notación funcional). Por
_______________________________________________________________________________________________________
PROGRAMACION ORIENTADA A OBJETOS ING. OSORNIO 44
CLASES UNIDAD II
_______________________________________________________________________________________________________
ejemplo,
int (**ppfn)();
ppfn = new (int(*[30])());
En este ejemplo, new asigna memoria para un array de punteros de 30 elementos a
funciones que no toman argumentos y que devuelven un entero. Si en la expresión anterior no se
utiliza la notación funcional, el compilador visualiza un error. Esto es,
C++ también permite inicializar un objeto en el momento de crearlo. En este caso la sintaxis de
new sería:
puntero = new tipo-del-objeto (inicializadores)
Cuando se inicializa un objeto de esta forma, lo que sucede es que se invoca automáticamente al
constructor del tipo de objeto. Este constructor existe para objetos de un tipo predefinido y hay que
implementarlo para objetos de un tipo definido por el usuario, cuestión que veremos en el siguiente
capítulo. Cuando el objeto no se inicializa explícitamente, la inicialización ocurre implícitamente igual
que sucede con cualquier variable definida en C. Esto indica que existe un constructor predefinido sin
argumentos para cada objeto.
Esto significa que cuando el compilador encuentra el operador new realiza una llamada a
tipo-objeto::operator new( sizeof( tipo-objeto ) ) o, si la función operator new no está definida para
esa clase de objetos, entonces llama a la función global ::operator new( sizeof( tipo-objeto )).
Insuficiente memoria
Si al asignar memoria para un objeto se produce un error por falta de memoria, new devuelve
un puntero nulo (NULL). Para verificar si este suceso ocurre, será necesario añadir código similar al
siguiente:
int *pi = new int [NMAX];
1f( pi = 0 )
{
cerr << "Insuficiente memoria" « end1 ;
return -1;
}
Otra manera de verificar este suceso es escribir una función que manipule de forma
personalizada el suceso y registrar tal función invocando a la función predefinida
_set_new_handler.
Operador delete
El operador delete destruye un objeto creado por el operador new, liberando así la memoria
ocupada por dicho objeto. El operador delete sólo se puede aplicar a punteros que han sido
retomados por el operador new. Un puntero a una constante no puede ser borrado. Sin embargo, sí
_______________________________________________________________________________________________________
PROGRAMACION ORIENTADA A OBJETOS ING. OSORNIO 45
CLASES UNIDAD II
_______________________________________________________________________________________________________
se puede aplicar delete a un puntero nulo (un puntero con valor cero).
En este ejemplo, la primera línea delete libera el bloque de memoria apuntado porp y
asignado a un entero, y la segunda línea delete libera el bloque de memoria apuntado por parray y
asignado a un array de n enteros.Observe que la sintaxis varía en el caso de tener que liberar la
memoria ocupada por un array. En este caso, el compilador invoca al destructor apropiado para cada
uno de los elementos del array, desde parray [0] a parray [n-1].
El siguiente ejemplo muestra cómo liberar la memoria asignada para un array de dos dimensiones.
int di = 4;
int d2= 5;
// Asignar memoria para un array de dos dimensiones
double **a = new double *[d1]; // array de punteros a
for (int i = 0; i < d1; i++)
a[i] = new doub1e[d2]; // array a[i]
Observe cómo primero se libera la memoria asignada para cada fila del array y por último se
libera el array de punteros que referenciaba dichas filas. Este otro ejemplo que se expone a
continuación es otra versión del mismo ejemplo anterior. A diferencia de la versión anterior, ahora el
número de columnas, por definición, es constante.
int di = 4;
const int d2 = 5;
int (*a)[d2] = new int[d1][d2]; // devuelve el tipo (*)[]
Esto significa que cuando el compilador encuentra el operador delete realiza una llamada a
una de las dos funciones siguientes:
: :operator delete(puntero_al_objeto);
tipo-objeto::operator delete(puntero_al_objeto, tamaño);
La segunda forma es particularmente útil cuando una función operator delete de una clase
base se utiliza para liberar la memoria de un objeto de una clase derivada. Si la función operator
delete no está definida para una determinada clase de objetos, entonces se invoca a la función
global:
: :operator delete(puntero_al_objeto);
Así mismo, la función operator delete global es siempre invocada para arrays de cualquier tipo
_______________________________________________________________________________________________________
PROGRAMACION ORIENTADA A OBJETOS ING. OSORNIO 46
CLASES Y FUNCIONES MIEMBRO UNIDAD I
____________________________________________________________________________________________________
_________________________________________________________________________________________________
PROGRAMACION ORIENTADA A OBJETOS ING. OSORNIO
47