Você está na página 1de 9

APUNTADORES

Un apuntador o puntero es una variable que contiene una dirección de memoria, la cual
corresponderá a un dato o a una variable (que contiene el dato). Quiere decir que el
puntero apunta al espacio físico donde esta el dato o la variable.
Puntero
p 2049

Direcciones de Memoria
2047 2048 2049 2050 2051

Características de los apuntadores


 Los apuntadores o punteros permiten manipular la memoria del ordenador de
forma eficiente.
 Los apuntadores son variables y deben declararse e inicializarse.
 Representan la posición de memoria (más que el valor) de otro dato.
 Los punteros siempre apuntan a variables del mismo tipo. Si se mezclan los tipos,
los resultados son erróneos. Es importante inicializar los punteros antes de
utilizarlos. Si no se inicializan, es decir, si no apuntan a algún sitio válido, se
pueden alterar otras zonas de memoria, ya sea del propio programa o del sistema
operativo.
 Los punteros se pueden comparar entre ellos. Además, se pueden decrementar o
incrementar. El incremento o el decremento varía según el tipo de dato al que
apunten. A los punteros también se les puede sumar o restar números enteros.
 Proporcionan una forma de devolver varios datos desde una función mediante los
argumentos de la función.

Declaración de punteros en lenguaje C


En lenguaje C, para realizar la declaración de un puntero se indica el tipo de dato a
almacenar y el identificador de la variable al cual se le antepone el símbolo “*”. El *
informa al compilador que lo que queremos es una variable de tipo apuntador, es decir,
que se reserven los bytes necesarios para alojar una dirección en la memoria
tipo_de_dato *nombre_de_apuntador;
Ejemplo:
char *ch1; // Apuntador, Tipo de dato almacenado: char. Identificador: ch1.
float *valor; // Apuntador, Tipo de dato almacenado: float. Identificador: valor.

Verificación de tipos en apuntadores


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 solo se
le pueden asignar direcciones de variables del tipo especificado en la declaración del
apuntador.
Ejemplo:
int *p1;
float *p2;
int x;
p1 = &x; // Esto es válido
p2 = &x; // Esto no es válido (el compilador genera un error).
Operaciones con apuntadores
Sobre una variable puntero se pueden realizar una serie de operaciones como se describe
a continuación:
− Dirección o asignación (inicializar):
Se pueden asignar a un apuntador direcciones de variables a través del operador de
referenciación (‘&’) o direcciones almacenadas en otros apuntadores. El operador de
dirección no es realmente uno que se aplique sobre las variables puntero (normalmente),
sino sobre otros tipos de variable. Este operador, representado con el símbolo ampersand
(&) obtiene la dirección de memoria de la variable a la que precede.
int i = 5;
int *p, *q;
p = &i; // Se le asigna a ’p’ la dirección de ’i’.
q = p; // Se le asigna a ’q’ la dirección almacenada en ’p’ (la misma de ’i’).
 Puntero nulo
Un puntero se puede inicializar como cualquier otra variable, aunque los únicos valores
significativos son NULL o la dirección de un objeto previamente definido.
NULL es una constante definida en el fichero studio.h, cuando un puntero apunta a NULL
significa que apunta a la dirección 0 del sistema, y esta a la espera de que se le asigne una
dirección valida.
− Indirección o des-referenciación de apuntadores:
La des-referenciación es la obtención del valor almacenado en el espacio de memoria
donde apunta un apuntador. Este operador se representa por un asterisco (*) y devuelve
un valor del tipo apuntado por el operando. Este valor es el contenido en la posición
apuntada por el puntero. Así, en el siguiente código se muestra un ejemplo.
float *p;
float q= 1.44;
p= &q;
print("%f\n", *p);
el valor que se imprime es 1.44, ya que p apunta a la dirección de q.
− Incremento, decremento: los valores de tipo puntero se pueden incrementar y
decrementar, siempre en valores enteros. Se admiten los operadores: ’+’ , ’−’ , ’++’ y
’−−’.
− Resta de punteros: se puede hallar la diferencia entre dos punteros, que es la distancia
que separa las direcciones a las que apuntan en memoria.
− Operadores relaciones y de igualdad
Operador Relación probada
< Primer operando menor que el segundo operando
> Primer operando mayor que el segundo operando
<= Primer operando menor o igual que segundo operando
>= Primer operando mayor o igual que segundo operando
== Primer operando igual a segundo operando
¡= Primer operando no igual a segundo operando

 Para los operadores de igualdad (==) y desigualdad (!=), el resultado de la


