Está en la página 1de 19

REPUBLICA BOLIVARIANA DE VENEZUELA

MINISTERIO DEL PODER POPULAR PARA LA EDUCACION


UNIVERSIDAD POLITÉCNICA TERRITORIAL DEL ALTO APURE
“PEDRO CAMEJO”

PUNTEROS
INDICE:

DESCRIPCION: PAGINA

INTRODUCCION ………………………………………… N° 01

DEFINICIÓN, DECLARACIÓN Y OPERACIÓN ………………………………………… N° 02

PUNTEROS Y FUNCIONES ………………………………………… N° 15

PUNTEROS Y ESTRUCTURA ………………………………………… N° 17


INTRODUCCION:
Un puntero es una clase de variable cuyo contenido no es un dato
directamente usable, sino que almacena la posición de memoria (la dirección de
memoria) de otro dato, tal como una variable o un elemento de un arreglo. Los
punteros son usados frecuentemente en C y tienen gran cantidad de aplicaciones.
El uso cuidadoso de los punteros provee claridad y simplicidad al programa. Los
punteros, por ejemplo, pueden usarse para pasar información entre una función y
sus puntos de llamada, y en particular proporcionan una forma de devolver varios
datos desde una función mediante los propios argumentos de la misma. Además,
permiten asignar dinámicamente la memoria necesaria para una variable o conjunto
de variables, manejar listas encadenadas y acceder a distintas posiciones dentro de
una misma estructura de datos, por ejemplo arreglos, cadenas, estructuras, etc. Los
punteros están muy relacionados con los arreglos y proporcionan una vía alternativa
de acceso a los elementos individuales de los mismos. Es más, proporcionan una
forma conveniente para representar arreglos multidimensionales.
CONTENIDO

DEFINICION DEL PUNTERO

Un puntero es una variable que contiene la dirección de memoria de otra


variable que contiene al dato en un arreglo. Esto quiere decir, que el puntero apunta
al espacio físico donde está el dato o la variable. Un puntero puede apuntar a un
objeto de cualquier tipo, como, por ejemplo, a una estructura o una función. Los
punteros se pueden utilizar para referencia y manipular estructuras de datos, para
referenciar bloques de memoria asignados dinámicamente y para proveer el paso
de argumentos por referencias en las llamadas a funciones.

Muchas de las funciones estándares de C, trabajan con punteros, como es el caso


del scanf o strcpy. Estas funciones reciben o devuelven un valor que es un puntero.
Por ejemplo: A scanf se le pasa la dirección de memoria del dato a leer...

char a;
scanf ("%c",&a);

En este caso se le pasa la dirección de memoria de la variable a, la cual tiene


reservado un espacio en la memoria por el compilador. Podríamos hacer lo mismo
con este código:

char *a = (char*) malloc (sizeof (char));


scanf ("%c", a);

En este código, al scanf le pasamos el puntero en sí. El puntero es una


variable, como bien se ha dicho anteriormente, que contiene una dirección de
memoria, por tanto, no es necesario el uso de & para pasarle la dirección de
memoria a scanf. Anteriormente hemos tenido que reservar un espacio de memoria
con malloc(), que se verá su uso más adelante.

Este código sería erróneo y daría una violación de segmento:


char *a;
scanf ("%c", a);

En este caso, no hemos reservado memoria, por lo tanto, la función escribirá


en una dirección de memoria que no le pertenece, y por ello se producirá la
susodicha violación de segmento.

DECLARACION DE PUNTERO:

Ya se dijo que un puntero es una variable que guarda la dirección de memoria


de otra variable. Un puntero se declara igual que cualquier otra variable, pero
anteponiendo un * (asterisco) antes del nombre de la variable.

Su sintaxis sería:
tipo *NombrePuntero = NULL; Se iguala a NULL para saber que no tiene
asignada ninguna dirección.

Donde tipo es el tipo de dato al que referenciará este puntero, es decir, que, si
se necesita guardar la dirección de memoria de un dato int, se necesita un puntero
de tipo int.

