5.- OBJETOS Y CLASES

5.1 Introducción

Los tipos definidos por el usuario o tipos abstractos de datos (TAD) empaquetan elementos dato con las operaciones que se realizan sobre esos datos. C++ soporta los TAD con el tipo clase que puede ser implementado con estructuras, uniones y clases.

5.2 Abstracción de datos

Una característica importante de cualquier lenguaje de programación es la capacidad de crear tipos de datos definidos por el usuario. Aunque se pueden crear en C sus propios tipos, utilizando las palabras reservadas typedef y struct, los tipos resultantes no se pueden integrar fácilmente en el resto del programa. Además, en C, sólo se pueden definir los tipos en términos de datos; es decir, las funciones utilizadas para manipular esos tipos no son parte de la definición del tipo.
Una definición de un tipo que incluye datos y funciones y el modo para encapsular los detalles, se conoce como tipo abstracto de dato. En C++ se implementa mediante el uso de tipos de datos definidos por el usuario, llamados clases.                         
clase = datos + funciones
Una diferencia importante entre C y C++, es que en C++ se pueden declarar funciones dentro de una estructura (no se requiere declarar punteros a funciones). Las estructuras pueden tener también especificadas regiones de acceso (medios en que se puede controlar el acceso a los datos).
La abstracción de datos en C++ se obtiene con los tipos de datos estructura (struct) y clase (class).

 5.3 Concepto de clase

 Una clase es un tipo de dato que contiene uno o más elementos dato llamados miembros dato, y cero, una o más funciones que manipulan esos datos (llamadas funciones miembro). Una clase se puede definir con struct, union o class. La sintaxis de una clase es:

class nombre_clase

{

  miembro1;

  miembro2;

  ...

  funcion_miembro1();

  funcion_miembro2();

  ...

};

Una clase es sintácticamente igual a una estructura, con la única diferencia de que en el tipo class todos los miembros son por defecto privados mientras que en el tipo struct son por defecto públicos.
En C se utiliza el término variable estructura para referirse a una variable de tipo estructura. En C++ no se utiliza el término variable de clase, sino instancia de la clase.
El término objeto es muy importante y no es más que una variable, que a su vez no es más que una instancia de una clase. Por consiguiente una clase es:

class cliente

{

  char nom[20];

  char num;

};

 y un objeto de esta clase se declara:     cliente cli; 

Una definición de una clase consta de dos partes: una declaración y una implementación. La declaración lista los miembros de la clase. La implementación o cuerpo define las funciones de la clase.
Una de las características fundamentales de una clase es ocultar tanta información como sea posible. Por consiguiente, es necesario imponer ciertas restricciones en el modo en que se puede manipular una clase y de cómo se pueden utilizar los datos y el código dentro de una clase.
Una clase puede contener partes públicas y partes privadas. Por defecto, todos los miembros definidos en la clase son privados. Para hacer las partes de una clase públicas (esto es, accesibles desde cualquier parte del programa) deben declararse después de la palabra reservada public. Todas las variables o funciones definidas después de public son accesibles a las restantes funciones del programa. Dado que una característica clave de la POO es la ocultación de datos, debe tenerse presente que aunque se pueden tener variables públicas, desde un punto de vista conceptual se debe tratar de limitar o eliminar su uso. En su lugar, deben hacerse todos los datos privados y controlar el acceso a ellos a través de funciones públicas.

class artiiculo

{

  private:

float precio;

char nombre[];

  public:

void indicar();

};

Por defecto u omisión todo lo declarado dentro de una clase es privado y sólo se puede acceder a ello con las funciones miembro declaradas en el interior de la clase o con funciones amigas
Los miembros que se declaran en la sección protegida de una clase sólo pueden ser accedidos por funciones miembro declaradas dentro de la clase, por funciones amigas o por funciones miembro de clases derivadas.
A los miembros que se declaran en la región pública de una clase se puede acceder a través de cualquier objeto de la clase de igual modo que se accede a los miembros de una estructura en C.

class alfa

{

  int x;          //miembros dato privados

  float y;

  char z;

public:

  double k;              //miembro dato público

  void fijar(int,float,char);     //funciones miembro públicas

  void visualizar();

};

void main()

