Disponible la nueva versión "donationware" 7.3 de OrganiZATOR
Descubre un nuevo concepto en el manejo de la información.
La mejor ayuda para sobrevivir en la moderna jungla de datos la tienes aquí.

Curso C++

[Home]  [Inicio]  [Índice]


4.2.1f   Punteros a clases

§1 Sinopsis

Como preámbulo al tema, no estarían de más dos pequeñas puntualizaciones: en primer lugar, recordar que para esta, como para muchas otras cuestiones relacionadas con las clases, es importante pensar en ellas como tipos de objetos. Tipos nuevos, definidos por el usuario, pero que en lo demás no se distinguen gran cosa de otros tipos preconstruidos en el lenguaje. Por ejemplo de la clase de los enteros int.

En segundo lugar, advertir que el objeto x de una clase X, tiene existencia en memoria en forma de una estructura que contiene los miembros no estáticos de la clase. La dirección del objeto es la del primer miembro de esta estructura, y las direcciones del resto de miembros se pueden calcular considerando desplazamientos respecto a esta dirección inicial [1]. Esta circunstancia es fundamental para entender el funcionamiento de los punteros-a-clase que tratamos aquí, y de los punteros-a-miembro, que tratamos en el próximo capítulo ( 4.2.1g). Dicho esto, añadir que los punteros a clases no se diferencian de los que hemos considerado genéricamente como punteros a objetos ( 4.2.1), aunque presentan algunas peculiaridades que conviene conocer [2].

§2 Declaración

Del mismo modo que la forma genérica de puntero a la clase int es tipo int*, y podemos declarar un puntero a cualquier elemento de dicho tipo como:

int* ptri;   // ptri es puntero genérico a tipo int


la forma genérica de puntero a la clase C es C*, y consecuentemente, podemos declarar un puntero a cualquier objeto de la case como:

C* ptrc;     // ptrc es puntero genérico a tipo C

más tarde, en uno y otro caso podemos iniciar dichos punteros a instancias concretas. Por ejemplo:

int x = 5;   // iniciar un objeto x de la clase int
ptri = &x;   // asociar ptri al objeto x
C c;         // iniciar un objeto c de la clase C
ptrc = &c;   // asociar ptrc al objeto c


Observe que en el caso de los enteros no es válida la expresión:

ptri = ∫   // Error

ya que int no es un objeto concreto de la clase. Por la misma razón tampoco es válida la sentencia:

ptrc = &C;     // Error


En multitud de ocasiones los "objetos" normales (variables y constantes) se manipulan y referencian a través de sus punteros (esta es justamente la razón de su existencia). La sintaxis, que se ha señalado anteriormente, utiliza el operador de indirección ( 4.9.11a) en la forma:

*ptri = 10;      // Asignar el valor 10 a la variable x
int y = *ptri;   // y == x


Del mismo modo, las instancias de clases pueden referenciarse a través de sus punteros. Para ilustrar su uso podríamos utilizar una aproximación paralela al caso anterior:

class C {
  public: int x;
}
...
C c, d;           // objetos c y d, instancias de la clase C
C* ptrc = &c;     // define ptrc puntero-a-tipoC señalando al objeto c
C* ptrd = new C;  // L.7: Ok. el operador new devuelve un puntero
(*ptrc).x = 10;   // L.8: Ok. c.x == 10
d.x = (*ptrc).x;  // L.9: Ok. d.x == c.x


Observe que en L.7 instanciamos un objeto de la clase C y lo referenciamos mediante un puntero; al contrario que sus hermanos, c y d, este objeto no tiene etiqueta (nombre); en adelante deberá ser accedido mediante el puntero ptrc (más aclaraciones al respecto en operador new en 4.9.20).

Aunque válida, la sintaxis utilizada en L.8 y L.9 no es la más elegante, ya que existe un operador específico para referirse a miembros de objetos a través de punteros a tales objetos: el selector indirecto -> ( 4.9.16). La notación canónica de las dos últimas líneas sería ( 4.11.2e):

ptrc->x = 10;    // L.8bis: Ok. c.x == 10
d.x = ptrc->x;   // L.9bis: Ok. d.x == c.x

Como puede verse, ambas expresiones son equivalentes:

ptrc->x  =  (*ptrc).x

§3 Ejemplo:

