Zona de programación, todo lo necesario para aprender a programar

 
 
 

 

 

Lenguaje c

 

8) Punteros

 

 

Los punteros en el Lenguaje C , son variables  que poseen la dirección de las ubicaciones en memoria de otras variables, y por medio de ellos se tendra un poderoso método de acceso a todas ellas .
Quizás esta parte sea la mas conflictiva del lenguaje , ya que muchos programadores   en C , lo ven como un método extraño ó al menos desacostrumbrado. Sin embargo , y en la medida que uno se va familiarizando con ellos , se convierten en la herramienta más cómoda y directa para el manejo de variables complejas , argumentos , parámetros , etc , y se empieza a preguntar como es que hizo para programar hasta aquí , sin ellos .
Veamos primero , como se declara un puntero :

 

tipo de variable apuntada   *nombre_del_puntero ;

 

int *pint ;

 

double *pfloat ;

 

char  *letra , *codigo , *caracter ;

 

En estas declaraciones sólo decimos al compilador que reserve una posición de memoria para albergar la dirección de una variable , del tipo indicado , la cual será referenciada con el nombre que hayamos dado al puntero .
Obviamente , un puntero debe ser inicializado antes de usarse , y una de las eventuales formas de hacerlo es la siguiente:

int var ;     /* declaro ( y se crea en memoria ) una variable entera ) */

 

int *pint ;    /*     un puntero que contendrá  la dirección de una variable entera */

 

pint = &var ;  /* escribo en la dirección de memoria donde está el

 

                    puntero la dirección de la variable entera */

Lo que hemos hecho se puede simbolizar de la siguiente manera :

donde dentro del recuadro está el contenido de cada variable .

Pint xxxxxx valor contenido por var Dirección de var yyyyyy (posición de memoria xxxxxx (posición de memoria ocupada por el puntero ) ocupada por la variable)

.
El símbolo & , ó dirección , puede aplicarse a variables , funciones , etc , pero nó a constantes ó expresiones , ya que éstas no tienen una posición de memoria asignada.
La operación inversa a la asignación de un puntero , de referenciación del mismo , se puede utilizar para hallar el valor contenido por la variable apuntada . Así por ejemplo serán expresiones equivalentes :

y = var ; y = *pint ; printf("%d" , var ) ; printf("%d" , *pint) ;

En estos casos , la expresión " *nombre_del_puntero " , implica " contenido de la variable apuntada por el mismo " Veamos un corto ejemplo de ello :

#include main() 

char var; /*una variable del tipo caracter */ 
char *pchar; /* un puntero a una variable del tipo caracter */ 
pc = &var; /*asignamos al puntero la direccion de la variable */ 
for (var = 'a'; var1 <<= 'z'; var1++) printf("%c", *pchar); 
/* imprimimos el valor de la variable apuntada */ 

return 0; 
}

PUNTEROS Y ARRAYS

El nombre de un array , para el compilador C , es un PUNTERO inicializado con la dirección del primer elemento del array . Sin embargo hay una importante diferencia entre ambos.
Veamos algunas operaciones permitidas entre punteros :

ASIGNACION 
float var , conjunto[] = { 8.0 , 7.0 , 6.0 , 5.0 , 4.0 ); 
float *punt; 
punt = conjunto; /* equivalente a hacer : 
punt = &conjunto [0] */ var1 = *punt; 
*punt = 25.1;

Es perfectamente válido asignar a un puntero el valor de otro , el resultado de ésta operación es cargar en el puntero punt la dirección del elemento [0] del array conjunto , y posteriormente en la variable var el valor del mismo (8.0) y para luego cambiar el valor de dicho primer elemento a 25.1 .
Veamos cual es la diferencia entre un puntero y el denominador de un array : el primero es una VARIABLE , es decir que puedo asignarlo , incrementarlo etc , en cambio el segundo es una constante , que apunta siempre al primer elemento del array con que fué declarado , por lo que su contenido no puede ser variado.


ARITMETICA DE PUNTEROS

La aritmética más frecuentemente usada con punteros son las sencillas operaciones de asignación , incremento ó decremento y dereferenciación . Todo otro tipo de aritmética con ellos está prohibida ó es de uso peligroso ó poco transportable . Por ejemplo no está permitido , sumar , restar , dividir , multiplicar , etc , dos apuntadores entre sí . Lo cual si lo pensamos un poco es bastante lógico , ya que de nada me serviría sumar dos direcciones de memoria , por ejemplo .
Otras operaciones estan permitidas , como la comparación de dos punteros , por ejemplo ( punt1 == punt2 ) ó ( punt1 < punt2 ) sin embargo este tipo de operaciones son potencialmente peligrosas , ya que con algunos modelos de pointers pueden funcionar correctamente y con otros no .

PUNTEROS Y VARIABLES DINAMICAS


