Universidad Tecnológica Nacional Facultad Regional Córdoba Departamento Ingeniería en Electrónica Catedra de Infomática 1 JTP Ing. Mascietti, Norma PA Pereira, Mauro Alejandro Punteros y aritmética de punteros 1. Introducción Una de las características más poderosas del C, es el puntero o apuntador. Los punteros permiten simular las llamadas por referencia y crear y manipular estructuras de datos dinámicas, que pueden crecer y encogerse en tiempos de ejecución, como las pilas, árboles y listas. Nos introduciremos ahora en los conceptos básicos de los punteros. 2. Definición de punteros Los punteros son variables cuyos valores son direcciones de memoria. Por lo general, una variable contiene un valor específico, un entero, un flotante, un carácter, etc. En cambio, una variable puntero contiene la dirección en memoria de una variable que contiene un valor específico. De esta manera, mediante el nombre o el identificador de una variable, hacemos referencia directa a un valor. En cambio, mediante el nombre o identificador de un puntero, hacemos referencia indirecta a un valor. Al proceso de referenciar a un valor a través de un puntero, se le llama indirección. Nombre Valor Direcciones FB21 cont 5 FB22 FB23 ptrcont hace referencia indirecta a una variable cuyo valor es 5 ptrcont FB22 FB24 FB25 FB26 ptrcont FB22 FB27 cont cont hace referencia directa al valor 5 5 FB28 3. Operadores con punteros & → Operador de Dirección * → Operador de Indirección ( o de referencia) ( o de desreferencia ) devuelve la dirección donde se almacena el operando devuelve el valor del objeto que apunta Hay que tener siempre en cuenta que los punteros son variables cuyos valores son direcciones de memoria, y que estos operadores nos permiten por un lado almacenar en un puntero la dirección de memoria de la variable y por otro lado, recuperar el valor al que está apuntando. En el siguiente ejemplo se puede apreciar esto: Ejemplo: #include <stdio.h> int main(int argc, char **argv) { int cont=5; //declaro una variable del tipo int y le asigno el valor 5 int *ptrcont; //declaro una variable que apunta a una variable del tipo int ptrcont = &cont; //con & pido la dirección de memoria de la variable cont //y dicha dirección queda guardada dentro de ptrcont 1 Universidad Tecnológica Nacional Facultad Regional Córdoba Departamento Ingeniería en Electrónica Catedra de Infomática 1 JTP Ing. Mascietti, Norma PA Pereira, Mauro Alejandro printf("Valor de la variable cont: %d, dirección de memoria: %p\n", cont, &cont); //referencia directa printf("Valor de la variable cont: %d, dirección de memoria: %p\n", *ptrcont , ptrcont); //referencia indirecta return 0; } 4. Llamada a funciones con paso de parámetros por puntero (por referencia) Existen dos formas de pasar argumentos a una función, por valor o por referencia. Las ventajas de la segunda con respecto a la primera son: • Puedo trabajar sobre varias direcciones de variables en la función y modificarles sus valores “automáticamente” en el main(), mientras que con una llamada por valor, sólo puedo retornar el valor de una variable, a través de return(). • Si tengo un objeto de gran tamaño, puedo sobrecargar el sistema con una llamada por valor (recuerde que es hacer una copia del objeto). Ejemplo de sintaxis: #include … void función_por_referencia(int *,int *); int main(int argc, char **argv) {int var1=0,var2=0; int *var3; var3=&(var2); … … función_por_referencia(&var1, var3); … … return 0; } void función_por_referencia(int *a,*b){ … … … } //prototipo //declaro una variable int //declaro un apuntador a una var. int //llamada a la función intercambio //cuerpo de la función Notese que en los argumentos del llamado de la función una de las variables tiene antepuesta el operador & para obtener la dirección de memoria de dicha variable (var1) y la segunda variable var3 no lo tiene. Esto es debido a que var1 es una variable común, y necesitamos pasarle la dirección de memoria de dicha variable a la función, para que la función la pueda trabajar como puntero; mientras que var3 ya es un puntero, por lo que no es necesario utilizar el operador & porque el valor de var3 es una dirección de memoria. ¿Confundido? Vea la siguiente imágen y analice los ejercicios. 2 Universidad Tecnológica Nacional Facultad Regional Córdoba Departamento Ingeniería en Electrónica Catedra de Infomática 1 JTP Ing. Mascietti, Norma PA Pereira, Mauro Alejandro Ejercicios: Realice un programa que eleve al cubo un número utilizando punteros e imprima el valor original y elevado en el main(). #include <stdio.h> void cuboporpuntero ( int *ptrnro ); //prototipo int main(int argc, char **argv) { int numero = 5; printf("El valor original de numero es %d\n", numero); cuboporpuntero ( &numero ); //llamada printf("El nuevo valor de numero es %d\n", numero); } return 0; void cuboporpuntero ( int *ptrnro ) { *ptrnro = *ptrnro * *ptrnro * *ptrnro; //cuerpo de la funcion } Escriba un programa en C que intercambie los valores de dos variables a través de una función, pasándole los parámetros por referencia. Los valores iniciales e intercambiados deben imprimirse en el main() #include <stdio.h> void swap ( int *, int * ) ; //prototipo int main(int argc, char **argv) { int var1 = 3; int var2 = 5; int *var3; var3=&var2; //el valor de var3 es igual a la dir. mem. de var2 printf("El valor original de la primera variable es : %d \n", var1); printf("El valor original de la segunda variable es : %d \n", var2); swap( &var1, var3 ); //llamada a la función intercambio printf("Despues de llamar a intercambio, el valor de las variables son... \n\n" ); printf("El valor de la primera variable es : %d \n", var1); printf("El valor de la segunda variable es : %d \n", var2); } return 0; void swap ( int *x, int *y ) { } 3 int tmp=0; tmp = *x; *x = *y; *y = tmp; //cuerpo de la función //variable auxiliar Universidad Tecnológica Nacional Facultad Regional Córdoba Departamento Ingeniería en Electrónica Catedra de Infomática 1 JTP Ing. Mascietti, Norma PA Pereira, Mauro Alejandro 5. Aritmética de punteros Con los apuntadores se pueden realizar un conjunto limitado de operaciones aritméticas. Un apuntador puede ser incrementado (++), decrementado (–), se puede añadir un entero a un apuntador (+ o +=), un entero puede ser restado de un apuntador (- o -=), o un apuntador puede ser sustraído o restado de otro. Nos enfocaremos en la adición. Suponga que cada variable int tiene 4 bytes, que ha sido declarado un arreglo int v[10] y su primer elemento está en memoria en la posición 3000. El apuntador vPtr ha sido inicializado para apuntar al primer elemento del vector, o sea v[0]. El valor de vPtr será 3000. En la aritmética convencional, la adición de 3000 + 2 nos daría 3002. Por lo regular, este no pasa en la aritmética de punteros. Cuando se añade o se resta un entero de un apuntador, el apuntador no incrementa o decrementa sólo por el valor de dicho entero, sino por el entero multiplicado por el tamaño del objeto al cual el apuntador se refiere. El número de bytes depende del tipo de datos del objeto. Por ejemplo, si se realiza la siguiente instrucción: vPtr +=2; producirá que el apuntador se desplace a la posición 3008 (3000 + 2 * 4) de memoria, no a la 3002. vPtr ahora apunta a v[2]. Pos final mem = Pos ini mem + ( incremento * tam del objeto) Supongamos ahora que int vale 2 bytes, tenemos un arreglo de enteros llamado Vec2[5] y un apuntador int Ptrvec2: Nombre Valor Dirección Si hacemos Ptrvec2 = Vec2 Vec2[0] Vec2[1] Vec2[2] Vec2[3] Vec2[4] 10 20 30 40 15 FFE0 FFE2 FFE4 FFE6 FFE8 Ptrvec2 Vec2[0] hace referencia directa al valor 10 FFE0 *Ptrvec2 hace referencia indirecta al primer elemento del vector de enteros cuyo valor es 10 Si ahora incrementamos en 2 el puntero: Ptrvec2 = Ptrvec2 + 2 FFE0 + 2*2 Vec2[0] 10 (2 es la cantidad de bytes que ocupa un entero) Ptrvec2 Vec2[2] FFE4 30 Ptrvec2 ahora vale FFE4 y apunta a 30 Otro ejemplo, es un arreglo de caracteres o string, en el cual cada carácter ocupa 1 byte: Nombre Vec1[0] Vec1[1] Vec1[2] Vec1[3] Vec1[4] 4 Valor ‘a’ ‘b’ ‘c’ ‘d’ ‘e’ Dirección FF00 FF01 FF02 FF03 FF04 Ptrvec1 FF00 *Ptrvec1 hace referencia indirecta al primer elemento del vector de caracteres cuyo valor es ‘a’ Vec1[0] hace referencia directa al valor ‘a’ Vec1[0] ‘a’ Universidad Tecnológica Nacional Facultad Regional Córdoba Departamento Ingeniería en Electrónica Si le sumamos 2 al puntero: Ptrvec1 = Ptrvec1 + 2 Catedra de Infomática 1 JTP Ing. Mascietti, Norma PA Pereira, Mauro Alejandro FF00 + 2*1 (1 es la cantidad de bytes que ocupa un caracter) Ptrvec1 ahora vale FF02 y apunta a ‘c’ 6. Ptrvec1 Vec1[2] FF02 ‘c’ Uso del calificador const con apuntadores El calificador const permite al programador informarle al compilador que el valor de una variable particular no deberá ser modificado. El buen eso de este calificador, permite programar en base al principio de mínimo privilegio. Si un valor no se modifica (o no debería modificarse) en el cuerpo de una función al cual es pasado, el valor deberá declararse como const, para asegurarse que no se modifica de forma accidental. Por ejemplo, considere una función a la que se le pasa por argumentos, un arreglo y su tamaño, e imprime el arreglo completo. Esta función que se basa en el tamaño para imprimir hasta el último valor nunca debería ser modificado, por lo que una buena práctica de programación sería que fuera declarado como const. 5