Você está na página 1de 34

CLASES UNIDAD II

_______________________________________________________________________________________________________

UNIDAD II

CLASES Y FUNCIONES MIEMBRO

2.1. Construccion de clases y objetos

2.1.1. Estructuras, uniones y palabra reservada class

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.

Entonces, vemos que los desarrollos se organizan alrededor de los datos


manipulados, y no alrededor de las funcionalidades. Esta forma de construir un programa nos lleva a
pensar en objetos y de ahí el nombre de Programación orientada a objetos.
Antes de explicar cómo definir una clase en C++ y con el fin de realizar una comparación,
vamos en primer lugar a crear un nuevo tipo de datos utilizando una estructura Una clase es un tipo
definido por el usuario que describe los atributos y métodos que definen las características comunes a
todos los objetos de esa clase. En C++, los atributos reciben el nombre de datos miembro y los
métodos el nombre de funciones miembro de la clase. Los datos miembro definen el estado de un
determinado objeto y las funciones miembro son las operaciones que definen su comportamiento.
Forman parte de estas funciones los constructores, que permiten inicializar un objeto, y los
destructores, que permiten destruirlo.

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.

strcpy(d.nom, “Pedro”); d.edad=40; d. estatura=170;

cout << d.nom;

cin << d.nom;

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.

strcpy(x[2].nom,”Raul”) cout << x[0].nom); cin << x[3].nom;

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' ;

La forma de realizar lo mismo , mediante el uso de un puntero, sería la siguiente :

struct conjunto {
int a ;
double b ;
char c[5] ;
} *ptrconj ;

ptrconj = (struct conjunto *)malloc( sizeof( struct conjunto )) ;

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 ;

ptrconj = ( conj *)malloc( sizeof( conj )) ;

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 ).

DEFINICIÓN DE UNA CLASE

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 ;

El cuerpo de la clase en general consta de especificadores de acceso (public, protected y


prívate), atributos (datos miembro de la clase), mensajes (declaraciones de funciones miembro de
la clase) y métodos (definiciones de funciones miembro de la clase). Un método implícitamente
define un mensaje. Por ejemplo:
class Test
{
prívate:
int n_entero;

// datos miembro privados


f1oat n_rea1;
protected:
char *p; // miembros protegidos
int EsNegativo(void *char);
public:
Test( int i, float r );
// funciones miembro públicas
// constructor
void Asignar( char *c );
void Visualizar ( int a, float b, char *c );
};

_______________________________________________________________________________________________________
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).

2.2. Funciones miembro (metodos, acciones u operaciones)

Miembros de una clase

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.

Datos miembro de una clase


Para declarar un dato miembro de una clase en C++, proceda exactamente igual que para
declarar cualquier otra variable. Por ejemplo:

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
};

Funciones miembro de una clase

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,

void Test ::Asignar (char *c) // funcion de la clase test


{
// cuerpo de la funcion
}

Test: .'Asignar especifica que la función Asignar pertenece a la clase Test


Si la definición de una función miembro se hace en el cuerpo de la cía es necesario especificar
a qué clase pertenece, puesto que este dato ya es c<do. 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);
}

Según vemos en el ejemplo anterior, un miembro de una clase puede ser:


• public; un miembro declarado público es accesible en cualquier parte del programa donde el
objeto de la clase en cuestión, sea accesible.
• prívate; un miembro declarado privado puede ser accedido solamente por las funciones
miembro de su propia clase o por funciones amigas (friend) de su clase. En cambio, no puede ser
accedido por funciones globales (funciones no miembro) o por las funciones propias de una clase
derivada.
• protected; un miembro declarado protegido se comporta exactamente igual que uno
privado para las funciones globales, pero actúa como un miembro público para las funciones
miembro de una clase derivada.

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.

Si inicialmente no se define un especificador de acceso, la primera sección es tratada como


privada y se extiende hasta el primer especificador de acceso, o en su defecto, hasta la llave del final
del cuerpo de la clase.

Por ejemplo, en la siguiente declaración, la primera sección es privada:

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.

IMPLEMENTACIÓN DE UNA CLASE

La programación orientada a objetos sugiere separar la interfaz de su implementación,


fundamentalmente para dar una idea más precisa del cometido de dichi terfaz. Por ello,
generalmente, las definiciones de las funciones miembro de clase se realiza fuera del cuerpo de la
clase. Por otra parte, recuerde que cui una función miembro se define dentro del cuerpo de la clase,
es calificadamáticamente función en línea (inline).

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