{

  alba obj;      //declaración de un objeto

  obj.fijar(3,2.1,'a');    //invocar a una función miembro

  obj.visualizar();               //invocar a una función miembro

  obj.x=4;      //error: no se puede acceder a datos privados

  obj.k=3.2;    //válido: k está en la región pública

}

La definición de funciones miembro es muy similar a la definición ordinaria de función. Tienen una cabecera y un cuerpo y pueden tener tipos y argumentos. Sin embargo, tienen dos características especiales:
a) Cuando se define una función miembro se utiliza el operador de resolución de ámbito (::) para identificar la clase a la que pertenece la función.
b) Las funciones miembro (métodos) de las clases pueden acceder a las componentes privadas de la clase.

Opción 1 Opción 2

class ejemplo

class ejemplo

{

{

  int x,y;

 int x,y;

public:

public:

  void f()

  void f();

  {

};

cout<<"x= "<<x<<" y= "<<y<<endl;

void ejemplo::f()

  }

{

};

cout<<"x= "<<x<<" y= "<<y<<endl;
}

En la primera opción la función está en línea (inline). Por cada llamada  a esta función, el compilador genera (vuelve a copiar) las diferentes instrucciones de la función. En la segunda opción (la más deseable) la función f se llamará con una llamada verdadera de función.
La declaración anterior significa que la función f es miembro de la clase ejemplo. El nombre de la clase a la cual está asociada la función miembro se añade como prefijo al nombre de la función. El operador :: separa el nombre de la clase del nombre de la función. Diferentes clases pueden tener funciones del mismo nombre y la sintaxis indica la clase asociada con la definición de la función.

5.4 Objetos

En C++ un objeto es un elemento declarado de un tipo clase. Se conoce también como una instancia de una clase.
Los objetos se pueden tratar como cualquier variable C. La principal diferencia es que se puede llamar a cualquiera de las funciones que pertenecen a un objeto, esto es, se puede enviar un mensaje a ella.

class rectangulo

{

  int base,altura;

public:

  void dimensiones(int,int);

  int area();

};

void rectangulo::dimensiones(int b,int h)

{

  base=b;

  altura=h;

}

int rectangulo::area()

{

  return base*altura;

}

void main()

{

  rectangulo r; //declarar el objeto

  r.dimensiones(3,5);  //definir el tamaño

  cout<<"area "<<r.area();

}

5.5 Acceso a los miembros de una clase

A los miembros de una clase se accede de igual forma que a los miembros de una estructura. Existen dos métodos para acceder a un miembro de una clase: el operador punto (.) y el operador flecha (->) que actúan de modo similar.

class contador

{

  public:leer()  {return 1;}

};

void main()

{

  contador c;

  contador *p=new(contador);

  leer();                  //error: función desconocida, no en ámbito

  cout<<c.leer();       //correcto

  cout<<p->leer();     //correcto

}

5.6 Clases vacías

Aunque el propósito de una clase es encapsular código y datos, una clase puede tener también una declaración vacía.             class vacia{};
Naturalmente, no se pueden realizar grandes cosas con esta clase, pero se pueden crear objetos de ella:        vacia obj;
Con frecuencia, en el desarrollo de un proyecto grande se necesitan comprobar implementaciones de primeras versiones en las que algunas clases no están totalmente identificadas o implementadas todavía. Se suelen denominar resguardos (stubs) y se diseñan para obtener códigos que se compilen sin errores, permitiendo comprobar alguna parte de ellos.

5.7 Clases anidadas

La potencia de abstracción de una clase se puede incrementar incluyendo otras declaraciones de clase. Una clase declarada en el interior de otra se denomina clase anidada, y se puede considerar como una clase miembro.
El identificador de una clase anidada está sujeto a las mismas reglas de acceso que los restantes miembros. Si una clase anidada se declara en la sección private de la clase circundante, la clase anidada será utilizable sólo por los miembros datos de la clase que la circunde. La clase que encierra puede acceder al nombre de la clase anidada sin resolución de ámbito. Si un nombre de una clase anidada es accesible a una clase o función que no la circunda, se debe aplicar el operador ::.

class externa

{

public:

  class interna

  {

  public:

int x;

  };

};

void main()

{

  externa::interna valor;

  int v=valor.x;

}

5.8 Los miembros dato

