You are on page 1of 65

4.2.

1d Puntero genérico
§1 Sinopsis La declaración: void* vptr;, formalmente: "puntero-a-void", declara que vptr es un puntero genérico, capaz de recibir sin problema el valor de cualquier puntero-a-tipoX (incluso nulo). Significa que pueden apuntar a objetos de cualquier tipo (con las excepciones señaladas ). Nota: la propia existencia de un puntero de estas características, que podríamos denominar de "manga ancha", y que acepta albergar la dirección de objetos de cualquier tipo, parece ir contra las rigideces en el tratamiento de los tipos de C++ ("Strong type checking" 2.2). Hay que entender que se trata de otro de los legados del C clásico, aceptado por C++ para poder compilar el código de su ancestro. No confundir un puntero genérico (a void) que aquí se describe, con el puntero nulo ( 4.2.1)

§2 Ejemplo: int x = 1; float r = 1.0; void* vptr = &x; int main () { *(int *) vptr = 2; vptr = &r; *(float *)vptr = 1.1; }

// // // // // //

puntero-a-void actualmente señalando a int x ============= M.1: modifica el valor de x actualmente apuntando a float r M.3: modifica el valor de r

Observe el "casting" ( 4.9.9) en M.1 y M.3 para que el puntero-a-void pueda ser deferenciado ( 4.9.11a) para acceder a cada tipo de variable. Los punteros-a-void no pueden ser deferenciados sin un modelado explícito, porque el compilador no puede determinar el tamaño del objeto al que referencian (este dato es imprescindible para una mínima aritmética de punteros) [1].

§3 Excepciones No obstante lo anterior, existen excepciones a la regla de libre asignación a los punteros genéricos. §3.1 No pueden asignárseles la dirección de una constante. Por ejemplo: int const x; void* p = &x;

// Error!

Para una asignación de este tipo es necesario hacer:

void* p = (void*) &x; void const* p = &x; int const* p = &x;

// Ok: con un pequeño truco... // Ok: // Ok: Esta es la forma canónica

§3.2 Los punteros-a-void tampoco pueden ser asignados a punteros-a-constante. Por ejemplo, siguiendo con el caso anterior: void* pv; pv = p; Observe que: int x = 10; void* vptr = &x; int* iptr = vptr; int* iptr = (int*) vptr;

// Error!

// Ok. // L.3: Error! // L.4: Ok.

La sentencia L.3 produciría un error de compilación: Cannot convert 'void *' to 'int *' in.... La razón es que a pesar de estar actualmente señalando la dirección de un int, para el compilador vptr es todavía un puntero-a-void, por lo que su valor no puede ser asignado a un puntero-a-int. Son tipos diferentes y el compilador indica que él por su cuenta, no puede hacer la conversión necesaria. En L.4 se permite la asignación porque previamente se ha "disfrazado" el tipo de vptr mediante un modelado adecuado. Forzamos explícitamente al compilador a hacer lo que de "motu proprio" no se ha atrevido.

§3.3 Los punteros-a-función ( asignados a punteros-a-void.

4..2.4) y los punteros-a-miembro (

4.2.1g) no pueden ser

Ver el operador dynamic_cast ( 4.9.9c) para una discusión sobre la conversión (modelado) de punteros-a-clase en punteros-a-void.

4.2.1e Puntero constante/ a constante
§1 Sinopsis Un puntero puede ser declarado con el modificador const; recuérdese que cualquier entidad declarada con esta propiedad no puede modificar su valor ( 3.2.1c). En el caso de la declaración de punteros, la posición del modificador es determinante, de forma que pueden darse dos situaciones cuyos resultados son bastante distintos.

§2 Puntero-a-constante

Las dos formas que siguen son análogas; en ambos casos representan puntero-a-tipoXconstante (el objeto al que se apunta es constante), abreviadamente: puntero-a-constante. Se utilizan para avisar al compilador que el objeto referenciado no puede ser modificado, ni aún a través de su puntero. tipoX const * puntero ... ; const tipoX * puntero ... ; Ejemplo: int const * xPtr = &x; const int * yPtr = &y; xPtr e yPtr son dos objetos del mismo tipo (puntero-a-int constante). Ambos señalan a sendos enteros cuyo Rvalue no pueden ser modificado, sin embargo, los punteros si pueden ser modificados. Por ejemplo, señalando a un nuevo objeto (siempre naturalmente que este sea un intconstante).

Los punteros-a-constante son de utilización más frecuente que los punteros constantes (ver a continuación). Se utilizan principalmente cuando se pasan punteros a funciones, porque se desea aligerar la secuencia de llamada ( 4.4.6b). En especial si los punteros señalan a objetos muy grandes y se desea evitar que la función invocada pueda modificar el objeto en cuestión. Ejemplo: class CMG {...}; void fun(const CMG*); // clase muy grande // función aceptando un puntero a la clase