Supongamos un caso típico , debemos recibir una serie de datos de entrada , digamos del tipo double , y debemos procesar según un determinado algoritmo a aquellos que aparecen una ó más veces con el mismo valor.

Si no estamos seguros de cuantos datos van a ingresar a nuestro programa , pondremos alguna limitación , suficientemente grande a los efectos de la precisión requerida por el problema , digamos 5000 valores como máximo , debemos definir entonces un array de doubles capaz de albergar a cinco mil de ellos , por lo que el mismo ocupará del orden de los 40 k de memoria.

Si definimos este array en main() , ese espacio de memoria permanecerá ocupado hasta el fín del programa , aunque luego de aplicarle el algoritmo de cálculo ya no lo necesitemos más , comprometiendo seriamente nuestra disponibilidad de memoria para albergar a otras variables 

Una solución posible sería definirlo en una función llamada por main() que se ocupara de llenar el array con los datos , procesarlos y finalmente devolviera algún tipo de resultado , borrando con su retorno a la masiva variable de la memoria.
Sin embargo en C existe una forma más racional de utilizar nuestros recursos de memoria de manera conservadora . Los programas ejecutables creados con estos compiladores dividen la memoria disponible en varios segmentos , uno para el código ( en lenguaje máquina ) , otro para albergar las variables globales , otro para el stack ( a travez del cual se pasan argumentos y donde residen las variables locales ) y finalmente un último segmento llamado memoria de apilamiento ó amontonamiento ( Heap ).

El Heap es la zona destinada a albergar a las variables dinámicas , es decir aquellas que crecen ( en el sentido de ocupación de memoria ) y decrecen a lo largo del programa , pudiendose crear y desaparecer (desalojando la memoria que ocupaban) en cualquier momento de la ejecución .
Veamos cual sería la metodología para crearlas ; supongamos primero que queremos ubicar un único dato en el Heap , definimos primero un puntero al tipo de la variable deseada :

double *p ;

notemos que ésta declaración no crea lugar para la variable , sino que asigna un lugar en la memoria para que posteriormente se guarde ahí la dirección de aquella Para reservar una cantidad dada de bytes en el Heap , se efectua una llamada a alguna de las funciones de Librería , dedicadas al manejo del mismo . La más tradicional es malloc() ( su nombre deriva de memory allocation ) , a esta función se le dá como argumento la cantidad de bytes que se quiere reservar , y nos devuelve un pointer apuntando a la primer posición de la "pila" reservada . En caso que la función falle en su cometido ( el Heap está lleno ) devolvera un puntero inicializado con NULL .

p = malloc(8) ;

acá hemos pedido 8 bytes  y hemos asignado a p el retorno de la función , es decir la dirección en el Heap de la memoria reservada.
Como es algo dificil recordar el tamaño de cada tipo variable , agravado por el hecho de que , si reservamos memoria de esta forma , el programa no se ejecutará correctamente , si es compilado con otro compilador que asigne una cantidad distinta de bytes a dicha variable , es más usual utilizar sizeof , para indicar la cantidad de bytes requerida :

p = malloc( sizeof(double) );

En caso de haber hecho previamente un uso intensivo del Heap , se debería averiguar si la reserva de lugar fué exitosa:

if( p == NULL ) rutina_de_error();

si no lo fué estas sentencias me derivan a la ejecución de una rutina de error que tomará cuenta de este caso . Por supuesto podría combinar ambas operaciones en una sola ,

if( ( p = malloc( sizeof(double) ) ) == NULL ) { printf("no hay mas lugar en el Heap ..... " ); exit(1) ; }

se ha reemplazado aquí la rutina de error , por un mensaje y la terminación del programa , por medio de exit() retornando un código de error .
Si ahora quisiera guardar en el Heap el resultado de alguna operación , sería tan directo como,

*p = a * ( b + 37 );

y para recuperarlo , y asignarselo a otra variable bastaría con escribir :

var = *p; 

PUNTEROS A STRINGS


No hay gran diferencia entre el trato de punteros a arrays , y a strings , ya que estos dos últimos son entidades de la misma clase . Así como inicializamos un string con un grupo de caracteres terminados en '\0' , podemos asignar al mismo un puntero :

p = "Esto es un string constante " ;

esta operación no implica haber copiado el texto , sino sólo que a p se le ha asignado la dirección de memoria donde reside la "E" del texto .

#include

 

#define TEXT1  "¿ Hola , como "

#define TEXT2  "estas ? "

 

main()

 

{

 

char pal[20] , *p ;

int i ;

p = TEXT1 ;

 

for( i = 0 ; ( pal[i] = *p++ ) != '\0' ; i++ ) ;

 

p = TEXT2 ;

printf("%s" , pal ) ;

 

printf("%s" , p ) ;

 

return 0 ;

 

}


