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.11.2e  Acceso a miembros

§1  Sinopsis:

El acceso a miembros de clases (propiedades y métodos), incluyendo el acceso a través de punteros, tiene una notación un tanto especial. En principio C++ dispone de tres operadores específicos de acceso que suelen ser causa de confusión en el principiante y precisamente uno de los "reproches" a C++ por parte de sus competidores (C#  por ejemplo). Son los siguientes:

::   Operador de resolución de ámbito o acceso a ámbito ( 4.9.19)

 .     Selector directo de miembro  ( 4.9.16)

->  Selector indirecto de miembro ( 4.9.16)


Aunque la explicación detallada de cada uno está en el apartado que se cita, intentaremos dar aquí una sinopsis conjunta de su utilización, reconociendo de antemano que en este punto tienen razón sus detractores, la utilización es confusa y para terminar de arreglarlo, con algunas excepciones.

§2  Acceso a miembros de clase

En principio, el operador de resolución de ámbito ::, se utiliza para acceso a miembros de subespacios y para miembros de clases, lo cual tiene su lógica, habida cuenta que las clases constituyen en sí mismas una especie de subespacios particulares ( 4.1.11; 4.1.11c1).

§2.1  Ejemplo:

#include <iostream.h>

namespace ALPHA {

  long double LD;       // declaración de una variable en el subespacio

  class C {             // definición de clase (subespacio en subespacio)

    public:

    int x;

    C(int = 0);         // prototipo de constructor por defecto

  };

}

ALPHA::C::C(int p1) {   // L.10 definición del constructor por defecto

  x = p1;

}


int main() {            // ===================

  ALPHA::LD = 1.0;      // M.1 Acceso a variable LD del subespacio

  cout << "Valor LD = " << ALPHA::LD << endl;
  ALPHA::C c1 = ALPHA::C(3);                nbsp; // M.3
  cout << "Valor c1.x: " << c1.x << endl;    // M.4
}

Salida:

Valor LD = 1
Valor c1.x: 3

Comentario:

Observe la notación de acceso al constructor de la clase (función-miembro) en L.10, así como en la sentencia de declaración del objeto c1 en M.3, incluyendo su invocación explícita al constructor de la clase.

Es interesante resaltar como, a pesar de lo que pudiera parecer, el objeto c1 pertenece al ámbito de la función main, no al subespacio ALPHA.  Esto es lo que permite que pueda utilizarse la sintaxis de M4. En caso contrario habría sido

cout << "Valor c1.x: " << ALPHA::c1.x << endl;    // M.4bis


§2.2  En realidad, la mayoría de las veces, este tipo de acceso a miembros de clase solo se utiliza para la definición de funciones miembro (como en L.10), ya que las propiedades no tienen existencia antes de la instanciación de objetos concretos, por lo que en este contexto, no tendría sentido una expresión como:

ALPHA::C::x = 10;


§2.3  Para acceder a la propiedad x es necesario referirse a una instancia concreta de la clase, aunque como se ve a continuación , en este caso ya no se utiliza el operador de resolución de ámbito :: sino otro. Sin embargo, en los casos en que estos miembros tienen existencia real fuera de las instancias concretas (como es el caso de los miembros estáticos 4.11.7), la notación anterior es perfectamente válida.  Por ejemplo:

#include <iostream>
using namespace std;

namespace ALPHA {
  class C {              // definición de clase (subespacio en subespacio)
    public:
    static int x;        // variable estática
    static void fun() {cout << "Soy una función estática" << endl; }
  };
  int C::x = 10;         // definición de la variable estática
}

int main() {             // ===================
  cout << "Variable ALPHA::C::x == " << ALPHA::C::x << endl; // L.13: Ok.
  ALPHA::C::fun();                                             // L.14: Ok.
}

Salida:

Variable ALPHA::C::x == 10
Soy una función estática

§3  Acceso a miembros de objetos

Hemos adelantado que el operador de acceso a ámbito :: no puede ser utilizado para acceder a miembros de objetos (instancias concretas de las clases), ya que los objetos no se consideran ámbitos. Por ejemplo:

#include <iostream.h>

namespace ALPHA {
  class C {

    public: int x;

  };

}

 

int main () {        // ==============
  ALPHA::C c;        // instancia un objeto de la clase
  c::x = 33;         // Error: c no es una clase o nombre de subespacio!!
  cout << "Valor c::x = " << c::x << endl;    // Error!!
}

En estos casos la sintaxis correcta implica utilizar el selector directo de miembro .  como se muestra en el siguiente ejemplo:

#include <iostream.h>

using namespace std;

namespace ALPHA {
  class C {

    char c;

    public:

    C(char ch = 'x') { c = ch; x = 0; }    // Constructor

    int x;

    char getC() { return c; }

  };
}

int main () {       // ===================

  ALPHA::C c1;      // instanciar objeto de la clase
  cout << "Valor x de c1: " << c1.x << endl;        // L.16:

  cout << "Valor c de c1: " << c1.getC() << endl;   // L.17:
}

Salida:

Valor x de c1: 0
Valor c de c1: x

Comentario

Observe como el selector directo permite acceder a cualquier tipo de miembro (público), tanto propiedades (L.16) como invocación de métodos (L.17).


§3.1  Si el acceso al objeto debe realizarse mediante un puntero, puede utilizarse el procedimiento general de utilizar el operador de indirección ( 4.9.11a) o bien el más específico selector indirecto anteriormente mencionado.

Ejemplo:

class C {

  char c;

  public:

  C(char ch = 'x') { c = ch; x = 0; }    // Constructor

  int x;

  char getC() { return c; }

};
...

C c1, d1;           // objetos c1 y d1, instancias de la clase C

C* ptrc = &c1;      // define ptrc puntero-a-C señalando a objeto c1

(*ptrc).x = 10;     // Acceso a miembro x de c1 mediante operador de indirección

d.x = (*ptrc).x;    // idem (notación desaconsejada para miembros)

ptrc->x = 10;       // Ok: Acceso a propiedad mediante selector indirecto

d1.x = ptrc->x;     // ídem (notación aconsejada para estos casos)

ptrc->getC();       // Ok: Acceso a método mediante selector indirecto

ptrc->C::getC();    // Ok: variación sintáctica de la anterior


§3.2  Ejemplo de acceso a miembros utilizando ambos selectores.

#include <iostream>
#include <typeinfo.h>

class X {             // clase raíz
  public: void func1() {std::cout << "func1" << std::endl; }
};
class Y : public X {  // Y deriva de X
  public: void func2() {std::cout << "func2" << std::endl; }
};

void main() {       // ==========================
  X mX;             // mX es instancia de X (clase raíz)
  mX.func1();       // llamada a func1 de mX

//primera forma de invocación (selector directo)
  Y mY;             // mY es instancia de Y (clase derivada)
   mY.func2();      // llamada a función miembro (privativa) de mY
   mY.func1();      // llamada a función miembro de mY (heredada)

//segunda forma de invocación, mediante puntero (selector indirecto)
  Y* ptY = & mY;    // puntero a mY
   ptY->func2();    // llamada a función miembro (privativa) de mY
   ptY->func1();    // llamada a función miembro de mY (heredada)
}

Salida:

func1
func2
func1
func2
func1

§5  Acceso a miembros de clases desde funciones miembro

Es interesante señalar que los miembros de clase, incluso privados, son accesibles desde el interior de las funciones miembro de dichas clases ( 4.1.3). La razón hay que buscarla en la forma en que está diseñado el mecanismo C++ de búsqueda de nombres ("Name-lookup 1.2.1).

Considere el siguiente ejemplo:

#include <iostream>

int x = 10;         // L.3:

class CL {
  int x, y;         // L.5: privados por defecto
  public:
  void setx(int i) { x = i; }  // L.7:
  void sety(int y) { CL::y = y; }
  int getxy();
};
int CL::getxy() { return x + y; }

int main() {        // ================
  CL c;
  c.setx(1);
  c.sety(2);
  std::cout << "X + Y == " << c.getxy() << std::endl;
  return 0;
}

Salida:

X + Y == 3

Comentario

Puede verse que las propiedades x e y (privadas), son directamente accesibles desde el interior de las funciones miembro setx, sety y getxy, sin que sea necesario ningún tipo de especificador de acceso; incluso a pesar de que esta última función se define fuera del cuerpo de la clase. La razón es que cualquier referencia a una variable en una función miembro, provoca que el compilador compruebe si existe tal variable en ese ámbito (el de la clase) antes que en el espacio exterior. De esta forma, no existe ambigüedad en la invocación a x dentro de la función setx de L.7. El compilador sabe que se refiere al miembro x definido en L.5 y no a la variable del ámbito global definida en L.3.

Observe que la presencia del especificador CL::y en el cuerpo de sety (L.8) se justifica porque la variable local y oculta al miembro del mismo nombre. Más adelante, al tratar del puntero this, se expone otra versión de la sintaxis utilizada en L.8 para distinguir entre la variable local y del miembro de igual nombre ( 4.11.6).

Nota: precisamente el hecho de que los miembros de clase, incluso privados, sean accesibles desde el interior de sus funciones miembro, es origen de otra de las ventajas de utilizar funciones miembro (métodos) y clases de la POO frente a funciones normales.  En efecto, las primeras suelen necesitar la utilización de menor número de parámetros que las funciones estándar, ya que la totalidad de propiedades del objeto a que se refieren, son accesibles directamente desde el interior del método (sin necesidad de que le sean pasados sus valores en forma de argumento).


§6
  Recuerde que la invocación explícita de constructores o destructores de clase, exige una sintaxis especial ligeramente diferente ( 4.11.2d).