comparación indica si los dos punteros direccionan la misma ubicación de memoria.
Para los otros operadores relacionales (<, >, <= y >=), el resultado de la comparación
indica la posición relativa de las dos direcciones de memoria de los objetos a los que
se señala. Los operadores relacionales comparan solo desplazamientos.
La comparación de puntero solo se define para partes del mismo objeto. Si los
punteros hacen referencia a miembros de una matriz, la comparación es equivalente
a la comparación de los subíndices correspondientes. La dirección del primer
elemento de la matriz es “menor que” la dirección del último elemento. En el caso
de las estructuras, los punteros a miembros de estructura declarados más tarde son
“mayores que” los punteros a miembros declarados antes en la estructura. Los
punteros a los miembros de la misma unión son iguales.
 Un valor de puntero se puede comparar con el valor constante 0 para igualdad (==)
o desigualdad (!=). Un puntero a un valor de 0 se denomina un puntero “NULL”; es
decir, no señala a una ubicación de memoria válida.

Apuntadores constantes y apuntadores a constantes


Es posible declarar apuntadores constantes. De esta manera, no se permite la modificación
de la dirección almacenada en el apuntador, pero sí se permite la modificación del valor
al que apunta. Ejemplo:

1 #include <stdio.h>
2 int main()
3 { int x = 5, y = 7;
4 int *const p = &x; // Declaración e inicialización del
5 apuntador constante
6 *p = 3; // Esto es valido
7 p = &y; // Esto no es válido (el compilador genera un
8 error)
9 }
También es posible declarar apuntadores a datos constantes. Esto hace que no sea posible
modificar el valor al que apunta el apuntador. Ejemplo:

1 #include <stdio.h>
2 int main()
3 { int x = 5, y = 7;
4 const int *p = &x; // Declaración e inicialización del
5 apuntador a constante
6 p = &y; // Esto es válido
7 *p = 3; // Esto no es válido (el compilador genera un
8 error)
9 y = 3; // Esto es válido}

Apuntadores y arreglos
Los arreglos y apuntadores están 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.
Cualquier operación que pueda lograrse por indexación de un arreglo también puede
realizarse con apuntadores.
Es posible sumar y restar valores enteros a un apuntador. El resultado de estas operaciones
es el desplazamiento de la dirección de memoria hacia adelante (suma) o hacia atrás
(resta) por bloques de bytes del tamaño del tipo de dato apuntado por el apuntador. Esto
permite recorrer arreglos utilizando apuntadores.
El siguiente ejemplo se muestra la relación entre apuntadores y arreglos, en este se declara
un dato estructurado tipo vector, y se declara una variable de tipo apuntador que hace
referencia a las localidades de memoria del vector, y se observa como no es necesario
hacer uso de los índices del arreglo, para realizar el recorrido del dato estructurado
(vector).
int a[10] ; // Declaración Vector - define un arreglo de tamaño 10, o sea un bloque de
10 enteros consecutivos que se acceden a través de a[0], a[1], . . . , a[9].

a:
a[0] a[1] a[2] a[3] a[4] a[5] a[6] a[7] a[8] a[9]

notación a[i] hace referencia al i-esimo elemento del arreglo. Supongamos que pa es un
puntero a enteros, declarado como
int *pa; // Declaración Puntero
entonces la asignación:
pa = &a[0]; // Inicialización del puntero - hace que pa apunte al elemento cero de a, o
sea pa contiene la dirección de a[0]. El nombre de un arreglo es un puntero a su primer
elemento (con lo cual podemos escribir el ejemplo anterior como pa = a). Si pa apunta a
un elemento particular de un arreglo, entonces por definición pa+1 apunta al siguiente
elemento, pa+i apunta i elementos más adelante y pa-1 apunta i elementos antes. Por lo
tanto si pa apunta a a[0],
*(pa + 1); // hace referencia al contenido de a[1], pa+i es la dirección de a[i] y *(pa+i)
es el contenido de a[i].
pa: pa+1:pa+2:
.

a:
a[0] a[1] a[2] a[3] a[4] a[5] a[6] a[7] a[8] a[9]

La correspondencia entre indexación y aritmética de apuntadores es muy estrecha. Por