Definimos primero dos strings constantes TEXT1 y TEXT2 , luego asignamos al puntero p la dirección del primero , y seguidamente en el FOR copiamos el contenido de éste en el array palabra , observe que dicha operación termina cuando el contenido de lo apuntado por p es el terminador del string , luego asignamos a p la dirección de TEXT2 y finalmente imprimimos ambos strings , obteniendo una salida del tipo : " ¿ Hola , como estas ? " ( espero que bien ) .
Reconozcamos que esto se podría haber escrito más compacto, si hubieramos recordado que palabra tambien es un puntero y NULL es cero , así podemos poner en vez del FOR

while( *pal++ = *p++ ) ;

ARRAYS DE PUNTEROS


Es una práctica muy habitual , sobre todo cuando se tiene que tratar con strings de distinta longitud , generar array cuyos elementos son punteros , que albergarán las direcciones de dichos strings.

char *flecha;

definía a un puntero a un caracter , la definición

char *carcaj[5];

implica un array de 5 punteros a caracteres .

 

INICIALIZACION DE ARRAYS DE PUNTEROS


Los arrays de punteros pueden ser inicializados de la misma forma que un array común , es decir dando los valores de sus elementos , durante su definición , por ejemplo si quisieramos tener un array donde el subíndice de los elementos coincidiera con una letra de las vocales , podríamos escribir :

char *vocales[] = { "letra no válida" , "a" , "e" , "i" , "o" , "u" , }

Igual que antes, no es necesario en este caso indicar la cantidad de elementos , ya que el compilador los calcula por la cantidad de términos dados en la inicialización. Asi el elemento vocales[0] será un puntero con la dirección del primer string, vocales[1], la del segundo, etc.

PUNTEROS A ESTRUCTURAS


Los punteros pueden también servir para el manejo de estructuras , y su alojamiento dinámico , pero tienen además la propiedad de poder direccionar a los miembros de las mismas utilizando un operador particular , el -> , (escrito con los símbolos "menos" seguido por "mayor" ).

Supongamos crear una estructura y luego asignar valores a sus miembros:

struct  conjunto {

 

                   int a     ;

 

                   double b  ;

 

                   char c[5] ;

 

                  } stconj    ;

stconj.a  = 10     ;

 

stconj.b  = 1.15   ;

 

stconj.c[0]  = 'A' ;

La forma de realizar lo mismo , mediante el uso de un puntero, sería la siguiente :

struct  conjunto {

 

                   int a     ;

 

                   double b  ;

 

                   char c[5] ;

 

                  } *ptrconj    ;

 

ptrconj = (struct conjunto *)malloc( sizeof( struct conjunto )) ;

 

ptrconj->a  = 10     ;

 

ptrconj->b  = 1.15   ;

 

ptrconj->c[0]  = 'A' ;

En este caso vemos que antes de inicializar un elemento de la estructura es necesario alojarla en la memoria mediante malloc(), observe atentamente la instrucción: primero se indica que el puntero que devuelve la función sea del tipo de apuntador a conjunto (ésto es sólo formal), y luego con sizeof se le da como argumento las dimensiones en bytes de la estructura.

PUNTEROS COMO PARAMETROS DE FUNCIONES 


Supongamos que hemos declarado una estructura , se puede pasar a una función como argumento:

struct conjunto { int a ; double b ; char c[5] ; } datos ; 
void funcion( struct conjunto datos );

Hicimos notar, en su momento, que en este caso la estructura se copiaba en el stack y así era pasada a la función, con el peligro que esto implicaba, si ella era muy masiva, de agotarlo.
Otra forma equivalente es utilizar un puntero a la estructura :

struct conjunto { int a ; double b ; char c[5] ; } *pdatos ; 
void una_funcion( struct conjunto *pdatos ) ;

Con lo que sólo ocupo lugar en el stack para pasarle la dirección de la misma. Luego en la función, como todos los miembros de la estructuras son accesibles por medio del puntero, tengo pleno control de la misma.


PUNTEROS COMO RESULTADO DE UNA FUNCION


Las funciones que retornan punteros son por lo general aquellas que modifican un argumento que les ha sido pasado por dirección ( por medio de un puntero ) , devolviendo un puntero a dicho argumento modificado , ó las que reservan lugar en el Heap para las variables dinámicas retornando un puntero a dicho bloque de memoria .
Así podremos declarar funciónes del tipo de:

char *funcion1( char * var1 ) ;

 

double *funcion2(int i , double j , char *k ) ;

 

struct item *funcion3( struct stock *puntst ) ;

             

El retorno de las mismas puede inicializar punteros del mismo tipo al devuelto , ó distinto , por medio del uso del casting . Algunas funciones , tales como malloc() y calloc() definen su retorno como punteros a void :

void *malloc( int tamano ) ;

de esta forma al invocarlas , debemos indicar el tipo de puntero de deseamos

p = (double *)malloc( 64 ) ;

 

 

Descargar gratis los programas y compiladores necesarios para aprender a programar

 
 

  ¿Buscas algo?

Google

 

Regresar al índice de lenguaje c

 

Regresar a la página principal