// - Verificar si una fecha es válida.


// Versión utilizando clases.

#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:
void AsignarFecha();
void ObtenerFecha( int *, int *, int * ) const;
int FechaCorrecta() const;
};

// Establecer una fecha


void CFecha::AsignarFecha()
{
cout << "día, ## : "; cin >> dia;
cout << "mes, ## : "; cin >> mes;
cout << "año, #### : "; cin >> anyo;
}

// 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 );
_______________________________________________________________________________________________________
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
}

// Verificar si el año es bisiesto


int CFecha::Bisiesto() const
{
if ((anyo % 4 == 0) && (anyo % 100 != 0) || (anyo % 400 == 0))
return 1; // año bisiesto
else
return 0; // año no bisiesto
}

// Obtener una fecha


void CFecha::ObtenerFecha(int *pd, int *pm, int *pa) const
{
*pd = dia, *pm = mes, *pa = anyo;
}

// Programa que utiliza la clase CFecha:


// Funciones prototipo
void VisualizarFecha( const CFecha &fecha );

// Establecer una fecha, verificarla y visualizarla


void main()
{
CFecha fecha; // fecha es un objeto de tipo CFecha

do
fecha.AsignarFecha();
while (!fecha.FechaCorrecta());

VisualizarFecha( fecha );
}

// Visualizar una fecha


void VisualizarFecha( const CFecha &fecha )
{
int dd, mm, aa;

fecha.ObtenerFecha( &dd, &mm, &aa );


cout << dd << "/" << mm << "/" << aa << "\n";
}

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.

2.2.1. RESOLUTO DE AMBITO

AMBITO DE UNA CLASE


Cualquier función miembro de una clase puede acceder a otro miembro de su misma 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:

• A través de un objeto de su clase o de una clase derivada de ésta utilizando el operador"."


• A través de un puntero a un objeto de su clase o de una clase derivada de ésta utilizando el
operador "->"
• A través del nombre de su clase o de una clase derivada de ésta utilizando el operador"::"

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

EL PUNTERO IMPLÍCITO this

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í,

CFecha *const this = &fecha1;

Y cuando realizamos la misma operación con otro objeto fecha2, fecha2.AsignarFecha();

C++ define el puntero this, para apuntar al objeto fecha!, de la forma:

CFecha *const this = &fecha2;

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;
}

Observe ahora el programa presentado anteriormente. En hemos declarado un objeto fecha


de la clase CFecha y posteriormente le hemos enviado un mensaje FechaCorrecta:

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.

FUNCIONES MIEMBRO Y OBJETOS CONSTANTES

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:

const CFecha cumpleanyos ;

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();

la función miembro FechaCorrecta, tiene obligatoriamente que declarase y definirse constante


(const); esto es,

class CFecha
{
// Datos y funciones miembro de la clase CFecha
// ...
int FechaCorrecta() const; // Fecha Correcta se declara constante
};

// FechaCorrecta se define constante


int FechaCorrecta() const
{

// cuerpo de la función
}

En este ejemplo, se ha declarado constante la función miembro FechaCorrecta. Cuando una


función miembro es constante, el objeto referenciado por el puntero this se asume constante. Esto
supone que el puntero this quede implícitamente declarado constante a un objeto constante; esto es,

const CFecha *const this;

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,

int CFecha::FechaCorrecta() const


{
// ...
if ( ((CFecha *)this)->Bisiesto() )
// ...
}

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:

void CFecha: :Cambiar( int dd, int mm, int aa ) const


{
((CFecha *)this)->dia = dd;
((CFecha *)this)->mes = mm;
((CFecha *)this)->anyo = aa;
}

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;

// ...
};

int CFecha:: Bisitesto( ) const


{
// Cuerpo de la función
}

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,

unsigned int edad;

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 * );
};

CComplejo el = (1.5, -2};

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:

• declarando un objeto global,


• declarando un objeto local u objeto temporal,
• invocando al operador new, o
• llamando explícitamente a un constructor.

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

Como ejemplo, vamos a añadir un constructor a la clase CFecha. El resultado es el programa


prog0303.cpp que se expone a continuación.

// PROG0303.CPP - Inicialización explícita de un objeto


// con un constructor.

#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;
}

// Establecer una fecha


void CFecha::AsignarFecha()
{
cout << "día, ## : "; cin >> dia;
cout << "mes, ## : "; cin >> mes;
cout << "año, #### : "; cin >> anyo;
}