La lista de miembros de una clase puede comprender cualquier tipo válido en C++. Puede contener tipos primarios, estructuras e incluso punteros a cualquier tipo válido. Los miembros dato pueden ser incluso clases. En cualquier caso sólo las instancias de clases definidas o declaradas previamente pueden ser miembros.
Los miembros dato declarados en la clase se deben considerar equivalentes a campos de una estructura, no a variables. Tal como las estructuras, se debe declarar un objeto de un tipo clase y a continuación se inicializan sus miembros dato.
Un miembro de una clase se puede declarar estático (static). Para un miembro dato, la designación static significa que existe sólo una instancia de ese miembro. Un miembro dato estático es compartido por todos los objetos de una clase.
A un miembro dato static se le asigna una zona fija de almacenamiento en tiempo de compilación, al igual que una variable global, pero el identificador de la variable está dentro de ámbito utilizando solamente el operador :: con el nombre de la clase.
Los miembros datos se asignan generalmente con la misma clase de almacenamiento. Para declarar o inicializar un miembro static se utiliza la misma notación que una variable global.

class ejemplo

{

public:

  static int valor;       //declarar miembro estático

};

int ejemplo::valor;     //definir miembro estático

void main()

{

  ejemplo e1,e2;

  e1.valor=1;

  e2.valor=10;

}

A los miembros dato static se puede acceder:
1. Utilizando el operador punto
2. Utilizando el operador flecha, si el lado izquierdo es un puntero a objeto
3. Utilizando el identificador de la clase sin referenciar un objeto real:    ejemplo::valor=3;

Los miembros datos static no siempre tienen que ser public.

class ejemplo

{

private:

  static int valor;       //declarar miembro estático

};

int ejemplo::valor=5; //definir miembro estático

void main()

{

  ejemplo e1;

  e1.valor=1;   //error: aceso no válido

}

Para acceder a un miembro dato private static se necesita utilizar el operador ::. Otros medios son:
1.- A través de una función miembro de la clase
2.- A través de una función declarada amiga de la clase

5.8 Ámbito de una clase

Una clase actúa como cualquier otro tipo de dato con respecto al ámbito. Todos los miembros de una clase se dice que están en el ámbito de esa clase; cualquier miembro de una clase puede referenciar a cualquier otro miembro de la misma clase.
Las funciones miembro de una clase tienen acceso no restringido a los miembros dato de esa clase. El acceso a los miembros dato y funciones de una clase fuera del ámbito de la clase está controlado por el programador. La idea es encapsular la estructura de datos y funcionalidad de una clase, de modo que el acceso a la estructura de datos de la clase desde fuera de las funciones miembro de la clase, sea limitada o innecesaria.
El nombre de la clase tiene que ser único dentro de su ámbito.

5.9 Especificadores de acceso a los miembros de una clase

En una definición de clase, un especificador de acceso se utiliza para controlar la visibilidad de los miembros de una clase fuera del ámbito de la clase.
Los miembros de una clase pueden ser públicos, privados o protegidos. Las palabras reservadas public, private y protected se utilizan para controlar el modo de acceso a la clase.
Dentro de una declaración de clase, cada una de estas palabras se puede utilizar para preceder a una o más declaraciones de los miembros de una clase
·       Acceso protegido. Los miembros protegidos significan que sólo se puede acceder a ellos por funciones miembro dentro de la misma clase y por funciones miembro de clases derivadas de esta clase.
·       Acceso público. Los miembros públicos son accesibles por cualquier parte del programa.
·       Acceso privado. Los miembros privados sólo pueden ser utilizados por las funciones miembro de la clase y las funciones amigas de la clase.

5.10 Funciones miembro

Las funciones miembro son miembros de una clase y son funciones diseñadas para implementar las operaciones permitidas sobre los tipos de datos de una clase. Para declarar una función miembro hay que situar su prototipo en el cuerpo de la clase. No hay que definir la función dentro de la clase; dicha definición puede estar fuera de la clase e incluso en un archivo independiente, aunque también pueden ser definidas en línea dentro de la clase.
Las funciones miembro son necesarias para acceder a los datos privados. En general, son públicas; si no lo fueran, ninguna otra función podría llamarlas. Se pueden declarar para devolver valores con tipos incluyendo objetos de clases, punteros o referencias. Pueden ser declaradas también para aceptar cualquier número y tipo de argumentos. Los argumentos por defecto están permitidos, así como la notación de puntos suspensivos.