int main() { // =========== CMG* aptr = new CMG; ... fun(aptr); // fun no puede modificar el objeto }

§3 Puntero constante La expresión que sigue significa puntero-constante-a-tipoX (el puntero es constante), abreviadamente: puntero constante. Se utiliza para informar al compilador que el valor del puntero no puede cambiar, aunque el objeto al que apunta si pueda cambiar (si no es constante). tipoX * const puntero ... ; Puesto que en este tipo de sentencias el puntero es declarado constante y su valor no puede ser modificado con posterioridad, hay que establecer su valor en el mismo momento de la declaración. Es decir, debe ser una definición del tipo: tipoX * const puntero = dirección-de-objeto ; Ejemplo:

// cp es puntero-constante-a-int (iniciado). // ERROR: asignar nuevo valor a puntero constante const int * pci = &ci. // Ok: Asigna constante-int al objeto apuntado por un puntero-constante-a-int (el objeto apuntado i. // ERROR: modificar valor de constante int * pi. Como puede verse en la última línea del ejemplo que sigue. // ERROR: asignar valor a constante ci--. puntero-a-tipoX y puntero-a-tipoX-constante son tipos distintos. El puntero es constante. int* ptr. const int nC = 7. // Ok: Incrementa puntero-a-constante-int const int * const cpc = &ci. // pi es puntero-a-int (no iniciado) int * const cp = &i. // pci es puntero-a-constante-int *pci = 3. int *const pt1 = &nN. int x = 75. // Ok: Asignación correcta §5 Es ilegal crear un puntero que pudiera violar la no asignabilidad de un objeto constante. ambos casos pueden darse simultáneamente (puntero constante a constante): int nN = 7. La asignación no es posible const int* ptk. const int * const pt3 = &nC Como resumen podemos afirmar que:   Un puntero constante no puede ser modificado Un puntero-a-constante apunta a un objeto cuyo Rvalue no puede cambiar // // // // // entero iniciado a 7 entero constante iniciado a 7 puntero constante a int puntero a constante tipo int puntero constante a constante tipo int Observe que. // ci es int constante (iniciado a 7) i = ci. // Ok: Asigna constante-int a int (i == 7) ci = 0. por tanto debe señalar al entero x durante toda su vida. // puntero-a-int ptr = &kte. §4 No confundir puntero constante con puntero-a-constante. // i es int (no iniciado) const int ci = 7. Considere los ejemplos siguientes: int i. Ejemplos: const int kte = 75. del mismo modo que tipoX y tipoX-constante son también tipos distintos. const int * pt2 = &nC. desde la óptica del C++. no es constante) cp = &ci.int* const xPtr = &x. // cpc es puntero-constante-a-constante-int . En este caso xPtr es un puntero constante-a-int. // Error. apunta a un int no constante (i) *cp = ci. // puntero-a-int-constante ptk = &kte. // ERROR: asignar valor al objeto apuntado por pci (es puntero-a-constante) ++pci. lo que significa que su Rvalue no puede ser cambiado.

1. a través de un puntero.2. asignándole un valor de 35. Modifica el valor de la constante de forma indirecta. // L4: -> 40 printf("%3i\n". la expresión *iptr = 35 asigna 35 a la "variable" señalada por iptr. edad). // L3: Se acepta la instrucción sin error printf("%3i\n". La explicación de esta aparente contradicción es que (por razones de eficacia en la ejecución del código). merece una explicación: L1: La variable edad se define como constante-tipo-int.que era constante). sería posible asignar al valor apuntado por pci (una constante) mediante asignación a la dirección apuntada por pi. cuando en la línea 5 se le pide que muestre el verdadero valor (el que hay en la zona de almacenamiento de variables [1]) muestra el valor modificado. L5: Acto seguido. // ERROR: si se permitiese la asignación. // Ok: Asigna puntero-constante-a-constante-int a un puntero-a-constante-int cpc++. *(int *)&edad). L4: Esta sorprendente sentencia indica que edad sigue teniendo el valor 40 (su valor inicial). Observe que ambos: const ( y volatile ( 4.1c ) §5. y nos muestra el valor modificado. Si embargo. La explicación es que la dirección &edad. // L2: -> 40 *(int *)&edad = 35. de forma indirecta. 3.pci = cpc. Finalmente. que es la dirección de una constante-tipoint. lo que es perfectamente legal. en la línea 4 el compilador utiliza el valor de la constante resuelta en tiempo de compilación (se le había dicho -Línea 1.1 A pesar a pesar de lo indicado anteriormente. Reglas similares pueden aplicarse al modificador volatile. es posible modificar una constante. a través de un puntero. Considere el siguiente ejemplo [2]: int const edad = 40. edad). es modificada por el operador de modelado(int *) que la promueve a puntero-a-int (aquí se pierde el carácter de constante).9) pueden aparecer como modificadores del mismo identificador. a este puntero lo denominaremos provisionalmente iptr. Se inicia al valor 40 L2: Se muestra su valor: 40 como cabría esperar. 35. pedimos que nos muestre el valor guardado en la dirección iptr (la dirección de edad). un tanto sorprendente. . Lvalue(edad). Este es el valor que nos muestra en la línea 4. // ERROR: modificar valor de puntero-constante pi = pci. // L5: -> 35 Comentario: El ejemplo. L3: Esta sentencia se ejecuta sin error. // L1: constante tipo int iniciada a 40 printf("%3i\n". El compilador supone que será realmente constante y en el segmento de código correspondiente a la sentencia de impresión pone directamente el valor 40 obtenido en tiempo de compilación.

recordar que para esta. es importante pensar en ellas como tipos de objetos. y las direcciones del resto de miembros se pueden calcular considerando desplazamientos respecto a esta dirección inicial [1]. aunque presentan algunas peculiaridades que conviene conocer [2]. // ptrc es puntero genérico a tipo C más tarde. C c. en uno y otro caso podemos iniciar dichos punteros a instancias concretas. // Error ya que int no es un objeto concreto de la clase. Dicho esto. Esta circunstancia es fundamental para entender el funcionamiento de los punteros-a-clase que tratamos aquí.2.2.2. Por ejemplo de la clase de los enteros int. y de los punteros-a-miembro.1). y consecuentemente. Tipos nuevos. tiene existencia en memoria en forma de una estructura que contiene los miembros no estáticos de la clase. y podemos declarar un puntero a cualquier elemento de dicho tipo como: int* ptri. pero que en lo demás no se distinguen gran cosa de otros tipos preconstruidos en el lenguaje. La dirección del objeto es la del primer miembro de esta estructura. §2 Declaración Del mismo modo que la forma genérica de puntero a la clase int es tipo int*. no estarían de más dos pequeñas puntualizaciones: en primer lugar. Por la misma razón tampoco es válida la sentencia: ptrc = &C.1g).4. que tratamos en el próximo capítulo ( 4. definidos por el usuario. podemos declarar un puntero a cualquier objeto de la case como: C* ptrc. En segundo lugar. ptrc = &c. como para muchas otras cuestiones relacionadas con las clases. // Error . añadir que los punteros a clases no se diferencian de los que hemos considerado genéricamente como punteros a objetos ( 4. Por ejemplo: int x = 5. // ptri es puntero genérico a tipo int la forma genérica de puntero a la clase C es C*. advertir que el objeto x de una clase X. ptri = &x. // // // // iniciar asociar iniciar asociar un objeto x de ptri al objeto un objeto c de ptrc al objeto la clase int x la clase C c Observe que en el caso de los enteros no es válida la expresión: ptri = &int.1f Punteros a clases §1 Sinopsis Como preámbulo al tema.

d.x = 10. las instancias de clases pueden referenciarse a través de sus punteros.h> class C { public: int x.9bis: Ok. c y d. instancias de la clase C define ptrc puntero-a-tipoC señalando al objeto c L. } . char* c.x Como puede verse.x = (*ptrc).x.x == 10 // L.7: constructor por defecto x = 13. c = "AEIOU". en adelante deberá ser accedido mediante el puntero ptrc (más aclaraciones al respecto en operador new en 4. C* ptrc = &c. d. 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.x Observe que en L. d. c. (*ptrc).9.16).20).x §3 Ejemplo: #include <iostream. ambas expresiones son equivalentes: ptrc->x = (*ptrc). int* p. d.8: Ok.9.7 instanciamos un objeto de la clase C y lo referenciamos mediante un puntero.11a) en la forma: *ptri = 10. int y = *ptri. este objeto no tiene etiqueta (nombre). utiliza el operador de indirección ( 4. p = &x.7: Ok.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). d.9: Ok. // L.9.x == c. la sintaxis utilizada en L. que se ha señalado anteriormente. el operador new devuelve un puntero L.x == c. // // // // // objetos c y d.8bis: Ok. C* ptrd = new C.x = ptrc->x. C c. La notación canónica de las dos últimas líneas sería ( 4. Aunque válida.2e): ptrc->x = 10.8 y L. Para ilustrar su uso podríamos utilizar una aproximación paralela al caso anterior: class C { public: int x..x == 10 L.11. } C () { // L.. void fun () { cout << "Valor miembro x == " << x << endl. La sintaxis. al contrario que sus hermanos. .9 no es la más elegante. c. // Asignar el valor 10 a la variable x // y == x Del mismo modo.

Además de la clase. cp->fun(). // L.x == " << c1. } Salida: 1 c1. que es asignado a una instancia concreta. En la función main hemos utilizado un puntero genérico a clase.x == 13 6 c1. int main (void) { C c1.c cout << "9 c1.x << endl. en las tres primeras los miembros son propiedades (variables).p == 13 3 c1.p == " << *c1.c == AEIOU 9 c1. // Salida-2 cout << "3 c1.p << endl. // Salida-4 f1(cptr). << cp->c << endl. << *(*cp). cptr. == == == == == == " " " " " " // definición de función << (*cp).x cout << "6 c1.c == AEIOU Valor miembro x == 13 4 c1.c == " << c1.9.c << endl. cptr = &c1.x == 13 5 c1. Las cuatro primeras salidas se deben a sentencias en la función main.c == AEIOU Valor miembro x == 13 Valor miembro x == 13 Comentario: La clase del ejemplo tiene cuatro miembros públicos: entero.p == 13 8 c1. puntero-a-int. C* cptr. . También se ha incluido un constructor por defecto que inicializa adecuadamente los objetos creados.} }. que es invocado.p cout << "8 c1.fun().c (*cp). y corresponden a invocaciones mediante el selector directo a miembro ( 4. puntero-a-char y una función. // Salida-1 cout << "2 c1.p == 13 7 c1. En la cuarta el miembro es un método (función) del objeto c1.x == 13 2 c1.13: prototipo de función normal (no-miembro) // // // // ======================== instancia de C puntero a clase asignado al objeto c1 cout << "1 c1.p cout << "7 c1. void f1 (C* cpt). << *cp->p << endl. << (*cp).x cout << "5 c1.24: El puntero se utiliza como argumento de f1 } void f1 (C* cp) { cout << "4 c1. << cp->x << endl. que acepta como argumento un puntero a la clase.p << endl. // Salida-3 c1.x << endl.c << endl.16). en el espacio global se ha definido una función f1. c1.fun(). // L.

Cuando se acceden a través de punteros objetos de clases que pertenecen a una jerarquía. utilizando los dos formatos posibles.2). al volver aquí comprobará que los punteros-a-métodos son una variedad de aquellos. también es posible establecer punteros a miembros concretos (propiedades y métodos). para declarar un puntero-a-entero se utiliza la expresión: int* ptri.1a) Tema relacionado:  Punteros a clases implícitas ( 4. en L. se cumplen los siguientes principios:   Los objetos de las clases derivadas pueden tratarse como si fuesen objetos de sus clasesbase cuando se manipulan mediante punteros y referencias.A continuación. Además.2b1). es importante tener en cuenta las precauciones indicadas en "Consideraciones sobre punteros en jerarquías de clases" ( 4. repase con detenimiento el apartado relativo a punteros-a-función ( 4.4. Ver: Congruencia estándar de argumentos ( 4. Ya dentro de dicha función. // declara puntero-a-int . Un puntero de una clase-base puede contener direcciones de objetos de cualquiera de sus clases derivadas.1f).11. aconsejamos al lector novel que antes de entrar en este capítulo. Así. Nota: en determinadas circunstancias el compilador puede realizar una conversión automática de un puntero-a-clase-derivada a puntero-a-clase-base. §4 Punteros en jerarquías de clases Cuando se definen punteros a clases-base.4).1g Punteros a miembros-de-clases Advertencia didáctica: la lectura de este capítulo exige un conocimiento previo de las clases ( 4. puesto que los punteros-a-miembros de clases incluyen los punteros-a-métodos. 4.2. Posteriormente.2. recordemos que la declaración genérica de puntero-a-tipoX adopta la forma tipoX*.2. que son un tipo de función.11). §1 Sinopsis Además de punteros genéricos a clases ( 4. Para establecer un cierto paralelismo. 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. se invocan las propiedades y métodos del objeto c1 (señalado por el puntero).24 se utiliza el puntero cptr como argumento de la función f1.12. de las que derivan otras.

y a miembros de estas (propiedades y métodos). int (C::* fptr())(). void (C::* fptr)(int). Puntero a miembro de C que es un puntero-a-int.2).1. Puntero a miembro de C que es un int.2d3). §2 Ejemplos A continuación se muestran algunos ejemplos de declaraciones de punteros a miembro-de-clase int C::* ptr. los punteros-a-miembro incluyen punteros-a- . Puntero a miembro de C que es un puntero-a-char. Puntero a función miembro de C que no recibe argumentos y devuelve un puntero-a-char constante. §3 Observaciones Recordemos que los punteros desempeñan un papel fundamental en C++. int* C::* ptr. Puntero a función miembro (método) de C que recibe un int y no devuelve nada.Posteriormente esta variable puede ser inicializada recibiendo un valor que es la dirección del objeto señalado. punteros a clases. Sin embargo. ptri = &x. ya que no es posible obtener las direcciones de este tipo de miembros ( 4. punteros a funciones.11) al nombre cualificado ( 4.9. Puntero a función miembro de C que no recibe argumentos y devuelve un puntero a función que no recibe argumentos y devuelve un int. algunas de las construcciones más elegantes y sofisticadas que pueden realizarse en él. la dirección cualificada de un miembro m de C es &C::m. char* C::* ptr.11. Precisamente.11. Nota: debe recordar que la asignación a miembros no está permitida en el cuerpo de la clase ( 4. En nuestro caso.11. A su vez.2d).11c) del miembro. los punteros a miembro-tipoX-de-claseC. Recordar también que no es posible definir punteros a losconstructores o destructores de clase. para iniciar un puntero a miembro es preciso aplicar el operador de referencia & ( 4. Del mismo modo. La declaración genérica de puntero a miembro tipoX-de-claseC adopta la forma tipoX C::*. como veremos a continuación. Por ejemplo: int x = 5. int (C::* fptr) (). definen un tipo de objeto (constante o variable) que sirve para alojar las direcciones de miembros de instancias de la clase [ 1].2a) y que la asignación a punteros debe seguir las mismas reglas que para el resto de miembros ( 4. char const * (C::* fptr)(). estos últimos (junto con los punteros-a-función) son quizás uno de los puntos de sintaxis C++ más compleja (por supuesto. los utilizan en sus diversas formas: punteros a objetos básicos (a tipos predefinidos en el lenguaje 2. Puntero a función miembro de C que no recibe argumentos y devuelve un int.

pues se aparta de la que venimos utilizando.2.9.1d) §4 Sobre el rendimiento Como consecuencia del mecanismo relativamente complicado que debe utilizarse. de un esfuerzo especial hasta lograr construir una imagen mental adecuada del mecanismo que los sustenta. el operador de indirección * ( 4.9. Como de costumbre..g1). si mptr es un puntero-a-miembro de un objeto obj. sobre todo para el principiante. Ver ejemplos en las páginas siguientes.11) sigue al selector directo .2. Cuando se trata de punteros a miembros de clase. la notación de punteros a métodos es similar a la utilizada para punteros a funciones normales ( 4.19). Es decir. Así pues.función-miembro). En cambio. que es común para todos los objetos de la clase.2.11.16) de miembro según el caso.) o indirecto (->) para determinar a qué instancia concreta pertenece el miembro.9. aunque además hay que aplicar el selector directo (.   La asignación se realiza de forma genérica (sobre miembros de clase). la utilización de funciones-miembro a través de punteros implica una gran penalización en términos del rendimiento .1.  Recordar que el puntero-a-void no puede ser utilizado para señalar a un miembro ( 4. aunque con el añadido de utilizar el operador de acceso a ámbito :: ( 4. hay que aplicar la indirección al puntero *mptr. existen varios puntos a los que aconsejamos prestar especial atención:  Los punteros a miembros estáticos ( 4. y optr es un puntero-alobjeto.1g1) o Punteros-a-miembros estáticos ( 4. pointers to members (which I consider an obscure corner of C++) " [2]. La notación utilizada para la invocación de los valores señalados por estos punteros es la que cabría esperar.7) tienen consideración distinta que los punteros a miembros no-estáticos. El propio creador del lenguaje opina que son un punto oscuro: " . Aunque lógicamente la utilización de estos punteros se realiza sobre instancias concretas de la clase (ver ejemplo 4. Esto se debe a que cada instancia de la clase contiene un subconjunto de los miembros no estáticos.. Stroustrup hace referencia a ellas señalando "the lack of readability of the C declarator sintax ".1a) en ellas. ( 4.2.4).1g2) Las declaraciones utilizan una notación un tanto especial.2.9. no sobre objetos concretos (miembros de instancia) como ocurría en el caso de punteros a clases.16) o indirecto -> ( 4. mientras que solo existe una instancia de los miembros estáticos. A esto se suma el hecho de que los punteros a miembro requieren. por lo que es muy frecuente la utilización de typedef ( 3. Esta diferencia nos obliga a dividir el estudio de estos punteros en dos subapartados: o  Punteros-a-miembros normales ( 4.2. el miembro señalado por mptr viene determinado por las expresiones: obj.*mptr y optr->*mptr. A lo largo de los ejemplos anteriores habrá observado que la notación utilizada para la declaración de punteros a propiedades es un tanto particular. por lo que estos miembros se parecen más a objetos normales de un subespacio que a miembros de clase.

al constituir las clases un subespacio o ámbito de nombres con ciertas peculiaridades. Observe que pmi no se inicia con ninguna . mpi = &i. la idea de un puntero que señale a una de estas entidades.11c). por lo que deberían evitarse en la medida de lo posible. Aquí existen sendos objetos que son punteros-a-miembro: el primero. miembro entero miembro puntero-a-entero constructor el puntero señala a un miembro // puntero-a-miembro entero-de-C.2. En especial si estos accesos se realizan en bucles muy repetitivos y/o se trata de funciones virtuales ( 4. . a su vez él mismo es miembro de la clase. A este tipo lo denominamos puntero interno.. destinado a señalar no a un entero cualquiera. int C::* pmi = &C::i. mpi. puede comprobarse que pmi es un puntero-a-entero de un tipo especial. el manejo de punteros a sus miembros necesariamente debe tener en cuenta esta circunstancia. // } }. Sin embargo. Si observamos la notación empleada en este último.12.8a). sino a un int del "subespacio" C.1. aunque podemos adelantar que. Temas relacionados:   Punteros a clases implícitas ( 4. // int* mpi.11) §5 Bibliografía  Christopher Skelly "Powerful Pointers To Member Functions" C/C++ Users Journal Octubre 1994 4.11. También es importante señalar que los punteros-a-miembros no estáticos no pueden ser accedidos fuera de su espacio de direcciones. y que denominamos externos. §1 Introducción En principio. por lo que su notación es muy parecida a la utilizada para acceder a miembros de subespacios ( 4. si consideramos que un miembro de clase (variable o función) es como otro objeto cualquiera del universo C++. es un puntero-amiembro. son también punteros-a-miembro pero no pertenecen a la clase. veremos que el concepto implica algunas singularidades que intentaremos mostrar en el siguiente ejemplo: class C { int i.global de la aplicación.2).9. no debería ser especialmente difícil. La indirección de punteros a clases y a miembros ( 4..1g1 Punteros a miembros normales ( no-estáticos) Nota: a lo largo del presente capítulo trataremos los aspectos de notación con el mayor detalle. para distinguirlo de los que como pmi. // C(int n) { // i = n.

no tiene sentido hablar del objeto señalado ( *pmi ) o del valor ( pmi ) aislado. B b.. sino de forma genérica...i y al mismo tiempo a la propiedad c2. A su vez. y los punteros a miembros no estáticos no son considerados como auténticos punteros a objetos [1]. un objeto está constituido por la yuxtaposición de subobjetos que contienen las propiedades heredadas de sus bases directas mas sus propios miembros privativos si los hubiere. // Objeto B C c.11.5). Llegados a este punto cabría preguntarse: puesto que pmi es un objeto único. 1 El objeto D es una zona contigua de memoria que comienza en un punto señalado por el puntero this ( 4.propiedad de instancia concreta (del tipo &cj. class D : public C { /* .. un puntero como pmi a una propiedad i de una clase C.p).i). cada miembro de un subobjeto.i? Desde luego parece una situación paradójica que podríamos sintetizar en el siguiente código: C c1 = C(1). En realidad las instancias de clase son una especie de estructuras (en el sentido C del término) en las que no están presentes las funciones miembro ni las variables o constantes estáticas (que tienen su existencia en el objeto-clase 4. Cada subobjeto empieza en un punto.. estriba en la propia estructura interna de los objetos C++. // Objeto D Fig. En consecuencia. contiene un valor que es el desplazamiento delta + offset del miembro i respecto de la estructura de elementos que comienza en el punto señalado por this. con la dirección de una propiedad de clase (&C::i). en que se heredan propiedades de otros ancestros ( 4.6). en cuyo caso el compilador obtiene la dirección concreta. y base para comprender el mecanismo de estos punteros. cuyo desplazamiento delta.2c). ya que solo tienen significado cuando se refieren al miembro de un objeto c específico *(c. es conocido por el compilador. */ }.. ni tampoco un miembro de clase como mpi ¿Como puede señalar en un momento dado a la propiedad c1. En realidad. */ }. ocupa una posición señalada por un desplazamiento offset respecto al anterior.. */ }. que también es conocido por el compilador. .11. . La figura 1muestra la disposición en memoria de varios objetos cuando una clase D deriva de otra C que deriva a su vez de una superclase B: class B { /* . class C : public B { /* .. incluyendo el subobjeto dominante (parte privativa de D). sumando el desplazamiento p = delta + offset del miembro a la dirección this de inicio del objeto. // Cual es ahora el valor *pmi 1 o 2? La solución al interrogante. // Objeto C D d. En el caso más general.11. como es el caso en los punteros a variables "normales". C c2 = C(2). Esto hace que los punteros a propiedades no-estáticas no almacenen una dirección determinada de memoria. no es una matriz de punteros.

Expresión no permitida: pmi no es miembro de C. Puesto que mpi es un puntero.mpi Valor del miembro mpi del objeto c1. Aunque el funcionamiento interno es controlado por el compilador de forma transparente. Estas expresiones pueden interpretarse como sigue: c1. considerando que referencian objetos del subespacio determinado por la clase. En el ejemplo anterior serían posibles expresiones como c1.1g1-3).*pmi Como colofón de lo anterior.pmi Error.. c1.1g1-2) y externos ( 4. aunque la mecánica de funcionamiento es totalmente distinta (distinta también según que las entidades señaladas sean propiedades o métodos). contiene el valor de otro miembro de la instancia c1. En este caso. podemos resumir que existen dos tipos de punteros-a-miembros no estáticos: internos ( 4. mientras que el externo se comporta como tal. La sintaxis de declaración es la siguiente: . veremos a continuación que el programador debe tener en cuenta algunas diferencias sintácticas en la utilización de unos y otros. exceptuando cuestiones de herencia.mpi) Contenido de la dirección de memoria señalada por el puntero-miembro. §2 Declaración La declaración de punteros-a-miembros no estáticos sigue las reglas que cabría esperar. c1. aunque a costa de una cierta complejidad en el mecanismo subyacente. por no decir idéntico. Complejidad que se incrementa extraordinariamente en los casos de herencia simple o múltiple. la sintaxis correcta sería c1.2. que se traduce en una menor eficiencia del código resultante. Es el valor de un miembro del objeto c1. Contenido de la dirección de memoria obtenida sumando el desplazamiento mpi a la dirección del objeto c1. pero no pertenece a la clase.2. Con la diferencia de que el puntero interno-a-miembro es una propiedad de la clase con todos los derechos. es un operador binario que exige que el operando situado a la derecha sea miembro de la clase.Nota: en la página adjunta se añaden algunos comentarios adicionales sobre el particular ( Nota) Este artificio es ejecutado automáticamente por el compilador..*pmi. o unión señalada por el operando de la izquierda. contiene una dirección de memoria. mientras que un intento similar con punteros externos (del tipoc1. *(c1. el comportamiento de los punteros-amiembro internos y externos es muy parecido. de forma que parece "como si" existiera una versión de pmi para cada instancia de la clase. Puesto que es un puntero-a-miembro.. En ambos casos la funcionalidad es idéntica. estructura. Recuerde que el selector directo de miembro .mpi y *(c1. Puede afirmarse que.pmi) conducirían a un error de compilación: 'pmi' is not a member of 'C' in.mpi).

in 24point block type it should say. <tipo_objeto> es el tipo de miembro al que señalará el puntero. // inicia puntero } } int C::* iptr = &C::x. Observe que no existe colisión de nombres porque ambos pertenecen a espacios distintos. Conviene recordar aquí lo indicado al tratar de la declaración de punteros ( 4. los tipos de iptr y cptr son distintos (int* y char* respectivamente).2. Ejemplo: class C { int x.2. char* cptr. iptr = &C::x. Por ejemplo: int A::* ipt1 = &A::x.1g1-2 Punteros internos a miembros normales ( no-estáticos) I think all C++ tutorials should be required by law to include a special page: At the top of this page would be a large icon of a chain saw. debe hacerse en el constructor. // declara puntero-a-miembro C (int p=0) { // constructor x = p. int C::* iptr. int B::* ipt2 = &B::x. los tipos de ipt1 y cpt2 son distintos (son respectivamente int A::* y int B::*).1a) donde señalamos que existen tantos tipos de punteros como tipos de objetos pueden ser señalados. Por supuesto. 4. tienen tipos distintos. Por ejemplo: int* iptr. "Warning: The Software General of the United States has determined that putting pointers in C++ classes can be hazardous .<tipo_objeto> <nombre_clase>::* <etiqueta_puntero>. Underneath. Observe también que el miembro C::iptr no puede ser iniciado en el mismo punto de su declaración. con la salvedad de que dos punteros señalando al mismo tipo de miembro de dos clases distintas. <nombre_clase> es la clase a que pertenece el miembro señalado <etiqueta_puntero> es el identificador (nombre) del puntero. Con los punteros-a-miembro ocurre lo mismo. uno es interno y el otro externo. // declara e inicia puntero En el ejemplo se han declarado dos punteros-a-miembro. el valor devuelto al deferenciar ambos punteros sería un int en los dos casos.

// // // // // // // // miembro int miembro array de char miembro funcion (metodo) miembro funcion (metodo) L.16 miembro puntero-a-int L. Su objeto es mostrar las diferencias. Destructors. y posterior referencia para su uso de punteros internos (uso que generalmente se concreta en acceder al miembro -propiedad o método. dos enteros y una matriz de caracteres. Algunos de estos punteros señalan a miembros de la propia clase.18 miembro puntero-a-matriz de char L. David Weber "Two C++ tutorials" C/C++ Users Journal.17 miembro puntero-a-miembro-int L. en los que se aprecia la sintaxis utilizada en la declaración. } int x = 103. así como la invocación de los referentes (miembros correspondientes) a través de ellos. char (*pachar1)[5]. char achar[5]. y. and Assignment Operators". aunque para mostrar las diferencias en la notación. que se instancian en M. char achar[5]. #include <iostream> using namespace std. } void fan () { cout << "Funcion externa-2" << endl. Se ha incluido también un constructor explícito que proporciona una inicialización adecuada a los objetos. El resto de miembros son punteros a objetos de distinto tipo. Copy Constructors. y = 301. §3. int* pint1. La clase incluye dos funciones: fun y fan. se incluyen también miembros que son punteros a objetos externos a la clase. void fan (). Con objeto de mostrar que no hay posibilidad de confusión si se utilizan los descriptores adecuados. int C::* pint2. §3 Punteros internos Ilustraremos este apartado con sendos ejemplos.19 miembro puntero-a-miembro-matriz . Esta salidas utilizan todos los punteros y en algunos casos se realizan por duplicado. asignación. dado que este ocupa un solo fuente). Marzo 1996. por lo que es extremadamente simple. void fun (). utilizando los dos objetos c1 y c2. en su caso. los nombres de los enteros y de las funciones miembro se han hecho deliberadamente iguales a las funciones y enteros del espacio global del fichero (que en este caso coincide con el del programa. entre los valores correspondientes a ambos objetos.to your health unless you fully understand Constructors. El cuerpo de main se reduce casi exclusivamente a una serie de salidas para ilustrar la forma de acceder a los punteros y a los objetos señalados por estos. class C { public: int x. // Ejemplo-1 void fun () { cout << "Funcion externa-1" << endl. char (C::* pachar2)[5].señalado). Observe con especial atención las formas de declaración y asignación de los estos punteros.1.1 Ejemplo-1 El programa solo pretende mostrar las posibilidades sintácticas y notacionales de este tipo de punteros.

pint2 == " << c1.pfe().pint2 << endl.20 miembro puntero-a-funcion externa void (C::* pfm)(). achar[4] = 'U'. cout << "7.25.pachar1 == " << c1. cout << "Algunas modificaciones runtime -----------------\n". achar[1] = 'E'. c2. c1.2.1: Instancias de C achar[0]='a'.20 cout << "8.14. cout << "4. achar[0] = 'A'.c1.c1.26 pachar1 = &::achar.Invocar c1.Invocar c1. achar[1]='e'. // M.pint1 == 0041A178 *(c1.*c1. pint1 = &::x.1.2.1. // L.*c1.*c1.pint1) << endl.pint1 << endl.2. } int main (void) { // ======================== C c1(10).*c1.pint2 == " << c1. // L.de char void (*pfe)().*c1.*c2.*(c1. achar[3] = 'O'.30 } }. // L.c1.8 ERROR cout << "4.c1.pint2 = &C::y.1. //cout << "1.*c1.pint1 = &y.12.14 cout << "6. } Salida: 1. x ==" << x << endl. //cout << "3. // L.1. c2(13).2. c1.pint2 << endl.*c1. x ==" << x << endl.12. cout << "6. cout << "5.c1. cout << "3.pfm == ".1. // M.Invocar c2.14.pfm == ".*c2.pachar1 << endl.pfm)(). // M.pint1) << endl.pint1) == " << *(c1.1.23 cout << "9.23.c2. cout << "8.pint2 == 10 c1.pfe = &fan. achar[2]='i'. c1. achar[3]='o'.pfe == ". (c1.*(c2.1. void C::fun() { cout << "Funcion interna-1.c1. achar[2] = 'I'.pachar1) == aeiou c1. // L. cout << "5. achar[4]='u'. // M.Invocar c2.pfm == ".2. cout << "9.pfe().pfm)().pfe == ".25 pint2 = &C::x. // L.*c1.pachar2 << endl.*c1.pachar1) << endl.*c2.2.pachar2 == " << c1.28 pfe = &::fun.pfm)().pachar1 == 00420670 *(c1.pachar2 M.*(c1.*(c1.1.pint2 == " << c2. cout << "1. c2.2. (c1.*c1.pint2 << endl.pint2 == " << c1. c2.pfe == ". // L.2.pint1) == 103 c1.29 pfm = &C::fun.17 cout << "7.pint2 M.Invocar c1. } void C::fan() { cout << "Funcion interna-2.pfe == Funcion externa-1 .pint1) == " << *(c2.pint1) << endl. return 0.pint1 == " << c1. y = 2*n.1c1.27 pachar2 = &C::achar.pfe().pint1) == " << *(c1.21 miembro puntero-a-funcion miembro C (int n = 0) { // constructor por defecto x = n. // L. cout << "2.c1.1.Invocar c1.2. c1.4 ERROR cout << "2.pfm = &C::fan.pachar2 == " << c1.pachar2 == AEIOU Invocar c1. // M. (c2.pachar1) == " << *(c1.

8).11c).30 no puede ser sustituida [5] por pfm = C::fun.pfm == Funcion Algunas modificaciones runtime 6.2. Es muy notable la diferente sintaxis empleada según se trate de un puntero a objeto externo o a miembro: *(c1.3) En las líneas L.4b).Invocar c2.pint1) c1.pint2. sí merece destacarse la sintaxis utilizada en L.pint2 == 10 7. conduce a error [4] (líneas M.Invocar c1.16/18/20 no ofrecen ninguna peculiaridad.2. c1.*c1. Pemos afirmar que se trata de una verdadera excepción en la sintaxis y semántica del lenguaje. Las declaraciones L.pint2 == 26 8. se declaran miembros que son punteros a objetos normales (no son miembros de clase) y su declaración es idéntica a la que se usaría con punteros del espacio global. Las asignaciones se realizan en las líneas L25/30 del constructor. x ==10 ----------------- externa-2 externa-1 interna-2.pfe == Funcion 8.Invocar c2. Nótese que las asignaciones L.4 y M.5. En cambio. sobre el objeto. La asignación de L.pint2 .1g1.1.*c1.pint1) == 103 6. Por ejemplo. Puesto que ambos son punteros. se muestra en hexadecimal ( 2.2.11c) del método cuya dirección se asigna al puntero. x ==13 Nota: respecto a la utilización de estos ejemplos con el compilador MS VC++ 6.1.pint1) == 301 7. // L.25/27/29 de los punteros que señalan a objetos "normales".*c2.1. pues utiliza explícitamente el identificador cualificado ( 4.1.2.pint1. Los grupos de salidas 2 y 4 muestran el valor de los objetos señalados por los punteros.Invocar c1. x ==10 interna-1.*(c2.2.1 muestran el valor de los miembros pint1 y pachar1 del objeto c1.pfm == Funcion Comentario: interna-1.2. Observe que un intento análogo para mostrar los valores de los punteros-a-miembros.1 y 3.2. utilizan el operador :: de resolución de ámbito sin ningún prefijo para referirse al espacio global de nombres ( 4. Por ejemplo: c1. No se cumple la regla general de que el identificador de la función es equivalente a su dirección ( 4.Invocar c1.0 ver " Notas particulares" ( 4.2.pfm == Funcion 9.c2. Las salidas 1.17/19/21 donde se declaran miembros que son punteros-a-miembros. Es decir.1.16/21 del cuerpo de la clase se realiza la declaración de los miembros que son punteros.30 es digna de mención.pfe == Funcion 9.c1.4a). utilizando el selector directo de miembro . la expresión pfm = &C::fun.1. estos valores son direcciones de memoria y como es tradición en C++.*(c1.