Veamos el siguiente código:

#include <stdio.h>

int main()
{
int a=0; //Declaración de variable entera de tipo entero
int *puntero; //Declaración de variable puntero de tipo
entero
puntero = &a; //Asignación de la dirección memoria de a

printf("El valor de a es: %d. \nEl valor de *puntero es: %d.


\n",a,*puntero);
printf("La dirección de memoria de *puntero es:
%p",puntero);

return 0;
}

Igual que cuando usamos un &, en la lectura de datos con scanf, igual de esta
forma lo usamos aquí, tal vez te acordarás que decíamos que las cadenas de
caracteres (%s) no usaban este operador, esto es porque en una cadena de
caracteres es un arreglo de caracteres, por lo que el primer carácter es la dirección
de inicio de la cadena.

El operador *, nos permite acceder al valor de la dirección del puntero, en este


caso nos permite acceder al valor que contiene a la variable a. De esta forma "a" y
"*puntero" muestran el mismo dato, pero esto no quiere decir que sea lo mismo, uno
es un entero y el otro un puntero.

La impresión con %p, es para poder imprimir la dirección de memoria en valor


hexadecimal (0x...), también podemos imprimir: ...%p",&a) y funciona de la misma
forma, y es lógico que tanto "a" como "puntero" tienen la misma dirección de
memoria.

¿Diferentes direcciones?

Tal vez notaste que en cada ejecución la dirección de memoria cambia, esto
es porque es el sistema operativo quien está encargado de administrar la memoria y
es éste quien dice qué espacios podrá tomar el programa.

Esto quiere decir que uno no puede asignarle una dirección de memoria a un
puntero directamente, es decir yo no puedo hacer lo siguiente.

int *puntero=0xbfc5b1c8;

Esto no puedo ni debo hacerlo ya que yo no sé que está haciendo esta dirección de
memoria, si el sistema la tiene o no disponible, etc... Pero sí puedo hacer esto:

int *puntero=NULL;

NULL, es el espacio en memoria con dirección 0, esto quiere decir que existe,
lo que significa que le asignamos una dirección válida al puntero, pero el valor que
tiene NULL no se nos permite modificarlo, ya que pertenece al sistema.

Uso en funciones

Los punteros se usan habitualmente como parámetros de funciones. El


siguiente código muestra como declarar una función que usa un puntero como
argumento. Teniendo en cuenta que C pasa todos los argumentos por valor, para
poder modificar un valor desde el código de la función, se debe usar un puntero al
valor a modificar. También se usan punteros a estructuras como argumentos de una
función aun cuando la estructura no sea modificada dentro de la función. Esto es
realizado para evitar copiar todo el contenido de la estructura dentro de la pila.

int MyFunction( struct MyStruct *pStruct );

OPERADORES:

Ya anteriormente te poníamos algunos ejemplos de cómo asignar la dirección


de memoria a un puntero y de cómo acceder al valor de este.
Operador de Dirección (&): Este nos permite acceder a la dirección de memoria de
una variable.
Operador de Indirección (*): Además de que nos permite declarar un tipo de
dato puntero, también nos permite ver el VALOR que está en la dirección asignada.
Incrementos (++) y Decrementos (--): Te darás cuenta que puedes usar un
puntero como si de un array se tratase, es por esto que permite estos operadores.
De lo anterior podemos decir que:

int a; int *puntero=&a;


printf("%d",a);
printf("%d",*puntero); \\Es Igual

OPERACIONES ARITMÉTICAS

Un puntero nos permite sumar o restar números enteros, pero su


funcionamiento ahora es de posiciones, es decir que nos permitirá movernos a la
siguiente dirección de memoria.

A un puntero no se le puede realizar multiplicaciones, divisiones, sumas o


restas con otro puntero o con un valor de tipo coma flotante (float, double...). Esto es
porque un puntero no es un valor, sólo es una dirección.

En un puntero se pueden realizar varias operaciones de tipo enteras, pero en


dependencia de cómo se usen, sus resultados pueden ser muy diferentes, a
continuación, les muestro algunas de estas operaciones:

//Definamos estas variables:


int x[100],b,*pa,*pb;
//...
x[50]=10; //Le asignamos el valor de 10, al array x en la
posicion #50
pa=&x[50]; //Le asignamos al puntero pa la direccion de memoria
que tiene x[50]

//Ahora mostramos algunas posibles operaciones:

b = *pa+1; //Esto es: Al valor que tiene el array de x[50]


sumarle 1.
//Esto es igual a: b=x[50]+1; => Su valor seria igual
a 11.

b = *(pa+1); //Esto primero pasa a la siguiente direccion de


memoria y luego lo referencia
//El resultado es: b = x[51];

pb = &x[10]; //al puntero pb se le asigna la direccion de x[10]

*pb = 0; //Al valor que tiene el puntero se le asigna 0


//Esto es igual que decir: x[10] = 0

*pb += 2; //El valor del puntero se incrementa en dos unidades,


es decir x[10] += 2

(*pb)--; //El valor del puntero se decrementa en una unidad.

x[0] = *pb--; //A x[0] se le pasa el valor de x[10] y el puntero


pb, pasa a apuntar a x[9]
//recuerda, que -- es post-decremento, primero
asignará y luego restará.

PUNTEROS CONSTANTES

Es posible que hayas pensado cómo declarar un puntero como una constante,
tal vez pensaste en un #define, o en un atributo const. ¿Bueno es posible usar el
atributo const, pero para un puntero hay que decidir qué es lo que se quiere volver
constante, el valor -objeto- o la dirección?

Valor Constante:

int a=10,b=20;
const int *p = &a; //objeto constante y puntero variable
*p = 15; // ERROR: el valor apuntado por p es constante.
p=&b; //Correcto: p pasa a apuntar a un nuevo objeto.
Pero de esta forma no es muy útil declararlo, pues el que hicimos constante
fue el valor al que apunte p, es decir, mejor hubiésemos hecho que el puntero fuese
una constante.

Dirección de memoria constante:

int a=10,b=20;
int * const p = &a; //objeto variable y puntero constante
*p = 15; // Correcto: El valor apuntado es variable.
p=&b; //ERROR: p es constante.

Para recordar de una manera fácil, todo lo que está a la izquierda o antes del
“*“ es el tipo de dato al que apunta el puntero.

const int *p en este caso la variable es un entero constante.

int *p const en este caso la variable es un entero pero como la palabra


clave const esta a la derecha o despues del “*”, entonces la constante es el puntero.

PUNTEROS GENÉRICOS

Un puntero a cualquier tipo de dato puede convertirse a un puntero del tipo


void *. Por esto un puntero a void *, recibe el nombre de puntero genérico.

En C, se permite la conversión implícita de punteros, pero en C++ esto no es


posible, así que por compatibilidad y buena practica recomendamos usar la
conversión explícita (cast).

Supongamos:

int *puntero;
funcion (*puntero);
....
void funcion (void *p)

int *q;
q=(int *)p; //En C se podria hacer q = p;
Es decir que un puntero a void se puede usar sin importar el tipo de dato,
recuerden que uno no puede trabajar con punteros que referencia a un tipo de dato
diferente, como lo es un puntero a char, con un puntero a int.

PUNTEROS Y MATRICES
Anteriormente decíamos que una matriz es una secuencia de espacios en
memoria, que nos permitían alojar datos en cada uno y un puntero es una variable
que guarda la dirección de memoria, también decíamos cómo recorre las
direcciones de memoria con los operadores ++ y --.

Aquí veremos cómo puede usarse un puntero como si de una matriz se


tratase, luego de que veas que es técnicamente lo mismo, te preguntaras por que
usar punteros, pero estos son muy necesarios y únicos que nos permiten realizar
cosas que con un array normal, no se puede, como asignarle memoria dinámica,
etc...

#include <stdio.h>

int main()
{

int array[10]={0,2,3,5,5,6,7,8,9,0}; //Declarar e inicializar un


arreglo.
int *puntero = &array[0]; //Le damos la dirección de inicio del
arreglo
int i; //variable contadora...

for (i=0;i<10;i++)
printf("%d\n",*(puntero+i)); //imprimimos los valores del puntero
que es equivalente a imprimir los valores del arreglo.

return 0;
}

Habrás notado que he usado *(puntero+i), así como explica la sección de


operaciones aritméticas, pero también podemos acceder de otras maneras como lo
son:

array[i] => Accede como un array normal


*(array+i) => También accede como un array normal.
puntero[i] => Accedemos al valor de puntero sub i
*(puntero+i) => Accedemos al valor de puntero + i.

PUNTEROS A CADENAS DE CARACTERES


Ya hemos visto el uso que se le puede dar a un puntero como si de un array
se tratase, entonces usando esta misma lógica podemos hacer un array de
caracteres usando punteros.

char *nombre="Gustavo A. Chavarria";//Es como un array de 20


caracteres
printf("%s",nombre);

Sin embargo, al tratarse de una constante de caracteres no podemos


modificarla después de definir sus valores. Como por ejemplo no podemos
remplazar un carácter, o leer un nuevo valor.

gets(nombre); //ERROR en ejecución

Para poder modificar el valor de este puntero, este tendría que apuntar a una
dirección que no sea una constante, como un array.

char nombre[]="Gustavo A. Chavarria"; //declaramos un array de


caracteres
char *puntero=nombre;//Asignamos al puntero el comienzo del array
printf("%s \nIngrese otro nombre: ",puntero);//Escribimos en
pantalla nombre...
gets(puntero); //leemos otro nombre
printf("%s",puntero); //escribimos el nuevo nombre...

Esta vez pudiste notar que sí se pudo remplazar el valor del nombre, pero aun
la cantidad de caracteres está limitada por el array original, mas adelante veremos
cómo solucionar esto con memoria dinámica.

MATRICES DE PUNTEROS

Es muy probable que ya te hayas dicho: Si un puntero es una variable,


¿Puedo tener un array de punteros? La respuesta sería NO Exactamente.

Como ya habíamos explicado antes, un puntero trabaja de la misma forma


que un array, tanto que podemos decir que un puntero es un array, entonces si
definimos un array de un array, nos daría como resultados un array bidimensional.

En la práctica es mucho más común utilizar un array de punteros que un array


bidimensional.
Hay que recordar que siguen siendo punteros, es decir sólo apuntan a una
dirección de memoria, por ende, hay que asignarles la dirección de memoria a cada
uno de los elementos del puntero. Sin embargo, podemos asignar en un solo ciclo
todas las filas.

Ejemplo:

#include <stdio.h>

int main()
{

int *puntero[5]; //array de puntero


int a[5][5]; //Array bidimensional.
int i;

for (i=0;i<5;i++){
puntero[i]=&a[i]; //Asignamos las filas al puntero.
}
//Pueden imprimir tambien en un ciclo
//Tambien pueden acceder mediante un ciclo anidado a la variables
del puntero[i][j]

PUNTEROS A PUNTEROS
Es una variable que contiene la dirección de memoria de un puntero, el cual a
su vez contiene la dirección de memoria de un tipo de dato. Se usa ** para definir un
puntero a un puntero. Recuerden que un puntero sigue siendo un espacio en
memoria, pero en vez de almacenar un valor almacena una dirección.

Si decimos que:

int a=0; //Supongamos que es una variable cuya direccion es 0x1601


int *puntero1=&a; //El puntero tiene guardada la direccion de a,
//pero este tiene como direccion 0x1890
int **puntero2=&puntero1; //**puntero2 guarda la direccion 0x1890

Ahora se entiende mejor. Al uso de punteros se le llama variables con niveles


de indirección, ya que no apuntan directamente a un valor, sino que apuntan a
alguien que lo tiene. Basándonos en esto podemos decir que *puntero1 es un
puntero con un nivel de indirección y *puntero2 es un puntero con dos niveles de
indirección.

En general estos tipos de datos son usados con matrices multidimensionales.


MATRICES DE PUNTEROS A CADENAS DE CARACTERES

No hablaremos sobre este tema, debido a que es prácticamente una matriz de


caracteres que se usa mediante punteros, y esto es técnicamente lo mismo que
hemos estado viendo anteriormente.

También podemos usar punteros a punteros y guardar múltiples cadenas.

EJEMPLOS
Tenga en cuenta el siguiente bloque de código que declara 2 punteros

/*1*/ struct MyStruct {


/*2*/ int m_aNumber;
/*3*/ float num2;
/*4*/ };
/*5*/
/*6*/ int * pJ2;
/*7*/ struct MyStruct * pAnItem;

Las primeras 4 líneas definen la estructura. La línea 6 declara una variable


que apuntará a un entero, y la línea 7 declara una variable que apunta a algo de la
estructura MyStruct. Entonces declarar un puntero es algo que apunta a algo de
algún tipo, más que contener el tipo. El asterisco (*) se coloca antes del nombre de
la variable.

En las siguientes líneas de código, var1 es un puntero a un entero largo (long)


mientras var2 es un entero largo (long) y no un puntero a un entero largo. En la
segunda línea se declara p3 como un puntero a un puntero de un entero.

long * var1, var2;


int ** p3;

ASIGNANDO VALORES A PUNTEROS

Continuamos con el proceso de asignar valores a los punteros, para esto


utilizamos el operador & o 'la dirección de'.
int myInt;
int *pPointer;
struct MyStruct dvorak;
struct MyStruct *pKeyboard;

pPointer = &myInt;
pKeyboard = &dvorak;

Aquí, pPointer apunta a myInt y pKeyboard apuntará a dvorak.

Los punteros también pueden ser asignados a referencias de memorias


dinámicas creadas comúnmente por las funciones malloc() y calloc().

#include <stdlib.h>
...
struct MyStruct *pKeyboard;
...
pKeyboard = malloc(sizeof(struct MyStruct));
...

La función malloc retorna un puntero de memoria asignada de manera


dinámica (o NULL si falla). El tamaño de esta memoria es definido de modo que
pueda contener la estructura MyStruct

El siguiente código es un ejemplo mostrando un puntero siendo asignado a


una referencia y se retorna el valor del puntero en la función.

static struct MyStruct val1, val2, val3, val4;


...
struct MyStruct *ASillyFunction( int b )
{
struct MyStruct *myReturn;

if (b == 1) myReturn = &val1;
else if (b==2) myReturn = &val2;
else if (b==3) myReturn = &val3;
else myReturn = &val4;

return myReturn;
}
...
struct MyStruct *strPointer;
int *c, *d;
int j;
...
c = &j; /* puntero asignado usando el operador & */
d = c; /* asignando un puntero a otro */
strPointer = ASillyFunction( 3 ); /* puntero retornado de la
funcion */

Cuando se retorna un puntero de una función, se tiene que tener cuidado de


que el valor apuntado no sea de una referencia de una variable local a la función,
porque estos valores desaparecen después de salir de la función y el puntero
estaría mal referenciado, causando errores en tiempo de ejecución en el programa.
La memoria creada dinámicamente dentro de la función puede devolverse como
puntero, el valor de retorno, aunque es creado en una variable local la dirección
como tal se devuelve por valor dejando esta información válida.

PUNTEROS Y FUNCIONES:

Los punteros proporcionan el soporte necesario para el potente sistema de


asignación dinámica de memoria de C. La asignación dinámica es la forma en la
que un programa puede obtener memoria mientras se está ejecutando.

El centro del sistema de asignación dinámica está compuesto por las


funciones (existentes en la biblioteca stdlib.h) malloc(), que asigna memoria; y free()
que la devuelve.

El prototipo de la función malloc() es: stdlib.h malloc(), que asigna memoria; y


free() que la devuelve .void *malloc(size_t número de bytes);

Tras una llamada fructífera, malloc() devuelve un puntero, el primer byte de


memoria dispuesta. Si no hay suficiente memoria libre para satisfacer la petición de
malloc(), se da un fallo de asignación y devuelve un nulo. El fragmento de código
que sigue asigna 1000 bytes de memoria:
char *p;
p = (char *) malloc(1000);

Después de la asignación, p apunta al primero de los 1000 bytes de la


memoria libre. El siguiente ejemplo dispone espacio para 50 enteros. Obsérvese el
uso de sizeof para asegurar la portabilidad: p apunta al primero de los 1000 bytes de
la memoria libre. El siguiente ejemplo dispone espacio para 50 enteros. Obsérvese
el uso de sizeof para asegurar la portabilidad:

int *p;
p= (int *) malloc(50*sizeof(int));

La función free() es la opuesta de malloc() porque devuelve al sistema la


memoria previamente asignada. Una vez que la memoria ha sido liberada, puede
ser reutilizada en una posterior llamada a malloc(). El prototipo de la función free()
es:

void free (void *p);


free(p);

PUNTEROS Y ESTRUCTURA:

Al igual que con el resto de tipos de variables, C permite crear variables


punteras a estructuras. La forma de declarar estas variables es:

struct nombre_estructura *nombre_varible_estructura;

Para poder acceder a los elementos individuales de una variable puntero a


estructura se utiliza el operador -> (el operador, denominado comunmente flecha,
está formado por un signo menos seguido de un signo de mayor, sin ningún espacio
en blanco entre ellos). Siguiendo con el ejemplo anterior de las coordenadas, vamos
a definir un puntero a una variable de estructura de la siguiente forma:
struct coordenada a, b, *p, *q;

a.x = 5;
a.y = 23;
a.z = 10;

b = a;
p = &b; /* &b nos devuelve la dirección de la estructura b */

p->x = 2;
p->y = a.y;
p->z = a.z;

q = p; /* p y q apuntan a la misma estructura */

Al igual que ocurría en el apartado anterior también existe la posibilidad de


declarar tablas de estructuras. El formato sería el siguiente:

struct nombre_estructura *nombre_varible_estructura[N];

struct nombre_estructura *nombre_varible_estructura[N][M];

Por supuesto también se pueden declarar tablas de punteros a estructuras de


más de dos dimensiones pero al igual que ocurría con la declaración de tablas de
tipos de datos básicos, no suelen ser muy usadas.

Un ejemplo de declaración de un vector de punteros a estructuras y un acceso


al campo de una estructura sería la siguiente:

struct coordenada coor1, *vector_punteros_coordenadas[10];

vector_punteros_coordenadas[4] = &coor1;
vector_punteros_coordenadas[4]->y = 2;

Al igual que ocurría con los punteros a tipos básicos de datos también se
pueden pasar punteros a estructuras en las llamadas a funciones. Así por ejemplo
tendríamos:
struct coordenada coordenada1, *p_coordenada1;
flota distancia;
..................................
coordenada1.x = 5;
coordenada1.y = -11;
p_coordenada1 = &coordenada1;
..................................
cambiar_a_punto_simétrico_en_cuadrante (p_coordenada1);

cambiar_a_punto_simetrico_en_cuadrante (&coordenada1);
..................................

....
....
....

/* La siguiente función intercambia la coordenada x por la y */


void cambiar_a_punto_simétrico_en_cuadrante(struct
coordenada*c)

{
int aux;

aux = (*c).x;
(*c).x = (*c).y;
(*c).y = aux;
}

Nótese la equivalencia entre la notación c->x y (*c).x


CONCLUCION:

Teniendo en cuenta todo lo antes descrito se puede llegar a concluir que su uso
permite el paso de argumentos por referencia, la construcción de estructuras
dinámicas de datos y la mejora en la velocidad de ejecución de algunos algoritmos.
Sin embargo, mal usados pueden producir errores graves, difíciles de detectar y, en
ocasiones, los peores errores posibles: los no reproducibles.

También podría gustarte