5.11 El puntero this

Nunca se puede llamar a una función miembro privada de una clase a menos que se asocie con un objeto. En C cada vez que se utiliza un puntero para acceder a los miembros de una estructura, debe utilizarse el operador de puntero (->) para acceder a los datos. ¿Cómo sabe una función miembro cuál es la instancia de una clase asociada con ella?
El método utilizado por C++ es añadir un argumento extra oculto a las funciones miembro. Este argumento es un puntero al objeto de la clase que lo enlaza con la función asociada y recibe un nombre especial denominado this.
Dentro de una función miembro, this apunta al objeto asociado con la invocación de la función miembro. El tipo de this en una función miembro de una clase T es 
T *const   es decir, un puntero constante a un objeto de tipo T.
Normalmente, el programador no necesita preocuparse por este puntero, ya que C++ realiza la operación automáticamente, haciendo el uso de this transparente a las funciones miembro que la utilizan. Dentro de una función miembro, se pueden hacer referencias a los miembros del objeto asociado a ella con el prefijo this y el operador de acceso ->. Sin embargo, este proceso no es necesario, ya que es redundante. Consideremos como ejemplo el constructor de una clase que manipula números complejos:
 

complejo::complejo (float a,float b)

{

real=a;

imag=b;

}

Este constructor se puede escribir:

complejo::complejo (float a,float b)

{

this->real=a;

this->imag=b;

}

this->nombre_miembro : apunta a un miembro

*this : es el objeto total. Es un valor constante

this : es la dirección del objeto apuntado

5.12 Funciones miembro estáticas

Las funciones miembro static sólo pueden acceder a otras funciones y datos declarados en una clase, pero no pueden manipular funciones ni datos no estáticos. La razón de esta característica es que una función miembro static no tiene asignado un puntero this y por ello no puede acceder a miembros dato de la clase a menos que se pase explícitamente este puntero this.

5.13 Constructores

Un constructor es una función especial que sirve para construir o inicializar objetos. En C++ la inicialización de objetos no se puede realizar en el momento en que son declarados; sin embargo, tiene una característica muy importante y es disponer de una función llamada constructor que permite inicializar objetos en el momento en que se crean.
Un constructor es una función que sirve para construir un nuevo objeto y asignar valores a sus miembros dato. Se caracteriza por:

- Tener el mismo nombre de la clase que inicializa
- Puede definirse inline o fuera de la declaración de la clase
- No devuelve valores
- Puede admitir parámetros como cualquier otra función
- Puede existir más de un constructor, e incluso no existir

Si no se define ningún constructor de una clase, el compilador generará un constructor por defecto. El constructor por defecto no tiene argumentos y simplemente sitúa ceros en cada byte de las variables instancia de un objeto. Si se definen constructores para una clase, el constructor por defecto no se genera.
Un constructor del objeto se llama cuando se crea el objeto implícitamente: nunca se llama explícitamente a las funciones constructoras. Esto significa que se llama cuando se ejecuta la declaración del objeto. También, para objetos locales, el constructor se llama cada vez que la declaración del objeto se encuentra. En objetos globales, el constructor se llama cuando se arranca el programa.
El constructor por defecto es un constructor que no acepta argumentos. Se llama cuando se define una instancia pero no se especifica un valor inicial.
Se pueden declarar en una clase constructores múltiples, mientras tomen parte diferentes tipos o número de argumentos. El compilador es entonces capaz de determinar automáticamente a qué constructor llamar en cada caso, examinando los argumentos.
Los argumentos por defecto se pueden especificar en la declaración del constructor. Los miembros dato se inicializarán a esos valores por defecto, si ningún otro se especifica.
C++ ofrece un mecanismo alternativo para pasar valores de parámetros a miembros dato. Este mecanismo consiste en inicializar miembros dato con parámetros.

class prueba

{

  tipo1 d1;

  tipo2 d2;

  tipo3 d3;

public:

  prueba(tipo1 p1, tipo2 p2, tipo3 p3):d1(p1),d2(p2),d3(p3)

  {  }

};