pfe)(). // M. uno de esos casos que justifican quizás la "mala fama" del C++ [1]. aunque con pequeñas modificaciones.pfe == ".pfe == ". Las sentencias M. } int x = 103.pfm(). Las salidas son básicamente las mismas.*c1.*c1. cout << "Invocar c1. // Ejemplo-2 void fun () { cout << "Funcion externa-1" << endl. Su contrapartida son las salidas 5.14/17/20/23 muestran la forma de modificar estos miembros de objetos (punteros).1. corresponden a invocaciones de las funciones externas a través de sus punteros.pfm)().2 Ejemplo-2 Presentamos aquí una versión prácticamente idéntica al ejemplo anterior.pfm == ". cout << " Invocar c1. y no es equivalente a c1.pfe))().1 y 9. sin embargo si puede ser asignado: c2.pint2 no pueda ser mostrado ni almacenado .1 y 8.*c1. Las salidas 5. c1.4b).2. } void fan () { cout << "Funcion externa-2" << endl.*(c1. La única singularidad a resaltar aquí es que la invocación utiliza los identificadores de los punteros en sustitución de los identificadores de las funciones ( 4. char achar[5]. pero se han trasladado desde el cuerpo de main a una función auxiliar fs. que representan la invocación de las funcionesmiembro mediante sus punteros.*.pfe(). // ERROR!! Nota: la eliminación del paréntesis interior es posible porque el operador deferencia de punteros-a-miembros de clase . 8 y 9). #include <iostream> using namespace std. c1.En mi opinión. Observe que algunos accesos utilizan una doble indirección: primero se accede al objeto a través de un puntero.pfe == ".pfm == ". // Ok. Observe que esta diferencia se mantiene en todas las expresiones de salida que utilizan el valor del puntero. ya que se pretende mostrar la sintaxis cuando el acceso se realiza a través de un puntero a la clase (en realidad un puntero al objeto).2. Las salidas que les siguen muestran el resultado de los cambios. es la simplificación de (c1.pfm))(). (c1.17 §3. . (*(c1.: cout << " Invocar c1.2. incluyendo las invocaciones a funciones-miembro (salidas 5. Recuerde que las siguientes sentencias son equivalentes ( Nota): cout << "Invocar c1. después se accede al miembro a través de otro.*c1.. 9.pfm(). una vez eliminado el paréntesis interno.pint2 = &C::y. Es digno de mención que un valor como c2.2. cout << "Invocar c1. 8. (*c1. Tenga en cuanta que en este caso (c1.pfm)(). tiene una precedencia menor que la del selector directo . y = 301.

2. // L.16 cout << "8. // FS.14 cout << "7. // L.*(cpt->pachar1) == " << *(cpt->pachar1) << endl.24 pint2 = &C::x. (cpt->*(cpt->pfm))(). pachar1 = &::achar. cpt->pfe = &fan. cout << "3. pfe = &::fun. cpt->pfe(). achar[4]='U'.12 cout << "6. //cout << "3.cpt->pint1 == " << cpt->pint1 << endl. cout << "Algunas modificaciones runtime -----------------\n".16 miembro puntero-a-int int C::* pint2.1.*(cpt->pint1) == " << *(cpt->pint1) << endl. cout << "4. // miembro array de char void fun (). cout << "2.2. C* pc1 = new C(10).18 .Invocar cpt->pfm == ".29 } }. // FS.*(cpt->pint1) == " << *(cpt->pint1) << endl. achar[3]='O'.20 miembro puntero-a-funcion externa void (C::* pfm)().2. // funcion de salidas int main (void) { // ======================== achar[0]='a'. // M.cpt->pint2 == " << cpt->pint2. // L.cpt->*cpt->pachar2 == " << cpt->*cpt->pachar2 << endl.cpt->pachar1 == " << cpt->pachar1 << endl.Invocar cpt->pfe == ". achar[2]='i'.1. ERROR cout << "2. achar[3]='o'. achar[1]='e'. y.cpt->pachar2 == " << cpt->pachar2.28 pfm = &C::fun. pachar2 = &C::achar. achar[0]='A'.21 miembro puntero-a-funcion miembro C (int n = 0) { // constructor por defecto x = n. // miembro int char achar[5]. // L.2.1. // miembro funcion (metodo) void fan ().1.1. } void fs(C* cpt) { cout << "1. // L.17 miembro puntero-a-miembro-int char (*pachar1)[5].cpt->*cpt->pint2 == " << cpt->*cpt->pint2 << endl. x ==" << x << endl. cout << "5. void C::fun() { cout << "Funcion interna-1. cpt->pint2 = &C::y.Invocar cpt->pfe == ". y = 2*n. return 0. } void fs(C*).2 fs(pc1). ERROR cout << "4. //cout << "1.class C { public: int x. achar[4]='u'.19 miembro puntero-a-miembro-matriz de char void (* pfe)(). cpt->pfe(). // L. // miembro funcion (metodo) int* pint1.cpt->*cpt->pint2 == " << cpt->*cpt->pint2 << endl.1.1. cpt->pfm = &C::fan. achar[1]='E'. achar[2]='I'. } void C::fan() { cout << "Funcion interna-2. // FS. // L. pint1 = &::x.1. // FS. // L.18 miembro puntero-a-matriz de char char (C::* pachar2)[5]. cpt->pint1 = &y. cout << "5. x ==" << x << endl. // L.2.

2.1. } Salida: 1.1 adoptaría la forma: cout << "5. Simplemente se ha sustituido el prefijo c1. las invocación que podríamos llamar "ortodoxa" en las salidas 5.9.1.1.1.2. cpt->pfe(). (cpt->*cpt->pfm)(). se hace necesaria una nueva indirección para acceder al objeto final.1.Invocar cpt->pfe == Funcion externa-1 5.1.2 carece de identificador. (*(cpt->pfe))(). 6 y 7). por las razones ya comentadas. (cpt->pfe)(). para recordar que no es posible obtener el valor de uno de estos punteros [4]. objeto + selector directo de miembro ( 4.cpt->pachar1 == 00420648 4. . de forma que la salida 5.cpt->*cpt->pint2 == 20 8. (*(cpt->pfe))().1.0a). x ==10 Algunas modificaciones runtime ----------------6.Invocar cpt->pfm == Funcion interna-1.1. Sin embargo. a continuación se accede directamente al miembro..Invocar cpt->pfe == ". cout << "5.16) por el prefijo cpt->. Todas las expresiones de acceso utilizadas en las salidas comienzan con una indirección de este puntero para acceder al objeto..1.cpt->*cpt->pint2 == 10 3.1. Incluso se han mantenido algunas sentencias que producen error.*(cpt->pint1) == 301 7.*(cpt->pachar1) == aeiou 4.Invocar cpt->pfe == Funcion externa-2 9.. Recuerde que. x ==10 Comentario: Las salidas se han trasladado desde main a la función fs.cpt->*cpt->pachar2 == AEIOU 5.Invocar cpt->pfe == ". ->* es un solo operador (indirección de punteros a punteros a miembros) con precedencia menor que la del selector indirecto -> ( 4.cout << "9.9.. (cpt->*cpt->pfm)().2.9.1. Es digno de mención que el objeto instanciado en M.2.1. y son simétricas a las del ejemplo anterior. las que siguen son equivalentes: cout << "5.Invocar cpt->pfm == Funcion interna-2..Invocar cpt->pfm == ". Observe también que al ser pfe un puntero-a-función.Invocar cpt->pfm == ".Invocar cpt->pfe == ". En los casos en que este es a su vez un puntero (salidas 2. puntero + selector indirecto ( 4. cout << "5. Observe que las instrucciones también guardan una simetría. En realidad solo se conoce su puntero pc1 (que es pasado como argumento a la función de salida fs).16).cpt->pint1 == 0041A178 2.Invocar cpt->pfe == ".1.2 puede ser expresada mediante: cout << "5.1 y 8. en C++.1.*(cpt->pint1) == 103 2.. 4.

Por ejemplo.. } ..*ct. float propio(float). item < items..3 Utilización de los miembros-puntero Los miembros de clases que son a su vez punteros. else if (peso < 10) costo = &CTransporte::mensajeria. se prestan a las mismas técnicas y aplicaciones que los punteros normales. } return cTot. El puntero es iniciado con la dirección del método adecuado en el momento de la construcción del objeto en función la clase de envío: class CTransporte { private: float correo(float). public: float (CTransporte::* costo)(float).. for (int item = 0. */ }. // constructor }. // puntero CTransporte(float). Deseamos obtener el costo mediante invocación de uno de sus métodos en función del peso total del envío. para menos de 1 Kg se utiliza correo ordinario. float CTransporte::propio (float peso) { /* .. */ }. float CTransporte::mensajeria (float peso) { /* . else costo = &CTransporte::propio. Entre los muchos ejemplos que podrían plantearse. else if (peso < 20) costo = &CTransporte::agencia. int items) { // calcula el costo de expedicion float cTot = 0. float agencia(float).. y para más de 20 Kg medios propios.costo)(exp[item]). float CTransporte::correo (float peso) { /* ... aunque con el coste de eficiencia ya señalado ( 4. por lo que utiliza métodos distintos. Cada una de estas formas de envío tiene distintas formas de cálculo de costo. supongamos que una clase CTransporterepresenta el costo de envío de los productos de un fabricante. CTransporte::CTransporte(float peso) { if (peso < 1) costo = &CTransporte::correo. mensajería normal. . para 1 a 10 Kg. */ }. Una posibilidad sería diseñar distintos métodos que representaran las distintas formas de cálculo de costo. utilizando un puntero-a-método para acceder a la función correspondiente. ...Las asignaciones FS12/14/16/18 se corresponden con M14/17/20/23 del ejemplo anterior. float CTransporte::agencia (float peso) { /* . float mensajeria(float). pero los métodos utilizados varían.1g1).. §3. y muestran la forma de modificar los miembros de instancia a través del puntero al objeto. */ }. para 10 a 20 Kg agencia de transporte exterior.2. cTot += (ct. item++) { CTransporte ct(exp[item]).. } float costoExp(float exp[].

float propio(float). public: float (CTransporte::* costo)(float). y la utilización de un puntero-a-método para acceder a la función correspondiente. El argumento items puede calcularse mediante la expresión: int items = sizeof exp / sizeof exp[0]. return 4*peso. float CTransporte::correo (float peso) { cout << "Correo" << endl. return 3*peso. Por ejemplo. pero los métodos utilizados varían. y para más de 20 Kg medios propios. en el que una claseCTransporte representa el costo de envío de los productos de un fabricante. Es una versión ejecutable (compilable) del caso planteado en la página anterior. La solución adoptada consiste en diseñar distintos métodos que representaran las distintas formas de cálculo de costo. y deseamos obtener el costo mediante invocación de uno de sus métodos en función del peso total del envío. . float agencia(float). mensajería normal. class CTransporte { private: float correo(float). // constructor }. float mensajeria(float). Punteros a miembros no estáticos de clases §1 Ejemplo-1 Muestra de utilización de miembros-puntero. Cada una de estas formas de envío tiene distintas formas de cálculo de costo. return peso. } float CTransporte::agencia (float peso) { cout << "Agencia" << endl. para menos de 1 Kg se utiliza correo ordinario. El puntero es iniciado con la dirección del método adecuado en el momento de la construcción del objeto en función la clase de envío: #include <iostream> using namespace std. En la página adjunta se muestra una versión ejecutable del mismo ejemplo ( Ejemplo). } float CTransporte::mensajeria (float peso) { cout << "Mensajeria" << endl. para 10 a 20 Kg agencia de transporte exterior.Comentario: La función costoExp recibe una matriz de float que contiene los pesos de los paquetes que componen una expedición. return 2*peso. y un int que indica el número de paquetes que la componen. // puntero CTransporte(float). por lo que utiliza métodos distintos. para 1 a 10 Kg. } float CTransporte::propio (float peso) { cout << "M Propios" << endl.

return 0. Como solución proponemos un diseño alternativo en el que la decisión del método de cálculo utilizado. 16. } Salida: Correo Mensajeria Mensajeria Mensajeria Agencia M Propios Costo total: 111. // M7: } cout << "Costo total: " << cTot << " Euros" << endl. else costo = &CTransporte::propio. } int main() { // ============== float exp[6] = {0. lo que supone un consumo considerable de memoria y tiempo de construcción de los objetos. Es suficiente con la versión por defecto proporcionada por el compilador. 2. sustituiría las sentencias M6 y M7 por las siguientes: CTransporte* cPt = new CTransporte(exp[item]).1.4.*ct. Como consecuencia.costo)(exp[item]). cTot += (cPt->*(cPt->costo))(exp[item]).16. . 4. item < items.8. int items = sizeof exp / sizeof exp[0].2. item++) { CTransporte ct(exp[item]). else if (peso < 10) costo = &CTransporte::mensajeria. que ahora pasa a denominarse pfcost. se realiza en el cuerpo de un método auxiliar costo() que es la interfaz de la clase con el exterior. Este método sustituye al puntero del ejemplo anterior. no es necesario definir un constructor explícito. 32.32}. // M6bis // M7bis §2 Ejemplo-2 En cualquier caso.} CTransporte::CTransporte(float peso) { if (peso < 1) costo = &CTransporte::correo. 8. // M6: cTot += (ct. else if (peso < 20) costo = &CTransporte::agencia.24 Euros Comentario: Una forma equivalente de construir el bucle de cálculo. for (int item = 0. float cTot = 0. la solución propuesta presenta un inconveniente importante: el bucle for construye tantos objetos CTransporte como bultos componen la expedición.

} La salida es exactamente análoga a la del ejemplo anterior. float cTot = 0. float mensajeria(float). float agencia(float). return peso. return 3*peso. } cout << "Costo total: " << cTot << " Euros" << endl. int items = sizeof exp / sizeof exp[0]. return 4*peso. 16.16. // un único objeto for (int item = 0. CTransporte ct.1. else if (peso < 20) pfcost = &CTransporte::agencia.4. } float CTransporte::mensajeria (float peso) { cout << "Mensajeria" << endl.8. float propio(float).#include <iostream> using namespace std. } float CTransporte::agencia (float peso) { cout << "Agencia" << endl. else pfcost = &CTransporte::propio. }. class CTransporte { private: float correo(float). 2. } float CTransporte::costo(float peso) { if (peso < 1) pfcost = &CTransporte::correo. item++) { cTot += ct. 8. y de invocarlo a continuación utilizando el puntero. float (CTransporte::* pfcost)(float).32}.2. return (this->*(this->pfcost))(peso). item < items. Comentario: El método público costo() es ahora el encargado de iniciar el puntero pfcost con el valor del método adecuado en función del peso del envío. 4.costo(exp[item]). } float CTransporte::propio (float peso) { cout << "M Propios" << endl. return 2*peso. . float CTransporte::correo (float peso) { cout << "Correo" << endl. else if (peso < 10) pfcost = &CTransporte::mensajeria. // puntero public: float costo(float). 32. } int main() { // ============== float exp[6] = {0. return 0.

