Académique Documents
Professionnel Documents
Culture Documents
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;
//
c::x = 33;
//
cout << "Valor c::x =
}
==============
instancia un objeto de la clase
Error: c no es una clase o nombre de subespacio!!
" << 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 (pblico), tanto
propiedades (L.16) como invocacin de mtodos (L.17).
3.1 Si el acceso al objeto debe realizarse mediante un puntero, puede utilizarse el procedimiento
Ejemplo:
class C {
char c;
public:
C(char ch = 'x') { c
int x;
char getC() { return
};
...
C c1, d1;
//
C* ptrc = &c1;
//
(*ptrc).x = 10;
//
indireccin
d.x = (*ptrc).x;
//
ptrc->x = 10;
//
d1.x = ptrc->x;
//
ptrc->getC();
//
ptrc->C::getC();
//
= ch; x = 0; }
// Constructor
c; }
objetos c1 y d1, instancias de la clase C
define ptrc puntero-a-C sealando a objeto c1
Acceso a miembro x de c1 mediante operador de
idem (notacin desaconsejada para miembros)
Ok: Acceso a propiedad mediante selector indirecto
dem (notacin aconsejada para estos casos)
Ok: Acceso a mtodo mediante selector indirecto
Ok: variacin sintctica de la anterior
func2
func1
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 (mtodos) y clases de la POO frente a funciones normales. En efecto, las
primeras suelen necesitar la utilizacin de menor nmero de parmetros que las funciones
estndar, ya que la totalidad de propiedades del objeto a que se refieren, son accesibles
directamente desde el interior del mtodo (sin necesidad de que le sean pasados sus valores
en forma de argumento).
Nota: existe una cierta tradicin en C++ de utilizar maysculas en la inicial de nombres de
clases.
3 Usar typedefs
En ocasiones, en vez de asignar un nombre a la clase, se utiliza un typedef ( 3.2.1a), lo que es
exactamente anlogo. Esta sustitucin de nombres de clases por "typedefs" es una prctica muy
frecuente en las clases y estructuras definidas en los ficheros de cabecera de los propios
compiladores.
Aqu existen dos formas posibles de uso: utilizando clases annimas o nominadas (con nombre).
Seran las situaciones siguientes:
typedef class {
...
COMPLEX* cptr;
COMPLEX() { /* ... */ }
~CPMPLEX() { /* ... */ }
// Error!!
// Error!!
// Error!!
} COMPLEX;
// typedef de clase annima
...
COMPLEX c1, *cPtr, arrc[10];
El problema es el ya sealado de que la clase annima no puede contener constructores o
destructores explcitos ni auto-referencias. Por esta razn es muy raro utilizar typdefs sobre clases
annimas. S en cambio sobre estructuras:
typedef struct {
...
COMPLEX* cptr;
// Error!!
} COMPLEX;
// typedef de estructura annima
...
COMPLEX c1, *cPtr, arrc[10];
El uso de un typedef con una estructuras y clases nominadas es la solucin que suele utilizarse en
los ficheros de cabecera de los compiladores. Respondera al siguiente esquema:
typedef class C {
...
C* cptr;
// Ok.
C() { /* ... */ }
~C() { /* ... */ }
// Ok.
// Ok.
} COMPLEX;
// typedef de clase con nombre
...
COMPLEC c1, *cPtr, arrc[10];
Esta definicin permite que los objetos de la clase C puedan contener auto-referencias, as como
constructores y destructores explcitos. Como se ha visto, en ambos casos es posible obtener
instancias de la clase o estructura utilizando el typedef en sustitucin del nombre.
Nota: el ANSI C++ no permite estructuras annimas que no declaren un objeto. Sin embargo,
aunque los compiladores C++ de Borland y Microsoft lo permiten en algunas circunstancias (
4.11.3a Estructuras annimas), recomendamos vivamente seguir el Estndar.
1 Presentacin
El ANSI C++ permite solamente estructuras annimas que declaren un objeto (como en &3
),
pero el compilador Borland C++ permite varios tipos de estructuras annimas que extienden el
ANSI (que no declaren un objeto, como en &2
).
Estas estructuras annimas tienen la siguiente forma:
struct { lista-de-miembros };
&2 Estas estructuras annimas deben ser anidadas, es decir, declaradas dentro de otra clase
(clase, estructura o unin) por ejemplo:
struct my_struct {
int x;
struct {
// estructura annima anidada en my_struct
int i;
};
inline int func1(int y);
} ;
&3 La estructura externa debe estar identificada, es decir, tener un nombre (como en el ejemplo
anterior) o declarar un objeto como en el ejemplo que sigue (tambin pueden tener ambas cosas):
struct {
int x;
struct {
// estructura annima anidada en my_struct
int i;
};
inline int func1(int y);
} S;
&4 Puesto que no existe variable de instancia, la sintaxis C++ para las estructuras annimas no
pueden referenciar el puntero this ( 4.11.6). Por consiguiente, mientras que las estructuras C++
tienen generalmente funciones miembro, las annimas no pueden tenerlas (solo pueden tener
datos). Estos datos (propiedades) pueden ser accedidos directamente, dentro del mbito en que la
estructura ha sido declarada, sin utilizar la sintaxis x.y o p->y.
Ejemplo
#include <iostream>
using namespace std;
struct my_struct {
int x;
struct{
// estructura annima anidada
int i;
};
int func1(int y) {return y + i + x;}
} S;
int main() {
S.x = 6;
// ================
S.i = 4;
// Observe este acceso al miembro i
int y = S.func1(3);
cout << "y == " << y << endl;
return 0;
}
Salida:
y == 13
3 Declaracin adelantada
Una declaracin de clase se dice adelantada o incompleta cuando no se hace la correspondiente
definicin como en el ejemplo:
class X;
// An no tiene definicin!
La declaracin adelantada puede ser til porque una declaracin de este tipo ya permite definir
ciertas referencias a la clase declarada (generalmente referencias a punteros-a-objetos de la
clase), antes que la misma haya sido completamente definida. Ejemplo:
struct X;
// declaracin incompleta
4.11.2a1) de
En todos los casos la entidad de declaracin adelantada debe ser completamente definida antes
de poder utilizarla para otros usos que requieran conocer el tamao del objeto. Ejemplo:
class B;
// declaracin adelantada de B
class C;
// declaracin adelantada de C
class A {
...
friend B;
// Ok.
B b1;
// Error! clase B no definida an
friend C::operator* (A&, C&);
// Ok!.
};
...
class B {...};
class C {...};
3.2 typedef (
// Ilegal!!
Para ms informacin ver "Declaracin de miembros". Ver tambin las declaraciones adelantadas
para las clases VCL.
2 Declaracin de clase
El primero, declaracin de clase, es simplemente asignarle un nombre; una sentencia que
establece la conexin entre el identificador y el objeto al que representa (en este caso una clase).
La declaracin asocia el nombre con un tipo de dato, lo que supone definir como se usa, que
operaciones son permitidas y que sentido tienen estas operaciones [6]. La declaracin sera algo
as:
class Hotel;
Una declaracin de este tipo, sin definicin, se denomina adelantada (
4.11.4a).
3 Definicin de clase
La definicin de clase es el proceso de definir cuales sern sus propiedades y mtodos; proceso
que crea un nuevo tipo [2]. Lo mismo que con las variables normales, con frecuencia la declaracin
y definicin de una clase ocurren simultneamente en la misma sentencia (a menos que se trate de
una declaracin adelantada
).
La definicin de la clase puede ser un proceso muy simple (caso de la herencia simple o mltiple).
Ejemplo:
class Hotel: public Pension, Residencia {};
4 El objeto-clase
Cuando la clase est declarada y definida, termina el trabajo del programador. A partir de aqu, el
compilador traslada dicha declaracin a unafuncin-clase, que es la forma en que existe la clase
en el ejecutable. Ms tarde, en tiempo de ejecucin, la funcin-clase crea un objeto-claseque
representa desde este instante a la clase en cuestin. Existe un solo objeto-clase de cada clase y
solo l puede crear instancias de dicha clase. Como se ha sealado, este objeto-clase tiene sus
propias variables y mtodos (propiedades de clase y mtodos de clase). Una variable
(propiedad) declarada como "de clase" existe una sola vez en cada clase y es similar a una
variable definida como esttica de fichero en la programacin clsica. Por su parte, como veremos
inmediatamente
, los mtodos son por definicin, y por lgica [4], "de clase". Un mtodo de
clase solo puede ser ejecutado por un objeto (instancia) de dicha clase.
Por definicin un objeto-clase tiene como mnimo cuatro mtodos de clase: un constructor por
defecto; un destructor; un constructor-copia y una funcin-operador de asignacin operator=(). En
caso que el programador no los haya definido de forma exsplcita, son proporcionados por el
compilador.
Un objeto-clase no puede ser usado directamente, podemos figurarnos que no es un objeto
concreto. Del mismo modo que para usar un entero hay que declarar uno, con un nombre,
especificando que pertenece a la clase de los enteros y en su caso, iniciarlo a un valor. Para
utilizar un objeto-clase hay que declararlo; en estos caso ms que "declarar" un objeto de la clase
se dice instanciar la clase, que equivale a disponer de un objeto concreto (instancia) de la clase.
Nota: por supuesto es necesario hacer una definicin completa de la clase, con todos sus
miembros, antes de que pueda instanciarse uno de sus objetos. Adems, algunos tipos de
clases, las denominadas abstractas, no sirven para instanciar objetos directamente, solo para
derivar de ellas otras clases en las que se perfilarn detalles concretos, lo que les permitir ser
instanciables (
4.11.8c).
5 Las funciones-miembro
Al llegar a este punto es preciso hacer una observacin de la mayor trascendencia: cuando se
instancia una clase, se crea un objeto que contiene un subconjunto particular de todas las variables
(no estticas
4.11.7) de la clase a que pertenece. Pero aunque coloquialmente se dice que el
objeto tambin "tiene" los mtodos de la clase, en realidad esto no es cierto. El objeto no contiene
una copia de todos sus mtodos, lo que supondra una repeticin innecesaria del mismo cdigo en
todos los objetos de la clase. Los mtodos solo existen en el objeto-clase descrito anteriormente.
En las instancias concretas solo hay una tabla de direcciones (denominada vtable) a los mtodos
de la clase, y el acceso a dichas funciones se realiza a travs de esta tabla de punteros [1]. En el
apartado dedicado al puntero this (
4.11.6) se ampla informacin sobre esta importante
cuestin terica y sus implicaciones prcticas.
6 El resumen del proceso hasta aqu descrito puede ser sintetizado como sigue:
Programador
Compilador
Ejecucin
Ejecucin
// 7a
declara playa como perteneciente a la clase Hotel; esta sentencia relaciona el identificador con
un tipo especfico de objeto (de la clase Hotel[3]), del mismo modo que la sentencia int
x; relaciona el identificador x con el tipo de los enteros. Una vez tenemos un objeto, habra que
inicializarlo (aqu se dice construirlo), asignndole espacio en memoria e iniciando en su caso sus
variables [5].
En el caso del entero, aunque la expresin int x; no es formalmente una definicin, en realidad
contiene todo lo que el compilador necesita saber sobre ella para poder asignarle un espacio en
memoria, aunque inicialmente este espacio pueda contener basura si no ha sido inicializado a
ningn valor concreto (
4.1.2).
En el caso del objeto playa, en realidad el compilador toma la sentencia 7a
como una
declaracin mas una definicin. Siempre que se crea un objeto se realiza una llamada implcita o
explcita a un constructor que se encarga de inicializar los miembros del objeto. En el caso de 7a,
adems de asociar el identificador playa con el tipo Hotel, el compilador incluye una invocacin
al constructor por defecto de la clase ( 4.11.2d1), que se encarga a su vez de inicializar el
nuevo objeto correctamente (ver
4.11.2d3 para una ms detallada exposicin del proceso de
inicializacin de los miembros de los objetos).
El resultado es que a partir de dicha declaracin-definicin del objeto, ya podemos utilizarlo
directamente. Por ejemplo, en este caso, utilizando uno de sus mtodos pblicos getroom():
playa.getroom(37);
Por supuesto pueden definirse ms objetos del mismo tipo (instancias de la clase) y objetos
derivados del nuevo tipo, como punteros-a, referencias-a, matrices-de, etc.)
class X { ... };
// define la clase X
X x, &xr, *xptr, xarray[10]; /* instancia 4 objetos derivados de X:
tipo X, referencia-a-X, puntero-a-X y matriz-de-X */
Incluso pueden crearse otros objetos de la misma clase por copia del existente por ejemplo:
X y = x;
Aqu el objeto y se crea por copia del objeto x ya existente. En este caso, se invoca un tipo
especial de constructor, el constructor copia ( 4.11.2d4).
8 Destruccin
Finalmente debemos aadir que cuando el objeto sale definitivamente de mbito, es destruido. De
esta tarea se encarga un operador especial, que tiene la forma de un mtodo-de-clase, que se
encarga limpiar los miembros del objeto y de liberar los recursos asignados inicialmente (como
mnimo, espacio de memoria) antes que el propio objeto se auto-destruya
(destructores
4.11.2d2).
1 Sinopsis
Hemos dicho anteriormente ( 4.11.2b) que cada instancia de una clase tiene su propio juego
de variables; propiedades privativas o heredadas (segn el caso), y que unas y otras se
direccionan del mismo modo. Sin embargo, aunque esto es cierto para las propiedades
no estticas ( 4.11.7); no lo es para los mtodos. Existe una sola versin de los mtodos de
clase en el objeto-clase ( 4.11.5), que solo pueden ser invocados por los objetos de la clase
(podramos decir que son funciones de uso restringido). Pero entonces surge una cuestin: cuando
un objeto invoca uno de estos mtodos Cmo sabe la funcin sobre que instancia de la clase
debe operar?. O dicho con otras palabras: Que juego de variables debe utilizar ?.
Para fijar ideas consideremos el caso del ejemplo siguiente:
#include <iostream>
using namespace std;
class X {
public:
int x;
void pow2() {cout << "El cuadrado es: " << x*x << endl; }
};
void main() {
X x1;
x1.x = 2;
X x2;
x2.x = 5;
x1.pow2();
x2.pow2();
}
// =========
// x1 una instancia de X
// x2 otra instancia de X
// M.5: invocacin de func desde x1
// M.6: invocacin de func desde x2
Salida:
El cuadrado es: 4
El cuadrado es: 25
Observando la salida se hace evidente que en uno y otro caso, la invocacin a pow2 se ha
realizado correctamente (aunque no se le hayan pasado parmetros !!). El compilador sabe sobre
que instancia debe operar y ha utilizado el juego de variables correspondiente.
Recuerde que aunque coloquialmente se pueda decir que x2.pow2() es la "Invocacin del
mtodo pow2() del objeto x2", en el fondo es incorrecto. No existe tal pow2() del objeto x2. Es
ms cercano a la realidad decir: "es la invocacin del mtodo pow2() de la clase X utilizando el
juego de variables del objeto x2".
2 El argumento oculto
La respuesta a como las funciones miembro operan con el conjunto de variables de los objetos
para los que son invocadas, est en que C++ incluye en tales funciones (como pow2) un parmetro
especial oculto denominado this (es una palabra clave C++). this es un puntero al objeto
invocante [2]. Este puntero es pasado automticamente por el compilador como argumento en
todas las llamadas a funciones miembro (no estticas). Como su inclusin es automtica y
transparente para el programador, es frecuente referirse a l como argumento implcito u oculto.
El resultado es que cuando el compilador encuentra una invocacin (con o sin argumentos) del
tipo x.pow2(), calcula la direccin del objeto (&x), y realiza una invocacin del tipo pow2(&x),
utilizando esta direccin como valor del argumento oculto this. Por supuesto, el compilador aade
por su cuenta el argumento correspondiente en la definicin de la funcin X::pow2().
Nota: en realidad, el objeto x de una clase C tiene existencia en memoria en forma de una
estructura que contiene los miembros no estticos de la clase. La direccin del objeto es la del
primer miembro de esta estructura. Las direcciones del resto de miembros se consideran
desplazamientos respecto a esta direccin inicial. Esta arquitectura es la clave del
funcionamiento de los punteros-a-clases ( 4.2.1f) y punteros-a-miembros ( 4.2.1g).
class X {
public:
int x;
void pow2(X* this) {
cout << "El cuadrado es: " << this->x * this->x << endl;
}
};
y a su vez, las invocaciones de M.5 y M.6 se sustituyen por:
X* xptr1 = &x1;
X* xptr2 = &x2;
X::pow2(xptr1);
X::pow2(xptr2);
//
//
//
//
puntero-a-tipoX sealando a x1
puntero-a-tipoX sealando a x2
invocacin de X::func referida a x1
invocacin de X::func referida a x2
Tenga en cuenta que estas lneas solo tratan de ofrecer una imagen didctica, ya que this es
un puntero muy especial que no puede ser declarado explcitamente, por lo que la definicin void
pow2(X* this) { /* ... */ } no sera vlida. Tampoco puede tomarse su direccin o ser
utilizado como Rvalue para una asignacin del tipo this = x (si puede en cambio ser utilizado
como Lvalue). Por otra parte, las invocaciones en la forma X::pow2(xptr); tampoco son
// ======================
// x1 una instancia de X
// x2 otra instancia de X
// puntero-a-tipoX sealando a x1
// puntero-a-tipoX sealando a x2
El problema de identificacin antes sealado no existe para las funciones miembro estticas;
veremos ( 4.11.7) que no tienen puntero this, por lo que este tipo de funciones pueden ser
invocadas sin referirse a ningn objeto particular. La contrapartida es que un mtodo esttico no
puede acceder a miembros no estticos sin utilizar los selectores de miembro . o -> ( 4.11.2e).
Es decir, hay que indicarle explcitamente sobre que juego de variables debe operar.
Las funciones friend (
4.11.2a1) que no son miembros de ninguna clase tampoco disponen de
este argumento oculto (no disponen de punterothis). Tampoco las estructuras annimas (
4.11.3a)
4 En el siguiente ejemplo se definen dos clases, idnticas salvo en la forma de referenciar a sus
miembros; en una se utiliza el puntero this de forma explcita, en otra de forma implcita, ambas
son equivalentes, aunque es ms normal utilizar la forma implcita.
#include <iostream.h>
class X {
int x;
public:
int getx() { return x; }
void putx (int i) { x = i; }
};
class Y {
int x;
public:
int getx() { return this->x; }
void putx (int i) { this->x = i; }
};
void main() {
// ==========
X x1;
x1.putx(10);
cout << "Valor de x1.x: " << x1.getx() << endl;
Y y1;
y1.putx(20);
cout << "Valor de y1.x: " << y1.getx() << endl;
}
Salida:
Valor de x1.x: 10
Valor de y1.x: 20
5 A continuacin se expone una variacin del ejemplo presentado al tratar del acceso a
miembros de clases desde funciones miembro ( 4.11.2e). En esta ocasin se utiliza el
puntero this en vez del operador de acceso a mbito.
#include <iostream.h>
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) { this->y = y; }
int getxy();
};
x1 = 4
x2 = 25
x2 = 16
Comentario
Comprobamos que las salidas son las mismas que en el primer ejemplo. Las operaciones se han
realizado sobre las variables del objeto correspondiente, pero adems, en este caso el mtodo
devuelve un objeto, por lo que puede ser utilizado como Rvalue de la asignacin M.7. En
consecuencia, el valor 4 del objeto x1 es transformado a 4 * 4 = 16 en la invocacin del lado
derecho de la asignacin. Posteriormente este valor es asignado al objeto x2. Este es el resultado
que se obtiene en la tercera salida.
7 La expresin *this tambin puede ser utilizada para devolver una referencia (
objeto invocado por la funcin-miembro. El ejemplo puede modificarse para hacer
que pow2 devuelva una referencia al objeto:
4.2.3) al
#include <iostream>
using namespace std;
class X {
public:
int x;
X& pow2() {
x = x * x;
return *this;
}
};
void main() {
// ==============
X x1, x2;
// instancias de X
x1.x = 2; x2.x = 5;
x1.pow2();
// invocacin de func desde x1
x2.pow2();
// invocacin de func desde x2
cout << "x1 = " << x1.x << endl;
cout << "x2 = " << x2.x << endl;
x2.pow2() = x1.pow2();
// M.7:
cout << "x2 = " << x2.x << endl;
}
Salida:
x1 = 4
x2 = 25
x2 = 16
Comentario
Las salidas son las mismas que en el ejemplo anterior, aunque con una diferencia significativa: la
referencia devuelta por la invocacin pow2 sobre el objeto x1 (en el lado derecho de la asignacin
M.7), es utilizado como Rvalue de la asignacin, y aplicada a la referencia devuelta por la
asignacin del lado izquierdo (que es ahora un Lvalue).
Esta capacidad de las referencias: ser un Rvalue cuando se utilizan a la derecha de una
asignacin y un Lvalue, cuando se sitan a la izquierda, es precisamente la razn por la que se
introdujeron en el lenguaje, siendo una propiedad muy utilizada en la sobrecarga de las funcionesoperador. En el captulo dedicado a la sobrecarga del operador preincremento se expone en
detalle esta caracterstica (
4.9.18c).
4.9.18.e y
4.9.18.e;
2 Propiedades estticas
El punto importante para comprender el porqu y el cmo de los miembros estticos, es conocer
que con ellos ocurre algo parecido que con las funciones-miembro ( 4.11.5). Aunque
convencionalmente se acepta que los objetos contienen un sub-conjunto de "todas" las
propiedades de la clase, esto solo es cierto para las variables no-estticas. Las propiedades
estticas se comportan aqu de forma parecida a las variables estticas de funciones normales.
Solo existe una copia de ellas, que en realidad est en el objeto-clase ( 4.11.5). Se trata por
tanto de propiedades de clase. A cada instancia le basta un puntero a los valores del objeto-clase y
cuando desde una instancia cualquiera accedemos a una de estas propiedades, en realidad se
accede a esta nica copia.
Lo ponemos de manifiesto con un sencillo experimento:
#include <iostream.h>
class C { public: static int x; };
int C::x = 13;
// L.3: definicin
int main () {
C c1, c2;
cout << "Valor
cout << "Valor
c1.x = 22;
cout << "Valor
cout << "Valor
}
Salida:
// ================
c1.x == " << c1.x << endl;
c2.x == " << c2.x << endl;
c1.x == " << c1.x << endl;
c2.x == " << c2.x << endl;
Valor
Valor
Valor
Valor
c1.x
c2.x
c1.x
c2.x
==
==
==
==
13
13
22
22
En el ejemplo se han instanciado dos objetos, c1 y c2. El valor inicial 13 asignado a la variable
esttica x en L.3 (volveremos de inmediato a esta "extraa" sentencia
), es puesto de manifiesto
en las dos primeras salidas. A continuacin vemos como la modificacin del valor x en el
objetoc1 tiene la virtualidad de cambiar dicha propiedad x en el segundo objeto.
Observe que definiciones como la de L.3 del ejemplo anterior, solo son permitidas para
miembros estticos y son posibles con independenciade que sean pblicos o privados es decir:
class C {
static char*
public:
static char*
char* ptr3;
};
char* C::ptr1 =
char* C::ptr2 =
char* C::ptr3 =
ptr1;
ptr2;
// declaracin
"Adios";
"mundo";
"cruel";
// Ok. Definicin
// Ok
// Error !! ptr3 no es esttica
Lo anterior no significa que las propiedades estticas, privadas o protegidas, puedan ser accedidas
directamente desde el exterior es decir:
...
func () {
...
cout << "Valor de ptr1: " << C::ptr1 << endl;
accesible!
cout << "Valor de ptr2: " << C::ptr2 << endl;
}
// Error: no
// Ok: -> "mundo"
De todo esto se derivan algunas consecuencias tericas y prcticas. La primera importante es que
al existir en el objeto-clase, las propiedades estticas no dependen de ninguna instancia para su
existencia. Es decir, existen incluso antes que ninguna instancia de la clase. Esto ha sido ya
puesto de manifiesto en la lnea 3 del ejemplo anterior
, donde hemos iniciado la variable
esttica x antes que se haya instanciado ningn objeto de la clase.
public: int x;
static int* p;
static char* c;
static int gety () { return y; }
};
Al compilar se producirn tres errores de enlazado Unresolved external...,
correspondientes a los miembros C::y, C::p y C::c; sealando que las variables estticas estn
declaradas pero no definidas (no tienen espacio de almacenamiento). Para evitarlo, podemos
hacer:
class C {
static y;
public: int x;
static int* p;
static char* c;
static int gety () { return y; }
};
...
int C::y = 1;
// no es necesario poner static (pero si int!!)
int* C::p = &C::y;
// dem int*
char* C::c = "ABC";
// dem char*
...
Las asignaciones de las tres ltimas lneas proporcionan espacio de almacenamiento, a la vez que
pueden servir de inicializadores de los miembros correspondientes. Observe especialmente la
notacin empleada. Los especificadores de tipo: int, int* y char* son necesarios. En cambio, no es
preciso repetir aqu la palabra static.
Nota: esta inicializacin de las constantes estticas fuera del cuerpo de la clase, es una
excepcin de la regla general C++ de que las propiedades de clases solo pueden inicializarse
en el cuerpo del constructor o en la lista de inicializadores (
4.11.2d3). La razn es que los
miembros estticos no son parte de los objetos de la clase sino objetos independientes [3].
Observe que en este caso an es necesario declarar el miembro fuera de la clase, aunque no es
necesaria aqu su inicializacin.
Nota: recordar que es posible sustituir una constante esttica entera por un enumerador (
4.11.2a).
A este respecto tenga en cuenta que los miembros estticos de una clase global pueden ser
inicializados como objetos globales ordinarios, pero solo dentro del mbito del fichero.
Es oportuno sealar que la inclusin de un constructor explcito en la declaracin de la
clase no hubiese evitado tener que incluir las tres ltimas sentencias para definicin de los
miembros respectivos. Por ejemplo, el cdigo que sigue dara los mismos errores de compilacin
que el anterior.
class C {
static y;
public: int x;
static int* p;
static char* c;
static int gety () { return y; }
C () {
// constructor por defecto
y = 1;
p = &y;
c = "ABC";
};
La razn es evidente: el constructor es invocado cuando se instancia un miembro de la clase,
mientras que los miembros estticos (que en realidad "pertenecen" a la clase y no a las instancias),
tienen existencia incluso antes de existir ninguna instancia concreta
.
Como consecuencia directa, si se incluye una asignacin a un miembro esttico dentro del
constructor, al ser esta asignacin posterior a la que se realiza en la definicin, los valores
indicados en el constructor machacarn a los que existieran en la definicin.
Ejemplo
#include <iostream>
using namespace std;
class A {a
public: static int x;
// miembro esttico
A(int i = 12) { x = i; }
// constructor por defecto
};
int A::x = 13;
// definicin de miembro
int main() {
//
cout << "Valor de A.x: "
A a1;
//
cout << "Valor de A.x: "
return 0;
}
==============
<< A::x << endl;
Invoca al constructor.
<< A::x << endl;
Salida:
Valor de A.x: 13
Valor de A.x: 12
Para verificar la sucesin de los hechos, construimos otro sencillo experimento, aadiendo algunas
instrucciones al ejemplo anterior:
#include <iostream.h>
class C {
static y;
// int por defecto
public: int x;
static int* p;
static char* c;
static int gety () { return y; }
C () {
// constructor por defecto
y = 1;
// iniciadores de "instancia"
x = 3;
p = &y;
c = "ABC";
}
};
int C::y = 20;
// iniciadores de "clase"
int* C::p = &C::y;
char* C::c = "abc";
int main () {
cout << "Valor
cout << "Valor
cout << "Valor
/*cout << "Valor
Member C::x
C c1;
cout << "Valor
cout << "Valor
cout << "Valor
cout << "Valor
}
// ===============
.y == " << C::gety() << endl;
.p == " << *(C::p) << endl;
.c == " << C::c << endl;
.x == " << C::x << endl; ERROR:
cannot be used without an object in function main() */
c1.y
c1.p
c1.c
c1.x
==
==
==
==
"
"
"
"
<<
<<
<<
<<
Salida:
Valor
Valor
Valor
Valor
Valor
Valor
Valor
.y == 20
.p == 20
.c == abc
c1.y == 1
c1.p == 1
c1.c == ABC
c1.x == 3
En este sencillo programa comprobamos un buen montn de las caractersticas especiales de los
miembros estticos:
Las tres primeras salidas se producen antes de instanciar ningn objeto:
Los miembros estticos tienen existencia antes que cualquier instancia de la clase.
Los valores iniciales son debidos a los iniciadores de clase.
El intento de construir una cuarta salida siguiendo la pautas de las anteriores con el miembro x noesttico conduce al previsible error de compilacin (muy explcito por cierto).
095