‘Declaración de punteros’§1 Presentación La declaración de punteros utiliza un asterisco *, que en este caso actúa como calificador de tipo, en una sintaxis muy parecida a la utilizada en la declaración de objetos normales. En este capítulo nos referimos exclusivamente a declaración de punteros a objetos; la declaración de punteros a función será comentado en otro apartado ( 4.2.4). Cuando los punteros que señalan a miembros de clase (propiedades o métodos) presentan ciertas características especiales por lo que también serán tratados aparte ( 4.2.1g).
§2 La sintaxis general de la declaración de puntero a objeto es: <tipo_objeto> * <etiqueta_puntero> [ = <iniciador> ]
<tipo_objeto> tipo del objeto al que apuntará el puntero que se declara. Puede ser cualquiera, incluso otro puntero; de hecho existen tantas clases de punteros como tipos de objetos. Un puntero debe ser declarado como apuntando a algún tipo particular, incluso si el tipo es void (que en realidad significa puntero a nada), o a un tipo definido por el usuario (por ejemplo una clase). En lo sucesivo, el puntero declarado queda constreñido a apuntar a este tipo de objeto.
Ejemplos: int * ptr; // declara ptr puntero a entero (int) void * ptr; // declara ptr puntero a void (genérico) char * ptr; // declara ptr puntero a carácter (char) En estas declaraciones se utiliza el asterisco (*) como especificador de tipo, indicando que la variable es de tipo puntero en alguna de sus variedades (ver comentario 4.2.1aw1). Considere como modifica el asterisco el significado de las siguientes sentencias: int ptr; // declara que ptr es int int* ptr; // declara que ptr es puntero-a-int
También hay que advertir que es indiferente la colocación de un espacio antes o después del asterisco. Las expresiones que siguen son equivalentes [1]: int * ptr; int* ptr; int *ptr; int*ptr;
Recordemos que declaraciones de este tipo no son definiciones (no inician el puntero) y que es necesario iniciar los punteros antes de su utilización. Recordar también que, además de especificador de tipo, el operador “*” puede ser usado como operador de indirección ( 4.9.11a) y como operador de multiplicación ( 4.9.1),
§2.1 Aunque según lo dicho hasta aquí, una expresión con int* no tiene significado, es frecuente encontrar este tipo de expresiones. Por ejemplo, void* para indicar, en abstracto, un puntero-a-void. En general, la expresión tipoX* significa puntero-a-tipoX. Ejemplos: • ”the void* pointer type was invented for ANSI C and first implemented in C++” ( Stroustrup 1ª Ed. Pág. 5). • double func(char*); indica que func devuelve un double y acepta por argumento un puntero-a-carácter (recuerde que los prototipos de funciones pueden omitir los nombres de los parámetros). • return (char*)ptr; indica que el valor a devolver, ptr, debe ser promovido a puntero-a-carácter (modelado 4.2.1b). • p = (int *) malloc(SIZE * sizeof(int)); obtener memoria para almacenar SIZE enteros en forma de un puntero-a-int señalando al comienzo de dicho espacio. §3 Peligro en la declaración de punteros Aunque las declaraciones múltiples (en la misma sentencia) son aceptables en C++, con los punteros deben evitarse, porque es más que probable que el compilador interprete erróneamente las declaraciones sucesivas. Ejemplo: int * ptr1 ,ptr2; // Posible error !! En estos casos es mejor hacer: int * ptr1; int * ptr2; Ver ejemplo al respecto ( 4.9.14). §4 Creación y destrucción Los punteros siguen las reglas de creación y destrucción del resto de las variables, sin embargo hay que recordar que los objetos tienen duración independiente de los posibles que los señalan, de forma que cuando un puntero-a-objeto sale de ámbito, no se invoca implícitamente ningún destructor para el objeto señalado. A la inversa, la destrucción del objeto señalado no supone necesariamente la destrucción de los punteros que los referencian. En la práctica pueden presentarse ambas circunstancias, ocasionando situaciones potencialmente peligrosas o erróneas. Por ejemplo, el primer caso puede ocurrir con objetos persistentes creados con los operadores new o new[]. Este tipo de objetos son accesibles a través de un puntero, generalmente un objeto automático que es destruido automáticamente al salir de ámbito. Pero el objeto señalado permanece ocupando memoria, por lo que se hace necesaria una invocación explícita al operador delete ( 4.11.2d2) para destruirlo antes que sea destruido el puntero, pues de lo contrario se produciría una perdida irrecuperable de memoria. El segundo caso, la destrucción de un objeto sin que sean destruidos los punteros que lo señalan, también es bastante frecuente, dando lugar a los denominados punteros descolgados (“Dangling pointers”), cuyo uso inadvertido es especialmente peligroso, pues señalan a zonas de contenido erróneo. §5 Punteros constantes y a-constantes La expresión de declaración de un puntero puede llevar opcionalmente el especificador const ( 4.2.1e) para indicar que es: • Puntero-a-constante: Apunta a un objeto de tipo constante. El puntero puede variar, con lo que apuntaría a otro objeto; en cambio el objeto señalado no puede cambiar su valor. En consecuencia, el puntero no puede ser utilizado para cambiar indirectamente el valor del objeto al que señala. int * pt1; // declara pt1 puntero variable a int variable const int * pt2; // declara pt2 puntero variable a constante tipo int int const * pt2; //equivalente al anterior char const * pt3; // declarapt3puntero variable aconstante tipochar const char * pt4; // equivalente al anterior
Como se haseñalado ( 3.2.1c),intyconstintson tipos distintos, por lo que a su vezpt1ypt2son punteros de tipo distinto.
• Puntero-constante: El puntero es constante; el objeto señalado podríavariar, pero el valor del puntero -dirección de memoria que señala- no. Estosignifica que está indefectiblemente ligado a un mismo objeto. int* const pt5 =&e1; // definept5punteroconstante aintvariable char* const pt6 =&e2; // definept6punteroconstante acharvariable Naturalmente ambas condiciones pueden darse simultáneamente:puntero-constante (que no puede alterar su valor) señalando a unobjeto-constante: const int* const pt7 =&kte; // definept7punteroconstante aintconstante
Observe que, como ocurre con otros tipos de constantes, cuando el punteroes constante, la iniciación debe realizarse forzosamente en el momento de sudeclaración. Ejemplo: const int ki = 33; int* const pk; pk =&ki; // Error!! asignación a constante int* const pk =&ki // Ok. §6 Ejemplos: Como puede verse, la sintaxis de declaración de punteros sigue reglasanálogas a la declaración de objetos, con la diferencia de incluir el símbolo dedeclaración de puntero*entre elcalificador de tipo y el nombre del objeto. Con objeto de familiarizar al lectorcon la notación utilizada, se exponen varios casos de declaraciones ydefiniciones de punteros de tipos diversos. Declaración de objeto Declaración depuntero-al-objeto
int x; xes un entero (no iniciado) int* ptr; ptrpuntero-a-entero (no iniciado) int x = 25; xes un entero (iniciado) int* ptr =&x; ptrpuntero-a-entero (iniciado) int* ptr =&x; ptrpuntero-a-entero (iniciado) int** pptr =&ptr; pptrpuntero-a-puntero a entero (iniciado) tipoX x; xes un objeto tipoX tipoX* ptx; ptxes un puntero a tipoX (no iniciado) const int ci = 7; cies una constante tipointiniciada a 7 const int* pci; pcies un puntero a constante tipoint tipoX m[3]; mes una matriz de 3 objetos tipoX tipoX* ptr =&m[0]; ptres un puntero al primer elemento dem(iniciado). Es portanto un puntero a tipoX
tipoX (*ptr)[] =&m;
ptres puntero-a-matriz abierta de tipoX (iniciado)
tipoX (*ptr)[3] =&m;
ptres puntero-a-matriz de tres objetos tipoX (iniciado) tipoX* mp[3]; mpes una matriz de 3 punteros-a-tipoX tipoX* (*ptr)[] =∓ ptres puntero-a-matriz abierta de punteros-a-tipoX(iniciado) char* aP[] = {”Bien”, “Mal”}; aPmatriz de punteros-a-char(iniciada) char* (*aPp) [] =&aP; aPppuntero-a-matriz de punteros-a-char(iniciado) char (* mi[2])[3]; mimatriz de dos punteros-a-matriz-de-treschar char (*(* ptr)[2])[3] =&mi; ptrpuntero-a-matriz de dos punteros-a-matriz de treschar §6.1 Otras expresiones con punteros tipoX (*ptr)[5][7]; ptres puntero-a-matriz de tipoX de dimensiones 5 y 7 void* ptr; ptrpuntero-a-void(genérico 4.2.1d) Ejemplo: int n = 33; void* ptr =&n; const void* ptr; ptrpuntero-a-void(genérico) a constante Ejemplo: const int ki = 33; const void* ptr =&ki; tipoX* func(); Funcion devolviendo puntero-a-tipoX void*&pword(int); Función que acepta uninty devuelve una referencia 4.2.3) aun puntero genérico 4.2.1d).
void func(int*); Función devolviendovoidy recibiendopuntero-a-int tipoX (*ptr)(); ptres puntero a función que devuelve tipoX ( 4.2.4)
char* const ptr = “Hola”; ptres puntero constante a cadena-de-char( 3.2.3f)
char const* ptr = “Hola”; ptres puntero-a-constante tipo cadena-de-char const char* ptr = “Hola”; Equivalente a la anterior
Observe quedefiniciones comolas dos últimas, crean una constante tipo cadena (en estecaso, de contenido “Hola\0″), alojada en algún lugar de la memoria,pero sin un identificador para manejarla. Solo es posible referenciarla(dirigirse a ella) a través del puntero. En otras palabras, no es posible usarlapor su nombre, sino por su dirección.
Observe que en las declaraciones de punteros en las que intervienen matricesy/o funciones, es muy importante la correcta situación de los paréntesis: char* ptr []; // Matriz de punteros-a-char char (* ptr)[]; // Puntero-a-matriz de char char* (* ptr) []; // Puntero-a-matriz de punteros a char Ejemplo:
- include<iostream>
using namespace std;
charachar[5] = {’A’,’E’,’I’,’O’,’U’}; char * punt1 [5]; char (* punt2)[5]; char* (* punt3) [5];
int main (void) { //==================
punt1[0] =&achar[0]; punt1[4] =&achar[4];
cout<<”Comienzo: == “<<*punt1[0]<<endl;
cout<<”Final: == “<<*punt1[4]<<endl; punt2 =&achar; cout<<”Contenido: == “<<*punt2<<endl; punt3 =&punt1; cout<<”Contenido: == “<<*punt3<<endl; cout<<”Contenido: == “<<**punt3<<endl; return 0;
} Salida: Comienzo: == A Final: == U Contenido: == AEIOU Contenido:== 0047A85C Contenido: == AEIOU §7Expresiones peligrosas Se ha dicho que “No hay nada mástemible que un puntero descontrolado”. Generalmente dan lugar a uno delos problemas más difíciles de depurar en un programa y en consecuencia, hay queprestar especial atención en todas las sentencias relacionadas con ellos. Porejemplo, sentencias como las que siguen pueden causar un desastre. int* iptr;
- iptr = 100; //muy peligroso!!
La razón es que en la primera línea declaramosiptrcomopuntero-a-entero, no se inicia, por lo que no sabemos a donde apunta.Posiblemente el Lvalue de la variable esté ocupado por basura, aunque puedetratarse de una zona sensible (actualmente apunta a un sitio impredecible). Enla segunda línea el sitio apuntado poriptrlo sustituimos con 100,posiblemente machaquemos una zona de memoria equivocada con las consecuenciasfáciles de prever.Es más correcto hacer: int z; // L.1 int* iptr =&z;// L.2
- iptr = 100; // L.3
La explicación es que en L1 el compilador ha iniciado una variablezcon una dirección de memoria adecuada, aunque momentáneamente noinicializada (contiene basura 4.1.2). En L2 el puntero apunta a esevalor. Finalmente, la basura original es sustituida por un valor conocido en L3.