Clases

Sitio: Facultad de Ingeniería U.Na.M.
Curso: Computación ET-344
Libro: Clases
Imprimido por: Invitado
Día: miércoles, 3 de julio de 2024, 06:35

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 Apagar
	bool 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

Inicializaciones de Objetos.
Los constructores son funciones miembro especiales que sirven para inicializar un objeto de una determinada clase al mismo tiempo que se declara, vimos para el ejemplo del objeto tv14p que el volumen y el canal tenían basura al comenzar a utilizar el objeto.
Es por eso que se recomienda que el constructor CONFIGURE el valor de las propiedades al momento de crear el objeto, de manera de evitar  indefiniciones de la misma.
En caso de NO ser necesario asignar valores a propiedades privadas al momento de crear el objeto, por que NO existan propiedades privadas o por que la lógica del problema no lo necesite NO es necesario definir un  Constructor, el compilador lo hace solo y de manera automática y NO aparece ningún error por falta del mismo.
Los constructores no se pueden llamar explícitamente como si fueran funciones miembro regulares. Solo se ejecutan una vez, cuando se crea un nuevo objeto de esa clase.
Sería algo parecido a poner:
int a=20; /* defino el tipo e inicializo */

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,
No tendría ningún sentido declarar un constructor como privado, ya que siempre se usan desde el exterior de la clase al crear el objeto , tampoco como protegido, ya que no puede ser heredado, esto se verá mas adelante
Añadamos un constructor a nuestra clase pareja:

  • # 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;
    
    }
    
Si una clase posee constructor, será llamado siempre que se declare un objeto de esa clase, y si requiere argumentos, es obligatorio suministrarlos.
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.
Tambíen se podría incorporar OTRO constructor, definiendo valores para las propiedades del objeto, por ejemplo:

    •  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.
En el primer caso , se podría instanciar el objeto :

Televisor tv14p(true,10,5); //Instancio el objeto tv14p con la clase Televisor

Por ejemplo, las siguientes declaraciones son ilegales:

Televisor tv14p(true,10);
Televisor tv14p();

La primer  declaración esta mal por que los constructores  para nuestro caso reciben cero argumento o tres argumentos. Por supuesto los tipos de los argumentos pasados al constructor deben respetar el tipo de datos.
La segunda es ilegal por otro motivo más complejo. Aunque existiese un constructor sin parámetros, no se debe usar esta forma para declarar el objeto, ya que el compilador lo considera como la declaración de un prototipo de una funciónque devuelve un objeto de tipo "Televisor" y no admite parámetros. Cuando se use un constructor sin parámetros para declarar un objeto no se deben escribir los paréntesis.
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

Observaciones:

Se pueden definir los Constructores de 2 maneras distintas, para el caso de la clase "Televisor":

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:

Podemos sustituir el constructor por:

Televisor(bool on_off, int canal, int volumen):m_bEncedido(on_off),m_uiCanal(canal),m_uiVolumen(volumen){}//Constructor con 3 argumentos.
Que es un anuncio - Gestion.Org
Conclusiones:
  • 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

Como cualquier otra función, un constructor también se puede sobrecargar con diferentes versiones tomando diferentes parámetros: con un número diferente de parámetros y / o parámetros de diferentes tipos.
El compilador llamará automáticamente a aquel cuyos parámetros coincidan con los argumentos:

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
Ejemplo de Sobrecarga de Funciones.


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 !

Mecanografía para niños. Programas y juegos gratis para aprender. 

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”

El puntero this se usa dentro de una clase y hace referencia al propio objeto. Lo normal es no utilizar este puntero salvo casos muy particulares como ser:
  • 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:

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

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