Sunteți pe pagina 1din 9

Computación II - Punteros 1

PUNTEROS :
Para entender mejor los punteros es necesario revisar algunos conceptos basicos:
Variable.
La memoria de un computador está compuesta por unidades básicas llamadas "Bytes". Cada byte posee 8
bits los cuales sólo pueden tomar dos valores: 0 ó 1. Cada Byte tiene una identificación o dirección,
llamada "Dirección de Memoria"
Una computadora opera manipulando direcciones de memoria y los valores almacenados en dichas
direcciones. Una variable es un identificador, que representa una localidad de memoria. La ubicación de
la variable es hecha por el compilador.
La cantidad de espacio ocupado por un tipo estandar especifico, depende del tipo de computador y del
sistema operativo.
Las variables tienen entonces 2 valores asociados a ella: Un valor es la dirección de memoria
que le fué asignada por el compilador y que no puede ser cambiada mientras el programa se
está ejecutando y otro valor que es el contenido de la variable misma, el cual sí puede ser
cambiado por las instrucciones del programa.
Cuando se declara una variable, se asocian tres atributos con la misma : su nombre, su tipo y su
dirección en memoria.
Ejemplo :
La instrucción int n asocia al nombre de variable n ,el tipo entero y una dirección de memoria donde se
almacena el dato que contiene n.
Al valor del dato que contiene la variable se accede por su nombre, a la dirección de la variable se
accede por medio del operador de dirección &.
Un PUNTERO es una variable que contiene la dirección de otra variable, en otras palabras, los
punteros son variables que almacenan direcciones de memoria.
USOS Y VENTAJAS DE LOS PUNTEROS:
• Permiten el acceso a cualquier posición de la memoria, para ser leída o para ser escrita (en los casos
en que èsto sea posible)
• Permiten la transferencia de argumentos a las funciones, de modo que puedan retener un valor
nuevo, que resulta de aplicarles la función.
• Se usan en el proceso de definición de memoria dinámica
• Son el soporte de enlace que utilizan estructuras avanzadas de datos en memoria dinámica como las
listas, pilas, colas y árboles.
• Operan más eficientemente en los arreglos, en comparación con el uso de subíndices.
OPERADORES DE PUNTEROS :
Operador Objetivo
& Se antepone a un nombre de función ó variable para acceder a la dirección de la misma
(significa dirección de)
* Se utiliza para declarar un puntero y para accesar al contenido de la variable apuntada por
el puntero. Es complemento del operador &. ( Significa contenido de la dirección )

DECLARACIÓN DE PUNTEROS:
Para declarar un puntero se usa el formato: tipo *nombre donde:
tipo indica el tipo de variables que se van a manejar a través del puntero y corresponde a los
tipos de variable definidos en C o algún tipo definido por el usuario (struct). El tipo permite
conocer la cantidad de bytes a ser leìdos al usar el puntero.
(*) indica que la variable que se encuentra a continuación es un puntero
Computación II - Punteros 2

nombre es el nombre del puntero. Cualquier Identificador vá lido en c.


Ejemplo: int *pent; Declara un puntero a un dato de tipo entero.
char *ptexto; Declara un puntero a un dato de tipo caracter.
float *preal; Declara un puntero a un dato de tipo real.
INICIALIZACIÓN DE PUNTEROS :
Un puntero, como cualquier variable, además de ser declarado (para comenzar a existir) necesita ser
inicializado (darle un valor de modo controlado). Al ser declarado posee un valor, el problema es que se
trata de un valor aleatorio. Intentar operar con un puntero sin haberlo inicializado es una frecuente causa
de problemas. En relación a la inicialización de un puntero existen tres posibilidades:
1. Estar indefinido (lo más peligroso)
2. No apuntar a nada
3. Apuntar a una variable o dirección de memoria determinada.
En el siguiente ejemplo se muestra la inicialización de un puntero.
Formas Incorrectas de inicializaciòn Formas Correctas de inicializaciòn
int a, *b; int a, *b;
b=a; // Error falta & b=&a; // b apunta a ‘a’
int alfa, *pa; int alfa, *pa = &alfa;
*pa = &alfa; // Error sobra * *pa = 99; // alfa = 99
int alfa, *pa; int *pa = NULL; // pa apunta a nada
*pa=99; // Error puntero indefinido