#include <iostream.h>
class C {
  public: int x;
  int* p;
  char* c;
  void fun () { cout << "Valor miembro x == " << x << endl; }
  C () {               // L.7: constructor por defecto
    x = 13;
    p = &x;
    c = "AEIOU";
  }
};
void f1 (C* cpt);      // L.13: prototipo de función normal (no-miembro)

int main (void) {      // ========================
  C c1;                // instancia de C
  C* cptr;             // puntero a clase
  cptr = &c1;          // asignado al objeto c1

  cout << "1 c1.x == " << c1.x << endl;   // Salida-1
  cout << "2 c1.p == " << *c1.p << endl;  // Salida-2
  cout << "3 c1.c == " << c1.c << endl;   // Salida-3
  c1.fun();                                // Salida-4
  f1(cptr);     // L.24: El puntero se utiliza como argumento de f1
}

void f1 (C* cp) {     // definición de función
  cout << "4 c1.x == " << (*cp).x << endl;
  cout << "5 c1.x == " << cp->x    << endl;
  cout << "6 c1.p == " << *(*cp).p << endl;
  cout << "7 c1.p == " << *cp->p  << endl;
  cout << "8 c1.c == " << (*cp).c << endl;
  cout << "9 c1.c == " << cp->c    << endl;
  (*cp).fun();
  cp->fun();
}

Salida:

1 c1.x == 13
2 c1.p == 13
3 c1.c == AEIOU
Valor miembro x == 13
4 c1.x == 13
5 c1.x == 13
6 c1.p == 13
7 c1.p == 13
8 c1.c == AEIOU
9 c1.c == AEIOU
Valor miembro x == 13
Valor miembro x == 13

Comentario:

La clase del ejemplo tiene cuatro miembros públicos: entero; puntero-a-int; puntero-a-char y una función. También se ha incluido un constructor por defecto que inicializa adecuadamente los objetos creados. Además de la clase, en el espacio global se ha definido una función f1, que acepta como argumento un puntero a la clase. En la función main hemos utilizado un puntero genérico a clase, cptr, que es asignado a una instancia concreta, c1.

Las cuatro primeras salidas se deben a sentencias en la función main, y corresponden a invocaciones mediante el selector directo a miembro ( 4.9.16); en las tres primeras los miembros son propiedades (variables). En la cuarta el miembro es un método (función) del objeto c1, que es invocado.

A continuación, en L.24 se utiliza el puntero cptr como argumento de la función f1. Ya dentro de dicha función, se invocan las propiedades y métodos del objeto c1 (señalado por el puntero), utilizando los dos formatos posibles. Observe atentamente la notación utilizada en las sentencias del cuerpo de f1 para obtener los valores señalados por los punteros; todas ellas son muy ilustrativas.

§4 Punteros en jerarquías de clases

Cuando se definen punteros a clases-base, de las que derivan otras, se cumplen los siguientes principios:

  • Los objetos de las clases derivadas pueden tratarse como si fuesen objetos de sus clases-base cuando se manipulan mediante punteros y referencias.
  • Un puntero de una clase-base puede contener direcciones de objetos de cualquiera de sus clases derivadas.

Cuando se acceden a través de punteros objetos de clases que pertenecen a una jerarquía, es importante tener en cuenta las precauciones indicadas en "Consideraciones sobre punteros en jerarquías de clases" ( 4.11.2b1).

Nota: en determinadas circunstancias el compilador puede realizar una conversión automática de un puntero-a-clase-derivada a puntero-a-clase-base. Ver: Congruencia estándar de argumentos ( 4.4.1a)

Tema relacionado:
  • Punteros a clases implícitas ( 4.12.2).

  Inicio.


[1] En realidad, estas representaciones de los objetos en memoria pueden contener algunas otras cosas "ocultas" para el usuario. Por ejemplo, si pertenecen a una jerarquía de clases polimórficas, un puntero a la vtable ( E1.4.4).

[2] Observe el lector que coloquialmente nos referimos a ellos como "punteros a clases", aunque en realidad su sentido exacto es "punteros a objetos (instancias) de clases". Literalmente la expresión "puntero a clase-C" no tiene mucho sentido, sería como decir "puntero a int", es un concepto genérico que solo puede tener existencia real cuando señala a un objeto concreto, en este caso, a una instancia c del tipo C.