Está en la página 1de 9

Programación en C/Punteros

< Programación en C

Sumario
Concepto
Declarando punteros
Explicación
Operadores
Operaciones Aritméticas
Punteros Constantes
Punteros Genéricos
Punteros y Matrices
Punteros a cadenas de caracteres
Matrices de Punteros
Punteros a Punteros
Matrices de punteros a cadenas de caracteres
Ejemplos
Asignando valores a punteros

Concepto
Un puntero es una variable que contiene la dirección de memoria de un dato o 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 devuelve 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 violacion 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.

Declarando punteros
Ya se dijo que un puntero es una variable que guarda la dirección de memoria de otra variable, haciendo
lógica a esto, decimos que 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.

Explicación
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 direccion 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 direccion válida al puntero, pero el valor que tiene NULL no se nos permite modificarlo, ya
que pertenece al sistema.

Operadores
Ya anteriormente te poníamos algunos ejemplos de como asignar la dirección de memoria a un puntero y de
como 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); Es Igual printf("%d",*puntero);

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 por que 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 como 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 #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 como decir el 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 como 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 hacerlo de otra
forma.

FORMA ERRADA:

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.

FORMA CORRECTA:

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.

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 como
recorre las direcciones de memoria con los operadores ++ y --.

Aquí veremos como 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 array.


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

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

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 despues 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 esta
limitada por el array original, mas adelante veremos como 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 practica es mucho mas 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. 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; //**puntero 2 guarda la direccion 0x1890

Ahora se entiende mejor. Al uso de punteros se le llama variables con niveles de indireccion, 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 indireccion y *puntero2 es un puntero con dos niveles de
indireccion.

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 linea 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;

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 aún 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 );

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 apuntara a myInt y pKeyboard apuntara a dvorak.

Los punteros también pueden ser asignados a referencias de memorias dinamicas 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 funcion, se tiene que tener cuidado de que el valor apuntado no sea de
una referencia de una variable local a la funcion, porque estos valores desaparecen despues de salir de la
funcion y el puntero estaria mal referenciado, causando errores en tiempo de ejecucion en el programa. La
memoria creada dinamicamente dentro de la funcion puede devolverse como puntero, el valor de retorno
aunque es creado en una variable local la direccion como tal se devuelve por valor dejando esta informacion
valida.

Obtenido de «https://es.wikibooks.org/w/index.php?title=Programación_en_C/Punteros&oldid=370836»

Esta página se editó por última vez el 20 nov 2019 a las 16:08.

El texto está disponible bajo la Licencia Creative Commons Atribución-CompartirIgual 3.0; pueden aplicarse términos
adicionales. Véase Términos de uso para más detalles.

También podría gustarte