Asignatura: TALLER DE LENGUAJES I – 2014 Carrera: PROGR. UNIVERSITARIO / LIC EN INFORMATICA Dictado: Ing. Juan Manuel Conti.
APUNTADORES, repaso. Un apuntador, o puntero, es un identificador de programa, ya sea:
una variable una constante o una función, cuyo contenido intrínseco es una dirección. Así por ejemplo: int int*
Peso = 1250; Fuerza = 1250;
poseen distinto sentido: la variable Peso puede ser un operando común en una expresión de cálculo, en tanto que Fuerza indica una dirección (1250) donde presumiblemente se halla el dato que podría ser utilizado en una expresión cálculo. Puesto de otra manera estos mismos datos, podríamos entender mejor estos conceptos:
int int*
Peso = 1250; pPeso = 14500;
(que “sabemos” se halla en la dirección 14500) (le asignamos esa dirección)
Esquemáticamente tendríamos lo siguiente:
14500 1250 pPeso
Peso
14500 Cuando se trabaja con punteros surgen dos conceptos:
Dirección Indirección La dirección es el contenido en sí del identificador, en este caso el valor 14500. La indirección es aquello que se halla referenciado o almacenado en dicha dirección, en este caso la magnitud 1250.
Asignación de direcciones a un apuntador. Asignación directa: Apuntador = Valor. Esto suele ocurrir normalmente cuando se conoce de antemano la dirección de memoria: por ejemplo la memoria de video o ciertas posiciones bajas normalmente utilizadas por el sistema operativo para el reloj, el buffer de teclado, etc. Se debe tener mucho cuidado para no ingresar datos en direcciones equivocadas que produzcan funcionamiento anormal del programa o del sistema.
Taller de Lenguajes I – Clase Teórica Nº 1
Nº1 Pág 1/13
Asignatura: TALLER DE LENGUAJES I – 2014 Carrera: PROGR. UNIVERSITARIO / LIC EN INFORMATICA Dictado: Ing. Juan Manuel Conti. Asignación a través de operadores o funciones. Apuntador = &Identificador
(operador de dirección amphersand)
Para el ejemplo anterior tendríamos: pPeso = &Peso, donde el operador “&” retorna la dirección de su operando: en este caso la dirección de la variable Peso. Es de destacar que este operador devuelve una dirección del mismo tipo del operando sobre el cual actúa: en este caso retorna un puntero a int.
Las Indirecciones. La palabra indirección (o indirection en ingés) podríamos pensarla más bien como:
in-dirección (lo que se halla “en” la dirección) Podemos asignar o referenciar los que se halla en la dirección apuntada por el identificador. Asignación:
*pPeso = 2500
Está diciendo: en la dirección apuntada por el puntero pPeso, almacene la magnitud 2500. IMPORTANTE: Note bien la diferencia entre pPeso=…. y *pPeso=… En el primer caso se asigna una DIRECCION a la variable en sí, y en el segundo caso se asigna UN VALOR en la dirección apuntada por la variable. El dato almacenado en la dirección pPeso (o del puntero que se tratase) goza de todas las propiedades para ese tipo de dato, en este caso un int (entero):
Cociente de una División Entera. Resto de una División Entera. Puede utilizarse como operando en expresiones aritméticas. Etc.
Ejemplos:
(*pPeso)*1000 Peso equivalente en gramos. if((*pPeso) >=100) { ………….. } En realidad el paréntesis no es necesario pero da mayor claridad a las operaciones.
Taller de Lenguajes I – Clase Teórica Nº 1
Nº1 Pág 2/13
Asignatura: TALLER DE LENGUAJES I – 2014 Carrera: PROGR. UNIVERSITARIO / LIC EN INFORMATICA Dictado: Ing. Juan Manuel Conti. Un ejemplo para aclarar dudas: Considerar el siguiente esquema: p1ENT
p2ENT
int *
&ENT
128
ENT
int *
cuyo código vienen dado a continuación: #include void main() { int *p1ENT; int *p2ENT; int ENT = 128; clrscr(); highvideo(); p1ENT =&ENT; p2ENT =p1ENT; if(p1ENT==p2ENT) { cprintf("DIRECCIONES APUNTADAS IGUALES\r\n"); getch(); } if(*p1ENT==*p2ENT) { cprintf("CONTENIDOS REFERENCIADOS IGUALES\r\n"); getch(); } } La dirección asignada al apuntador p1ENT se obtuvo mediante el operador “&”, en tanto que la dirección de p2ENT fue tomada directamente del puntero p1ENT. En la línea: if(p1ENT==p2ENT), estamos preguntando si el contenido intrínseco de la variable p1ENT es igual al de la variable p2ENT, o lo que es lo mismo, si la dirección apuntada por p1ENT es idéntica a la apuntada por p2ENT. En cambio en la instrucción: if(*p1ENT==*p2ENT) averiguamos por el contenido de las direcciones apuntadas por p1ENT y p2ENT. Hay una diferencia neta con el caso anterior: aquí estamos comparando dos enteros y anteriormente comparábamos dos direcciones.
Taller de Lenguajes I – Clase Teórica Nº 1
Nº1 Pág 3/13
Asignatura: TALLER DE LENGUAJES I – 2014 Carrera: PROGR. UNIVERSITARIO / LIC EN INFORMATICA Dictado: Ing. Juan Manuel Conti.
Clasificación de los punteros. El siguiente cuadro nos permite tener una imagen general de los punteros: Aritmética de punteros (++ / -- / + = k / etc.) Tipificados
Referencia datos específicos (int * / char * / etc.) Permite operaciones aritméticas (operandos)
PUNTEROS
No-Tipificados (void *) Direcciones puras (reservas dinámicas) No permiten aritmética de punteros. 65536 direcciones Cortos (2 bytes)
Tinny / Small
Tamaño 1048576 direcciones Largos (4 bytes) Compact / Huge etc.
¿Qué tipo de dato es una dirección? Una dirección hace referencia a una posición específica de memoria, las cuales comienzan en 0 y se extienden hasta un cierto valor que depende de varias cosas, pero lo importante es que se trata de un valor numérico entero: específicamente es un unsigned int. En el apartado que viene a continuación, aprenderemos una herramienta muy interesante que brinda una gran potencialidad a la manipulación de punteros: los cast’s, y recién a partir de ahí podremos analizar ejemplos más completos e interesantes.
CAST’s Se denomina con este nombre a un rótulo entre paréntesis, que se coloca delante de un identificador de programa, normalmente una variable de algún tipo, a fin de forzar al compilador a realizar un cambio hacia “otro tipo” de dato, por ejemplo:
int Veloc = 125; double Velocidad; Velocidad=(double)Veloc; lo cual transforma a Veloc que era de tipo int, en un dato de tipo double. Alguien podría considerar la aplicación de este cast totalmente innecesario debido a que “C” realiza una conversión automática de tipo…y tendría toda la razón. Sin embargo analicemos el siguiente caso:
int Fuerza = 125; int Superf = 63; double Presion; Taller de Lenguajes I – Clase Teórica Nº 1
Nº1 Pág 4/13
Asignatura: TALLER DE LENGUAJES I – 2014 Carrera: PROGR. UNIVERSITARIO / LIC EN INFORMATICA Dictado: Ing. Juan Manuel Conti. Presion = Fuerza/Superf;
(fórmula elemental de la Física).
El resultado obtenido sería 1 en lugar de 1.984 debido a que el compilador analiza que a la derecha del signo igual se realizará una operación donde todos los operandos son magnitudes enteras, para lo cual asigna buffers transitorios de ese tipo y ejecuta las operaciones basándose en enteros, en este caso un cociente entero: la parte 0.984 es directamente truncada y se queda con lo que está a la izquierda del signo igual. ¿Qué error se ha cometido por el redondeo? Nada menos que el 49,6% con respecto a lo esperado. Estos errores son muy difíciles de detectar y producen serios dolores de cabeza a la hora de depurar. La solución surge de dos posibles acciones: a) Cambiar el “tipo” de las variables Fuerza y Superf a double. b) Utilizar cast’s:
Presion = (double)Fuerza / (double)Superf con lo cual todo funciona bien y no se pierde la parte decimal. Sin embargo una de las mayores aplicaciones de los cast’s ocurre con los apuntadores, donde es muy común asignar a un puntero tipificado una dirección donde reside un dato de otro tipo. En el siguiente ejemplo se dispone de un conjunto de variables de distinto tipo a las cuales se le extrae su dirección mediante el operador &, y se las asigna a un arreglo de unsigned int. Posteriormente estas magnitudes serán reconvertidas en direcciones a través de las cuales se mostrará por pantalla los datos originales.
/* --------- Ejemplo de clase Nro 1 ------------------------------------------------------------ */ #include #include #include typedef unsigned int INT; // ---------------------------------------------------------void main() { INT Direcc[4] = { 0,0,0,0 }; double UnDouble = M_PI; char *UnaCadena = "UNA CADENA CUALQUIERA"; int UnEntero = 15620; long UnLong = 6128635; clrscr(); highvideo(); Direcc[0]=(INT)&UnDouble; Direcc[1]=(INT)UnaCadena; Direcc[2]=(INT)&UnEntero; Direcc[3]=(INT)&UnLong;
Taller de Lenguajes I – Clase Teórica Nº 1
Nº1 Pág 5/13
Asignatura: TALLER DE LENGUAJES I – 2014 Carrera: PROGR. UNIVERSITARIO / LIC EN INFORMATICA Dictado: Ing. Juan Manuel Conti. cprintf(" Un Double = %lf\r\n",*((double *)Direcc[0]) ); cprintf(" Una Cadena = %s\r\n", (char *)Direcc[1] ); cprintf(" Un cprintf(" Un
Entero = %d\r\n", Long = %ld\r\n",
*((int *)Direcc[2]) ); *((long *)Direcc[3]) );
getch(); } // ----------------------------------------------------------Nótese que la operación es siempre la misma:
Direcc[ ] = (INT)&Identificador donde (INT) es un cast que convierte una dirección tipificada en su módulo y recién es almacenado en el arreglo. Un detalle interesante es que cuando se trata de una cadena no se requiere el operador & debido a que “C” maneja los arreglos como punteros implícitos llevando en su nombre la dirección de comienzo de la misma.
Notación implícita de apuntadores. Llamamos de esta manera a una notación especial en la cual no se ha declarado explícitamente un puntero, sino que la dirección es construida mediante una cierta sintaxis:
cprintf("Un Double = %lf\r\n",*((double *)Direcc[0]) ); Direcc[0] contiene un unsigned int correspondiente al módulo de una dirección, pero falta explicitar que se trata de un puntero. Para ello utilizaremos un cast: (double *). De esta manera:
(double *)Direcc[0] se trata de un apuntador a double. Si queremos tener acceso a lo que él referencia debemos indireccionar. La notación completa sería entonces:
*((double *)Direcc[0]) *((double)Direcc[0] [0]
apuntador a double
módulos de las direcciones
Taller de Lenguajes I – Clase Teórica Nº 1
Nº1 Pág 6/13
Asignatura: TALLER DE LENGUAJES I – 2014 Carrera: PROGR. UNIVERSITARIO / LIC EN INFORMATICA Dictado: Ing. Juan Manuel Conti. Otro ejemplo con conversión de datos. “Un arreglo de char de 24 elementos debe contener 11 valores de tipo unsigned int que serán asignados a través de un puntero correspondiente y por generación completamente aleatoria. El último domicilio contendrá la dirección de inicio del vector char en si. Con el mismo puntero pENT recorra el arreglo incrementando su dirección con el operador (++) para verse forzado a chequear y tomar la dirección de inicio del ultimo domicilio. Obviamente deberá ir mostrando por pantalla el contenido (fila a fila)”. ---------------------------------------------------------------- */
#include #include typedef unsigned int INT; const int DIM = 12; const int ESC = 27; void main() { char Buff[24]; INT *pENT = (INT *)&Buff[0]; int i; clrscr(); highvideo(); randomize(); // --- Llenado del arreglo de char -------------------for(i=0;i