Ejemplo :
int i ; // declara una variable i de tipo entero
int *p ; // declara un puntero a una variable de tipo entero
p= &i ; //inicializa el puntero p ( asigna la dirección de i a p )
EJEMPLO DEL USO DE PUNTEROS :
1 int m,n,*p;  Declara 2 variables enteras y un puntero a variables
enteras.
2 m = 2;  Asigna el valor 2 a la variable m
3 p = &m  Asigna la dirección de la variable m al puntero p, o sea
que p apunta a m (Inicializa el puntero p).
4 n = *p  Asigna a n el contenido de la dirección que apunta p o sea
m (n = 2)
5 *p = 10  Asigna al contenido de la dirección que apunta p el valor
de 10 o sea m = 10

Si se representa el contenido de la memoria en cada paso del programa se tiene:


Contenido de la memoria

Variable Dirección Paso1 Paso2 Paso3 Paso4 Paso5


0000
:
m 0100-0103 2 2 2 10
0104
n 0105-0108 2 2
0109
:
p 1100-1103 0100 0100 0100
1104
:
Computación II - Punteros 3

OPERACIONES CON PUNTEROS :


 Asignación/comparación
Es posible asignar a una variable puntero el valor de otra variable puntero, siempre que las dos variables
sean del mismo tipo: int *ptr1, *ptr2; ptr1=ptr2;
Se puede comparar dos punteros usando operadores relacionales de igualdad (==), desigualdad (¡=) y
comparacion (<, >, =<, >=).
Ejemplo:
int *p1, *p2, m;
Este programa escribirá en pantalla:
m = 2;
p1 = &m; p1 y p2 son iguales
p2 = p1; p1 apunta a 0x23672420
if (p1==p2) p2 apunta a 0x23672420
cout<<”p1 y p2 son iguales”<<endl; Dirección de m = 0x23672420
cout<<”p1 apunta a “<<p1<<endl;
cout<<”p2 apunta a “<<P2<<endl;
cout<<”Dirección de m = “<<&m;
 Incremento/decremento de punteros :
Los punteros también se pueden incrementar/decrementar y dependiendo del tipo de dato que apunten
varía el efecto de la acción. Ejemplo:
int tabla[5],*p1;
p1 = tabla; //Es equivalente a escribir: p1=&tabla[0];
p1++;
En las instrucciones se asigna a p1 la dirección de la matriz tabla. Al incrementarse p1, apuntará al
siguiente elemento del tipo definido en el puntero, es decir, tabla[1]. Si el tipo tiene 4 bytes se ha
incrementado el puntero en 4. Si p1 fuera del tipo caracter se incrementaría en 1.
 Suma/resta de números enteros:
Ejemplo:
int tabla[5],*p1;
p1 = tabla;
p1 = p1 + 4;  apunta a tabla[4], el ultimo elemento

Estas son las operaciones posibles sobre punteros. Si se intenta multiplicar, dividir, sumar o restar dos
punteros se produce un error. Tampoco se puede aplicar otros operadores además de los ya
mencionados.
EJEMPLOS DE OPERACIONES CON PUNTEROS:
Ejemplo 1: Ejemplo 2:
#include <iostream.h> #include <iostream.h>
void main () void main ()
{ int val1 = 5, val2 = 15; { int val1 = 5, val2 = 15, *p1, *p2;
Int *punt; p1=&val1; // p1=dirección valor 1
punt = &val1; p2=&val2; // p2=dirección valor 2
*punt = 10; *p1=10; // val apunta p1=10
punt = &val2; *p2=*p1; // val apunta p2=val apunta p1
*punt = 20; p1=p2; // p1=p2 (val p2=p1)
cout<<"val1="<<val1; *p1=20; // val apunta p1=20
cout<<"val2="<<val2; cout<<"val1="<<val1<<endl <<"val2="<<val2;
} }

Al ejecutar este programa: Al ejecutar este programa:


Computación II - Punteros 4

val1=10 val1=10
val2=20 val2=20