definición, el valor de una variable o expresión de tipo de arreglo es la dirección del
elemento cero del arreglo. Así, que después de la asignación
pa = &a[0]; // pa y a tienen valores idénticos para la celda con índice 0.
Puesto que el nombre de un arreglo es un sinónimo para la localidad del elemento
inicial, la asignación pa = &a[0]; puede escribirse también como pa=a;
Una referencia a a[i] también puede escribirse como *(a+1). Al evaluar a[i], en lenguaje
C se convierte inmediatamente a: *a(a+i); las dos formas son equivalentes. Si pa, es un
apuntador, las expresiones pueden usarlo con un subíndice; pa [i] es idéntico a *(pa+i).
En resumen, cualquier expresión de arreglo e índice es equivalente a una expresión escrita
con un apuntador y un desplazamiento.
Punteros a arreglos de caracteres
Los apuntadores tipo carácter se utilizan con frecuencia para tener acceso a cadenas de
caracteres. Cadenas de caracteres y apuntadores a carácter pueden ser tratados en forma
indistinta. El nombre de un arreglo de caracteres (sin el uso de un subíndice), se considera
como apuntador al primer elemento del arreglo de caracteres.
El lenguaje C no soporta el tipo string, por ello las operaciones sobre strings en realidad
trabajan sobre arreglos de caracteres que siguen algunas convenciones. Generalmente,
una variable de tipo string se declara como un puntero a char (en realidad, un puntero al
primer elemento de un array de tipo chart).

Por ejemplo
Un string escrito como “Soy una cadena”. Es un arreglo de caracteres. El arreglo esta
determinado por el carácter nulo ‘\0’ para que el programa puede encontrar el fin.
Char *pmessage; // Declaración de la variable (apuntador) pmessage
pmessage = “ya es el tiempo”; // Asigna a pmessage un apuntador al arreglo de
caracteres.
Existe una importante diferencia entre estas definiciones:
Char amessage [] = “ya es el tiempo”; /*arreglo */
Char *pmessage= “ya es el tiempo”; /*apuntador*/
amessage es un arreglo suficientemente grande como para contener la secuencia de
caracteres y el ‘/0’ que lo inicializa. Se pueden modificar caracteres individuales dentro
del arreglo, pero amessage siempre se referirá a la misma localidad de almacenamiento.
Por otro lado, pmessage es un apuntador, inicializado para apuntar a una cadena
constante; el apuntador puede modificarse posteriormente para que apunte a algún otro
lado, pero el resultado es indefinido si trata de modificar el contenido de la cadena

pmessage: . ahora es el tiempo \0

amessage: ahora es el tiempo \0

Arreglo de punteros
Un array multidimensional puede ser expresado como un array de punteros en lugar de
como un puntero a un grupo contiguo de arrays. En estos casos el nuevo array será de una
dimensión menor que el array multidimensional. Cada puntero indicará el principio de un
array de dimensión (n-1).
En términos generales, un array bidimensional puede ser definido como un array
unidimensional de punteros. Los punteros pueden ser almacenados en arreglos por el
hecho de ser variables.

Los arreglos de punteros son comunes cuando se desea tener un array de cadenas de
caracteres. Si una cadena de caracteres puede escribirse como char *c; un array de
cadenas de caracteres podrá escribirse como char **c; o char *c[x];.

Para ilustrar este hecho vamos a ver un ejemplo de un programa que ordena las líneas de
texto en orden alfabético; haciendo uso de un arreglo de punteros

. defghi . defghi

. jklmnopqrst . jklmnopqrst

. abc . abc

Dos líneas pueden ser comparadas pasando sus punteros a strcmp. Cuando dos líneas
desordenadas tienen que intercambiarse, se intercambian los apuntadores en el arreglo de
apuntadores, no las líneas de texto. Esto elimina el doble problema de un manejo
complicado de almacenamiento y exceso de procesamiento que se produciría al mover
las líneas.
Este segundo ejemplo, muestra un arreglo o vector de punteros y se observa como cada
elemento del arreglo apunta a una localidad de memoria perteneciente a una variable
diferente.

Punteros vs arreglos multidimensionales (diferencias)


Existe una diferencia entre apuntadores y arreglos multidimensionales y esta radica en el
número de elementos y la forma en como es declarado cada uno.
Por ejemplo, dada las siguientes declaraciones de arreglos bidimensionales:
int a [10] [20];
int *b [10];
La diferencia radica en que el primer arreglo las filas pueden únicamente tener 20 celdas;
en cambio en el arreglo de punteros, las filas pueden ser de tamaño variable.
La ventaja importante del arreglo de apuntadores es que los renglones del arreglo pueden
ser de longitudes diferentes. Es decir, no es necesario que cada elemento de b apunte a un
vector de veinte elementos; alguno puede apuntar a dos elementos, otros a cincuenta y
algún otro a ninguno.
La comparación de un arreglo de apuntadores y un arreglo bidimensional se muestra a
continuación:
char * nombre [] = {"Mes ilegal", "Ene", "Feb", "Mar"};
nombre:

