Filmina 01
Operaciones con punteros:
Aunque no son muchas las operaciones que se pueden hacer con los punteros, cada una tiene sus peculiaridades.
Asignación.
Ya hemos visto cómo asignar a un puntero la dirección de una variable. También podemos asignar un puntero a otro, esto hará que los dos apunten a la misma posición:
int *q, *p; int a; q = &a; /* q apunta al contenido de a */ p = q; /* p apunta al mismo sitio, es decir, al contenido de a */
Operaciones aritméticas.
También hemos visto como afectan a los punteros las operaciones de suma con enteros. Las restas con enteros operan de modo análogo.
Pero, ¿qué significan las operaciones de suma y resta entre punteros?, por ejemplo:
int vector[10]; int *p, *q; p = vector; /* Equivale a p = &vector[0]; */ q = &vector[4]; /* apuntamos al 5º elemento */ cout << q-p << endl;
El resultado será 4, que es la "distancia" entre ambos punteros. Normalmente este tipo de operaciones sólo tendrá sentido entre punteros que apunten a elementos del mismo array.
La suma de punteros no está permitida.
Comparación entre punteros.
Comparar punteros puede tener sentido en la misma situación en la que lo tiene restar punteros, es decir, averiguar posiciones relativas entre punteros que apunten a elementos del mismo array.
Existe otra comparación que se realiza muy frecuente con los punteros. Para averiguar si estamos usando un puntero es corriente hacer la comparación:
if(NULL != p)
o simplemente
if(p)
Y también:
if(NULL == p)
O simplemente
if(!p)
Filmina 02
Punteros genéricos.
Es posible declarar punteros sin tipo concreto:
void *<identificador>;
Estos punteros pueden apuntar a objetos de cualquier tipo.
Por supuesto, también se puede emplear el "casting" con punteros, sintaxis:
(<tipo> *)<variable puntero>
Por ejemplo:
#include <iostream>
using namespace std;
int main() {
char cadena[10] = "Hola";
char *c;
int *n;
void *v;
c = cadena; // c apunta a cadena
n = (int *)cadena; // n también apunta a cadena
v = (void *)cadena; // v también
cout << "carácter: " << *c << endl;
cout << "entero: " << *n << endl;
cout << "float: " << *(float *)v << endl;
cin.get();
return 0;
}
El resultado será:
carácter: H entero: 1634496328 float: 2.72591e+20
Vemos que tanto "cadena" como los punteros "n", "c" y "v" apuntan a la misma dirección, pero cada puntero tratará la información que encuentre allí de modo diferente, para "c" es un carácter y para "n" un entero. Para "v" no tiene tipo definido, pero podemos hacer "casting" con el tipo que queramos, en este ejemplo con float.
Nota: el tipo de línea del tercer "cout" es lo que suele asustar a los no iniciados en C y C++, y se parece mucho a lo que se conoce como código ofuscado. Parece como si en C casi cualquier expresión pudiese compilar.
Filmina 03
Variables dinámicas:
Donde mayor potencia desarrollan los punteros es cuando se unen al concepto de memoria dinámica.
Cuando se ejecuta un programa, el sistema operativo reserva una zona de memoria para el código o instrucciones del programa y otra para las variables que se usan durante la ejecución. A menudo estas zonas son la misma zona, es lo que se llama memoria local. También hay otras zonas de memoria, como la pila, que se usa, entre otras cosas, para intercambiar datos entre funciones. El resto, la memoria que no se usa por ningún programa es lo que se conoce como "heap" o montón. Cuando nuestro programa use memoria dinámica, normalmente usará memoria del montón, y no se llama así porque sea de peor calidad, sino porque suele haber realmente un montón de memoria de este tipo.
C++ dispone de dos operadores para acceder a la memoria dinámica, son "new" y "delete". En C estas acciones se realizan mediante funciones de la librería estándar "stdio.h".
Hay una regla de oro cuando se usa memoria dinámica, toda la memoria que se reserve durante el programa hay que liberarla antes de salir del programa. No seguir esta regla es una actitud muy irresponsable, y en la mayor parte de los casos tiene consecuencias desastrosas. No os fiéis de lo que diga el compilador, de que estas variables se liberan solas al terminar el programa, no siempre es verdad.
Filmina 04
Operadores de Referencia (&) e Indirección (*)
El operador de referencia (&) nos devuelve la dirección de memoria del operando.
Sintaxis:
&<expresión simple>
El operador de indirección (*) considera a su operando como una dirección y devuelve su contenido.
Sintaxis:
*<puntero>
Filmina 05
Operadores . y ->
Operador de selección (.). Permite acceder a variables o campos dentro de una estructura.
Sintaxis:
<variable_estructura>.<nombre_de_variable>
Operador de selección de variables o campos para estructuras referenciadas con punteros. (->)
Sintaxis:
<puntero_a_estructura>-><nombre_de_variable>
Filmina 06
Operador de preprocesador
El operador "#" sirve para dar órdenes o directivas al compilador. La mayor parte de las directivas del preprocesador se verán en capítulos posteriores.
Veremos, sin embargo dos de las más usadas.
Directiva define:
La directiva "#define", sirve para definir macros. Esto suministra un sistema para la sustitución de palabras, con y sin parámetros.
Sintaxis:
#define <identificador_de_macro> <secuencia>
El preprocesador sustituirá cada ocurrencia del <identificador_de_macro> en el fichero fuente, por la <secuencia> aunque hay algunas excepciones. Cada sustitución se conoce como una expansión de la macro, y la secuencia es llamada a menudo cuerpo de la macro.
Si la secuencia no existe, el <identificador_de_macro> será eliminado cada vez que aparezca en el fichero fuente.
Después de cada expansión individual, se vuelve a examinar el texto expandido a la búsqueda de nuevas macros, que serán expandidas a su vez. Esto permite la posibilidad de hacer macros anidadas. Si la nueva expansión tiene la forma de una directiva de preprocesador, no será reconocida como tal.
Existen otras restricciones a la expansión de macros:
Las ocurrencias de macros dentro de literales, cadenas, constantes alfanuméricas o comentarios no serán expandidas.
Una macro no será expandida durante su propia expansión, así #define A A, no será expandida indefinidamente.
Filmina 07
Operador new:
El operador new sirve para reservar memoria dinámica.
Sintaxis:
[::]new [<emplazamiento>] <tipo> [(<inicialización>)] [::]new [<emplazamiento>] (<tipo>) [(<inicialización>)] [::]new [<emplazamiento>] <tipo>[<número_elementos>] [::]new [<emplazamiento>] (<tipo>)[<número_elementos>]
El operador opcional :: está relacionado con la sobrecarga de operadores, de momento no lo usaremos. Lo mismo se aplica a emplazamiento.
La inicialización, si aparece, se usará para asignar valores iniciales a la memoria reservada con new, pero no puede ser usada con arrays.
Las formas tercera y cuarta se usan para reservar memoria para arrays dinámicos. La memoria reservada con new será válida hasta que se libere con delete o hasta el fin del programa, aunque es aconsejable liberar siempre la memoria reservada con new usando delete. Se considera una práctica muy sospechosa no hacerlo.
Si la reserva de memoria no tuvo éxito, new devuelve un puntero nulo, NULL.
Filmina 08
Operador delete:
El operador delete se usa para liberar la memoria dinámica reservada con new.
Sintaxis:
[::]delete [<expresión>] [::]delete[] [<expresión>]
La expresión será normalmente un puntero, el operador delete[] se usa para liberar memoria de arrays dinámicas.
Es importante liberar siempre usando delete la memoria reservada con new. Existe el peligro de pérdida de memoria si se ignora esta regla.
Cuando se usa el operador delete con un puntero nulo, no se realiza ninguna acción. Esto permite usar el operador delete con punteros sin necesidad de preguntar si es nulo antes.