Ejemplo 3:
#include <iostream.h>
void main(void)
{
int x[20],*p,*q, i, j, k, l, m, n, o, u, t;
x[0] = 1;
x[1] = 2;
x[2] = 3;
p = &x[0]; /* p apunta a la matriz x */
q = x; /* q apunta a la matriz x */
i = *p; /* i = 1 */
p++; /* p apunta a x[1] */
j = *p; /* j = 2 */
k = *q + 40; /* k = 41 */
l = *(q + 1); /* l = 2 */
m = *++q; /* q apunta a x[1], m = 2 */
n = *p++; /* n = 2, p apunta a x[2] */
o = *p**q; /* multiplica *p por *q o = 6 */
u = (*p)*(*q); /* multiplica *p por *q u = 6 */
t = ++*q; /* *q es 2, t = 3 */
cout<<"i = "<<i<<" j = "<<j<<" k = "<<k<<endl;
cout<<"l = "<<l<<" m = "<<m<<" n = "<<n<<endl;
cout<<"o = "<<o<<" u = "<<u<<" t = "<<t<<endl;
}
La salida en pantalla muestra:
i=1 j=2 k = 41 l=2 m=2 n=2
o=6 u=6 t=3
Ejemplo 4:
// Función que calcula las raices de una ecuación de 2º grado
La función devuelve 1 si las raices son reales y 0 si son imaginarias.
int RAICES(int A, int B, int C, float *p1, float *p2)
{ int k;
k=pow(B,2)-4*A*C;
if(k>=0)
{ *p1=(-B+sqrt(k))/(2*A);
*p2=(-B-sqrt(k))/(2*A);
return 1;
}
else return 0;
}

//Programa principal

void main(void)
{
int A,B,C,bandera;
float raiz1,raiz2,*p1,*p2;
p1 = &raiz1, p2 = &raiz2;
cout<<"Coeficientes:A,B,C "<<endl;
cin>>A>>B>>C;
bandera = RAICES(A,B,C,p1,p2);
Computación II - Punteros 5

if(bandera == 0)
cout<<"Raices complejas"<<endl;
else
cout<<"R1 = "<<raiz1<<endl<<"R2 = "<<raiz2;
getch();
}

PUNTEROS Y ARREGLOS :
El concepto de arreglo está muy ligado al de puntero. De hecho, el identificador o nombre de un arreglo
es equivalente a la dirección de su primer elemento y como un puntero es equivalente a la dirección del
primer elemento que apunta, entonces son la misma cosa.
Ejemplo:
Dadas las siguientes declaraciones:
int temp[20];
int *pp
La siguiente declaración sería válida :
pp = temp;
Esto hace que pp y temp sean equivalentes aunque existe una diferencia importante la cual permite que
a pp se le pueda asignar cualquier otro valor mientras que temp siempre apuntará al primer elemento
del arreglo de 20 elementos temp. De esta manera se dice que pp es una variable puntero, mientras
que temp es una constante puntero (el nombre de un arreglo es una constante puntero ). Como
consecuencia de lo anterior la siguiente asignación no es válida:
temp = pp
Cuando se declara un arreglo, se puede acceder a sus elementos de forma convencional, indexando los
elementos, o también usando punteros. La ventaja de usar punteros es mayor velocidad de
procesamiento. Por ejemplo el último elemento de la matriz:
int tabla[5];
Se puede acceder indexando la matriz: tabla[4] o
con su nombre como un puntero: *(tabla+4);
En este caso el compilador suma a la dirección de comienzo de la matriz el número de bytes
correspondientes al último elemento.
Si fuera bidimensional se puede acceder al elemento (0,2):
indexando la matriz: tabla[0][2] o
con su nombre como puntero: *(tabla+2)
La dirección de una matriz de cualquier dimensión se obtiene del nombre o de la dirección del primer
elemento:
tabla o &tabla[0][0]…[0]
Ejemplo:
int *ptabla; int tabla[5];
ptabla=&tabla[0]; o ptabla=tabla; // Inicialización del puntero ptabla
Es un error escribir ptabla=&tabla;
El ejemplo del recuadro permite ver como se #include<iostream.h>
puede mostrar el contenido de un vector void main(void)
usando punteros: {
int A[5]={2,3,9,2,1}, *p, n, i;
La sentencia sizeof() devuelve el numero de
p=A;
bytes que tiene el argumento: sizeof(A)
Computación II - Punteros 6

devuelve el valor de 10 ya que tiene 5 n=sizeof(A)/sizeof(A[0]);


elementos y cada elemento ocupa 2 bytes. for(i=0;i<n;i++)
cout<<*(p+i);
cout<<endl;
}

El siguiente ejemplo permite ver el contenido de un arreglo de dos dimensiones:


include<iostream.h>
void main(void)
{
int A[3][5]={2,3,9,2,1,4,6,3,8,9,1,1,7,5,3};
int *p;
int n,i;
p=&A[0][0];
n=sizeof(A)/sizeof(int);
for(i=0;i<n;i++)
cout<<*(p+i);
cout<<endl;
cout<<n;
}
Dado el siguiente programa:
void main ()
{ int N[5], n, * pp; //Declara un puntero pp y un arreglo N
pp = N; //Apunta PP al inicio del arreglo
*pp = 10; //El contenido de la direccion que apunta pp = 10
pp++; //pp se mueve al segundo elemento
*pp = 20; //El contenido de la direccion que apunta pp = 20
pp = &N[2]; //pp apunta al tercer elemento de N
*pp = 30; //El contenido de la direccion que apunta pp = 30
pp = N + 3; //pp es igual a la direccion inicial + 3
*pp = 40; //El contenido de la direccion que apunta pp = 40
pp = N; //pp apunta al primer elemento de N
*(pp+4) = 50; //El contenido de (pp+4) = 50
for(n=0;n<5;n++) //El lazo muestra El contenido de (pp+n)
cout<<*(pp+n)<<’\t’; //con n desde 0 hasta 4
}
Debido a la naturaleza ″variable″ de pp, en el programa ejemplo, todas las asignaciones son válidas.
Al ejecutar el programa del recuadro se obtiene la siguiente salida:

10 20 30 40 50
PUNTEROS Y CADENAS:
En C, las cadenas son arreglos de caracteres terminados con un cero binario (escrito '\0' ).
Si se tiene la siguiente declaración: char mi_cadena[40];
Se puede inicializar la cadena de las siguientes mane ras:
mi_cadena[0] = ' U ';
mi_cadena[1] = ' N '; char mi_cadena[40]={ ' U ', ' N ', ' E ', ' X ', ' P ', ' O ', ' \0 '}
mi_cadena[2] = ' E ';
mi_cadena[3] = ' X ';
mi_cadena[4] = ' P ';
mi_cadena[5] = ' O '; char mi_cadena[40]={ " UNEXPO " }
mi_cadena[6] = ' \0 ';
Todas estas formas dan el mismo resultado. Es importante observar el uso de las comillas. Unas son
simples y otras son dobles; si se usan comillas dobles, el caracter ‘\0’ se agrega automáticamente al
final de la cadena.
Computación II - Punteros 7

En el ejemplo de la derecha se declaran dos #include<iostream.h>


cadenas de 80 caracteres c/u. char cadA[30] = "Una cadena de prueba";
char cadB[30];
Debido a que se definen "globales", son
void main(void)
inicializadas con '\0' en todos sus elementos. cadA
{ char *pA;
se inicializa con el texto "Una cadena de prueba"
char *pB;
mas el carácter '\0' al final.
cout<<cadA;
En el programa principal se declaran dos punteros cout<<endl;
pA y pB de tipo char, luego se muestra cadA. El pA = cadA;
puntero pA se apunta a cadA mediante la cout<<pA;
asignación pA = cadA y luego se muestra lo que pB = cadB;
apunta pA. cout<<endl;
while(*pA != '\0') *pB++ = *pA++;
La instrucción while se ejecuta mientras el
*pB='\0';
contenido de la dirección que apunta pA sea
cout<<cadB;
diferente a '\0'.
}
La instrucción que se repite en el ciclo while es "copiar el contenido de la dirección que apunta pA, en el
contenido de la dirección que apunta pB", luego se incrementa en una unidad la dirección que apuntan
pA y pB.
El lazo finaliza cuando se consigue el carácter '\0' en la cadena cadA, sin embargo no se copia este ultimo
carácter, por lo tanto es necesario agregar el fin de cadena a cadB.
En resumen en el programa se muestra la cadA de dos formas diferentes y luego se copia la cadena en
cadB y se muestra esta ultima produciendo el mismo resultado.
Lo que muestra el programa es una forma char *CopiarCadena(char *Destino, char *Origen)
de copiar una cadena de caracteres. De { char *p = Destino;
esta forma se podría definir una función while (*Origen != '\0') *p++ = *Origen++;
*p = '\0';
CopiarCadena como se muestra a la
return Destino;
derecha: }
La definición de la función, según el esquema:
<tipo><nombre_de_funcion>(parámetros)

indica que el tipo de dato devuelto es un puntero (*) y los parámetros de la función son también
punteros. Se observa en la función que no aparece por ningún lado tamaños de los arreglos, ya que se
pasan solo las direcciones iniciales Destino[0], Origen[0]. Esta estrategia puede ser usada también con
arreglos de datos tipo enteros/reales teniendo en cuenta que el fin del arreglo no estaría determinado
con un carácter especial, como el nul.
Por ejemplo se podria copiar un arreglo de valores enteros positivos marcando el final del arreglo con un
valor entero negativo. Otra forma seria pasar junto con los punteros Origen/Destino, el número de
elementos a copiar.
PUNTEROS Y ESTRUCTURAS:
Un puntero también puede apuntar a una estructura tipo registro. La declaración del puntero se hace
usando el mismo formato:
tipo de dato * nombre del puntero;