. Mes ilegal \0
. Ene \0
. Feb \0
. Mar \0

char aname [] [15] = {"Mes ilegal", "Ene", "Feb", "Mar"};

aname:

Mes ilegal \0 Ene\0 Feb\0 Mar\0


0 15 30 45
Punteros y funciones (paso por referencia)
Uno de los usos de los punteros es el paso de parámetros por referencia a una función.
Los parámetros de una función se pueden pasar por valor o por referencia. Si se pasan por
valor, las modificaciones que se hagan sobre ellos no se verán en el programa principal.
Si se pasan por referencia, sí se reflejan en el programa principal estas modificaciones.
En lenguaje C, todos los parámetros de las funciones se pasan por valor, sin excepción.
Para simular un paso de parámetro por referencia en C, lo que se hace es pasar un puntero
al objeto que se pasa. Así, la función tiene acceso no sólo al valor del parámetro sino
también a su situación en memoria, lo que permite su modificación.
El paso por referencia posibilita que una función pueda generar más de un valor. La
función devolverá (con return) uno de los valores de retorno y almacenará en parámetros
pasados por referencia el resto de los valores.
Un ejemplo:

1 #include <stdio.h>
2 int main(){
3 int x= 14;
4 inc(&x);
5 ...
6 void inc (int *par) {
7 (*par)++;
8 }
9 }
Resultado
x: 5 y:10
x:10 y:5

Donde vemos que esta vez sí se modificó el contenido de las variables en cuestión, ya que
se da acceso no solo al parámetro si no a la localidad de memoria.

Puntero a funciones
Un puntero a función es una variable que almacena la dirección de una función. Esta
función puede ser llamada más tarde, a través del puntero. Este tipo de construcción es
útil pues encapsula comportamiento, que puede ser llamado a través de un puntero.
Veamos cómo funciona mediante un ejemplo sencillo que crea un puntero a una función
de imprimir y lo invoca:
1 #include <stdio.h>
2 void imprime()
3 {
4 printf("Imprimiendo un message\n");
5 }
6 int main()
7 {
8 void (*ptr_funct)(void)=imprime;
9 ptr_funct(); //Llama a imprime
10 return 0;
11 }
Devolución de punteros
Una función puede retornar un tipo de datos puntero. La función se declararía así tipo
*función (argumentos).
Este tipo de funciones se suelen usar para reservar memoria o crear elementos en
estructuras dinámicas de datos.
Apuntadores y gestión dinámica de memoria
La gestión de memoria dinámica se refiere a la gestión de memoria manual. Esto le
permite obtener más memoria cuando sea necesario y liberarla cuando no sea necesario.
Aunque en lenguaje C, de forma inherente no tiene ninguna técnica para asignar memoria
dinámicamente, hay 4 funciones de biblioteca definidas en <stdlib.h> para la asignación
dinámica de memoria, usando malloc() (memory allocate = asignar memoria) , calloc(),
o cualquier otra función de reservación de memoria en tiempo de ejecución
Usar este método (malloc), permite posponer la decisión del tamaño del bloque de
memoria necesario para guardar, por ejemplo, un arreglo, hasta el tiempo de ejecución; o
permite usar una sección de la memoria para guardar un arreglo de enteros en un tiempo
determinado, y posteriormente, cuando esa memoria no sea necesaria, liberarla para otros
usos (método: free), como para guardar un arreglo de estructuras.
void *malloc (size_t_size) // El argumento de la función malloc especifica el número de
bytes de memoria que el usuario quiere reservar y devuelve la dirección de memoria de
la zona de memoria reservada.
En forma similar se puede lograr liberar la memoria, la función free permite liberar un
espacio de memoria gestado por la función malloc. Para realizar esta operación, sólo hay
que pasarle como parámetro da dirección de inicio del bloque. El prototipo de la esta
función se muestra a continuación:
void *free (void *ptr) // libera la memoria apuntada por ptr, ptr puede ser de cualquier
tipo. Los bloques a liberar debieron ser asignados de manera dinámica.

1 #include <stdio.h>
2 #include <alloc.h>
3 int main (void){
4 int * pt;
5 int numElem;
6 printf("Ingrese el número de datos del arreglo: ");
7 scanf("%d%", &numElem);
8 pt = (int *) malloc( numElem * sizeof(int) );
9 pt[2] = 177;
10 *pt = 88;
11 *(pt+2) = 92;
12 free(pt); // el bloque fue liberado ···
13 }

Você também pode gostar