Clases
Sitio: | Facultad de Ingeniería U.Na.M. |
Curso: | Computación ET-344 |
Libro: | Clases |
Imprimido por: | Invitado |
Día: | miércoles, 22 de enero de 2025, 14:05 |
1. Introducción
Aunque te parezca mentira, hasta ahora no hemos visto casi nada de C++. La mayor parte de lo incluido hasta el momento forma parte de C, salvo muy pocas excepciones.
Ahora vamos a entrar a fondo en lo que constituye la mayor diferencia entre C y C++: las clases. Así que debemos prepararnos cambiar la mentalidad, y el enfoque de la programación tal como lo hemos visto hasta ahora.
Recodemos que en nuestro entorno todo se puede ver como un objeto , con funcionalidades (funciones) y propiedades; como el caso de un automovil , tiene propiedades como el color, tipo de combutisble, capacidad, etc. y funciones como arrancarse, detenerse, acelerar. etc. en definitiva se puede ver casi todo como un objeto.
En las próximas paginas iremos introduciendo nuevos conceptos que normalmente se asocian a la programación orientada a objetos, como son: objeto, mensaje, método, clase, herencia, interfaz, etc.
POO Programación Orientada a Objetos.
Siglas
de "Programación Orientada a Objetos". En inglés se pone
al revés "OOP". La idea básica de este tipo de
programación es agrupar los datos y los procedimientos para
manejarlos en una única entidad: el objeto.
Un programa es un objeto, que a su vez está formado de objetos. La idea de la programación estructurada no ha desaparecido, de hecho se refuerza y resulta más evidente, como se puede comprobar cuando veamos conceptos como la herencia.
2. Clase
http://www.cplusplus.com/doc/tutorial/classes/
Clase
Una clase se puede considerar como un patrón de datos o colección de datos
.Recodemos el caso de estructuras, en principio existen muchas
similitudes , hasta incluso en la manera que se definen.
En C++ una clase tiene como objetivo crear un nuevo tipo de datos que hasta puede contener funciones como mienbros de la clase, y el conjunto de miembros se pueden manejar como si fuese un elemento único.
Es importante notar que las clases no se deben Inicializar .. ya
que no son variables sinó patrones de datos.
Algo también
importante de destacar que una diferencia fundamental con
estructuras, es ellas cada miembro podía se accedido sin
limitaciones , podríamos decir que los miembros eran publicos por
defecto, en clases esto no es así, existen especificadores o
modificadores de acceso que definen cuales de sus miembros se pueden
acceder, en principio TODOS los miembros de una clase son
PRIVADOS, salvo indicación contraria, pero, mas adelante
daremos mas detalles de esto.
- A los miembros de las clases se los suele llamar PROPIEDADES.
- A los miembros que son funciones se los suele llamar MÉTODOS.
Objeto
Un objeto es una variable creada a partir de una clase, un objeto es una instancia de la clase.
Recordemos para
el caso de la estructura , esto es similar creábamos una instancia a
partir de una estructura, el proceso de crear un objeto ( colección
de variables y funciones) partir de una clase se llama instanciar.
Es importante distinguir entre objetos y clases, la clase es simplemente una
declaración, no tiene asociado ningún objeto ( colección de
variables y funciones).
Para dar un ejemplo usemos una analogía, tomemos como que la clase sería como el tipo de variable ( int, float, etc)
int x => int sería equivalente a la clase y x sería el objeto !! ( ESTO ES SOLO UNA ANALOGÍA)
Un objeto es una unidad que engloba en sí mismo datos y
procedimientos o funciones necesarios para el tratamiento de esos datos.
Hasta
ahora habíamos hecho programas en los que los datos y las funciones
estaban perfectamente separadas, cuando se programa con objetos esto
no es así, cada objeto puede contener datos (Propiedades) y funciones (Métodos).
Un programa se construye como un conjunto de objetos, o incluso como un único objeto. Es importante notar que un objeto si se puede inicializar a diferencia de una clase.
class <identificador de clase> { <lista de miembros> // miembros o propiedades separados por ; } [<lista de objetos>]; //vemos que existe una similitudo con estrucutras
Un ejemplo:
class Televisor { bool m_bEncedido; //propiedades de la clase unsigned int m_uiCanal; unsigned int m_uiVolumen; } tv14pulgadas ; tv17pulgadas ; // existen 2 objetos para esta clase=>Instanciar
// estamos instanciado un objeto tv14pulgadas a partir de una clase teelvisor // estamos instanciado un objeto tv17pulgadas a partir de una clase teelvisor
Método
Se trata de otro concepto de POO, en C++ un método no es otra cosa que una función procedimiento perteneciente a un objeto, hasta ahora solamente hemos incorporado como miembros de la clase variables...bueno también pueden ser funciones.
Veamos un ejemplo.. supongamos que se quiere modificar la propiedad Volumen deberíamos estar seguro que el televisor esté encendido, esto es lo normal, también sería algo parecido si queremos cambiar de canal, veamos como sería:
# include <iostream> using namespace std; class Televisor { bool m_bEncedido; //miembros o propiedades de la clase public: // Todos los metodos y propiedades //pueden ser accedidos desde afuera unsigned int m_uiCanal; // la m_ indica que es un miembro unsigned int m_uiVolumen; void Encender() //Definicion del metodo { m_bEncedido=true; }void Apagar (); //Protipo de Apagarbool ModificarVolumen( unsigned int uiVolumen);//Prototipo bool ModificarCanal ( unsigned int uiCanal)//Definicion del metodo {if (m_bEncedido) {m_uiCanal=uiCanal; return true; } else return false; } } ; //Como solo tenía el prototipo defino el metodo bool Televisor::ModificarVolumen ( unsigned int uiVolumen) {if (m_bEncedido) {m_uiVolumen=uiVolumen; return true; } else return false; } // Como solo estaba el prototipo defino el metodo void Televisor::Apagar() //debo hacer ref. a la clase que pertenece {m_bEncedido=false; } int main() {Televisor tv14p; //Instancio el objeto tv14p con la clase Televisor //Enciendo el televisor tv14p.Encender(); //Modifico el volumen tv14p.ModificarVolumen(10); // cout << "El televisor está : "<< tv14p.m_bEncedido << endl; //ERROR !! cout << "El volumen es :" << tv14p.m_uiVolumen << endl; return 0; }
Observaciones:
-Vemos que algunos métodos están dentro de la definición de la clase y otros afuera, conviene declarar los métodos afuera para simplificar la lectura en la clase ,salvo que sean muy simples como sería el caso de Apagar, que en este caso esta afuera.
-Vemos que cuando se definen afuera los métodos de la clases, se utiliza el operador de ambito “::”, este es el que vincula el método a la clase, si esto no existiera ,parecería la definición de una función cualquiera .-
-Vemos que una línea del código esta comentada:
// cout << "El televisor está : "<< tv14p.m_bEncedido << endl; //ERROR !!
si no lo hiciéramos el compilador tiraría un error, ya que la propiedad o miembro m_bEncendido es de carácter privado y no se puede acceder, descomente esta linea y compile para ver el error, veremos en el ejemplo siguiente como alterar una propiedad, para ello definimos un método.
3. Ejemplo
# include <iostream>
using namespace std;
class Televisor
{//Definimos la plantilla de propiedades y métodos de la clase Televisor.
bool m_bEncedido; //miembros o propiedades de la clase, es privado
unsigned int m_uiCanal;// la m_ indica que es un miembro o propiedad
unsigned int m_uiVolumen; //miembro o propiedad
public: //los metodos y propiedades que continúan línea abajo son públicos
//métodos publicos
void Encender() {m_bEncedido=true;};//Defino el método dentro de la clase
void Apagar(){m_bEncedido=false;};//Defino el método dentro de la clase
bool ModificarVolumen( unsigned int uiVolumen);//método
bool ModificarCanal ( unsigned int uiCanal);//método
unsigned int ConsultaVolumen ();//método
unsigned int ConsultaCanal ();//método
} ;
//Fin de definición de la clase Televisor
//Metodo de cambia de Canal,definido Fuera de Clase por eso el op. de ambito ::
bool Televisor::ModificarCanal ( unsigned int uiCanal)
{
if (m_bEncedido)
{m_uiCanal=uiCanal;return true;}
else return false;
}
//Metodo Cambia el Volumen,definido Fuera de Clase por eso el op. de ambito ::
bool Televisor::ModificarVolumen ( unsigned int uiVolumen)
{
if (m_bEncedido)
{m_uiVolumen=uiVolumen;return true;}
else return false;
}
//Metodo Consulta el Volumen,definido Fuera de Clase por eso el op. de ambito ::
unsigned int Televisor :: ConsultaVolumen ()
{return m_uiVolumen;}
//Metodo Consulta el Canal,,definido Fuera de Clase por eso el op. de ambito ::
unsigned int Televisor :: ConsultaCanal ()
{return m_uiCanal;}
//Comienza la Funcion main------------------------
int main()
{
char opcion; int volumen, canal;
Televisor tv14p; //Instancio el objeto tv14p con la clase Televisor
do
{
cout << " Indique la acción :" << endl;
cout << " 1 para Encender" << endl;
cout << " 0 para Apagar" << endl;
cout << " 2 Para modificar el Volumen" << endl;
cout << " 3 Para modificar el Canal "<< endl;
cout << " Otra tecla para Salir "<< endl;
cin >> opcion;
switch (opcion)
{
case '1': tv14p.Encender();break;//Enciendo el televisor
> case '0': tv14p.Apagar();break ;//Apago el televisor
case '2': cout << "EL volumen esta en: "<< tv14p.ConsultaVolumen();
cout << "Ingrese el nuevo valor del Volumen: "<< endl;
cin >> volumen;
tv14p.ModificarVolumen(volumen);break;
case '3': cout << "EL Canal actual esta en: " << tv14p.ConsultaCanal();
cout << "Ingrese el nuevo valor del Canal: " << endl;
cin >> canal;
> tv14p.ModificarCanal(canal);break;
>
}
}while ('0'==opcion||'1'==opcion||'2'==opcion||'3'==opcion);
return 0;
}
Observaciones:
-Vemos que para alterar las propiedades privadas recurrimos a métodos , por ejemplo Enceder , Apagar , estos alteran la propiedad privada llamada m_bEncendido.
-Si corremos el código y consultamos el valor del canal y del volumen antes de asignar algún valor veremos que el mismo contiene BASURA , veremos mas adelante que exiten métodos o funciones para inicializar el objeto , estos son conocidos como “CONSTRUCTORES”.
4. Constructores
Los constructores :
- tienen el mismo nombre que la clase, es fácil identificarlos
- NO retornan ningún valor
- NO pueden ser heredados. Además
- deben ser públicos,
-
# include <iostream> using namespace std; class Televisor {//Definimos la plantilla de propiedades y métodos de la clase Televisor. bool m_bEncedido; //miembros o propiedades de la clase, es privado unsigned int m_uiCanal;// la m_ indica que es un miembro o propiedad unsigned int m_uiVolumen; //miembro o propiedad public: //los metodos y propiedades que continúan línea abajo son públicos //métodos publicos Televisor(){m_bEncedido=false;m_uiCanal=1;m_uiVolumen=0;}//Constructor void Encender() {m_bEncedido=true;};//Defino el método dentro de la clase void Apagar(){m_bEncedido=false;};//Defino el método dentro de la clase bool Estado(){return m_bEncedido;} bool ModificarVolumen( unsigned int uiVolumen);//método bool ModificarCanal ( unsigned int uiCanal);//método unsigned int ConsultaVolumen ();//método unsigned int ConsultaCanal ();//método } ; //Fin de definición de la clase Televisor //Metodo de cambia de Canal,definido Fuera de Clase por eso el op. de ambito :: bool Televisor::ModificarCanal ( unsigned int uiCanal) { if (m_bEncedido) {m_uiCanal=uiCanal; return true;} else return false; } //Metodo Cambia el Volumen,definido Fuera de Clase por eso el op. de ambito :: bool Televisor::ModificarVolumen ( unsigned int uiVolumen) { if (m_bEncedido) {m_uiVolumen=uiVolumen; return true;} else return false; } //Metodo Consulta el Volumen,definido Fuera de Clase por eso el op. de ambito :: unsigned int Televisor :: ConsultaVolumen () {return m_uiVolumen;} //Metodo Consulta el Canal,,definido Fuera de Clase por eso el op. de ambito :: unsigned int Televisor :: ConsultaCanal () {return m_uiCanal;} //Comienza la Funcion main------------------------ int main() { char opcion; int volumen, canal Televisor tv14p; //Instancio el objeto tv14p con la clase Televisor do { cout << " Indique la acción :" << endl; cout << " 1 para Encender" << endl; cout << " 0 para Apagar" << endl; cout << " 2 Para modificar el Volumen" << endl; cout << " 3 Para modificar el Canal "<< endl; cout << " Otra tecla para Salir "<< endl; cin >> opcion; switch (opcion) { case '1': tv14p.Encender();break;//Enciendo el televisor case '0': tv14p.Apagar();break ;//Apago el televisor case '2': cout << "EL volumen esta en: "<< tv14p.ConsultaVolumen(); cout << "Ingrese el nuevo valor del Volumen: "<<endl; cin >> volumen; tv14p.ModificarVolumen(volumen); break; case '3': cout << "EL Canal actual esta en: " <<Tv14p.ConsultaCanal(); cout << "Ingrese el nuevo valor del Canal: " << endl; cin >> canal; tv14p.ModificarCanal(canal);break; case '4': cout<<"El Estado del TV es: "<<tv14p.Estado()<<endl; cout<<"El canal al inicio es: "<<tv14p.ConsultaCanal()<<endl; cout<<"El volumen al inicio es: "<<tv14p.ConsultaVolumen()<<endl; } }while ('0'==opcion||'1'==opcion||'2'==opcion||'3'==opcion ||'4'==opcion); return 0; }
En el ejemplo anterior nuestro constructor NO recibe argumento.
Se declara en la línea 11 dentro de la clase y se ejecuta cuando se crea el objeto en la línea 51.
-
Televisor(){m_bEncedido=true;m_uiCanal=1;m_uiVolumen=0;}//Constructor
-
Televisor(bool on_off, int canal, int volumen) {m_bEncedido=on_off;m_uiCanal=canal;m_uiVolumen=volumen;}//Constructor con 3 argumentos.
Televisor tv14p(true,10);
Televisor tv14p();
Cuando no especifiquemos un constructor para una clase, el compilador crea uno por defecto sin argumentos. Por eso en los ejemplos anteriores funcionaba correctamente.
Cuando se crean objetos locales, los datos miembros no se inicializarían, contendrían la "basura" que hubiese en la memoria asignada al objeto al crear el compilador un constructor por defecto esto no sucede. Si se trata de objetos globales, los datos miembros se inicializan a cero.
Para declarar objetos usando el constructor por defecto o un constructor que hayamos declarado sin parámetros no se debe usar el paréntesis
Forma 1: (Ya mostrada)
Televisor(bool on_off, int canal, int volumen) {m_bEncedido=on_off;m_uiCanal=canal;m_uiVolumen=volumen;}//Constructor con 3 argumentos.
Forma 2:
Televisor(bool on_off, int canal, int volumen):m_bEncedido(on_off),m_uiCanal(canal),m_uiVolumen(volumen){}//Constructor con 3 argumentos.
- El constructor es Public !! ( no tendría sentido otro tipo de acceso)
- El constructor NO TIENE TIPO
- El nombre del Constructor es IGUAL al de la CLASE
Sobrecarga de constructores
Televisor(bool on_off, int canal, int volumen){m_bEncedido=on_off;m_uiCanal=canal;m_uiVolumen=volumen;}//Constructor
Televisor(){m_bEncedido=false;m_uiCanal=1;m_uiVolumen=0;}//Constructor
5. Interface
Las clases y por lo tanto también los objetos, tienen partes públicas y partes privadas.
Algunas veces llamaremos a la parte pública de un objeto su interfaz.
Se trata de la única parte del objeto que es visible para el resto de los objetos, de modo que es lo único de lo que se dispone para comunicarse con ellos.
6. Herencia
Veremos que es posible diseñar nuevas clases basándose en clases ya existentes.
En C++ esto se llama derivación de clases, y en POO herencia.
Cuando se deriva una clase de otra, normalmente se añadirán nuevos métodos y datos.
Es posible que algunos de estos métodos o datos de la clase original no sean válidos, en ese caso pueden ser enmascarados en la nueva clase o simplemente eliminados. El conjunto de datos y métodos que sobreviven, es lo que se conoce como herencia.
Una vez que una clase se ha escrito creado, depurado y utilizado, se puede difundir entre otros programadores para que pueden reutilizarla en sus programas.
Un programador puede tomar la clase existente, y sin modificarla añadir características adicionales a la misma. Estas operaciones se realizan por derivación de una clase nueva a partir de una clase ya existente.
La reutilización del software es un término muy importante, es similar a la utilización del hardware mediante circuitos integrados.
Las
clases que heredan de clases base se denominan derivadas, estas a su vez pueden ser clases
bases para otras clases derivadas. Se establece así una clasificación jerárquica,
similar a la existente en Biología con los animales y las plantas
Este tema no es parte de la materia.
Mensaje
El mensaje es el modo en que se comunican los objetos entre si. En C++, un mensaje no es más que una llamada a una función de un determinado objeto. Cuando llamemos a una función de un objeto, muy a menudo diremos que estamos enviando un mensaje a ese objeto.
En este sentido, mensaje es el término adecuado cuando hablamos de programación orientada a objetos en general.
Polimorfismo
En la POO el polimorfismo se refiere al hecho de que una misma operación puede tener diferente comportamiento en diferentes objetos.
En otras palabras, diferentes objetos reaccionan al mismo mensaje de manera diferente.
Por ejemplo, supongamos un número de figuras geométricas que responden todas al mensaje Dibujar.
Cada objeto reacciona a este mensaje visualizando su figura en la pantalla. Obviamente, el mecanismo real para dibujar los objetos difiere de una figura a otra, pero todas las figuras realizan esta tarea en respuesta al mismo mensaje.
7. Especificadores de acceso
Dentro de la lista de miembros, cada miembro puede tener diferentes niveles de acceso, hasa ahora hemos visto los privados y los públicos.
En nuestros ejemplo hemos usado dos de esos niveles,
- public
- Cuando usted declara público ( public) un miembro de una clase, usted permite el acceso a tal miembro desde dentro y fuera de la clase.
- private
- Cuando un miembro de una clase es declarado privado ( private ) es ináccesible no sólo desde otras clases y otras partes del programa, sino también desde sus clases derivadas
- protected
- Los miembros de datos que son declarados protegidos ( protected ) son únicamente accesibles por funciones miembro de la clase, pero no se pueden acceder a ellos desde otras clases.
La sintaxis sería:
class <identificador de clase>
{ public: //<-etiqueta!! <lista de miembros> private://<-etiqueta!! <lista de miembros> protected: //<-etiqueta! <lista de miembros> };
Acceso privado, private
Los miembros privados de una clase sólo son accesibles por los propios miembros de la clase y en general por objetos de la misma clase, pero no desde funciones externas o desde funciones de clases derivadas.
Acceso público, public
Cualquier miembro público de una clase es accesible desde cualquier parte donde sea accesible el propio objeto. Veamos un Ejemplo:
#include <iostream>
using namespace std;
class X {public:
int a;
};
class Z {public:
int b;};
{
X a1,a4;
Z a2,a5;
int a3;
a1.a=3;
//a2=a1; //Esta línea tiraría error, no se pueden igualar Clases Distintas !!
//a3=a1 ; //Esta línea tiraría error, No se puede convertir X a int !!
a5.b=8;
a4=a1 ; //esto es correcto
a2=a5; // esto es correcto
}
Observación: este mismo ejemplo es válido si en lugar de la palabra class se usa struct !
Se pide al alumno, compilar el código sacando las etiquetas public de la clase Z y ver que error tira.
Acceso protegido, protected
Con respecto a las funciones externas, es equivalente al acceso privado, pero con respecto a las clases derivadas se comporta como público.
Cada una de éstas palabras, seguidas de ":", da comienzo a una sección, que terminará cuando se inicie la sección siguiente o cuando termine la declaración de la clase. Es posible tener varias secciones de cada tipo dentro de una clase.
Si no se especifica nada, por defecto, los miembros de una clase son privados. Como en la materia NO se ve Herencia , este tema no se profundiza.
8. Punteros a Clases
Un objeto o instancia de una clase puede ser apuntado por un puntero.
El puntero a un objeto DEBE ser del mismo "TIPO" , ahora sabemos que se debe decir CLASE.
La forma de utilizarse es la misma que la vista para estructuras.
#include <iostream>
using namespace std;
class Rectangulo {
int ancho, alto;
public:
Rectangulo(int x, int y) : ancho(x), alto(y) {}
int area(void) { return ancho * alto; }
};
int main() {
Rectangulo obj (3, 4);//instancio objeto obj
Rectangulo * foo, * bar, * baz;//punteros
foo = &obj;//el puntero foo apunta al objeto obj
bar = new Rectangulo (5, 6);//este es otro puntero que apunta a otro objeto sin nombre
baz = new Rectangulo[2] { {2,5}, {3,6} };//este es un arreglo de punteros que apuntan a otro objeto sin nombre
cout << "Area del objeto: " << obj.area() << '\n';
cout << "Area del puntero *foo: " << foo->area() << '\n';
cout << "Area del puntero *bar: " << bar->area() << '\n';
cout << "Area del la instancia baz[0]:" << baz[0].area() << '\n';
cout << "Area de la instancia baz[1]:" << baz[1].area() << '\n';
delete bar;
delete[] baz;
return 0;
}
9. Puntero “this”
- necesito saber la dirección del objeto
- evitar ambigüedades
en este caso hay una propiedad de la clase y una variable local del método que se llaman "a". Para acceder a la propiedad de la clase dentro del método debo utilizar this
10. Destructores
Los destructores son funciones miembro especiales que sirven para eliminar un objeto de una determinada clase, liberando la memoria utilizada por dicho objeto. Al igual que existe el new y el delete.
Los destructores tienen el mismo nombre que la clase, pero con el símbolo ~ delante, no retornan ningún valor y no pueden ser heredados.
Cuando se define un destructor para una clase, éste es llamado automáticamente cuando se abandona el ámbito en el que fue definido, esto es así salvo cuando el objeto fue creado dinámicamente con el operador new, ya que en ese caso, si es necesario eliminarlo, hay que usar el operador delete.
En general, será necesario definir un destructor cuando nuestra clase tenga datos miembro de tipo puntero, aunque esto no es una regla estricta. El destructor no puede sobrecargarse, por la sencilla razón de que no admite argumentos.
Ejemplo:
#include <iostream>
#include <cstring>
using namespace std;
class cadena {
public:
cadena(); // Constructor por defecto
cadena(char *c); // Constructor desde cadena c
cadena(int n); // Constructor de cadena de n caracteres
cadena(const cadena &); // Constructor copia
~cadena(); // Destructor
void Asignar(char *dest);
char *Leer(char *c);
private:
char *cad; // Puntero a char: cadena de caracteres
};
cadena::cadena() : cad(NULL) {}
cadena::cadena(char *c) {
cad = new char[strlen(c)+1];// Reserva memoria para cadena
strcpy(cad, c); // Almacena la cadena
}
cadena::cadena(int n) {
cad = new char[n+1]; // Reserva memoria para n caracteres
cad[0] = 0; // Cadena vacía
}
cadena::cadena(const cadena &Cad) {
// Reservamos memoria para la nueva y la almacenamos
cad = new char[strlen(Cad.cad)+1];
// Reserva memoria para cadena
strcpy(cad, Cad.cad); // Almacena la cadena
}
cadena::~cadena() {
delete[] cad; // Libera la memoria reservada a cad
}
void cadena::Asignar(char *dest) {
// Eliminamos la cadena actual:
delete[] cad;
// Reservamos memoria para la nueva y la almacenamos
cad = new char[strlen(dest)+1];
// Reserva memoria para la cadena
strcpy(cad, dest); // Almacena la cadena
}
char *cadena::Leer(char *c) {
strcpy(c, cad);
return c;
}
int main() {
cadena Cadena1("Cadena de prueba");
cadena Cadena2(Cadena1); // Cadena2 es copia de Cadena1
cadena *Cadena3; // Cadena3 es un puntero
char c[256];
// Modificamos Cadena1:
Cadena1.Asignar("Otra cadena diferente");
// Creamos Cadena3:
Cadena3 = new cadena("Cadena de prueba nº 3");
// Ver resultados
cout << "Cadena 1: " << Cadena1.Leer(c) << endl;
cout << "Cadena 2: " << Cadena2.Leer(c) << endl;
cout << "Cadena 3: " << Cadena3->Leer(c) << endl;
delete Cadena3; // Destruir Cadena3.
// Cadena1 y Cadena2 se destruyen automáticamente
cin.get();
return 0;
}
Vamos a hacer varias observaciones sobre este programa:
-
Hemos implementado un constructor copia. Esto es necesario porque una simple asignación entre los datos miembro "cad" no copiaría la cadena de un objeto a otro, sino únicamente los punteros.
Por ejemplo, si definimos el constructor copia como:
cadena::cadena(const cadena &Cad) { cad = Cad.cad; }
En lugar de cómo lo hacemos, lo que estaríamos copiando sería el valor del puntero cad, con lo cual, ambos punteros estarían apuntando a la misma posición de memoria. Esto es desastroso, y no simplemente porque los cambios en una cadena afectan a las dos, sino porque al abandonar el programa se intenta liberar automáticamente la misma memoria dos veces. Lo que realmente pretendemos al asignar cadenas es crear una nueva cadena que sea copia de la cadena antigua. Esto es lo que hacemos con el constructor copia, y es lo que haremos más adelante, y con más elegancia, sobrecargando el operador de asignación.
La definición del constructor copia que hemos creado en este último ejemplo es la equivalente a la del constructor copia por defecto.
-
La función Leer, que usamos para obtener el valor de la cadena almacenada, no devuelve un puntero a la cadena, sino una copia de la cadena. Esto está de acuerdo con las recomendaciones sobre la programación orientada a objetos, que aconsejan que los datos almacenados en una clase no sean accesibles directamente desde fuera de ella, sino únicamente a través de las funciones creadas al efecto. Además, el miembro cad es privado, y por lo tanto debe ser inaccesible desde fuera de la clase. Más adelante veremos cómo se puede conseguir mantener la seguridad sin crear más datos miembro.
-
La Cadena3 debe ser destruida implícitamente usando el operador delete, que a su vez invoca al destructor de la clase. Esto es así porque se trata de un puntero, y la memoria que se usa en el objeto al que apunta no se libera automáticamente al destruirse el puntero Cadena3.
11. Miembros estáticos de una clase (Static)
Ciertos miembros de una clase pueden ser declarados como static. Los miembros static tienen algunas propiedades especiales.
En el caso de los datos miembro static sólo existirá una copia
que compartirán todos los objetos de la misma clase.
Es decir todos los objetos COMPARTEN PROPIEDADES!!.
Si consultamos el valor de ese dato desde cualquier objeto de esa clase obtendremos siempre el mismo resultado, y si lo modificamos, lo modificaremos para todos los objetos.
Observar que es necesario declarar e inicializar los miembros static de
la clase, esto es por dos motivos. El primero es que los miembros static
deben existir aunque no exista ningún objeto de la clase, declarar la
clase no crea los datos miembro estáticos,
es necesario
hacerlo explícitamente. El segundo es porque si no lo hiciéramos,
al declarar objetos de esa clase los valores de los miembros estáticos
estarían indefinidos, y los resultados no serían los esperados.
Por ejemplo:
#include <iostream>
using namespace std;
class showstatic{
public:
static int nStatic; // Valor de nStatic es el mismo entre cada llamado de != objetos
void mostrar(void){cout << "nStatic = " << nStatic << endl;}
void cargar(int y){nStatic=y;}
};
int showstatic::nStatic=1; //IMPORTANTE DEBO inicializar static en la clase!!
int main() {
int x;
showstatic Objeto1, Objeto2;
cout<<"Ingrese un valor para la variable static de Objeto1:" <<endl;
cin>>x;
Objeto1.cargar( x );
cout<<"Mostramos la varaible en Objeto2 que no fue inicializado :" <<endl;
Objeto2.mostrar();
cout<<" Por tal motivo vemos que la variable Static <<endl;
cout<<" es comun para los dos Objetos"<<endl;
}
Otro ejemplo:
DEBO inicializar static en la clase!!
#include <iostream> using namespace std; class Numero { public: Numero(int v = 0); ~Numero(); void Modifica(int v); int LeeValor() const { return Valor; } int LeeCuenta() const { return Cuenta; } int LeeMedia() const { return Media; } private: int Valor; static int Cuenta; static int Suma; static int Media; void CalculaMedia(); }; Numero::Numero(int v) : Valor(v) { Cuenta++; Suma += Valor; CalculaMedia(); } Numero::~Numero() { Cuenta--; Suma -= Valor; CalculaMedia(); } void Numero::Modifica(int v) { Suma -= Valor; Valor = v; Suma += Valor; CalculaMedia(); } // Definición e inicialización de miembros estáticos int Numero::Cuenta = 0; //IMPORTANTE debo inicializar static
!! int Numero::Suma = 0; //IMPORTANTE debo inicializar static!! int Numero::Media = 0; //IMPORTANTE debo inicializar static!! void Numero::CalculaMedia() { if(Cuenta > 0) Media = Suma/Cuenta; else Media = 0; } int main() { Numero A(6), B(3), C(9), D(18), E(3); Numero *X; cout << "INICIAL" << endl; cout << "Cuenta: " << A.LeeCuenta() << endl; cout << "Media: " << A.LeeMedia() << endl; B.Modifica(11); cout << "Modificamos B=11" << endl; cout << "Cuenta: " << B.LeeCuenta() << endl; cout << "Media: " << B.LeeMedia() << endl; X = new Numero(548); cout << "Nuevo elemento dinámico de valor 548" << endl; cout << "Cuenta: " << X->LeeCuenta() << endl; cout << "Media: " << X->LeeMedia() << endl; delete X; cout << "Borramos el elemento dinámico" << endl; cout << "Cuenta: " << D.LeeCuenta() << endl; cout << "Media: " << D.LeeMedia() << endl; cin.get(); return 0; }
12. in-line vs out-line
La función en línea es una de las características importantes de C ++.
class A
{
void member(){}
};
Entonces, primero entendamos por qué se utilizan las funciones en línea y cuál es el propósito de la función en línea.
Cuando el programa ejecuta la instrucción de llamada de función, la CPU almacena la dirección de memoria de la instrucción que sigue a la llamada de función, copia los argumentos de la función en la pila y finalmente transfiere el control a la función especificada. Luego, la CPU ejecuta el código de función, almacena el valor de retorno de la función en una ubicación / registro de memoria predefinido y devuelve el control a la función que llama. Esto puede convertirse en una sobrecarga si el tiempo de ejecución de la función es menor que el tiempo de conmutación de la función llamante a la función llamada (destinatario). Para funciones que son grandes y / o realizan tareas complejas, la sobrecarga de la llamada a la función suele ser insignificante en comparación con la cantidad de tiempo que la función tarda en ejecutarse.
Sin embargo, para las funciones pequeñas de uso común, el tiempo necesario para realizar la llamada a la función suele ser mucho mayor que el tiempo necesario para ejecutar realmente el código de la función. Esta sobrecarga se produce para funciones pequeñas porque el tiempo de ejecución de la función pequeña es menor que el tiempo de conmutación.
C ++ proporciona funciones en línea para reducir la sobrecarga de llamadas a funciones.
La función en línea es una función que se expande en línea cuando se llama. Cuando se llama a la función en línea, el código completo de la función en línea se inserta o sustituye en el punto de llamada de la función en línea. Esta sustitución la realiza el compilador de C ++ en tiempo de compilación. La función en línea puede aumentar la eficiencia si es pequeña.
Las funciones de out line se pueden crear durante las compilaciones optimizadas.
class B
{
void member();
};
// Implementation file (.cpp)
void B::member(){}
Representan código que no se ejecuta normalmente, específicamente código que no se ejecuta durante la ejecución de entrenamiento utilizado para generar la retroalimentación para la compilación optimizada final, este código no se inserta en la línea de llamada de la función, si no en otro lugar
13. Ejercicios Propuestos
Ejercicio 1:
Crear una Clase Televisor con propiedades privada de estado ( on/off) y
de Valores de Canal y Volumen. Crear métodos públicos para encender,
apagar , cambiar de canal y de volumen. Luego desde main crear un menú
que permita manejar un objeto tv14p.-
Ejercicio 2:
Crear una clase empleado que contenga como propiedades privadas: el
nombre, dirección y sueldo . Como métodos públicos poner y obtener para
cada uno de sus propiedades y LeerDatos() y VerDatos que leen datos del
teclado y lo visualizan en pantalla respectivamente.
Ejercicio 3:
Escribir una Clase Pila de Reales con funciones miembro para poner un
elemento y sacar un elemento de la pila. La pila debería ser del tipo
LIFO ( ultimo en Entrar es el primero en Salir). Como métodos publicos
se usarán poner que añade un elemento a la pila y sacar que borra un
elemento de la pila. Tomar como tamaño maximo de pila un valor de 10 con
#define.
Ejercicio 4:
Crear una clase llamada hora que tenga miembros separados de tipo int
para horas, minutos, segundos. Un constructor inicializará este dato a o
y otro lo inicializará a valores fijos ( ej : 12:00:00). Un método
deberá visualizar la hora en formato : hh:mm:ss . Otro método sumará dos
objetos de tipo hora pasados como argumentos. Desde main se crean dos
objetos inicializados y otro sin inicializar. Sumar los valores
inicializados y poner el resultado en el no inicializado. Por último
visualizar el valor resultante.
Ejercicio 5:
Cree una clase Rectangulo con los atributos longitud y ancho, cada uno con un valor predeterminado igual a 1.
Proporcione métodos (funciones miembro) que calculen el perímetro y el área del rectángulo.
Además proporcione métodos establecer y obtener (leer) para los
atributos longitud y ancho, que permitan establecer los valores y leer
los valores respectivamente. La función establecer debe verificar que
longitud y ancho contengan números de punto flotante mayores que 0.0 y
menores que 20.0.
Ejercicio 6:
Cree una clase llamada Complejo para realizar aritmética con números complejos. Escriba un programa para usar su clase.
(Recordar que los números complejos tienen la forma: parteReal + parteImaginaria*i
donde i es la raíz cuadrada de -1)
Utilice variables double para representar datos de tipo private de una
clase. Proporcione un constructor que permita inicializar un objeto de
esta clase cuando se declare. El constructor debe contener valores
predeterminados en caso de que no se proporcionen inicializadotes.
Proporcione funciones miembro tipo public para realizar lo siguiente:
a)Suma de dos números complejos (las partes reales se suman juntas y las imaginarias se suman juntas)
b)Resta de dos números Complejos (idem ant pero restando)
c)Impresión de números complejos de la forma (a, b) donde a es la parte real y b es la parte imaginaria.
Ejercicio 7:
Escribir un código que tenga una clase de nombre fecha, esta deberá
tener un constructor que inicialice una propiedad hoy con la fecha
actual y un par de métodos que permitan:
Si se pasan dos fechas en formato dd mm aa dd mm aa , devuelve la cantidad de días entre esas dos fechas.
Si se pasan una fecha en formato dd mm aa, dice la cantidad de años, meses días desde esa fecha a la de hoy.-
Desde main se presentará un menú que permita elegir alguna de las opciones.
A modo de aclaración , los años biciestros son aquellos que son divisibles por 400 o por 4 y no por 100.
Ejercicio 8:
Escribir una Clase Pila de Reales con funciones miembro para poner un
elemento y sacar un elemento de la pila. La pila debería ser del tipo
FIFO ( primero en Entrar es el primero en Salir). Como métodos publicos
se usarán poner que añade un elemento a la pila y sacar que borra un
elemento de la pila. Tomar como tamaño maximo de pila de manera dinámica
en tiempo de ejecución.
Ejercicio 9:
Escribir una Clase que permita cargar y crear una lista enlazada simple.
Buscar que el /los puntero/s y la o las variables ( estructuras , etc)
sea privados.-
Ejercicio 10:
Escribir una Clase que permita cargar y crear una lista enlazada doble.
Buscar que el /los puntero/s y la o las variables ( estructuras , etc)
sea privados.-