Ejemplo:

#include<iostream.h>
#include<iomanip.h>
Computación II - Punteros 8

struct t_persona
{
char nombre[30];
int edad;
int altura;
int peso;
};

void main(void)
{
int i;
t_persona EMPLEADOS[] = {{"Mora, Jose", 47, 182, 85},
{"Gil, Ana", 39, 170, 75},
{"Perez, Pedro", 18, 175, 79}};
t_persona *pp; //Declaración del puntero pp del tipo de dato registro t_persona
pp = EMPLEADOS; //Inicialización del puntero pp
cout<<" nombre edad altura peso"<<endl;
for(i = 0;i < 3;i++,pp++)
{
cout << setw(15)<< pp -> nombre :
<< setw(5)<<pp -> edad ;
<< setw(5)<<pp -> altura ;
<< setw(5)<<pp -> peso << endl;
}
}
Observe en el ejemplo anterior que para accesar a los campos del registro, en lugar de usar el operador
punto ( . ), se usa el operador de punteros ( -> ).
ASIGNACIÓN DINÁMICA DE MEMORIA ( ADM):
Las variables, tanto simples (int, float, char) como estructuradas (arreglos, registros) son variables
estáticas. Esto significa que comienzan a existir y ocupan un espacio constante en la memoria del
programa (segmento del programa), desde el momento en que son declaradas y el espacio que
ocupan no puede ser cambiado mientras se está ejecutando el programa. En el caso de los arreglos, es
necesario definir el tamaño en el momento de la declaración y no se puede cambiar mientras el
programa se está ejecutando. Esta memoria se libera al terminar el programa.
Si no se conoce el tamaño del arreglo es necesario estimar un tamaño y la estimación podria quedar
corta lo que haria fallar el programa, o muy grande, lo que significa un desperdicio del recurso memoria.
Una solución deseada sería poder leer el tamaño necesario y luego con ese valor dimensionar el arreglo.
Existe una forma de resolver este inconveniente usando punteros y técnicas de asignación dinámica
de memoria (ADM) que provee el lenguaje c++ a través de los operadores new y delete.
El operador new.
Este operador asigna un bloque de memoria que es el tamaño del tipo de dato. El dato puede ser un int,
float, un arreglo, una estructura o cualquier otro tipo de dato. El operador new devuelve un puntero,
que contiene la dirección inicial del bloque de memoria asignado, el cual se usa para referenciar el bloque
de memoria.
El formato del operador es:
tipo *puntero = new tipo  Simple
tipo *puntero = new tipo[tamaño]  Arreglo
El formato simple se usa para datos básicos y el otro se usa para arreglos.
Ejemplo :
Si se quiere reservar memoria para un arreglo de 100 enteros, se escribe:
int *bloq_mem; //declaración del puntero
Computación II - Punteros 9

bloq_mem = new int[100] // asignación de dirección del bloque


La reserva de n caracteres se puede declarar asi:
int n;
El operador new está disponible en C++ sin incluir ningún
char *s;
archivo de cabecera. El operador devuelve un puntero.
cin >> n;
s = new char[n];

El operador delete.
Cuando se ha terminado de usar un bloque de memoria previamente asignado por new, se puede liberar
el espacio de memoria y dejarlo disponible para otros usos, mediante el operador delete. El bloque de
memoria liberado queda disponible para ser reasignado nuevamente.
La forma general del operador es:
delete puntero  libera memoria asignada a una variable simple.
delete [] puntero  libera memoria asignada a un arreglo.
Ejemplo:

El ejemplo muestra como se define un vector


en forma dinámica usando los operadores
new y delete.
El algoritmo pide el numero de elementos que
va a tener el vector, lo define, lo llena con
valores aleatorios entre 0 y 99, busca el
elemento mayor y luego libera el espacio
reservado. De esta forma se pasa por las tres
etapas, creación, uso y liberación de un
vector de forma dinámica.
En la linea 5 se define el puntero vector de
tipo entero que va a almacenar la dirección del
primer elemento del vector dinámico.

Luego en la linea 9, después de conocer el número


de elementos que va a tener el vector (n) se hace la
definición dinámica del vector y de alli en adelante se
puede usar vector para referenciar los elementos del
vector ya sea usando indices o usando punteros.
Se puede hacer la referencia a los elementos del
vector usando punteros