// 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
_______________________________________________________________________________________________________
PROGRAMACION ORIENTADA A OBJETOS ING. OSORNIO 31
CLASES UNIDAD II
_______________________________________________________________________________________________________
}
else
return 1; // fecha correcta
}

// Verificar si el año es bisiesto


int CFecha::Bisiesto() const
{
if ((anyo % 4 == 0) && (anyo % 100 != 0) || (anyo % 400 == 0))
return 1; // año bisiesto
else
return 0; // año no bisiesto
}

// Obtener una fecha


void CFecha::ObtenerFecha(int *pd, int *pm, int *pa) const
{
*pd = dia, *pm = mes, *pa = anyo;
}
/////////////////////////////////////////////////////////////////
// Programa que utiliza la clase CFecha:
// Funciones prototipo
void VisualizarFecha( const CFecha &fecha );

// Establecer una fecha, verificarla y visualizarla


void main()
{
CFecha OtroDia;
CFecha hoy(10, 2, 1997); // crear e inicializar el objeto hoy
VisualizarFecha( hoy );
}

// Visualizar una fecha


void VisualizarFecha( const CFecha &fecha )
{
int dd, mm, aa;

fecha.ObtenerFecha( &dd, &mm, &aa );


cout << dd << "/" << mm << "/" << aa << "\n";

}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;
};

CFecha::CFecha( int dd, int mm, int aa ) // Constructor


{
dia = dd; mes = mm; anyo = aa;
}

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;

CFecha hoy( 10, 2, 1997 );


CFecha OtroDia;
// ...
OtroDia = hoy;

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:

Nombre_clase &nombre_clase :: operatot=( const nombre_clase &);

Como ejemplo observe como el operador por asignación por omision de la clase Cfecha:

// PROG0304.CPP - Operador de asignación


#include <iostream.h>
#include <stdlib.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
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;
}

// Establecer una fecha


void CFecha::AsignarFecha()
{
cout << "día, ## : "; cin >> dia;
_______________________________________________________________________________________________________
PROGRAMACION ORIENTADA A OBJETOS ING. OSORNIO 35
CLASES UNIDAD II
_______________________________________________________________________________________________________
cout << "mes, ## : "; cin >> mes;
cout << "año, #### : "; cin >> anyo;
}

// 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
}

// Verificar si el año es bisiesto


int CFecha::Bisiesto() const
{
if ((anyo % 4 == 0) && (anyo % 100 != 0) || (anyo % 400 == 0))
return 1; // año bisiesto
else
return 0; // año no bisiesto
_______________________________________________________________________________________________________
PROGRAMACION ORIENTADA A OBJETOS ING. OSORNIO 36
CLASES UNIDAD II
_______________________________________________________________________________________________________
}

// Obtener una fecha


void CFecha::ObtenerFecha(int *pd, int *pm, int *pa) const
{
*pd = dia, *pm = mes, *pa = anyo;
}
/////////////////////////////////////////////////////////////////

// Programa que utiliza la clase CFecha:

// Funciones prototipo
void VisualizarFecha( const CFecha &fecha );

// Establecer una fecha, verificarla y visualizarla


void main()
{
CFecha hoy(10, 2, 1997); // crear e inicializar el objeto hoy
VisualizarFecha( *pOtroDia );
system ("pause");
}

// Visualizar una fecha


void VisualizarFecha( const CFecha &fecha )
{
int dd, mm, aa;

fecha.ObtenerFecha( &dd, &mm, &aa );


cout << dd << "/" << mm << "/" << aa << "\n";
}

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,

CFecha hoy( 10, 2, 1997 );


CFecha OtroDia = hoy;

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:

nombre_clase::nombre_clase( const nombre_clase &);

Si no especificamos un constructor copia, el compilador proporciona un constructor copia por


omisión público para cada clase definida. También, cuando sea necesario, podemos codificar nuestra
propia versión del constructor copia.

Como ejemplo, observe como es el constructor copia por omisión de la clase CFecha:

CFecha::CFecha(const CFecha &ObFecha)


j
dia = ObFecha.di a;
mes = ObFecha.mes;
anyo = ObFecha.anyo;

Añada esta función al programa realizado anteriormente. El resultado será el que se muestra a
continuación:

// PROG0304.CPP - Operador de asignación y constructor copia.


#include <iostream.h>
#include <stdlib.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

_______________________________________________________________________________________________________
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
}

