Está en la 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
{
char nom[20];
int edad;
int estatura;
} d,x[5];
_______________________________________________________________________________________________________
PROGRAMACION ORIENTADA A OBJETOS ING. OSORNIO 14
CLASES UNIDAD II
_______________________________________________________________________________________________________

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

struct agenda
{
char nom[20];
char tel [15];
int edad;
_______________________________________________________________________________________________________
PROGRAMACION ORIENTADA A OBJETOS ING. OSORNIO 15
CLASES UNIDAD II
_______________________________________________________________________________________________________
} 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" ) .

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] ;
_______________________________________________________________________________________________________
PROGRAMACION ORIENTADA A OBJETOS ING. OSORNIO 16
CLASES UNIDAD II
_______________________________________________________________________________________________________
} 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:

*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:

_______________________________________________________________________________________________________
PROGRAMACION ORIENTADA A OBJETOS ING. OSORNIO 17
CLASES UNIDAD II
_______________________________________________________________________________________________________
*( 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
{
void Test ::Asignar (char *c) // funcion de la clase test
{
// cuerpo de la funcion
_______________________________________________________________________________________________________
PROGRAMACION ORIENTADA A OBJETOS ING. OSORNIO 20
CLASES UNIDAD II
_______________________________________________________________________________________________________
}
};

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;
//…
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.
_______________________________________________________________________________________________________
PROGRAMACION ORIENTADA A OBJETOS ING. OSORNIO 21
CLASES UNIDAD II
_______________________________________________________________________________________________________

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

_______________________________________________________________________________________________________
PROGRAMACION ORIENTADA A OBJETOS ING. OSORNIO 22
CLASES UNIDAD II
_______________________________________________________________________________________________________
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 );
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 23
CLASES UNIDAD II
_______________________________________________________________________________________________________

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

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.

_______________________________________________________________________________________________________
PROGRAMACION ORIENTADA A OBJETOS ING. OSORNIO 24
CLASES UNIDAD II
_______________________________________________________________________________________________________
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
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

_______________________________________________________________________________________________________
PROGRAMACION ORIENTADA A OBJETOS ING. OSORNIO 25
CLASES UNIDAD II
_______________________________________________________________________________________________________
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())

//...

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
_______________________________________________________________________________________________________
PROGRAMACION ORIENTADA A OBJETOS ING. OSORNIO 26
CLASES UNIDAD II
_______________________________________________________________________________________________________
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 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;
_______________________________________________________________________________________________________
PROGRAMACION ORIENTADA A OBJETOS ING. OSORNIO 27
CLASES UNIDAD II
_______________________________________________________________________________________________________
}

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

_______________________________________________________________________________________________________
PROGRAMACION ORIENTADA A OBJETOS ING. OSORNIO 28
CLASES UNIDAD II
_______________________________________________________________________________________________________
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.

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.

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

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

_______________________________________________________________________________________________________
PROGRAMACION ORIENTADA A OBJETOS ING. OSORNIO 30
CLASES UNIDAD II
_______________________________________________________________________________________________________
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


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

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

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

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;
_______________________________________________________________________________________________________
PROGRAMACION ORIENTADA A OBJETOS ING. OSORNIO 32
CLASES UNIDAD II
_______________________________________________________________________________________________________
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:
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
_______________________________________________________________________________________________________
PROGRAMACION ORIENTADA A OBJETOS ING. OSORNIO 33
CLASES UNIDAD II
_______________________________________________________________________________________________________
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.

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;
_______________________________________________________________________________________________________
PROGRAMACION ORIENTADA A OBJETOS ING. OSORNIO 34
CLASES UNIDAD II
_______________________________________________________________________________________________________
}
// 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;
}

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

_______________________________________________________________________________________________________
PROGRAMACION ORIENTADA A OBJETOS ING. OSORNIO 35
CLASES UNIDAD II
_______________________________________________________________________________________________________
{
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 hoy(10, 2, 1997); // crear e inicializar el objeto hoy
VisualizarFecha( *pOtroDia );
system ("pause");
}

// Visualizar una fecha

_______________________________________________________________________________________________________
PROGRAMACION ORIENTADA A OBJETOS ING. OSORNIO 36
CLASES UNIDAD II
_______________________________________________________________________________________________________
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


protected:
int Bisiesto() const;
public:

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

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;
}
// Verificar si una fecha es correcta
int CFecha::FechaCorrecta() const

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

{
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
void CFecha::ObtenerFecha(int *pd, int *pm, int *pa) const
{
*pd = dia, *pm = mes, *pa = anyo;

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

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

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

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,

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
_______________________________________________________________________________________________________
PROGRAMACION ORIENTADA A OBJETOS ING. OSORNIO 43
CLASES UNIDAD II
_______________________________________________________________________________________________________
(
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 co n
paréntesis del operador new (notación funcional). Por 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,

_______________________________________________________________________________________________________
PROGRAMACION ORIENTADA A OBJETOS ING. OSORNIO 44
CLASES UNIDAD II
_______________________________________________________________________________________________________
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í 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
_______________________________________________________________________________________________________
PROGRAMACION ORIENTADA A OBJETOS ING. OSORNIO 45
CLASES UNIDAD II
_______________________________________________________________________________________________________
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

También podría gustarte