int* pint1. void fun (). Se trata de un mero conjunto de ejemplos sintácticos relativos al uso de punteros-a-miembro. Es importante reseñar que estos nueve elementos no son matrices ni miembros de ninguna clase. §4. char (*pachar1)[5].24 .37/38/39) que señalan a un int. char achar[5]. pint2 = &C::x.1g1-2).2. de forma que los objetos ya definidos adopten nuevos valores.1 Ejemplo-3 La estructura del espacio global de este programa es calcada del ejemplo-1 ( 4. } int x = 103.21 miembro puntero-a-funcion miembro // constructor por defecto // L. Para ilustrar su mecánica de uso utilizaremos sendos ejemplos que son una prolongación de los anteriores (utilizados para ilustrar los punteros internos). #include <iostream> using namespace std. a una matriz de char y a un método de la clase. void fan (). Salvo los tres primeros (L. los demás apuntan a miembros que son a su vez punteros. y = 2*n.Observe que en realidad. char (C::* pachar2)[5]. A pesar de su aspecto complicado el programa es análogo a los anteriores. // // // // // // // // miembro int miembro array de char miembro funcion (metodo) miembro funcion (metodo) L. char achar[5]. son aquellos que no son miembros de la clase cuyos miembros se referencian.16 miembro puntero-a-int L. y = 301. pint1 = &::x. int C::* pint2.2. y.18 miembro puntero-a-matriz de char L. void (C::* pfm)().20 miembro puntero-a-funcion externa // L. } void fan () { cout << "Funcion externa-2" << endl.19 miembro puntero-a-miembro-matriz // L. de char void (* pfe)(). La función main solo incluye instrucciones de salida y unas pocas asignaciones. el método costo() actúa como "handle" o interfaz del algoritmo que realmente se encarga de la computación. class C { public: int x.17 miembro puntero-a-miembro-int L. 4.1g1-3 Punteros externos a miembros normales ( no-estáticos) §4 Punteros externos Recordemos que los punteros-a-miembros no estáticos que hemos denominado "externos". son simples punteros del espacio global. C (int n = 0) { x = n. La única novedad es que en las líneas 37 a 45 hemos incluido 9 punteros externos que señalan a los miembros de la clase (que ya nos son conocidos). // Ejemplo-3 void fun () { cout << "Funcion externa-1" << endl.

cout << "12. // M.*iptr << endl. // L.c1. pfe = &::fun.1. // L. cout << "Algunas modificaciones runtime ---------------\n".1.pint1 == " << c1.x == " << c1. // M. (c2. cout << "14. ERROR cout << " 9.1.41 p-a-m miembro int char (*C::* paptr1)[5] = &C::pachar1.c1.*(c1.*paptr1) << endl.achar[0]='A'.x == " << c1. } // definici¢n de punteros externos-a-miembro (p-a-m) int C::* iptr = &C::x. *(c1. achar[3]='o'.2.c1.(c1.iptr.1. achar[2]='I'.pint1 == " << c2.*pptr2.*(c1. (c2. // L.Invocar c2.38 p-a-m void (C::* fptr)() = &C::fun.::x == " << *(c2. .2. // L.y << endl.*iptr) = 123. cout << " 5. pachar1 = &::achar.c2.fun == ".c1.1. (c1. // L.28 *((c1.40 p-a-m int C::* C::* pptr2 = &C::pint2.*pfptr2))().1.*acptr) = 'F'.*paptr1) << endl.*acptr << endl. ERROR cout << " 5.*paptr2) << endl.c1. cout << "16.43 p-a-m matriz de char void (*C::* pfptr1)() = &C::pfe.Invocar c1.::achar == " << *(c1. achar[1]='E'. (c1.*acptr)+4) = 'Z'. cout << "13.37 p-a-m char (C::* acptr)[5] = &C::achar.achar == " << c1. cout << "10. achar[4]='u'.pptr1. cout << "11.1.1. //cout << " 7.*pptr1 << endl. cout << " 6.1.*pfptr1)().c2. //cout << " 4. } void C::fan() { cout << "Funcion interna-2.3.28 pfm = &C::fun.*pptr2) << endl.*fptr)(). // M. // L.1.*(c1. achar[3]='O'.1: Instancias de C achar[0]='a'.45 p-a-m miembro int matriz de char metodo puntero-a-int puntero-apuntero-a-matriz puntero-a-miembro puntero-a-funcion puntero-a-funcion int main (void) { // ======================== C c1(10).c1.*acptr)[2] = 'R'. (c1.2. x ==" << x << endl.y == " << c1.22 cout << "16.29 cout << "17.iptr == " << c1.1.1.25 cout << "16. // M. ERROR //cout << " 8.*iptr << endl.achar == " << c1.1.::achar[0] == " << **(c1.*(c1.fun == ".y == " << c2. cout << "15. // L.29 } }. // L.*pptr1 << endl.c1.fun == ".Invocar ::fun == ".y == " << c2. c2(13).c1.*fptr)().*iptr << endl. // M. // L. cout << " 2.2.c1.c1.pint2 == " << c1. // M.c1.1. void C::fun() { cout << "Funcion interna-1.*pptr1) << endl.42 p-a-m de char char (C::*C::* paptr2)[5] = &C::pachar2.c1.achar == " << c1.pptr1 == " << c1.27 (c1. achar[1]='e'. // L. // L.achar[0] == " << *(c1. ERROR cout << " 1.Invocar c1.pint2 == " << c1. achar[2]='i'.*paptr2)) << endl.*pptr2).1.44 p-a-m externa void (C::*C::* pfptr2)() = &C::pfm.*acptr << endl. iptr = &C::y.39 p-a-m int* C::* pptr1 = &C::pint1.c2.1. cout << " 3. pachar2 = &C::achar. cout << " 3. achar[4]='U'. x ==" << x << endl.1. //cout << " 0.