// Verificar si el año es bisiesto


int CFecha::Bisiesto() const
{
if ((anyo % 4 == 0) && (anyo % 100 != 0) || (anyo % 400 == 0))
return 1; // año bisiesto
else
return 0; // año no bisiesto
}
// Obtener una fecha

_______________________________________________________________________________________________________
PROGRAMACION ORIENTADA A OBJETOS ING. OSORNIO 40
CLASES UNIDAD II
_______________________________________________________________________________________________________

void CFecha::ObtenerFecha(int *pd, int *pm, int *pa) const


{
*pd = dia, *pm = mes, *pa = anyo;
}
//////////////////////////////////////////////////////////////// Programa que utiliza la clase CFecha:
// Funciones prototipo
void VisualizarFecha( const CFecha &fecha );

// Establecer una fecha, verificarla y visualizarla


void main()
{
CFecha hoy(10, 2, 1997); // crear e inicializar el objeto hoy
CFecha *pOtroDia = new CFecha( hoy ); // llama al constructor copia
VisualizarFecha( *pOtroDia );
delete pOtroDia;
system ("pause");
}
// Visualizar una fecha
void VisualizarFecha( const CFecha &fecha )
{
int dd, mm, aa;

fecha.ObtenerFecha( &dd, &mm, &aa );


cout << dd << "/" << mm << "/" << aa << "\n";
}

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.

Por ejemplo, el destructor para la clase CFecha es declarado como

-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.

Un destructor también se puede llamar explícitamente utilizando su nombre completo,


objeto.nombre_clase::~nombre_clase();
pob]eto—>nombre_clase::~nombre_clase();

El hecho de que se incluya el nombre de la clase a la que pertenece el destructor es debido a


la tilde (~), para que no sea interpretada como el operador complemento a 1.

LOS OPERADORES new Y delete


Para llevar a cabo la asignación dinámica de la memoria libre, C++ dispone de los operadores
new y delete. Los operadores new y delete son parte de C++; esto es, no están disponibles en
ANSÍ C, en donde la asignación dinámica de memoria por lo general se lleva a cabo con las funciones
malloc y free de la biblioteca de C.
La sintaxis para utilizar estos operadores es:

puntero = new tipo-del-objeto


puntero = new (tipo-del-objeto)

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

Cuando el tipo especificado corresponde a un array, el operador new devuelve un puntero al


primer elemento de dicho array. En el ejemplo anterior, el array queda referenciado por a y los
elementos del array son a[0], a[l], a[2],...

A la hora de especificar la dimensión de un array, no sólo podemos utilizar constantes o variables,


sino también expresiones. Por ejemplo, el siguiente código crea un array a de dos dimensiones. Para
acceder a sus elementos podemos utilizar la notación a[0][0], a[0][l], a[0][2], ...

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:

double *a = new doub1e[d]: // devuelve el tipo (*)

Y si se utiliza para asignar memoria para un array de objetos multidimensional, devuelve un


puntero al elemento primero del array que a su vez es un array; por ejemplo, un array de dos
dimensiones, es un array de una dimensión donde cada elemento es a su vez un array. El tipo
resultante conserva el tamaño de todo menos de la dimensión primera del array. Esto implica que
todas las dimensiones menos la primera tienen que ser constantes. Por ejemplo:

int d1 = 4;
const int d2 = 5;
new doub1e[d1][d2];

devuelve el tipo (*)[d2J. Gráficamente puede imaginarlo así:

(*)[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:

double *a - new doub1e[dl][d2];

=
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,

new int(*[30])(); // error

da lugar a un error porque se analiza como:

(new int) (*[30])(); // error

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.

El siguiente ejemplo inicializa el objeto de tipo double con el valor 3.25:

double *pd = new double (3.25);

El operador global ::new aparece originalmente declarado de la forma siguiente:


void *operator new( size_t tamaño_en_bytes_del_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).

int *p, *parray;


// ...
p = new int;
parray = new int[n];
// ...
delete p;
delete [] parray;

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]

// Liberar la memoria ocupada por el array


for (1 = 0; i < d1; i++)
delete [] a[i]; // array a[i]
delete [] a; // array de punteros

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 (*)[]

// Liberar la memoria ocupada por el array


delete [] a;

El operador global ::delete aparece originalmente declarado de la forma siguiente:

void *operator delete( void *puntero_al_objeto );


void *operator delete( void *puntero_al_pbjeto, size_t tamaño );

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

Você também pode gostar