1. Para declarar un apuntador se especifica el tipo de dato al que apunta, el operador ‘*’, y el nombre del apuntador. La sintaxis es la siguiente: <tipo de dato apuntado> *<indentificador del apuntador> A continuaci´on se muestran varios ejemplos:
1.1. int *ptr1; // Apuntador a un dato de tipo entero (int)
1.2. char *cad1, *cad2; // Dos apuntadores a datos de tipo car´acter (char)
1.3. float *ptr2; // Apuntador a un dato de tipo punto-flotante (float)
2. Se pueden asignar a un apuntador direcciones de variables a trav´es del operador de referenciaci´on (‘&’) o direcciones almacenadas en otros apuntadores. Ejemplos:
2.1. int i = 5;
2.2. int *p, *q;
2.3. p = &i; // Se le asigna a ’p’ la direcci´on de ’i’
2.4. q = p; // Se le asigna a ’q’ la direcci´on almacenada en ’p’ (la misma de ’i’)
3. La desreferenciación es la obtención del valor almacenado en el espacio de memoria donde apunta un apuntador. En C y C++ esto se hace a través del operador ‘*’, aplicado al apuntador que contiene la dirección del valor. Nótese que se trata de un operador unario. Ejemplos:
3.1. int x = 17, y;
3.2. int *p;
3.3. p = &x;
3.4. cout << "El valor de x es: " << *p << endl; // Imprime 17
3.5. y = *p + 3; // A ’y’ se le asigna 20
3.6. C++ adem´as provee el operador binario ‘->’, utilizado para obtener campos de un registro con un apuntador al mismo de una manera m´as f´acil y legible. Muchos compiladores de C tambi´en soportan este operador. Ejemplo:
3.6.1. struct Data
3.6.2. {
3.6.3. char nombre[20];
3.6.4. int edad;
3.6.5. };
3.6.6. Data d;
3.6.7. Data *pd = &d;
3.6.8. (*pd).edad = 23; // Acceso al campo ’edad’ utilizando el operador ’.’
3.6.9. pd->edad = 23; // Acceso al campo ’edad’ utilizando el operador ’->’
4. Normalmente, un apuntador inicializado adecuadamente apunta a alguna posición específica de la memoria. Sin embargo, algunas veces es posible que un apuntador no contenga una dirección válida, en cuyo caso es incorrecto desreferenciarlo (obtener el valor al que apunta) porque el programa tendrá un comportamiento impredecible y probablemente erróneo, aunque es posible que funcione bien. Un apuntador puede contener una dirección inválida debido a dos razones:
5. Dado que un apuntador es una variable que apunta a otra, f´acilmente se puede deducir que pueden existir apuntadores a apuntadores, y a su vez los segundos pueden apuntar a apuntadores, y as´ı sucesivamente. Estos apuntadores se declaran colocando tantos asteriscos (‘*’) como sea necesario. Ejemplo:
5.1. char c = ’z’;
5.2. char *pc = &c;
5.3. char **ppc = &pc;
5.4. char ***pppc = &ppc;
5.5. ***pppc = ’m’; // Cambia el valor de c a ’m’
6. Los arreglos y apuntadores est´an fuertemente relacionados. El nombre de un arreglo es simplemente un apuntador constante al inicio del arreglo. Se pueden direccionar arreglos como si fueran apuntadores y apuntadores como si fueran arreglos. Ejemplos:
6.1. int lista_arr[5] = {10, 20, 30, 40, 50};
6.2. int *lista_ptr;
6.3. lista_ptr = lista_arr; // A partir de aqu´ı ambas variables apuntan al mismo sitio
6.4. cout << lista_arr[0]; // Imprime 10
6.5. cout << lista_ptr[0]; // Instrucci´on equivalente a la anterior
6.6. cout << *lista_arr; // Instrucci´on equivalente a la anterior
6.7. cout << *lista_ptr; // Instrucci´on equivalente a la anterior
6.8. cout << lista_arr[3]; // Imprime 40
6.9. cout << lista_ptr[3]; // Instrucci´on equivalente a la anterior
6.10. Es posible sumar y restar valores enteros a un apuntador. El resultado de estas operaciones es el desplazamiento de la direcci´on de memoria hacia adelante (suma) o hacia atr´as (resta) por bloques de bytes del tama˜no del tipo de dato apuntado por el apuntador. Esto permite recorrer arreglos utilizando apuntadores. Ejemplos:
6.10.1. int lista[5] = {10, 20, 30, 40, 50};
6.10.2. int *p;
6.10.3. char cad[15];
6.10.4. char *q;
6.10.5. p = &lista[3]; // ’p’ almacena la direcci´on de la posici´on 3 del arreglo
6.10.6. p = lista + 3; // Instrucci´on equivalente a la anterior
6.10.7. cout << lista[2]; // Imprime 30;
6.10.8. cout << *(lista+2); // Instrucci´on equivalente a la anterior
6.10.9. // Las siguientes instrucciones imprimen la palabra "Programando"
6.10.10. /* Nota: Recu´erdese que una constante de cadena de caracteres es
6.10.11. una secuencia de caracteres en memoria seguidos del c´aracter nulo */
6.10.12. strcpy(cad, "Programando");
6.10.13. for (q = cad; *q != ’\0’; q++)
6.10.14. cout << q;
6.10.15. Tambi´en es posible restar dos apuntadores. El resultado de esta operaci´on es el n´umero de bloques de bytes que hay entre las dos direcciones del tama˜no del tipo de dato apuntado por los apuntadores. Ejemplo:
6.10.15.1. double x[5] = {1.1, 2.1, 3.1, 4.1, 5.1};
6.10.15.2. double *p = &x[1],
6.10.15.3. *q = &x[4];
6.10.15.4. int n;
6.10.15.5. n = q - p; // a ’n’ se le asigna 3
7. Los programas pueden crear variables globales o locales. Las variables declaradas globales en sus programas se almacenan en posiciones fijas de memoria, en la zona conocida como segmento de datos del programa, y todas las funciones pueden utilizar estas variables. Las variables locales se almacenan en la pila (stack) y existen s´olo mientras est´an activas las funciones donde est´an declaradas. En ambos casos el espacio de almacenamiento se reserva en el momento de la compilaci´on del programa.
7.1. Tambi´en es posible reservar y utilizar memoria din´amicamente, tomada de la zona de memoria llamada mont´ıculo (heap) o almac´en libre. En C est´an disponibles varias funciones que permiten realizar reservar y librerar memoria, pero C++ adem´as provee un m´etodo m´as f´acil y seguro de hacerlo.
8. Cuando se declara una variable, el compilador reserva un espacio de memoria para ella y asocia el nombre de ´esta a la direcci´on de memoria desde donde comienzan los datos de esa variable. Las direcciones de memoria se suelen describir como n´umeros en hexadecimal. Un apuntador es una variable cuyo valor es la direcci´on de memoria de otra variable. Se dice que un apuntador “apunta” a la variable cuyo valor se almacena a partir de la direcci´on de memoria que contiene el apuntador. Por ejemplo, si un apuntador p almacena la direcci´on de una variable x, se dice que “p apunta a x”.Cuando se declara una variable, el compilador reserva un espacio de memoria para ella y asocia el nombre de ´esta a la direcci´on de memoria desde donde comienzan los datos de esa variable. Las direcciones de memoria se suelen describir como n´umeros en hexadecimal. Un apuntador es una variable cuyo valor es la direcci´on de memoria de otra variable. Se dice que un apuntador “apunta” a la variable cuyo valor se almacena a partir de la direcci´on de memoria que contiene el apuntador. Por ejemplo, si un apuntador p almacena la direcci´on de una variable x, se dice que “p apunta a x”.
9. La referenciación es la obtención de la dirección de una variable. En C y C++ esto se hace a través del operador ‘&’, aplicado a la variable a la cual se desea saber su dirección. Nótese que se trata de un operador unario. Ejemplo:
9.1. int x = 25; cout << "La direcci´on de x es: " << &x << endl;
9.1.1. Este c´odigo imprime un valor del estilo “0x4fffd34”. Este valor puede variar durante cada ejecuci´on del programa, debido a que el programa puede reservar distintos espacios de memoria durante cada ejecuci´on.
10. Al igual que el resto de las variables, los apuntadores se enlazan a tipos de datos espec´ıficos (apuntadores a variables de cierto tipo), de manera que a un apuntador s´olo se le pueden asignar direcciones de variables del tipo especificado en la declaraci´on del apuntador. Ejemplo:
10.1. int *p1;
10.2. float *p2;
10.3. int x;
10.4. p1 = &x; // Esto es v´alido
10.5. p2 = &x; // Esto no es v´alido (el compilador genera un error)
11. 1. Cuando un apuntador se declara, al igual que cualquier otra variable, el mismo posee un valor cualquiera que no se puede conocer con antelación, hasta que se inicialice con algún valor (dirección). Ejemplo:
11.1. float *p;
11.2. cout << "El valor apuntado por p es: " << *p << endl; // Incorrecto
11.3. *p = 3.5; // Incorrecto
11.4. 2. Despu´es de que un apuntador ha sido inicializado, la direcci´on que posee puede dejar de ser v´alida si se libera la memoria reservada en esa direcci´on, ya sea porque la variable asociada termina su ´ambito o porque ese espacio de memoria fue reservado din´amicamente y luego se liber´o1 . Ejemplo:
11.4.1. int *p, y;
11.4.2. void func()
11.4.3. {
11.4.4. int x = 40;
11.4.5. p = &x;
11.4.6. y = *p; // Correcto
11.4.7. *p = 23; // Correcto
11.4.8. }
11.4.9. void main()
11.4.10. {
11.4.11. func();
11.4.12. y = *p; // Incorrecto
11.4.13. *p = 25; // Incorrecto
11.4.14. }
11.4.15. Si se intenta desreferenciar un apuntador que contiene una direcci´on inv´alida pueden ocurrir cosas como las siguientes:
11.4.15.1. Se obtiene un valor incorrecto en una o m´as variables debido a que no fue debidamente inicializada la zona de memoria que se accede a trav´es de la direcci´on en cuesti´on. Esto puede ocasionar que el programa genere resultados incorrectos. Si casualmente la direcci´on es la misma de otra variable utilizada en el programa, o est´a dentro del rango de direcciones de una zona de memoria utilizada, existe el riesgo de sobreescribir datos de otras variables. Existe la posibilidad de que la direcci´on est´e fuera de la zona de memoria utilizada para almacenar datos y m´as bien est´e, por ejemplo, en la zona donde se almacenan las instrucciones del programa. Al intentar escribir en dicha zona, f´acilmente puede ocurrir que el programa genere un error de ejecuci´on y el sistema operativo lo detenga, o que el programa no responda y deje al sistema operativo inestable. En muchos casos el sistema operativo detecta el acceso inadecuado a una direcci´on de memoria, en cuyo caso detiene abruptamente el programa.
11.4.15.1.1. Cuando no se desea que un apuntador apunte a algo, se le suele asignar el valor NULL, en cuyo caso se dice que el apuntador es nulo (no apunta a nada). NULL es una macro t´ıpicamente definida en archivos de cabecera como stdef.h y stdlib.h. Normalmente, en C++ se encuentra disponible sin incluir ning´un archivo de cabecera. NULL se suele definir en estas librer´ıas as´ı:
12. Es posible declarar apuntadores constantes. De esta manera, no se permite la modificaci´on de la direcci´on almacenada en el apuntador, pero s´ı se permite la modificaci´on del valor al que apunta. Ejemplo:
12.1. int x = 5, y = 7;
12.2. int *const p = &x; // Declaraci´on e inicializaci´on del apuntador constante
12.3. *p = 3; // Esto es v´alido
12.4. p = &y; // Esto no es v´alido (el compilador genera un error)
12.5. Tambi´en es posible declarar apuntadores a datos constantes. Esto hace que no sea posible modificar el valor al que apunta el apuntador. Ejemplo:
12.5.1. int x = 5, y = 7;
12.5.2. const int *p = &x; // Declaraci´on e inicializaci´on del apuntador a constante
12.5.3. p = &y; // Esto es v´alido
12.5.4. *p = 3; // Esto no es v´alido (el compilador genera un error)
12.5.5. y = 3; // Esto es v´alido
13. El lenguaje C no provee una manera de pasar par´ametros por referencia. Sin embargo, es posible hacerlo a trav´es del uso de apuntadores. A continuaci´on se muestra un ejemplo del paso de un par´ametro por referencia en C++, y luego un c´odigo equivalente en C o C++ utilizando un apuntador:
13.1. void suma(int a, int b, int& r)
13.2. {
13.3. r = a + b;
13.4. }
13.5. void main()
13.6. {
13.7. int x;
13.8. suma(7, 5, x);
13.9. cout << "7 + 5 = " << x;
13.10. }
13.11. void suma(int a, int b, int *r)
13.12. {
13.13. *r = a + b;
13.14. }
13.15. void main()
13.16. {
13.17. int x;
13.18. suma(7, 5, &x);
13.19. cout << "7 + 5 = " << x;
13.20. }
13.21. N´otese que en ambos casos se utiliza el operador ‘&’ para cosas distintas. El operador ‘&’ tiene dos significados como operador unario: se˜nalaci´on de par´ametro por referencia y operador de referenciaci´on.
14. Para asignar memoria din´amicamente en C++ se utilizan los operadores new y delete. El operador new reserva memoria para un tipo de datos espec´ıfico y retorna su direcci´on, o retorna NULL en caso de no haber conseguido suficiente memoria; y el operador delete permite liberar la memoria reservada a trav´es de un apuntador. La sintaxis de ambos operadores es como sigue:
14.1. Para reservar y liberar un solo bloque: <apuntador> = new <tipo de dato> delete <apuntador> Para reservar y liberar varios bloques (un arreglo): <apuntador> = new <tipo de dato>[<n´umero de bloques>] delete [] <apuntador>
14.1.1. El tipo del apuntador especificado del lado izquierdo del operador new debe coincidir con el tipo especificado del lado derecho. De no ser as´ı, se produce un error de compilaci´on. Ejemplos:
14.1.1.1. struct Data
14.1.1.2. {
14.1.1.3. char nombre[20];
14.1.1.4. int edad;
14.1.1.5. };
14.1.1.6. Data *p_data; // Declaraci´on de un apuntador a Data
14.1.1.7. int i;
14.1.1.8. p_data = new Data; // Reservaci´on de memoria para un registro
14.1.1.9. if (p_data != NULL) // Verificaci´on de reservaci´on
14.1.1.10. {
14.1.1.11. strcpy(p_data->nombre, "Rachel"); // Inicializaci´on de datos
14.1.1.12. p_data->edad = 21; // en la memoria reservada
14.1.1.13. delete p_data; // Liberaci´on de memoria
14.1.1.14. }
14.1.1.15. // Reservaci´on de memoria para un arreglo de 10 registros
14.1.1.16. p_data = new Data[10];
14.1.1.17. if (p_data != NULL) // Verificaci´on de reservaci´on
14.1.1.18. {
14.1.1.19. // Lectura de datos del arreglo
14.1.1.20. for (i = 0; i < 10; i++)
14.1.1.21. cin >> p_data[i].nombre >> p_data[i].edad;
14.1.1.22. // Liberaci´on de memoria del arreglo
14.1.1.23. delete [] p_data;
14.1.1.24. }