en especial las declaraciones que corresponden a la parte izquierda (Lvalues) de las expresiones de asignación.39 M.35 " << *(c2.c1.achar[0] == A 14.fun == Funcion interna-1.43 M.1.Invocar c2. // c1.c1.::x == 130 20. M.38 M. == ". .1.1. x ==13 5. 1.c1.2.1.achar == MELON Comentario El primer punto a destacar son las definiciones de los nuevos punteros (líneas L37/45).*(c1.fan == Funcion interna-2.2.1.*fptr)().*pptr2) << endl.Invocar c1.c2.1.x == 10 2.x == **(c1. Como hemos señalado anteriormente ( 4.9.c1.1. // (*(c1.1.*(c1.34 M.*(c1. Por su parte los Rvalues no presentan ninguna singularidad.pint1 == 0041B178 6.1. (c2.1. // cout << "19.*paptr1)+4) = 'z'.*pptr1) = 130.x == 310 21.::x == cout << "20.1.Invocar ::fun == Funcion externa-1 15.44 " << c1.1.*paptr2))[2]='L'.Invocar c2.40 " << *(c1.*paptr2) << endl.achar == return 0.achar == FEROZ 18. " << c1. // *(c1.*paptr1))[2] = 'r'. // cout << "21.2.c1.c2.Invocar c1.::achar[0] == a 12.y == 20 16.fun == Funcion interna-1.*paptr1) << endl.*paptr2)+4)='N'.*(c1.2.*paptr1) = 'f'.31 == ".c1.1.Invocar c1.c1.c1.::achar == *(c1.fun == Funcion interna-1.2.fan *(c1. // cout << "22.*paptr2)) ='M'. M.*pptr2) = 310.achar == AEIOU 13.3.11c) del miembro.Invocar c1.fptr = &C::fan. // *(*(c1.::x == 103 9.achar == AEIOU 3. // (c1.::achar == aeiou 11.2.pint1 == 0041B178 5.1.1.1. // cout << "18.fan == Funcion interna-2.y == 26 16.1.c1.y == 123 17.x == 10 10.c2.*pptr1) << endl.2. x ==10 Algunas modificaciones runtime --------------16.c1.Invocar c2. x ==13 19.1.*fptr)().11) al nombre cualificado ( 4.42 M.c1.::achar == feroz 22.1.1g) nos limitamos a aplicar el operador de referencia & ( 4.fan cout << "18.1.1.*(c1. x ==10 18.*(c1.1.1.1. (c1.c1. M. } Salida: M. x ==10 3.

Las diferencias consisten en que aquí los objetos se referencian a través de punteros externos. el camino seguido para alcanzar la propiedad es: objeto. tiene una precedencia menor que la del selector directo . tanto por su comportamiento como por la sintaxis empleada. de forma que el objeto se consigue con una indirección ( 4.*pfptr2))(). En especial. ( 4.*(c1. utilizando el identificador del objeto seguido del selector directo .*(c1.*paptr1) c1. 7 y 8 se han mantenido como demostración de que es erróneo cualquier intento de obtener el valor de un puntero a miembro (ver comentario en Ejemplo-1 4. En la segunda salida el camino es un poco más complicado: objeto.puntero-a-miembro.9.*puntero). resultando que son necesarias dos indirecciones para conseguir x: ( c1. Lo verdaderamente notable es que se han obtenido valores diferentes para la versión de un (aparentemente único) puntero sobre dos instancias de la clase. *(c1.16) y del nombre del miembro (objeto. En el ejemplo anterior accedíamos a los miembros de clase por el método estándar.45 y salida 15): void (C::*C::* pfptr2)() = &C::pfm. 4.2.*(c1. pero desde luego no "evidentes".*pptr2) ).11) del puntero (c1. Las instrucciones correspondientes a las salidas 0. Aquí el acceso se consigue sustituyendo el nombre del miembro por la indirección de su puntero (objeto. la declaración del puntero-a-miembro pfptr2 y su posterior utilización para invocar a la función (L.9. de forma que pueden compararse las salidas de igual número para verificar la simetría que existe entre ambos. instancia // declaración del puntero // invocación del método de El cuerpo de main es muy parecido estructuralmente al ejemplo-1. y las segundas a un array-miembro. Observe que las expresiones utilizadas son muy similares.*paptr2) // --> aeiou // --> AEIOU .Preste atención a las declaraciones de L44 y L45 cuyas sintaxis quizás sean lógicas. Puede comprobarse como. Las salidas 1 y 9 obtienen el mismo resultado. El grupo de salidas 3 muestra la invocación de los métodos de dos objetos a través de su puntero.*pptr2) // puntero-a-miembro que es puntero-a-int-externo // puntero-a-miembro que es puntero-a-int-miembro Las salidas 10 y 11 se corresponden con 12 y 13. (c1.*pptr1) c1. Las sentencias de salidas 6 y 9 evidencian la distinta sintaxis para acceder a un elemento mediante un puntero externo según el miembro referenciado sea puntero-a objeto-externo o puntero-a objeto-miembro (los paréntesis de estas expresiones no son prescindibles). Observe que las expresiones que proporcionan la salida completa de ambos arrays (10 y 12): *(c1.1g1-2).*.miembro).*iptr ).*(c1. En el primer caso. La diferencia estriba en que las primeras acceden a un array externo.. los nueve punteros externos-a-miembro podrían considerarse "casi" como auténticos miembros de la clase. Nota: recuerde que el operador de deferencia de punteros-a-miembros de clase . el valor actual (10) de la propiedad x del objeto c1.puntero-a-puntero-a-miembro.

3. usamos la de subíndices. Muestran la sintaxis apropiada para modificar los objetos señalados por los punteros. es de lo más interesante: representan la modificación de elementos de una matriz de caracteres a través de un puntero-a-miembro.a. que muestran el resultado para dos objetos de la clase. su indirección representa al primer elemento "A" de la matriz. vuelven a poner de manifiesto como los punteros-a-miembro externos pueden ser tomados casi como propiedades de clase.fun a través de puntero-a-puntero-a-función. Las salidas 18. M.*paptr1) *(c1. Observe que estamos aplicando la notación de subíndices (de matrices) a un puntero. A continuación la salida 17 sirve de comprobación de los cambios efectuados.*acptr)+4) = 'Z'. La sentencia M28 representa una variación del anterior. el primer elemento de ambas matrices. También aquí se asigna un nuevo valor a un punteroa-miembro externo fptr. obtenemos el objeto señalado por el puntero. La sentencia M. La sentencia M.22 realiza una nueva asignación al puntero iptr que modifica la que se realizó en su definición (L. El grupo de asignaciones que sigue. Esto se debe a que "aplicar subíndices a un puntero. así como algunas salidas para comprobación de los nuevos valores. valor que es sustituido por "F". El compilador se encarga de conocer que estos valores son el punto de inicio de sendos arrays de tamaño 5 y el objeto cout se encarga de mostrarlos en todo su contenido.*(c1.38) y que a su vez.27 // M. *((c1.*acptr)[2] = 'R'.2). La sentencia M. En estas expresiones tampoco son prescindibles los paréntesis.28 // M. Si aplicamos una indirección adicional a estos valores.29 representa la indirección del "puntero" achar después de sumarle cuatro unidades.25 modifica el objeto (propiedad de instancia) señalado por el puntero.22.*acptr) = 'F'. . Puesto que c1.en realidad contienen la dirección de inicio de ambas matrices (valor de los punteros-miembro).matriz equivale a aplicarle el operador de indirección" ( 4. achar puede ser tomado como puntero a su primer elemento ( 4.39).29 Recordemos que acptr es puntero-a-miembro-matriz achar de cinco elementos char (L.27/28/29. **(c1. (c1. por "Z". A su vez la sentencia M. que modifica el que se asignó en su definición (L. M. // M. lo que significa modificar el valor actual del último carácter "U" de la matriz.37).27 representa la modificación del objeto señalado por el puntero. Observe también como esta expresión tiene un operador de indirección * menos que cualquiera de las otras dos (que son equivalentes). El grupo de salidas 16 verifica el resultado de estas modificaciones.31 es muy parecida a M. En este caso.*paptr2)) // --> a // --> A Las salidas 14 y 15 representan respectivamente la invocación de una función ::fun y un método c.3. La parte de main que sigue a las modificaciones runtime contiene una serie de expresiones de asignación muy interesantes. *(c1.2). con la diferencia de que en vez de utilizar la aritmética de punteros.*acptr representa a achar.

achar[4]='U'. // L.43 *(c1.*(c1.42 (c1. en vez de ser referenciada mediante puntero-a-matriz. M. // L.*paptr2)+4)='N'.40 *(c1. // miembro funcion (metodo) int* pint1.20 miembro puntero-a-funcion externa void (C::*pfm)(). // L. La consecuencia es que donde antes se necesitaban una/dos indirecciones *. y = 301.*paptr2))[2]='L'. para mostrar la sintaxis de acceso mediante un puntero a la clase. §4.*paptr1) = 'f'. char achar[5].39 *(*(c1. M. // L. achar[3]='O'.*paptr1))[2] = 'r'. y.19 miembro puntero-a-miembro-matriz de char void (*pfe)().Los grupos M38/39/40 y M42/43/44 realizan función análoga a la del grupo M27/28/29 ya comentado . y = 2*n. } void fan () { cout << "Funcion externa-2" << endl. // M.*paptr2)) ='M'. // Ejemplo-4 void fun () { cout << "Funcion externa-1" << endl.16 miembro puntero-a-int int C::* pint2.*paptr1)+4) = 'z'. #include <iostream> using namespace std. achar[2]='I'.24 pint2 = &C::x. // miembro funcion (metodo) void fan ().2 Ejemplo-4 El caso que se presenta guarda con el anterior ( ) la misma relación que el ejemplo 2 con el 1.*(c1. // miembro int char achar[5]. Se mantiene la estructura con pequeñas modificaciones y también aquí se han trasladado las salidas a una función auxiliar fs. La diferencia es que ahora la cadena de acceso a la matriz de caracteres es un poco más larga. // L. Observe que la precedencia de los operadores involucrados hace imprescindible la presencia de paréntesis auxiliares en las sentencias 39 y 40. // miembro array de char void fun (). **(c1.44 // // // La diferencia entre ambos grupos estriba en que en el primero. // L.38 (*(c1.18 miembro puntero-a-matriz de char char (C::* pachar2)[5]. M. } int x = 103.21 miembro puntero-a-funcion miembro C (int n = 0) { // constructor por defecto x = n. el objeto accedido (matriz) es externo. ahora se necesitan dos o tres para acceder al objeto. aquí el acceso se realiza mediante puntero-a-puntero-a-matriz. // M.17 miembro puntero-a-miembro-int char (*pachar1)[5]. . achar[1]='E'. achar[0]='A'. mientras que en el segundo la matriz es miembro. // M.*(c1. // L. pint1 = &::x. class C { public: int x.

1.41 p-a-m puntero-a-miembro int char (*C::* paptr1)[5] = &C::pachar1. cout << " 9.1.fun == ". (cpt->*fptr)().achar[0] == " << *(cpt->*(cpt->*paptr2)) << endl.fan == ".c1.x == " << cpt->*iptr << endl.22 cout << "16.c1.29 } }.c1. cout << "15.2.37 p-a-m int char (C::* acptr)[5] = &C::achar.38 p-a-m matriz de char void (C::* fptr)() = &C::fun.3. // M.34 .28 // L. *(cpt->*acptr) = 'F'.c1.c1.Invocar c1. achar[3]='o'. fs(pc1).c1. // L. cout << " 5. (cpt->*iptr) = 123. cout << "10. } // definici¢n de punteros externos-a-miembro (p-a-m) int C::* iptr = &C::x.1. iptr = &C::y.y == " << cpt->*iptr << endl. cout << " 3.1. C* pc1 = new C(10). cout << " 6.1. pfe = &::fun.pachar1 = &::achar. } void C::fan() { cout << "Funcion interna-2.45 p-a-m puntero-a-funcioon miembro void fs(C*). x ==" << x << endl. // L. pachar2 = &C::achar.42 p-a-m puntero-a-matriz de char char (C::*C::* paptr2)[5] = &C::pachar2. (cpt->*(cpt->*pfptr2))(). // L. // M.1.1.43 p-a-m puntero-a-miembro matriz de char void (*C::* pfptr1)() = &C::pfe.::achar[0] == " << **(cpt->*paptr1) << endl.1. (cpt->*fptr)().39 p-a-m metodo int* C::* pptr1 = &C::pint1.achar == " << cpt->*acptr << endl.::achar == " << *(cpt->*paptr1) << endl. cout << "13. // funcion de salidas int main (void) { // ======================== achar[0]='a'.c1. void C::fun() { cout << "Funcion interna-1.c1. // L. x ==" << x << endl.achar == " << cpt->*(cpt->*paptr2) << endl.28 *((cpt->*acptr)+4) = 'Z'.Invocar c1.x == " << cpt->*(cpt->*pptr2) << endl.fun == ". // M.1.44 p-a-m puntero-a-funcion void (C::*C::* pfptr2)() = &C::pfm.40 p-a-m puntero-a-int int C::* C::* pptr2 = &C::pint2.1.y == " << cpt->*iptr << endl.1.29 cout << "17.::x == " << *(cpt->*pptr1) << endl.pint1 == " << cpt->*pptr1 << endl.c1. cout << "11.27 (cpt->*acptr)[2] = 'R'. achar[1]='e'. pfm = &C::fun. // L. cout << "14.1. cout << "Algunas modificaciones runtime ---------------\n". *(cpt->*pptr1) = 130. // L. achar[2]='i'.achar == " << cpt->*acptr << endl. achar[4]='u'. // L. // M. fptr = &C::fan.25 cout << "16. // M.Invocar c1.1. } void fs(C* cpt) { cout << " 1. // L. cout << " 2.Invocar ::fun == ". // L. cout << "12. return 0.1. // M. // M. (cpt->*pfptr1)(). // L.31 cout << "18.

::achar == " << *(cpt->*(cpt->*paptr2)) = 'M'.1.c1.1. Puesto que la instancia c de la clase C no es directamente accesible. x ==10 19.c1.c1. x ==10 Algunas modificaciones runtime --------------16.fun == Funcion interna-1.fun == Funcion interna-1.::achar == feroz 22. // cout << "22.1. // (*(cpt->*paptr1))[2] = 'r'.Invocar c1. A continuación una nueva indirección del puntero-a-miembro permite acceder a estos.38 M.c1.x == 310 21.c1.1.1.achar == MELON Comentario: A la luz de los aprendido en los ejemplos anteriores es fácil seguir la mecánica y notación utilizadas.::x == 130 20.2.1.achar == FEROZ 18.c1.45.Invocar c1.::x == 103 9.1.1.c1. // M.44 cpt->*(cpt->*paptr2) << endl.c1.achar == AEIOUÐ 3.::achar[0] == a 12.Invocar ::fun == Funcion externa-1 15. x ==10 5.c1.1.achar == AEIOU 13.achar == " << } Salida: *(cpt->*pptr1) << endl. M.y == 123 17.cpt->*(cpt->*pptr2) = 310.1.1.fan == Funcion interna-2.x == " << **(cpt->*paptr1) = 'f'. Aunque el procedimiento de obtención de resultados es ligeramente distinto.::x == " << cout << "20.1. el acceso se consigue aplicando el operador de indirección sobre su puntero cpt.c1. es también digna de mención la sintaxis de invocación del método de instancia a través de su puntero pfptr2 (salida 15): (cpt->*(cpt->*pfptr2))(). // *(*(cpt->*paptr1)+4) = 'z'.39 M.40 *(cpt->*paptr1) << endl.c1.achar[0] == A 14.x == 10 2. // cout << "21. estos son iguales que en el ejemplo precedente (ejemplo-3 ).44 y L.1. // (cpt->*(cpt->*paptr2))[2]= 'L'.1. 1.pint1 == 0041B178 6. (teniendo expresiones así ¿Quién necesita enemigos? :-)) §5 Notas particulares .35 cout << "19.1.1. ya señaladas en el comentario del ejemplo anterior.42 M. Aparte de las asignaciones L.x == 10 10.1. M.c1.1.1.43 M.1.1. // *(cpt->*(cpt->*paptr2)+4)= 'N'. cpt->*(cpt->*pptr2) << endl.c1.Invocar c1.1.::achar == aeiou 11.y == 20 16.3.

} // Ok. realizado en un caso sí y en otro no. §b Si en la inicialización existente en L. el procedimiento correcto de inicialización es: .25-bis OK!! ninguno de los compiladores probados (BC++ y GNU Cpp) produce error. o de un "casting" automático.28 del Ejemplo: error C2440: '=' : cannot convert from 'char (*)[5]' to 'char (C::*)[5]. MS VC++ 6. La situación puede ser esquematizada como sigue: class C { int x. Probablemente se trata de un error de ambos compiladores.95.26 para pint2 (miembro puntero-a-miembro-int) se sustituye el miembro C::x: pint2 = &C::x. // L. aunque supuestamente deberían indicarlo. salvo contadas excepciones. por la variable global ::x pint2 = &::x.2a).1g1-2). se sustituye la dirección de la variable ::x del espacio global: pint1 = &::x. las que han inducido al propio Stroustrup a calificar los punteros a miembros como un "punto oscuro" de C++. dentro de esta no están permitidas las asignaciones ( 4.26 ambos compiladores "protestan" y generan un error indicando que la conversión de tipo no es imposible (el puntero no es del tipo adecuado). ya que el tipo de x es distinto en ambos casos. Si en la inicialización de L.§a Los ejemplos anteriores compilan sin problemas con BC++ 5. quizás sea este tipo de peculiaridades y alguna otra que veremos al tratar de los punteros-a-miembros estáticos. Desde luego.25 para pint1 (miembro puntero-a-int) del Ejemplo 1 ( 4. con las opciones por defecto. Sin embargo. Posiblemente se deba a un error de dicho compilador o que se necesite una opción de compilación desconocida para mi.25 por la dirección del miembro x: pint1 = &x.2d1). por el compilador. o de una excepción intencionada (podría argumentarse que a fin de cuentas ambos objetos son int).5 y GNU Cpp 2.11. // L. de una conversión de tipo realizada automáticamente por el compilador. cuya existencia tiene precisamente esta finalidad. Como se ha visto en los ejemplos anteriores. Sin embargo. pero hemos repetido en varias ocasiones que. int C::* xptr = &C::x. // L. se trate de un error.2.11. declaración de miembro // ERROR!! Asignación no permitida La inicialización [1] de miembros debe realizarse en los constructores ( 4. la sustitución inversa no es posible.3.26-bis ERROR!! // L. §6 Advertencia Los miembros de clase deben ser declarados dentro del cuerpo de la clase.0 produce un error en L.

} } int C::* xPtr = &C::x. Por ejemplo. } // Ok. // L. class C { public: int x. definición externa En ocasiones podría parecer que esto también es posible con los punteros-a-miembro. int C::* xPtr.x == 10 El programa compila sin dificultad y las salidas proporcionadas son correctas.*xPtr << endl. } } // declaración de miembros // Constructor // Ok. añadiendo la siguiente sentencia a main.7: declaración // L. public: void putX(int a) { x = a. la utilización de xPtrproduciría un error. pero se produce un error fatal en runtime: .x == " << c1. Considere el siguiente ejemplo: #include <iostream> using namespace std.x == " << c1. } int getX() {} } int C::getX(){ return x. } Salida: c1. C(int a) { x = a. cout << " c1. int* xPtr. aunque la definición también puede estar fuera: class C { int x. Sin embargo. declaración+definición // Ok. En este caso la declaración y definición pueden realizarse dentro del cuerpo de la clase.3: return 0. cout << " c1. no se obtiene ningún error de compilación. C() { x = 0.12: asignación externa? int main (void) { // ======================== C c1 = (10). asignación correcta Existe una excepción cuando los miembros son funciones (métodos). declaración (prototipo) // Ok. en determinadas circunstancias.x << endl. xPtr = &C::x.class C { int x.x == 10 c1. // M.

sino la declaración de un nuevo puntero externoa-miembro en el espacio global: ::xPtr.x == " << *(c1.xPtr) << endl. Ejemplo: class C { public: static const int k1 = 3.cout << " c1.b. De forma que el miembro C::xPtr queda sin inicializar.12 no es la inicialización del miembro xPtr declarado en L.2.11. . Es decir. // M.2.. // Error!! . podríamos hacer aquí una subdivisión de los casos que pueden presentarse con punteros p a miembros estáticos m de una clase C: 1...Punteros que son miembros estáticos 2. pero M.1g2 Punteros a miembros estáticos §1 Sinopsis Recordemos que.1g). }.. Lo mismo que ocurre con los punteros a miembros-no estáticos.. const int* ptr2 = &C::k2 . solo pueden declarárseles punteros si se cumple dicha condición.Punteros que son miembros de C (punteros internos a miembros estáticos).Punteros que son objetos externos a la clase (punteros externos a miembros estáticos). la sentencia L. static const int k2 = 4.4: La explicación a este "extraño" comportamiento..4 es la indirección del miembro xPtr del objeto c1. es que en realidad. int main () { const int* ptr1 = &C::k1. los punteros a miembros no-estáticos no son considerados como auténticos punteros a objeto. el resultado puede ser igualmente basura o un error de runtime si la dirección señala a un punto fuera del espacio asignado a la aplicación.7) se parecen más a objetos normales de un subespacio que a miembros de clase. sus punteros sí pueden ser considerados como punteros ordinarios. tales miembros tienen unadefinición fuera de la clase.a.7. En M.. } // definicion // definición [1] // Ok. dado que los miembros estáticos ( 4. const int C::k1. Por contra. §2 Declaración de Punteros a miembros estáticos La primera observación a tener en cuenta es que puede tomarse la dirección de miembros estáticos si. 1. por las razones ya señaladas ( 4. y solo si. Como no ha sido inicializado correctamente y su valor es basura.. su valor es basura y señala a una posición de memoria arbitraria.Punteros que son miembros normales (no-estáticos) 1. 4.3 se está utilizando el puntero externo ::xPtr.

// M.2 puntero-a-int int** pptr. static char* c. // M. fptr = &C::fun.95 sí parece adecuarse al Estándar. static void fun (). // M. C c1.5 puntero-a-función // Asignaciones de punteros a miembros concretos (de clase): xptr = &C::x. // Punteros externos a miembros estáticos // // // // L.7 L. cout << "Valor c1. §2. char* C::c = "aeiou".5 no solo permite definir dichas propiedades en el interior de la clase.x == " << **pptr << endl.11 void C::fun () { cout << "Valor miembro x == " << x << endl. static int* p. sino incluso tomar la dirección de una de estas variables aunque no exista una definición en el exterior (probablemente esto último se deba a un error de este compilador). int main () { // ======================== // Declaraciones de punteros genéricos a cada tipo: int* xptr. // M.15 Iniciadores de miembros int* C::p = &C::x.x == " << *xptr << endl. // M. #include <iostream> using namespace std. } . GNU Cpp 2.c == " << *cptr << endl.7 pptr = &C::p. comenzaremos por un caso sencillo que contempla la segunda hipótesis del epígrafe anterior (punteros a miembros estáticos que son externos a la clase). // L.11 Instancia de C // Invocaciones de miembros (de instancia) a través de sus punteros cout << "Valor c1. En cambio Borland C++ 5. cout << "Valor c1.Comentario: respecto a este punto.16 return 0. } }.6 L. cptr = &C::c. Visual C++ 6. } int C::x = 3.4 puntero-a-puntero-a-carácter void (* fptr) (). (*fptr)(). c = "AEIOU". // M.9 constructor por defecto // L. el comportamiento de los compiladores difiere según su grado de adaptación al Estándar.0 de MS no permite realizar definiciones de propiedades estáticas enteras en el interior de la clase.8 L. C () { x = 13. p = &C::x. // M.3 puntero-a-puntero-a-int char** cptr. class C { public: static int x.1 Ejemplo-1 Para ilustrar el uso de punteros a miembros estáticos y las características especiales de este tipo de miembros.

Recordar que M. Es importante observar que si estos miembros no hubiesen sido estáticos.16b // M. Sin embargo..Salida: Valor Valor Valor Valor c1. aunque su mera declaración ha influenciado en los valores de salida por obra y gracia del constructor..11 ERROR!! se obtiene un error de compilación al intentar posteriormente la asignación en L.6b miembro puntero-a-miembro-int // L.11) sobre el puntero. el ejemplo está plagado de sorpresas.16 puede ser sustituida por: (fptr)().16c Es ilustrativo observar que en todo el programa no se ha utilizado para nada la instancia c1 de C.11). p y c fuera del cuerpo de la clase (líneas 15 a 17) que son necesarias en estos casos (§2 ).13/16 acceden a los objetos señalados al estilo tradicional. Curiosamente el compilador no considera que el tipo de &C::x sea puntero-a-miembro-de-C!!. sino que deben ser declarados como simples punteros a-tipoX.. como punteros a objetos normales.14) es siempre un caso especialmente autorizado. // L.. Recuerde que C++ no permite definiciones múltiples ( y que la definición de métodos fuera del cuerpo de la clase (L. A su vez las sentencias M.. p = &C::x. En primer lugar.2/5 definen cuatro punteros al estilo clásico que luego son asignados sin inconveniente a miembros de la clase (líneas M. utilizando el operador * de indirección ( 4. nos encontramos ante otra de esas inconsistencias que tanto se reprochan a C++. esto es.x == c1. Como podemos ver en el resto de asignaciones. Vemos que ahora no es posible la declaración de estos como punteros-a-miembro tipoX-declaseC... la sentencia L. Una vez más se pone de manifiesto que los miembros estáticos no son propiamente miembros de instancia. Es decir. hemos visto que. La segunda peculiaridad son las definiciones de las propiedades x. 4. . Así pues. estos miembros son considerados meramente como objetos de un subespacio y su existencia es totalmente independiente de la existencia de instancias concretas de la clase.7/10). aunque aceptable.11: Cannot convert 'int *' to 'int C::*'. pues su invocación ha modificado los valores de inicio de los miembros .9.. fptr()..6 no es la forma adecuada de designar un puntero-a-miembro (que es como se utiliza en L.2) Las líneas M.x == c1. // M.1.c == miembro 13 13 AEIOU x == 13 Comentario: A la luz de los expuesto en el capítulo anterior. se habría obtenido un error de compilación: Multiple declaration for 'C::x'.. al intentar utilizar la forma correcta: int C::* p.

} void func (C* cpt) cout << "S. // puntero-a-puntero-a-int asignado a miembro-p char** cptr = &C::c. el programa funciona correctamente.p == " c.4 "S.11 de main en la que se crea el objeto.estáticos [2]. ofrecemos una versión simétrica de la anterior pero añadiendo un puntero-aobjeto. void func (C*).2 c. // definición de punteros int* xptr = &C::x.5 "S. aunque la nueva salida es [3]: Valor Valor Valor Valor c1.6 "S.x cout << "S. static int* p. #include <iostream> using namespace std. } C () { // contructor por defecto x = 13.2 Ejemplo-2 Para completar. p = &x. " << *cptr << endl.p == c1.1 c. " << **pptr << endl. } }.c == miembro 3 3 aeiou x == 3 §2.5 "S.x cout << "S. c = "AEIOU". static void fun () { cout << "Valor miembro x == " << x << endl. " << *(cpt->c) << endl. // Iniciadores de miembros int* C::p = &C::x.3 c. << *(cpt->p) << endl.c[1]== . // prototipo de funcion auxiliar class C { public: static int x. << cpt->*cpt->p ERROR!! << cpt->c << endl. static char* c. // puntero-a-int asignado a miembro-x int** pptr = &C::p.7 // ======================== { == == == // función auxiliar " << *xptr << endl.x == " c. Puede comprobarse que si eliminamos la sentencia M. // puntero-a-puntero-a-char asignado a miembro-c int main (void) { C* cp = new C. func(cp).x == c1. int C::x = 3. char* C::c = "aeiou". c.c cout cout //cout cout cout } << << << << << "S.c == " c.x == " c. << cpt->p << endl.

2 Aritmética de punteros §1 Sinopsis La aritmética de punteros se limita a suma.2). no había sido posible anteriormente (como ejemplo. también por una larga tradición C. ver la salida 1.4 en el sentido que proporciona el valor del puntero (que es una dirección de memoria). Las tres primeras salidas no tienen ninguna consideración especial que no hayamos señalado antes.4 S. 4. S.6 S.1 S.x == 13 c.2.c[1]== A Comentario: Tal como afirmamos en el ejemplo anterior. La expresión *(cpt->c) de la salida S. comparación y asignación. En este caso el primer elemento de la cadena. pptr y cptrfuesen visibles (para esto las hemos sacado de main y las hemos situado en el espacio global del fichero).4.5 S.5 obtiene el valor del miembro x mediante la indirección de su puntero p (que es a su vez miembro de la clase).x == 13 c.6 es análoga a S. sin embargo la sintaxis utilizada *(cpt->p) es la que corresponde a puntero-interno-a-int. Como es usual al tratarse de un puntero.2 del ejemplo-2 en la página anterior 4. Aunque en este caso. resta. no la de puntero-interno-a-miembro-int cpt->*cpt->p.1g1).c == AEIOU c. Las salidas 4 a 7 si merecen un comentario especial: La primera sorpresa es la propia existencia de S. Tema relacionado:  Punteros a miembros de clases implícitas ( 4.3 S.c == AEIOU c. vemos que el objeto (lo denominamos c) solo tiene una influencia indirecta en los valores de salida a través del constructor (invocado por el operador new). de forma que estas sentencias podrían estar en cualquier parte siempre que las definiciones de xptr.x == 13 c.4b). Esta sentencia.7 obtiene el valor señalado por el puntero. Solo recordar que en realidad no utilizan el objeto c (ni su puntero cpt).2 S. el compilador proporciona la cadena en vez del valor hexadecimal. La salida S. que obtiene el valor de un puntero-a-miembro.p == 0041A220 c.12.2. Las operaciones aritméticas en los punteros de tipoX (punteros-a-tipoX) tienen automáticamente en cuenta el . su valor se obtiene en hexadecimal ( 2.2. y por tratarse de un puntero-a-cadena de caracteres.Salida: S.7 c.

suponiendo una matriz de double con 100 elementos. la sentencia ptr++.12) Asignación Asignación pt1 = void . y n un tipo entero o una enumeración. Es decir.tamaño real de tipoX. Por ejemplo.9.2. si ptr es un puntero a dicha matriz. supone incrementar el Rvalue de ptr en 6. ptr2 punteros a objetos del mismo tipo. §2 Operaciones permitidas Sean ptr1. las operaciones permitidas y los resultados obtenidos con ellas son: Operación pt1++ pt1-pt1 + n pt1 . La aritmética realizada internamente en los punteros depende del modelo de memoria en uso y de la presencia de cualquier modificador superpuesto. el número de bytes necesario para almacenar un objeto tipoX [2]. Nota: no confundir el puntero-a-matriz con un puntero a su primer elemento (que aquí sería puntero-a-double). porque el tamaño de la matriz es precisamente 100x64 bits.1b) booleano booleano puntero puntero genérico <R> es una expresión relacional ( 4. Las operaciones que implican dos punteros exigen que sean del mismo tipo o se realice previamente un modelado apropiado .400 bits.pt2 pt1 == NULL pt1 != NULL pt1 <R> pt2 pt1 = pt2 Resultado puntero puntero puntero puntero entero booleano Comentario Desplazamiento ascendente de 1 elemento Desplazamiento descendente de 1 elemento Desplazamiento ascendente n elementos [4] Desplazamiento descendente n elementos [4] Distancia entre elementos Siempre se puede comprobar la igualdad o desigualdad con NULL ( 3.n pt1 .

o un runtime. siptr1 apunta al tercer elemento de una matriz. §3 Homos señalado que cuando se realizan operaciones aritméticas con punteros. Sin embargo. ptr1-ptr2. <=. >=. etc). o uno después del último. el resultado ptr2-ptr1 es 7 (en realidad. <. Si ptr1 apunta al último elemento del array. siempre que ptr se mantenga en su rango legal (entre el primer elemento y uno después del último). desde luego no existe un elemento tal como: "uno después del último". añadir 5 al puntero-a-tipoX lo hace avanzar 50 bytes en memoria (si se trata de punteros a elementos de una matriz. pero ptr+2 es indefinido (lo que a efectos prácticos significa que devolverá basura. añadirle un entero n (al puntero) supone hacerlo hace avanzar un número n de objetos tipoX. Produce otro puntero. en estas condiciones los operadores relacionales ( 4. Del mismo modo. volcado de memoria. se tiene en cuenta el tamaño de los objetos apuntados. de modo que si un puntero es declarado apuntando-atipoX. §4 Ejemplos: N. Por ejemplo. aplicando el operador de indirección * a un puntero después del último conduce a una indefinición. funcionan correctamente. la diferencia de dos punteros solo tiene sentido cuando ambos apuntan a la misma matriz).9. Para esto es necesario que ptr1 y ptr2 apunten a elementos existentes. Si ip es un puntero al elemento m[j] de una matriz (dicho de otro modo: si *ip == &m[j] ). observe que no está definida la suma entre punteros. ptr-1 es legal (puntero al último elemento). 1. ptr+1 es legal. produce un entero n del tipo ptrdiff_t definido en <stddef. La resta de dos punteros a elementos de la misma matriz.12): ==. Si tipoX tiene un tamaño de 10 bytes. Expresión Resultado ip+10. el nuevo puntero apunta a otro elemento m[j+10] de la misma matriz. con lo que *(ip+10) == &m[j+10] . >. Si ptr es un puntero a un elemento de una matriz.La comparación de punteros solo tiene sentido entre punteros a elementos de la misma matriz. pero se permite que ptrtenga dicho valor. y ptr2 apunta al décimo elemento. !=. la diferencia entre dos punteros resulta ser el número de objetos tipoX que separa a dos punteros-a-tipoX.h> Este número representa la diferencia entre los subíndices i y j de los dos elementos referenciados (n = i-j). Si ptr apunta a uno después del último. supone avanzar n elementos en la matriz [3]). Informalmente puede pensarse en ptr + n como avanzar el puntero en (n * sizeof(tipoX)) bytes.

Es decir. ip++. en un destino definido por un puntero p. } .2. es decir. ++*ip. el resultado es un puntero al elemento m[j+1]. incrementa en 1 el valor del puntero.9. Dado que el paréntesis tiene máxima precedencia y asocia de izquierda a derecha ( 4. Observe que el paréntesis es necesario. Igual que el anterior: incrementa en 1 el valor del objeto referenciado por ip El resultado es otro puntero. Incrementa en 1 el valor (Rvalue) del objeto referenciado por ip.3f) definida por un puntero a su origen s.2. (*ip)++ 8. Si ip es un puntero al elemento m[j] de una matriz. *ip+1. Añade 10 al objeto *ip (objeto referenciado por ip) y lo asigna a y Equivale a: *ip = *ip + 1. Igual que el caso 4.9. Después se tomaría la indirección. Considerando que el final de la cadena está señalado por el carácter nulo. el resultado final sería el valor del elemento m[j+1].) Igual que el caso anterior. s++. y = *ip+10. la expresión *ip++ equivale a *(ip++) 3. sin él la expresión modifica la posición de memoria señalado por ip. // copiar carácter s++ . Por tanto.0a). 7. se realiza ip++. p++ . Lo anterior sería equivalente a: while (*s ) { *p = *s . ++ip. 5. 4. y que cualquier <expresión> es cierta si <expresión> != 0. con lo que el nuevo puntero señala a otra posición (ver caso 1. } Teniendo en cuenta que estamos usando el postincremento ( 4. p++. incrementa en 1 el valor del objeto referenciado por ip. El valor resultante es el de incrementar en 1 el valor del objeto apuntado por ip §5 Ejemplos Consideremos copiar una cadena NTBS ( 3.. el proceso podría ser: while (*s != 0) { *p = *s. Equivale a ip = ip+1. Incrementa en 1 el valor del puntero. es decir. *ip += 1.1) para s y p. 6.