Un constructor que crea un nuevo objeto a partir de uno existente se llama constructor copiador o de copias. El constructor de copias tiene sólo un argumento: una referencia constante a un objeto de la misma clase. Un constructor copiador de una clase complejo es:

complejo::complejo(const complejo &fuente)

{

  real=fuente.real;

  imag=fuente.imag;

}

Si no se incluye un constructor de copia, el compilador creará un constructor de copia por defecto. Este sistema funciona de un modo perfectamente satisfactorio en la mayoría de los casos, aunque en ocasiones puede producir dificultades. El constructor de copia por defecto inicializa cada elemento de datos del objeto a la izquierda del operador = al valor del elmento dato equivalente del objeto de la derecha del operador =. Cuando no hay punteros invicados, eso funciona bien. Sin embargo, cuando se utilizan punteros, el constructor de copia por defecto inicializará el valor de un elemento puntero de la izquierda del operador = al del elemento equivalente de la derecha del operador; es decir que los dos punteros apuntan en la misma dirección. Si ésta no es la situación que se desea, hay que escribir un constructor de copia.

5.14 Destructores

Un destructor es una función miembro con igual nombre que la clase, pero precedido por el carácter ~. Una clase sólo tiene una función destructor que, no tiene argumentos y no devuelve ningún tipo. Un destructor realiza la operación opuesta de un constructor, limpiando el almacenamiento asignado a los objetos cuando se crean. C++ permite sólo un destructor por clase. El compilador llama automáticamente a un destructor del objeto cuando el objeto sale fuera del ámbito. Si un destructor no se define en una clase, se creará por defecto un destructor que no hace nada.
Normalmente los destructores se declaran public.

5.15 Creación y supresión dinámica de objetos

Los operadores new y delete se pueden utilizar para crear y destruir objetos de una clase, así como dentro de funciones constructoras y destructoras.
Un objetro de una determinada clase se crea cuando la ejecución del programa entra en el ámbito en que está definida y se destruye cuando se llega al final del ámbito. Esto es válido tanto para objetos globales como locales, ya que los objetos globales se crean al comenzar el programa y se destruyen al salir de él. Sin embargo, se puede crear un objeto también mediante el operador new, que asigna la memoria necesaria para alojar el objeto y devuelve su dirección, en forma de puntero, al objeto en cuestión.

Los constructores normalmente implican la aplicación de new.

p =new int(9)  //p es un puntero a int inicializado a 9

cadena cad1("hola");

cadena *cad2=new cadena;

Un objeto creado con new no tiene ámbito, es decir, no se destruye automáticamente al salir fuera del ámbito, sino que existe hasta que se destruye explícitamente mediante el operador delete.

class cadena

{

  char *datos;

public:

  cadena(int);

  ~cadena();

};

cadena::cadena(int lon)

{

  datos=new char[lon];

}

cadena::~cadena()

{

  delete datos;

}

5.16 Funciones amigas

Una función amiga es una función no miembro de una clase que puede tener acceso a las partes privadas de una clase; se debe declarar como amiga de la clase mediante la palabra reservada friend.
Las funciones amigas se declaran situando su prototipo de función en la clase de la que son amiga precediéndola con la palabra reservada friend. Por ejemplo:

class cosa

{

  int datos;

 public:

  friend void cargar (cosa& t, int x);

};

void cargar(cosa& t, int x)

{

  t.datos=x;

}

Como la función cargar se declara amiga de la clase cosa puede acceder al miembro privado datos.
Las razones fundamentales para utilizar funciones amigas es que algunas funciones necesitan acceso privilegiado a más de una clase. Una segunda razón es que las funciones amigas pasan todos sus argumentos a través de la lista de argumentos y cada valor de argumento se somete a la conversión de asignación.

5.17 Clases amigas

No sólo puede ser una función, amiga de una clase, también una clase completa puede ser amiga de otra clase. En este caso todas las funciones de la clase amiga pueden acceder a las partes privadas de la otra clase.
Una clase amiga puede ser declarada antes de que pueda ser designada como amiga.

class animales;

class hombre

{

public:

friend class animales;

};

class animales

{//..

};

Cada función miembro de animales es amiga de la clase hombre. La designación de un amigo puede estar en una sección private o public de una clase.

dAnterior

Indice C++

Siguiente a