M. C. Bertha López Azamar Universidad del Papaloapan Materia: Programación Estructurada Tema: Punteros Expositor: M. C. Bertha López Azamar Tuxtepec, Oaxaca. Enero/2005-2006 1 M. C. Bertha López Azamar PUNTEROS Un tema muy ligado al uso de arreglos y paso de parámetros por referencia a funciones, es el uso de punteros. Un puntero, es una variable que contiene la dirección de memoria, de un dato o de otra variable que contiene al dato. (El puntero apunta al espacio físico donde está el dato de la variable). Puntero El contenido es una dirección de memoria 3 1000 2 M. C. Bertha López Azamar Antes de continuar es importante recordar aspectos como los siguientes: dato memoria 3 Espacio de almacenamiento Toda variable tiene: • nombre (identificador), • tipo de dato, que permite reservar un espacio de almacenamiento (dirección de memoria) y • contenido . 3 M. C. Bertha López Azamar El programador al realizar la declaración de la variable, reserva un espacio de almacenamiento en memoria, conoce el nombre, pero el Sistema Operativo (S.O.) es quien asigna la dirección de memoria. Ejemplo: Reserva 16 bits de memoria Permite referenciar ese espacio Tipo de dato nombre de memoria en el código int Edad; Edad = 28; //declaración de la variable //asignación del valor valor El S.O. encuentra una espacio libre de memoria y la asocia 3 Edad con el nombre de la nombre 1000 variable. contenido Dirección de memoria de la variable 4 M. C. Bertha López Azamar Un puntero puede apuntar a un objeto de cualquier tipo, incluyendo variables simples, arreglos, estructuras, funciones, etc. Puntero *p Puntero *q 3 1000 ‘C’ Por esa razón su contenido es una dirección de memoria y no un valor cualquiera. 5000 5 M. C. Bertha López Azamar Las variables tienen nombre, contenido y dirección de memoria. *p 1000 200 edad 3 1000 *q letra ‘C’ 5000 5000 300 6 M. C. Bertha López Azamar Al hablar de puntero, se distinguen dos operadores: - operador dirección de: & el cual devuelve como resultado la dirección de su operando. - operador de indirección: * toma el contenido de su operando como una dirección y nos da como resultado el contenido en esa dirección. 7 NOTA: M. C. Bertha López Azamar Los operadores unarios * y & tienen mayor precedencia que los operadores aritméticos +, –, *, /, % e igual precendencia que ++ y --. Mayor ( ) [ ] -> . Menor ! ~ ++ -- - (tipo) * & */% +<< >> < <= > => == != & ^ | && || ?: = += -= *= /= etc. , sizeof 8 Declaración de un puntero: M. C. Bertha López Azamar Un puntero se declara precediendo el identificador que referencia al puntero, por el operador de indirección ( * ), el cual significa “puntero a”. La declaración de un puntero tiene la siguiente sintaxis: tipo *var_puntero; donde: tipo.- especifica el tipo de objeto apuntador, puede ser cualquier tipo, incluyendo tipos agregados. var_puntero.- nombre de la variable. 9 Ejemplo: se declaran dos variables simples: int edad; char letra; M. C. Bertha López Azamar edad letra 1000 5000 Al declarar los punteros para que apunten a estas variables, estos deben ser del mismo tipo de dato que la variable a la que apuntarán. int *p; char *q; /*declara p como puntero a un entero*/ /*declara p como puntero a una cadena de caracteres*/ *q *p 200 300 10 M. C. Bertha López Azamar Para asignar valores a variables simples, podemos emplear el operador de asignación (=) o la lectura del valor mediante una función de lectura por teclado (por ejemplo: scanf) //asigna un valor a la variable edad = 3; letra = ‘C’; edad letra 3 1000 C 5000 Para asignar contenido a un PUNTERO, podemos emplear el operador de asignación (=) en conjunto con el operador de dirección (&). Pero NUNCA lea el contenido de una variable puntero con una función de lectura por teclado (por ejemplo: scanf) //asigna la dirección de memoria de la variable al puntero p = &edad; q = &letra ; *p edad 1000 3 1000 200 *q letra 5000 ‘C’ 5000 11 300 M. C. Bertha López Azamar Reuniendo el código tenemos: int edad, *p; char letra, *q; //Las asignaciones siguientes son correctas edad = 3; letra = ‘C’; p = &edad; /*al puntero ‘p’ le asigna la dirección de memoria de la variable edad. La cual es conocida por el sistema operativo.*/ q = &letra; //Las asignaciones siguientes son incorrectas: p = 5; /*no puedo asignarle un valor cualquiera a un puntero, solo direcciones de otra variable del mismo tipo*/ p = &letra; /*no puedo asignarle la dirección de una variable de otro tipo de dato distinto al cual se declaro el puntero.*/ 12 M. C. Bertha López Azamar Para obtener el valor del dato al cual apunta el puntero, se emplea el operador de indirección (*) junto al nombre del puntero. Si solo se pone el nombre del puntero, lo que se obtiene es el contenido de la variable puntero, es decir, una *p dirección de memoria. edad 1000 *p se puede leer como sigue: 200 3 1000 toma el contenido del puntero ‘p’ (dirección 1000), y dame el contenido que se encuentra en esa dirección de memoria (3). dame el contenido que se encuentra en la dirección de memoria que contiene la variable p. dame el contenido de la dirección de memoria a la que apunta p. 13 M. C. Bertha López Azamar Por lo tanto, para obtener el contenido de la *p variable ‘edad’, es decir el 3, usando el 1000 puntero, debe escribirse *p, con lo que 200 podemos accesar indirectamente al contenido que apunta el puntero ‘p’. Al escribir el código: edad = *p + 1; *p 1000 200 edad 3 1000 edad 4 1000 Toma indirectamente el contenido de la dirección de memoria a la que apunta ‘p’, es decir, el contenido de la variable ‘edad’ y le suma un uno y luego lo asigna a la variable edad (lo que hace es 3+1 4) . Es lo mismo que escribir: edad = edad + 1; si las variables ‘p’ y ‘edad’ se declararón en la misma función. 14 El código: M. C. Bertha López Azamar *p = 1; *p 1000 200 edad 1 1000 Lo que hace es asignar indirectamente un 1 al contenido de la dirección de memoria a la que apunta ‘p’, es decir, a la variable ‘edad’. Si lo que se quiere es leer el valor de una variable mediante un puntero, se debe recordar que al usar la variable scanf, el segundo argumento que requiere es una dirección de memoria de una variable, y que el puntero en si, ya contiene una dirección de memoria de una variable. 15 Así el código: M. C. Bertha López Azamar scanf(“%d”, &edad); El ampersand(&), permite obtener la dirección de memoria de la variable edad. Al no ser puntero, el colocar el ampersand esto es correcto. scanf(“%d”, p); En el caso de leer indirectamente mediante el puntero, el uso del ampersand es incorrecto, ya que el contenido del puntero en si es la dirección de memoria donde se ubica la variable edad. Al ser puntero, el colocar el ampersand esto sería incorrecto. 16 M. C. Bertha López Azamar Los punteros se pueden utilizar para: • Crear y manipular estructuras de datos, • Asignar memoria dinámicamente, y • Proveer el paso de parámetros por referencia en las llamadas a funciones. 17 Respecto al paso de parámetros: M. C. Bertha López Azamar Recordemos el ámbito de las variables: • locales • globales Variables globales Variables locales datos datos Paso de parámetros Función1 datos Variables locales Función2 datos 18 M. C. Bertha López Azamar Respecto al paso de parámetros: Básicamente puede realizarse el paso de parámetros de dos formas : • Por valor • Por referencia 19 M. C. Bertha López Azamar El paso por valor, copia el valor de un argumento en el parámetro formal de la subrutina. Los cambios realizados sobre el parámetro no afectan al argumento. { } A=5; B=7; Funcion( A, 18, B*3+4) 5 18 25 5 18 25 void Funcion(int x, int y, int z) { } Argumentos (parámetros actuales – pueden ser variables, constantes, o expresiones) Copias de valores parámetros formales 20 M. C. Bertha López Azamar La llamada por referencia, copia la dirección del argumento en el parámetro. Dentro de la subrutina se usa la dirección para acceder realmente al argumento usado en la llamada, con lo que los cambios sufridos por el parámetro se reflejan en el argumento. { A=5; B=7; Funcion( &A, &B); } argumentos (parámetros actuales – deben ser variables) direcciones de memoria void Funcion(int *x, int *y) { } parámetros formales (variables de tipo puntero) 21 M. C. Bertha López Azamar #include<stdio.h> #include<conio.h> void Funcion(int *, int *); void main() { int A =5, B=7; //muestra el contenido de A, y B, o sea 5 y 7 printf(“A = %d, B = %d”, A, B); printf(“Introduce el valor de A: ”); scanf(“%d”, &A); //lee un valor y lo guarda en A Funcion( &A, &B); //muestra el contenido de A, y B al regresar de la función printf(“A = %d, B = %d”, A, B); getch(); } 22 M. C. Bertha López Azamar void Funcion(int *x, int *y) { /*asigna un valor en la dirección a la que apunta ‘y’, es decir, en el contenido de la variable A.*/ *x = 20; //muestra el contenido de A, y B, o sea 20 y 7 printf(“A = %d, B = %d”, *x, *y); //lee el valor de ‘B’ através del puntero ‘y’ scanf(“%d”, y); //muestra el contenido de A, y B, printf(“A = %d, B = %d”, *x, *y;); } 23 PUNTEROS y ARREGLOS M. C. Bertha López Azamar Tratándose de arreglos, debe recordarse que en si el nombre del arreglo es en sí un puntero. Al declarar un puntero lo que se hace es reservar un espacio de memoria (según el tipo de dato especificado) para un número determinado de datos y adicionalmente se reserva espacio para una variable puntero, la cual es la que tiene el nombre. Entonces el puntero apunta al inicio del espacio de memoria reservado (la primera posición del arreglo) y permite referenciar las posiciones de memoria con ese nombre y un índice que indica en si la posición, (y el S.O. calculara con esto el desplazamiento en memoria. 24 M. C. Bertha López Azamar Así tenemos el código de declaración de un arreglo: int mes_d[31]; Gráficamente podemos representarlo de la siguiente forma: mes_d 5000 4000 5000 25 M. C. Bertha López Azamar En el caso del paso de parámetros de un arreglo, siempre se realiza por referencia, tenemos que pasar una dirección de memoria, entonces solo colocaríamos el nombre del arreglo en la llamada a la función, y el prototipo y la definición de la función deben tener un puntero que reciba la dirección. Ejemplo: void lee_ahorro(float *); void main() { float mes[31]; lee_ahorro(mes); } void lee_ahorro(float *m) { } 26 M. C. Bertha López Azamar En C existe una relación tal entre punteros y arreglos, que cualquier operación que se pueda realizar mediante la indexación de un array, se puede realizar también con punteros. La diferencia radica en la expresión para acceder a los elementos del arreglo: lista[ind] ó *(lista + ind) lo que es: arreglo[indice] ó *(arreglo + indice) En el arreglo del ejemplo sería: mes[i] ó *(mes + i) 27 M. C. Bertha López Azamar Dentro de la función el código que haga referencia al arreglo se maneja igual, mediante un índice se referencia la posición. (en el scanf se coloca el ampersan como siempre, ya que debe localizar la dirección de la referencia) Ejemplo: void lee_ahorro(float *); void main() { float mes[31]; lee_ahorro(mes); } void lee_ahorro(float *m) { m[2] = 10; scanf("%d", &m[2]); } 28 M. C. Bertha López Azamar En el caso de las cadenas de caracteres, se manejan indiferentemente como arrays, y se puede aplicar lo expuesto para los arrays. Particularmente, puede mencionarse que para hacer referencia a una cadena de caracteres, podemos utilizar un puntero o un array: /*utilizando un puntero*/ char *nombre = “Benito Juárez”; /*utilizando un arreglo*/ char nombre[ ] = “Benito Juárez”; 29 M. C. Bertha López Azamar ARRAYS DINÁMICOS Utilizando la función malloc() y otras que se presentan para la asignación de memoria dinámica, es posible definir arrays en tiempo de ejecución, denominados también arrays dinámicos. Ejemplo: int i, n_elementos, *a; scanf(“%d “, &n_elementos); a=(int *)malloc(n_elementos * sizeof(int)); for(i=0; i < n_elementos; i++) scanf(“%d”, &a[i]); 30 El programa completo es el siguiente: M. C. Bertha López Azamar #include<stdio.h> #include<conio.h> #include<stdlib.h> void main() { int i, n_elementos, *a; printf("Introduce el número de elementos para crear el arreglo : "); scanf("%d", &n_elementos); /*Asignacion del espacio de memoria para el puntero */ a=(int *)malloc(n_elementos * sizeof(int)); } for(i=0; i < n_elementos; i++) { printf("%d.- ",i+1); scanf("%d", (a+i)); /*en el scanf se coloca (a+i) pero pudo emplearse &a[i] también */ } system("cls"); for(i=0; i < n_elementos; i++) { printf("%d.- %d ",i+1, *(a+i)); /*aqui debe colocarse * antes */ } getch(); 31 Paso de parametros puntero #include<stdio.h> #include<conio.h> #include<stdlib.h> void llena(int *, int); void imprime(int *, int); void main() { int i, n_elementos, *a; printf("Introduce el número de “); printf(“elementos para crear el arreglo : "); scanf("%d", &n_elementos); /*Asignacion del espacio de memoria para el puntero */ a=(int *)malloc(n_elementos * sizeof(int)); llena(a, n_elementos); system("cls"); imprime(a, n_elementos); } getch(); M. C. Bertha López Azamar void llena(int *arr, int num) { int i; for(i=0; i < num; i++) { printf("%d.- ",i+1); scanf("%d", (arr+i)); /*en el scanf se coloca (a+i) pero pudo emplearse &a[i] también */ } } void imprime(int *arr, int num) { int i; for(i=0; i < num; i++) { printf("%d.- %d ",i+1, *(arr+i)); /*aqui debe colocarse * antes */ } } 32