Se asigna *p *s 2. el único problema es que sin un entrenamiento previo. De hecho. // str puntero a char // ip puntero a int // asignación str = ip (ip puntero a char) . El postincremento ++ no cambia p hasta que el carácter ha sido actualizado (primero se efectúa la asignación y después se incrementa). En mi opinión. son muy frecuentes en los bucles que involucran punteros. la expresión *++p indica el carácter apuntado por p después que ha sido incrementado.El incremento se puede expresar en la misma sentencia de asignación: while (*s ) { *p++ = *s++ . Aunque en realidad solo se trate de uno más de los "idioms" ( 4. int *ip. Ambos tipos de expresiones de asignación con punteros. Nota: esta capacidad de C++ para permitir formas de código tan extraordinariamente compactas. asignarlo a val son las sentencias estándar de cargar/descargar valores val de una pila LIFO [1] manejada por un puntero p. val = *--p. // §5. puede quedarse bastante perplejo.1 La razón es que después de la última asignación que correspondería al carácter ' \0' de s.Se incrementa p La comparación puede hacerse en el mismo momento que la copia de caracteres: while ( *p++ = *s++) . El orden de ejecución de las operaciones involucradas es: 1. el resultado de la asignación sería justamente este valor (0 == falso) y se saldría del while.1 anterior. las expresiones: *p++ = val. simultaneadas con pre/post incrementos/decrementos. Ejemplo: char *str.Se incrementa s 3.. Por la razón inversa.. la primera vez que se topa uno con una expresión como la §5. str = (char *)ip..13) de fondo de armario de cualquier programador C++ §6 Conversión de punteros Un puntero de un tipo (tipoX) puede ser convertido a otro (tipoY) usando el mecanismo de conversión o modelado de tipos. tiene fervientes defensores y acérrimos detractores. que utiliza el operador (tipoY*). // cargar la pila con val // sacar de la pila el último valor. } La razón es que *p++ indica el carácter apuntado por p antes que sea incrementado.

char needle) { unsigned long stLen = str.1.cptr + i (suma del puntero con el incremento). Observe que este comportamiento anómalo se produce a pesar de lo señalado al respecto por el Estándar [4]. 4.2 dedicado a los punteros.5. } Se trata de una función que proporciona la posición de la última ocurrencia de un carácter (needle) en un una cadena de caracteres (str). produce un error fatal de runtime. empezando a contar desde cero para el primer carácter.2. y en consecuencia no pueden realizarse con ellas . §1 Sinopsis Las referencias son un tipo de dato C++ estrechamente relacionado con los punteros. En concreto.9. en especial en el paso de parámetros a funciones. y haber sopesado todas las posibilidades (incluyendo que el compilador estuviese "poseido" :-). las referencias tienen muy poco o nada que ver con aquellos. Sin embargo. produce un extraño resultado negativo en las pruebas realizadas con el compilador BC++ 5.3 Referencias Nota: aunque las hemos incluido bajo el rubro general §4.2.De forma general. } return -1L. y de que losunsigned long son tipos enteros. Después de perder un par de horas intentando diagnosticar el problema. for (register unsigned long i = stLen .len(). Ningún otro tipo para i. La función devuelve -1 si el carácter no se encuentre en la cadena. la rutina anterior.9). --i) { if (*(str.cptr + i) == needle) return i. mientras que con GNU G++ 3.4. i>=0. Aunque compila sin dificultad. producía error.1b) "Modelado de tipos" ( 4. Una referencia de un objeto no es un objeto [5]. el métodolen() proporciona la longitud de la cadena. en el sentido que no tiene su propio espacio de almacenamiento como ocurre con los punteros. Por ejemplo int o long. existen similitudes en cuanto a su funcionalidad y a un cierto maridaje entre ambos tipos. En nuestro caso los objetos de la clase String albergan cadenas alfanuméricas. unos y otros son tipos distintos. Cuando i es un unsigned long.2-20040916-1 para Windows. el operador-moldeador (tipoX *) puede convertir el operando (un puntero de cualquier tipo) a un puntero-a-tipoX. resultó que estaba en el resultado de str. y el miembro cptr es un puntero al primer carácter. Como ejemplo de lo insidioso que pueden llegar a ser algunos rincones de los compiladores C++. la suma con el puntero produce un resultado negativo!!. En caso contrario devuelve la posición. Temas relacionados:   "Modelado de tipos con punteros" ( 4. desde el punto de vista de la tipología. considere el siguiente ejemplo: long strRchr (const String& str. en especial los operadores dynamic_cast y reinterpret_cast para convertir un puntero al tipo deseado.

La sintaxis general es: <tipo_objeto> & <etiqueta_referencia> [ = <iniciador> ] Ejemplo: int x. El paso de "referencias" a funciones esta suficientemente resuelto en C y C++ con los punteros. z = z * 4. Stroustrup pensó que hacían más simple la programación (los programadores noveles podrían evitar los punteros)". . En realidad.. este concepto. En adelante z actúa como un alias de x. int & z = x. crearlas con el operador new.. las referencias de Rvalue ("rvalue references") que son una adición que será incluida en la próxima revisión del Estándar C++. o crear matrices de referencias. La realidad es que las referencias fueron introducidas como un artificio imprescindible en el mecanismo de sobrecarga de operadores. 'referencia' // decimos que x es el 'iniciador' y que z es la Estas sentencias declaran e inicia la variable z como referencia-a-entero. Por ejemplo. x == 16 §2a En el párrafo anterior hemos dicho "casi" porque la referencia y el objeto referenciado son de tipo . Él mismo nos indica: "El uso principal de las referencias es para especificar argumentos (parámetros) y valores devueltos de funciones en general y para operadores sobrecargados en particular" [3]. es un recurso de C++ para pasar argumentos a funciones permitiendo que los argumentos no sean simples variables locales de la función. Como se verá a continuación . y la asocia con la variable x (que es un entero).9.18c) veremos una explicación del verdadero sentido de esta afirmación. que también existe en otros lenguajes [7].muchas de las operaciones que se relacionan con objetos. o de Lvalue ("lvalue references") para distinguirlas de otro tipo. cuyo símbolo es && [8]. si hacemos: int x = 4. // z es referencia-a-int. z == 4 // z == 16. En realidad puede considerarse que z es "casi" un sinónimo de x (como si fuesen la misma variable). Nota: en este capítulo nos referimos a las denominadas referencias tradicionales. Al tratar de la sobrecarga de operadores ( 4. lo que permite que la función pueda modificar objetos externos a ella. §2 Sintaxis: La declaración de una variable de este tipo se realiza mediante el declarador de referencia &. y desde luego es mi opinión que el Sr. Por ejemplo. Una referencia es una especie de alias o "alter ego" del objeto. obtener su dirección. de forma que cualquier operación sobre z equivale a hacerla sobre x. la justificación de este nuevo tipo es más profunda y sutil. Nota: hemos leído en algún libro [2] que "las referencias se incluyeron en C++ porque al Sr. int & z = x. sino objetos del ámbito que realiza la invocación. Stroustrup no estaba pensando precisamente en los programadores noveles cuando diseñó su lenguaje.

En cualquier caso. puntero a x §3 Observaciones En muchas situaciones prácticas puede ser indiferente utilizar una referencia o un puntero. Cualquiera de estas expresiones crea el Lvalue z como un alias para la variable x (suponiendo que el iniciador es del mismo tipo que la referencia).distinto. Cualquier operación en z tiene exactamente el mismo efecto que la operación en x.1 Observe que las expresiones: tipoX& var. por lo que resultan unidas de por vida al objeto inicial [4]. x es tipo int. de forma que tienen que estar indefectiblemente unidas a un objeto en su propia definición (deben ser inicializadas en la declaración). Por ejemplo: int& z. En consecuencia. int & z = x. Por ejemplo. . Además. Por ejemplo: z = 2 asigna 2 a x. la mejor regla es recordar que las referencias pueden considerarse como un sustituto del identificador de un objeto. // Ok. mientras que los punteros deben ser considerados siempre como objetos en sí mismos [6]. las tres expresiones que siguen también lo son [1]: int &z = x. tipoX &var y tipoX & var son todas equivalentes. una vez declaradas no pueden ser reasignadas a otro objeto (como los punteros). y &z devuelve la dirección de x. si nos referimos a las expresiones anteriores. Posibilidad de: Declaración independiente de la definición Asignarles un nuevo valor Referirse a un objeto de su mismo tipo Asignarles el valor void Referencias Punteros No No No No Si Si Si Si §4 Declaración Las referencias no pueden ser declaradas aisladas. int& z = x. // Ok. z es referencia-a-int (int&). §2. sin embargo mantienen importantes diferencias que conviene conocer y que resumimos en el cuadro adjunto antes de comentarlas más detenidamente. x y z son de tipos distintos. Esto significa que siguiendo la expresiónes §2a podemos expresar: int* iptr = &z. int& z = x // Error. Además sus aritméticas son distintas.

cout << "X = " << x. podemos imaginar que las referencias son una especie de punteros constantes. pero de otro lado. Existe un caso especial en la declaración de referencias: cuando estas son miembros de clase. // Ok. refi = y. puesto que tienen que estar unidas a un objeto. Ejemplo: int x = 10. Por ejemplo. §5 Reasignación de referencias: Después de la definición inicial. int x = 10. // Ok. en cuyo caso señalan al objeto inicial. // Ok. no pueden referenciar a void: int& z = void. Por la razón anterior. // Error.int& z = y. m[1] == 30 Según hemos visto hasta ahora. int (&rm)[5] = m. Referencias a objetos de clases implícitas ( 4. rm referencia a m // Ok. Sí pueden ser inicializadas a otra referencia del mismo tipo. pero que no aceptan el álgebra de punteros ni pueden ser manipuladas igual. int& b) { return (a >= b)? a : b. int& refi = x. r2 referencia a x int& r3 = max(x. rm[1] = R3.2d3).. cout << "X = " << refi.12. Pero estas asignaciones son en realidad al objeto inicialmente referenciado. int& r1 = x. // Error. Ejemplo: int& max (int& a. } . y = 20. la sintaxis C++ no permite asignaciones en el cuerpo de la clase. La discrepancia se ha resuelto con un mecanismo especial de inicialización que solo es válido en estos casos ( 4. r3 referencia a y También se pueden definir referencias a matrices: int m[5]. cout << "X = " << refi.2). Acabamos de indicar que deben ser inicializadas en la declaración. // Ok. a una referencia-a-int solo se le pueden asignar tipos int. y = 30.11. y).. r1 referencia a x int& r2 = r1. // // // // // definición inicial -> X = 10 (valor de x) Ojo!! Equivale a: x = y -> X = 20 (x es ahora 20) -> X = 20 (comprobación) §6 Modelado: . las sucesivas asignaciones a las referencias deben ser con objetos del mismo tipo.

// -> X = 1 float f = 12.5 // -> X = 12 (Atención!!) En la asignación L. refi = c1. ya que en ninguna circunstancia una estructura puede ser promovida a un entero. donde el float f es promovido a entero (lo que implica una pérdida de precisión) y el resultado asignado nuevamente a refi. la variable enumerada c1 es promovida a entero (cosa perfectamente factible.7).9b) de las sentencias anteriores: int x = 10. // L. // Error !! en ninguno de estos dos casos es posible el modelado.. la diferencia resultante entre f y x se muestra en las dos últimas salidas. COLOR c1 = VERDE.3: Ok!! (ahora x = 1) cout << "X = " << refi. Versión con modelado explícito ( 4. // modelado estático: Ok cout << "F = " << f. Por ejemplo. // -> X = 1 float f = 12. Aunque el nuevo Estándar recomienda realizar todos los modelados de forma explícita. Un caso parecido es el de la asignación L. cout << "X = " << refi. // -> X = 10 refi = y. // modelado estático: Ok. // Error!! refi = static_cast<C> (c1). AZUL}. VERDE. el resultado es asignado a refi. } c1. refi = static_cast<COLOR> (c1).6: Ok!! // -> F = 12. lo que a la postre equivale a asignarlo a x.5.En ocasiones el compilador realiza automáticamente determinadas promociones de tipo ( 4. // Ok!! (casting inecesario) cout << "X = " << refi.5 cout << "X = " << refi.9. refi = static_cast<float> (f). refi = c1.6. refi = f.9) para hacer posible la asignación.9. COLOR c1 = VERDE. cout << "X = " << refi. int& refi = x. para facilitar la búsqueda y depuración de posibles errores. por lo que el compilador muestra un mensaje de error al intentar la asignación: struct C {float x.5. por lo que el compilador avisa con un error: Cannot convert 'C' to 'int' in function. Hay que hacer notar que la sustitución del modelado implícito por uno explícito no altera para nada el resultado de este tipo de conversiones. // -> F = 12. // L. // -> X = 20 enum COLOR { ROJO. Entre otras razones. AZUL}..3. cout << "X = " << refi. float y. cout << "F = " << f. y = 20. . 4. VERDE. siguiendo con las definiciones anteriores (§5 ): enum COLOR { ROJO. // -> X = 12 (Atención!!) En ocasiones los tipos del Rvalue y Lvalue de la asignación son tan dispares que el modelado no es posible (ni implícito ni explícito).

§7 Punteros y referencias a referencias No es posible declarar punteros-a-referencias ni referencias-a-referencias: int x = 10; int& rti = x; int&* ptrti = &rti int&& rar = rti

// Ok referencia-a-x // Error!! Puntero-a-referencia // Error!! Referencia-a-referencia

Puesto que las referencias-a-tipoX son en realidad un "alter ego" del tipo referenciado, sí es posible utilizar referencias para la definición de otras referencias al mismo tipo: int& rti2 = rti; // Ok otra referencia-a-x

En contra de lo que ocurre con sus parientes cercanos los punteros, no es posible iniciar referencias con el operador new.

§8 Referencias a punteros Aunque de poca importancia práctica, la referencia-a-puntero, es un alias que puede ser utilizado a todos los efectos como si fuese el propio puntero. Ejemplo: #include <iostream.h> int main() { int x = 10; int* ptr = &x; int*& ref = ptr; cout << "ptr-a-X cout << "ptr-a-X cout << "Valor X cout << "Valor X } Salida: ptr-a-X ptr-a-X Valor X Valor X = = = = 0065FE00 0065FE00 10 10 // ====== // puntero-a-int // referencia-a-puntero-a-int ref << endl; ptr << endl; *ref << endl; // M.6 *ptr << endl;

= = = =

" " " "

<< << << <<

En este ejemplo es digna de mención la forma de declaración de ref, referencia-a-puntero; y como a todos los efectos, incluso para aplicarle el operador de indirección * ( 4.9.11), la referencia se comporta en M.6 como un alias perfecto del puntero, señalando al mismo objeto que aquel.

§9 Referencias a funciones C++ permite definir referencias a funciones, aunque carecen de importancia práctica. La sintaxis para su declaración es la misma que con los punteros, aunque como es usual, su inicialización

debe hacerse en el punto de la declaración. Así mismo, pueden invocarse funciones a través de sus referencias como si se tratara de punteros. Ejemplo: float sum(int i, int j) { float s = i + j; cout << "La suma es: " << s << endl; return s; } ... int main() { // =============== float (*fptr)(int, int) = sum; // puntero-a-función (iniciado) float (&fref)(int, int) = sum; // referencia-a-función int x = 2, y = 5; sum(x=2, y); // invocación estándar fptr(x*2, y); // invocación mediante puntero fref(x*2, y); // Ok. invocación mediante referencia int& rx = x; // nuevo alias de x int& ry = y; // nuevo alias de y fref(rx*2, ry); // Ok. invocación mediante referencia } Observe que los argumentos pasan "por valor" en todas las invocaciones (incluyendo la última).

§10 Si en la declaración de una referencia, el iniciador es una constante o un objeto de tipo diferente que el referenciado, entonces se crea un objeto temporal para el que la referencia actúa como un alias. Considere los siguientes ejemplos: int& z = 6; Se crea un objeto temporal tipo int que recibe el valor 6; se crea también una referencia-a-int de nemónico z, a ese objeto temporal. El compilador avisa de esta circunstancia con una advertencia: Temporary used to initialize 'z' in function.... Observe que el objeto temporal solo es accesible a través de su referencia (no tiene nemonico ni puntero específicos). float f = 12.1; int& z = f; Aquí se crea un objeto temporal tipo float de nemónico f y Rvalue 12,1. En la segunda sentencia se crea un objeto temporal tipo int que recibe el valor de f (que es promovido a int antes de la asignación). Se crea una referencia-a-int que señala a dicho objeto temporal. El compilador avisa de esta circunstancia con el mismo mensaje que en el caso anterior: Temporary used to initialize 'z' in function.... Como consecuencia final, z contiene una imagen de f que es como su "versión int". Completemos el ejemplo anterior con una asignación: float f = 12.1; int& z = f; z = 10;

El proceso es análogo al anterior. En la tercera sentencia se asigna un valor 10 al objeto temporal, mientras que f sigue conservando su valor inicial. Como comprobación, escribimos lo anterior en forma de programa ejecutable: #include <iostream.h> int main() { float f = 12.1; cout << "Valor f: int& z = f; cout << "Valor z: z = 10; cout << "Valor f: cout << "Valor z: } Salida: Valor Valor Valor Valor f: z: f: z: 12.1 12 12.1 10

" << f << endl; " << z << endl; " << f << endl; " << z << endl;

§11 Consideremos también una variación del ejemplo relativo al valor devuelto por una función ( 4.4.7), modificando el tipo aceptado en el segundo parámetro de la función max y añadiendo un "casting" para que en cualquier caso, el valor devuelto sea adecuado a la declaración. #include <iostream.h> int& max (int& a, long& b) {if (a >= b) return a; return int(b); } int main () { // =========== int x =12, y = 22; cout << "Máximo: " << max(x, y) << endl; // M.2: cout << "Valor inicial: " << y << endl; int& mx = max(x, y); // M.4: Ok asignación del mismo tipo mx = 30; // M.5: cout << "Valor final: " << y << endl; } Salida: Máximo: 22 Valor inicial: 22 Valor final: 22 En cada invocación a la función max (M.2 y M.4), el compilador nos señala idéntico mensaje de aviso: Temporary used for parameter 'b' in call to 'max(int &,long &)' in function main(). A su vez, la salida nos muestra que el valor y permanece inalterado. La razón, como se ha señalado antes, es que en cada invocación en el ámbito de main, se crea un objeto temporal de tipo referencia-a-long, que recibe el valor 22L, que es pasado a la función como segundo argumento (b). Este objeto temporal es promovido a int antes de su devolución por el return. En consecuencia, se devuelve una referencia a un objeto temporal, objeto que es

int sum = 3. la función recibe una copia de la variable sum. en la práctica su uso más frecuente es el paso de argumentos a funciones "por referencia". // L9: cout << "Valores modificados:" << endl. . ++*p. en especial cuando se trata de objetos definidos por el usuario (instancias de clases). Ejemplo: void func1 (int).4. el paso de argumentos a funciones "por referencia" solo proporciona cierta comodidad adicional a la funcionalidad proporcionada por los punteros. la variable y permanece inalterada durante todo el proceso . cout << "S6 p == " << p << endl. en C++ es posible pasar argumentos por valor y por referencia.modificado después en M. func2(sum). ++r. Tenga en cuenta que en ocasiones. Intentaremos aclararlo con un ejemplo muy sencillo: #include <iostream> using namespace std. Ver al respecto: Argumentos por valor/por referencia ( 4. int& r. cout << "S7 v == " << v << endl. El argumento sum pasado por referencia en func2. cout << "S5 r == " << r << endl.. el verdadero motivo de pasar objetos "por referencia" no es precisamente para que la función pueda modificar el argumento (incluso se intenta evitar esto declarando el argumento como referencia constante). void func2 (int&). puede ser modificado directamente desde dentro de esta función. Observe que mientras en C clásico solo se pasan argumentos por valor. Como queda de manifiesto en las salidas.5). Por contra. por lo que no puede modificar el valor original (la variable sum existente fuera de la función func1). // distintas formas de paso de argumentos int func(int v. cout << "S4 v == " << v << endl. func1(sum). §12 Argumentos por referencia: Aunque hemos insistido en que la razón última de la introducción de referencias en C++ es posibilitar la sobrecarga de operadores. como si fuese "por valor". int* p) { // L4: cout << "Valores recibidos:" << endl. ++v. más que alguna nueva "funcionalidad". La diferencia en uno y otro caso estriba solo en la forma de declarar los argumentos en la definición de la función. . especialmente cuando los argumentos son objetos (instancias de clases). en func1 el argumento pasa por valor.. En realidad. sino por razones de eficacia del código .5. // declara argumento de tipo int // declara argumento de tipo referencia-a-int // sum pasa por valor // sum pasa por referencia Observe la utilización del argumento es idéntica en la invocación de ambas funciones.

cout << "S3 z == " << z << endl. Por esta razón se ha utilizado la forma *++p para modificarla. int r = func(x. incluyendo un error fatal de runtime si la nueva dirección estuviese fuera del área asignada al programa por el SO. // M8: // M10: Comentario: En L4 definimos una función que recibe tres argumentos: un entero (int). Por consiguiente. endl. y. Esto podría tener resultados impredecibles. int y = 12. endl. Esta función se limita a mostrar los valores recibidos. } Salida: Valores S1 x == S2 y == S3 z == Valores S4 v == S5 r == S6 p == Valores S7 v == S8 r == S9 p == Valores Sa x == Sb y == Sc z == iniciales: 10 12 14 recibidos: 10 12 0065FDFC modificados: 11 13 0065FDFC finales: 10 13 15 finales:" " << x << " << y << " << z << << endl. return v. una alteración del tipo ++p en L9. endl. y un puntero-a-int (int*). modificaría su valor en el sentido de que señalaría a una posición distinta de la actual (la variable z de main). pz). // M1: int* pz = &z. . // M3: cout << "S1 x == " << x << endl. A continuación altera estos valores (locales a la función). } int main(void) { // ========= int x = 10. Observe que el argumento p es un puntero. cout << "Valores cout << "Sa x == cout << "Sb y == cout << "Sc z == return 0. una referencia-aint (int&). cout << "S2 y == " << y << endl. En realidad esta instrucción incrementa en una unidad el objeto señalado (la variable z de main). cout << "S9 p == " << p << endl.cout << "S8 r == " << r << endl. y finalmente muestra los valores modificados. int z = 14. cout << "Valores iniciales:" << endl.

no tiene absolutamente ninguna influencia sobre el argumento x utilizado por la función invocante (salidas S7 y Sa). En M8. En el ejemplo se ha pasado un int. de modo que el paso por referencia supone un incremento notable en la eficiencia del programa (volveremos sobre esto a continuación ).. Como segundo parámetro (r) la función espera una referencia-a-entero. Como en el caso anterior. Sin embargo no ha sido necesario construir previamente un objeto de este tipo.. estos valores serán utilizados más tarde como argumentos en la invocación de la función. pz).El programa comienza en main. para este argumento el compilador interpreta: int& r = y. Observe que aquí sí ha sido necesario construir un objeto del tipo adecuado (pz) para utilizarlo como argumento en la invocación. // M8: .  El tercer parámetro (p) esperado por la función es un puntero. Lo se comprueba en las salidas S8 y Sb.4. Es decir.. se invoca la función con los valores definidos en M1 y M2. Es aquí donde está el truco y la magia del paso de argumentos "por referencia". En L9 hemos incrementado y en una unidad través de su "alias" r. Observe que en la invocación int r = func(x. y. generalmente tipos abstractos (estructuras y clases) cuya copia puede resultar costosa en espacio de pila y en tiempo de carga en el marco correspondiente ( 4. Observe que un incremento análogo en el primer parámetro v pasado "por valor". Aquí es el compilador el que se encarga de construir un objeto local en la función. donde definimos tres enteros y un puntero-a-int. utilizar el entero z "tal cual" como tercer argumento. Segundo: el resultado es como si el ámbito del objeto representado se "alargara" al interior de la función (podríamos incluso utiliza el mismo nombre que en el exterior). lo que resulta en una gran comodidad sintáctica. se utiliza la variable x "tal cual" como primer argumento de la invocación. En consecuencia. de tipo referencia-a-entero.6b). pero cuando se pasan objetos por referencia suelen ser grandes. habría originado la protesta del compilador: Error: Cannot convert 'int' to 'int *' in function main(). solo se pasa una etiqueta (podríamos decir que un puntero muy especial). Aquí resultan pertinentes algunas observaciones: El primer parámetro (v) esperado por func es un entero. Un intento análogo a los anteriores. se pasa un entero (y) "tal cual" como segundo argumento. e iniciarlo con el valor de y. Son las sentencias M3/M5 que proporcionan el primer grupo de salidas con los resultados esperados. A partir de ahora el objeto r (local a func) es un alias del objeto y de la función invocante (main). El truco tiene en realidad dos partes:  Primero: no es necesario copiar el objeto. A continuación el programa muestra los valores iniciales de estos objetos.

4.3) y como operador de referencia ( 4.9.. ahora contiene la dirección de x int& p = x. está motivada solo por criterios de eficacia. En la salida Sc comprobamos cómo desde dentro de la función. dado que es necesario deferenciar el puntero. En tales casos suele recurrirse a declarar constantes los argumentos pasados "por referencia" para evitar que la función pueda alterar su valor. la sintaxis involucrada es algo más complicada.6b) implican la creación de todas las variables locales de la función (incluyendo el valor que será devuelto). sino el operador de referencia ( 4. §13 Valores devueltos por referencia: Otro uso común de este tipo de objetos es su utilización como valor de retorno de una función.11b) que obtiene la dirección de z. Por ejemplo. a veces muy complicados. return b. Cuando estos objetos son muy grandes. utilizando un puntero que ha pasado "por valor". y es este valor el que se pasa como argumento (ver observación al final ). Es el caso del valor devuelto por la función adjunta int& max (int& a.4.9.9.4. y. Nótese la diferencia entre las dos expresiones que siguen: int x. . en instancias de clases con muchos datos o que derivan de jerarquías complejas. // declara p puntero-a-entero.18a. las secuencias de llamada y retorno de funciones ( 4.11b).. int& b) { if (a >= b) return a. } Más información al respecto en: Valor devuelto ( 4. El mismo resultado podría obtenerse utilizando: int r = func(x. que pueden evitarse parcialmente utilizando referencias. Ver ejemplos al respecto: 4. se requieren procesos de creación y destrucción. así como la invocación del constructor-copia para todos los argumentos que no han sido pasados por referencia. int* p = &x.9.el tercer argumento contiene el valor de la dirección de la variable z. §14 Criterios de eficiencia En ocasiones la utilización de referencias para el paso de argumentos a funciones o en el valor devuelto.7). // M8b: Nótese que aquí el símbolo & no es un declarador de referencia como en L4.9. // declara p referencia-a-entero ahora es sinónimo de x . Sin embargo. &z).18b2 Además de ser utilizado como declarador de referencia. el símbolo & (ámpersand) puede ser utilizado como operador AND entre bits ( 4. En efecto. también ha sido posible modificar el valor de un objeto externo (variable z).

Tómese como ejemplo el de Kernighan y Ritchie ( por lo demás. Por ejemplo.2. pero generalmente tales punteros solo se utilizan para acceder a métodos estáticos ( 4. la mejor manera de pensar en ellos es considerarlos como una especie de "alias" de la función.7).4 Puntero a función §1 Sinopsis Los punteros a función son uno de los recursos más potentes y flexibles de C/C++.3. De hecho puede decirse que los punteros a funciones son en realidad un artificio de C++ para poder utilizar funciones como argumentos de otras funciones. Una vez establecido esto.else o switch ( 4. la dirección de memoria a que se transfiere el control cuando se la invoca (su punto de comienzo).11. no tiene que extrañar que puedan definirse variables de un tipo especial para apuntar a estas direcciones.. los autores están generalmente de acuerdo en que constituyen para el principiante uno de los puntos más confusos del lenguaje. Pero en general. es muy frecuente que en alguno de los pasos intermedios se utilicen tales funciones como argumentos de otras. no está permitida ninguna operación de aritmética (de punteros) con ellos [6]. o alterar el flujo de ejecución del programa. Así mismo. quizás sea el libro más conciso y mejor escrito sobre C. previamente hay que establecer que las funciones tengan "dirección".2. exponiendo el asunto con un ejemplo cuya lógica es de por sí bastante difícil de seguir. aconsejo al lector preste especial atención a este aspecto y repase las Reglas de lectura ( Apéndice 6. §2 La "dirección" de las funciones: Para hablar de punteros a funciones. En consecuencia. diseñar algoritmos muy compactos ("function dispatchers"). Por lo demás.10.En la primera línea se ha usado & como operador de referencia. se asume también que la dirección de una función es la del segmento de código ("Code segment" 1. aunque con una importante cualidad añadida: que pueden ser utilizados como argumentos de otras funciones.1g). cuando se utiliza este recurso. K&R) que En mi modesta opinión. escribir funciones que manejan diferentes tipos de datos. Técnicamente un puntero-a-función es una variable que guarda la dirección de comienzo de la función. Por supuesto que los punteros a función se utilizan en último extremo para acceder a la función señalada (las funciones existen para ser invocadas). 4.1). A esto se une que el tema suele ser despachado en una par de páginas como mucho.4. Es decir. que pueden sustituir largas cadenas if. ya que para acceder a los miembros de clases en general no se utilizan . Del mismo modo que en una matriz se asume que su dirección es la del primer elemento.1). modificando el orden de llamadas a funciones en base a determinadas prioridades ("adaptive program flow").. Pero como tendremos ocasión de comprobar.2). resultan de gran ayuda en programas de simulación y modelado. parte de culpa de la confusión reside en la complejidad de la notación utilizada para estos menesteres. permitiendo técnicas de programación muy eficientes. Por lo general. en la segunda como declarador de referencia. pueden definirse punteros a funciones miembro ( 4. Sin embargo. dado que la gramática de C++ no permite en principio utilizar funciones en la declaración de parámetros ( 4. Nota: en lo que respecta a las clases.2) donde comienza el código de dicha función [1].

fptr es puntero a función.11. LONG (PASCAL * lpfnWndProc)(). donde A son los argumentos que recibe la función y X es el tipo de objeto devuelto.4a) al tratar de la declaración de estos punteros.9.2d). fptr es puntero a función. int* (*fptr)(int*. o de cuaquier otro tipo). y devuelve un puntero a int. Cada una de las infinitas combinaciones posibles da lugar a un tipo específico de puntero-a-función. void (*fptr)(). fptr es un puntero a una función. void (_USERENTRY * fptr)(void). sin parámetros. el operador de resolución de ámbito :: ( 4. . Por otra parte. void (*fptr)(int). la notación se complica un poco más: int const * (*fptr)(). no es posible definir punteros a los constructores o destructores de clase. Considere detenidamente las declaraciones de los ejemplos siguientes (en todos ellos fptr es un puntero a función de tipo distinto de los demás). fptres un puntero a función que no recibe argumentos y devuelve un puntero a un int constante fptr es un puntero a función que recibe float (*(*fptr)(char))(int).punteros. char). fptr es un puntero a función que recibe un int como parámetro y devuelve void. que devuelve void. §3 Un puntero a función es una variable del tipo denominado: "puntero-a-función recibiendo A argumentos y devolviendo X". int (*fptr)(int.2. que acepta un int y un char como argumentos y devuelve un int. fptr es puntero a función. sin parámetros que devuelve void y utiliza la convención de llamada_USERENTRY [4] lpfnWndProc es puntero a función. Insistiremos en esta singularidad más adelante ( 4. char*). Cuando el valor devuelto por la función es a su vez un puntero (a función. sin parámetros que devuelve LONG y utiliza la convención de llamada PASCAL [5]. que acepta sendos punteros a int y char como argumentos. ya que son un tipo especial de funciones miembro de las que no puede obtenerse su dirección ( 4. Observe una característica que se repite: el nombre del puntero está siempre entre paréntesis. sino un operador especial [3].19).

Sin embargo. ... fptr es un puntero a función que recibe dos argumentos (int y float). void * (*(*fptr)(int))[5]. fptr es un puntero a función que no recibe argumentos y devuelve un puntero a un array de 5 punteros a función que no reciben ningún parámetro y devuelven long. Por ejemplo: int (* afptr[10])(int). la variable afptr es declarada como matriz de 10 punteros a función que reciben int y devuelven int. // matriz de 10 punteros a función De la propia definición se desprende que en estas matrices. Estas matrices permiten invocar funciones (a través de sus punteros) utilizando los miembros de la matriz mediante notación de subíndices. Por ejemplo.5)..4a Declaración y definición de punteros a función §1 Sintaxis Como se ha visto en los ejemplos ( función tiene la siguiente sintaxis: 4. fptr es un puntero a función que recibe un int como argumento y devuelve un puntero a un array de 5 punteros-a-void (genéricos). el núcleo de todas las declaraciones de punteros a <tipo_devuelto> . char (*(*fptr)(int.un char como argumento y devuelve un puntero a función que recibe un int como argumento y devuelve un float. (* nombre_puntero) (<parametros>) . como veremos más adelante. lo que da mucho juego en determinadas circunstancias. en la práctica son equivalentes. Más sobre matrices de punteros en ( 4. §4 Matrices de punteros a función Los punteros a función también pueden agruparse en matrices. float))().4). devolviendo un puntero a función que no recibe argumentos y devuelve un char. el caso anterior permitiría invocaciones del tipo: int z = afptr[n](x). todos los punteros señalan funciones que devuelven el mismo tipo de valor y reciben los mismos tipos de parámetros. En el ejemplo anterior. long (*(*(*fptr)())[5])().2. 4. Observe que la gramática C++ acepta la existencia de matrices de punteros-a-función.2.. para casi todos los efectos una función puede sustituirse por su puntero. pero no la existencia de matrices de funciones. que resultan muy útiles.3.

Observe que el paréntesis (* nombre_puntero) es fundamental. que no está en contradicción con el hecho repetidamente enunciado de que C/C++ nopermiten utilizar una función como argumento de otra función [3]. al menos con los compiladores Borland C++ 5. En la página adjunta ( Nota-1) se ofrece una explicación detallada del motivo. Aunque la sintaxis utilizada resulta difícil de interpretar en algunos casos. puede comprobarse que. Función que acepta un puntero-a-char y devuelve un puntero-a-char char const * func (). void func(int *fptr(int)).5 y MS Visual C++ 6. // puntero-a-función que recibe un int y devuelve un int int *fptr (int). // función que recibe un int y devuelve puntero-a-int En consecuencia: void func(int (*fptr)(int)). <parametros> es la lista de los argumentos aceptados por la función señalada por el puntero.0. a la derecha del núcleo se incluye la lista de parámetros (que puede estar vacía) y a su izquierda el valor devuelto por la función. La representación de un puntero a tal función se obtiene sustituyendo el nombre por la indicación de un puntero y separándolo del resto por un paréntesis. Puntero-a-función que acepta un puntero-achar y devuelve un puntero-a-char char const* (*func) (). ya que entonces no es considerada por el compilador como la declaración de una variable de tipo puntero-a-función.<tipo_devuelto> es el tipo de vuelto por la función señalada por el puntero. Si consideramos que la definición de una función tiene tres partes: valor devuelto. // Ok declaración correcta de una función // Error!! expresión ilegal en C/C++ Nota: a pesar de lo indicado en esta última sentencia. que debe ser único (puede servoid). Función que no acepta argumento y devuelve un Puntero-a-función que no acepta argumentos y puntero-a-charconstante. podríamos decir que es el tipo de variable señalada por el puntero en último extremo [2]. Declaración del puntero-a-función char* (*func) (char*). devuelve un puntero-a-char constante . y nombre. Por ejemplo: int (*fptr)(int). Ejemplos: Declaración de función char* func (char*). la notación de punteros a función sigue cierta regla que está relacionada con la sintaxis de la función a la que apuntan. argumentos utilizados. Esta parte es conocida como núcleo de la declaración. Sin el núcleo la declaración cambia drásticamente su sentido. una expresión como la anterior puede ser compilada sin que se presente ningún problema.

en lo que se refiere a la definición de punteros-a-función. los errores obtenidos en ambas líneas con Borland C++ son los siguientes (otros compiladores dan errores similares): L. devuelve un puntero a función miembro de la clase C que recibe un char y devuelve un puntero-a-int. Puntero-a-función que acepta un puntero-aestructura tipo S y devuelve un punter-a-int int* (C::* (*func)())(char). int* (*func) (struct S* Sptr). struct S (*func) (char*). ya que los punteros a funciones con distintas convenciones de llamada no son equivalentes entre sí.2 En concreto.1 // L.. la convención de llamada ( 4. En el compilador GNU esto se hace incluyendo la palabra __attribute__ seguida del nombre del especificador entre dobles paréntesis al final de la declaración.1 Convención de llamada Técnicamente. Función que acepta un puntero-a-char y devuelve una estructura tipoS. según sean los compiladores MSVC++ y BC++ o GNU gcc. el especificador de llamada se sitúa entre e valor devuelto y el paréntesis que contiene el nombre del puntero.6a) no forma parte del "tipo" de la función. Puntero-a-función que acepta un puntero-achar y devuelve una estructura tipo S. Función que acepta un puntero-a-estructura tipo S y devuelve un puntero-a-int int* (C::* func())(char).1: L. de forma que. sí es necesario tener en cuenta este detalle en la declaración. Por ejemplo. Función que no recibe argumentos. Puntero-a-función que no recibe argumentos.4. punteros-a-función que no aceptan argumentos y devuelven un int.: Earlier declaration of '__stdcall foo(float)' Error .struct S func (char*).: Type mismatch in redeclaration of '__stdcall foo(float)' Sin embargo. Función que no acepta argumentos. float foo (float x) { return 2 * x. §1. devuelve un Puntero-a-función que no acepta argumentos. las funciones que siguen son del mismo tipo y el compilador muestra el correspondiente error si se intentan compilar en la misma unidad de compilación: float __stdcall foo (float x) { return 2 * x. int (*(*(*f)())[10])().. por ejemplo..2: Error . puntero a una matriz de diez punteros-a-función devuelve un puntero a una matriz de diez que no aceptan argumentos y devuelven unint. devuelve un puntero-a-función miembro de la clase C que recibe un char y devuelve un punter-a-int.. } } // L. int* func (struct S* Sptr). La convención de llamada en la declaración de punteros-a-función se utilizan dos sintaxis distintas. En los dos primeros. int (*(*f())[10])(). las declaraciones de punteros para las funciones anteriores serían: .

o solo con el nombre func.4b) veremos que esta equivalencia puntero ↔ función (fptr == func) es utilizada también para la invocación de la función a través de su puntero [4]. incluye múltiples ejemplos de punteros-a-función que utilizan la dualidad puntero ↔ función antes señalada.2. que contiene una amplia colección de técnicas de programación avanzadas. fptr = &func. // para foo de L. Por ejemplo.3. fptr = func.2 todos los compiladores estándar §2 Definición: Como ocurre con los demás tipos de variables. C++ presenta aquí la misma situación sintáctica que cuando en 4. en este tipo de asignaciones es imprescindible que la función asignada al puntero responda a la definición de este.float __stdcall (* fooPtr) (float).2. Nota: recuerde que el puntero-a-void no puede usarse para señalar a una función ( void* fptr = &func. también son equivalentes a: char (*fptr)(int) = func. Es decir: tanto el tipo de dato devuelto como los . // para foo de L.1 MSVC++ y BC++ float (* fooPtr) (float) __atribute__((stdcall)). Esto puede hacerse en el mismo momento de la declaración o después. es decir.1d) Una observación importante a tener en cuenta desde el punto de vista de la sintaxis. Es decir: char func(int). // Error!! 4.11) &func [1].9.1 GNU g++ float (* fooPtr) (float). es que la dirección de una función func() puede indicarse con el operador de referencia & ( 4. // declara una función // declara fptr puntero-a-función // inicia fptr las dos últimas sentencias son equivalentes a: char (*fptr)(int). // define ptr (declara e inicia) // inicia fptr En el siguiente epígrafe ( 4. char (*fptr)(int). §2. para extender el mecanismo de sobrecarga de operadores en las operaciones de E/S.1 Por supuesto. antes de usar los punteros a función es necesario definirlos.2 decimos que el identificador de una matriz puede ser interpretado como la dirección de su primer elemento. en este caso la dirección de una función concreta. asignarles un valor. // para foo de L. Nota: la Librería Estándar C++.

} .parámetros deben ser adecuados. // prototipo de func2 void (* afptr[2])(char) = {func1. func2}. void func2(char letra). de forma que también se podría haber puesto: void (* afptr[])(char) = {func1. §3 typedef y la declaración de punteros a función Como hemos visto. int (*fptr)(int) = func. Por ejemplo: char func(int).4) que pudiera tener la función no se considera parte de su tipo y por consiguiente no afecta al tipo de puntero-.5 void fun1() { cout << "Primera" << endl. También puede iniciarse mediante subíndices (en este caso no puede ahorrarse el tamaño de la matriz en la declaración): void func1(char letra).2. en especial cuando se trata de definiciones ( 3. typedef void (*PFV)(). la notación utilizada por C++ en la declaración de punteros a función puede llegar a ser bastante compleja. también se pueden declarar e iniciar en la misma sentencia: void func1(char letra). // declaracion de afptr // inicia afptr[1] // inicia afptr[2] Observe que los elementos de una matriz (de punteros o de cualquier otro tipo de objetos) son del mismo tipo. el posible especificador de excepción ( 1. afptr[1] = func2.1) deben ser idénticos.4. // L.1a). // prototipo de func1 void func2(char letra). significa que estos deben apuntar a funciones recibiendo el mismo tipo de argumentos y devolviendo lo mismo. // definición de afptr en la última línea el tamaño de la matriz está implícito en la inicialización.6. por lo que es muy común la costumbre de utilizar typedef auxiliares. lo que significa a su vez que sus prototipos ( 4.2 Si se trata de definir una matriz de punteros. char(*fptr)(char) = func. void (* afptr[2])(char). Las diferencias solo pueden estar en el nombre y en el cuerpo de las funciones referenciadas. func2}. En el caso de una matriz de punteros. -en cambio. // Error: desacuerdo en valor devuelto // Error: desacuerdo en parámetro // Ok: todo correcto §2. Ejemplo: #include <iostream> #include <cstdlib> using namespace std. char(*fptr)(int) = func. afptr[0] = func1.

A continuación se definen cuatro funciones que corresponden con esta definición.4). un array de punteros a funciones que devuelven void y no reciben ningún argumento (precisamente utilizando el tipo PFV). &fun4 }.5 son la forma canónica de invocación de las funciones mediante sus punteros ( 4. &fun3. } void fun3() { cout << "Tercera" << endl. char * argv[] ) { PFV apf[] = { &fun1. fun2.4. fun4 }.1bis Las sentencias M. En los demás casos se invoca la función cuarta. El programa comienza definiendo apf. return 0. Este argumento puede ser un entero de 1 a 3. if (argc > 1 && *argv[1] > '0' && *argv[1] <= (*apf[atoi( argv[1] ) . fun3.5 y M. Observe que la sentencia M.void fun2() { cout << "Segunda" << endl.5 Comentario: El ejemplo comprueba el argumento pasado con el programa ( 4. &fun2. // L. las sentencias L. } int main( int argc. } void fun4() { cout << "Argumento No valido" << endl.3 y M. .1 Observe que en este caso.1 '4') // M. &fun4 }. &fun2. } // ================== // M. // M. En el ejemplo anterior. &fun3. la definición del puntero-a-función debe ser modificada convenientemente. PFV define una función que no recibe argumentos y devuelve void.3 // M.5 PFV* apf[] = { &fun1. else (*apf[3])(). mientras que apf sigue siendo una matriz de 4 punteros a funciones que no reciben argumento y devuelven void. En la segunda línea se ha utilizado un typedef para definir que PFV es un tipo puntero a función que devuelve void y no recibe ningún argumento.4b) Otra posibilidad sintáctica es utilizar el typedef auxiliar para definir un tipo de función en sí en lugar del puntero-a-función. invocándose la función correspondiente al número utilizado.1])().2.1 podrían tener el siguiente aspecto: typedef void PFV(). En la misma línea se inicializa con las direcciones de las cuatro funciones fun1 a fun4. En este caso. // M.1 podría haberse sustituido por otra equivalente más elegante: PFV apf[] = { fun1.