Punteros CURSO DE PROGRAMACIÓN EN C Centro de Investigación y de Estudios Avanzados del IPN. CINVESTAV - Tamaulipas. Febrero 2016 [Curso de programación en C] - Punteros 1/30 Punteros Las celdas de memoria dentro de la computadora están numeradas consecutivamente. Cada dato almacenado ocupa una o más celdas contiguas de memoria. El número de celdas de memoria requeridas para almacenar un dato depende de su tipo, e.g. un caracter se almacenará normalmente en un byte; un entero usualmente necesita dos bytes contiguos; etc. El número asociado con cada celda de memoria es conocido como la dirección de la celda. h 1700 [Curso de programación en C] - Punteros 38 1000 34 1002 3 1004 o 1701 l 1702 a 1703 \0 1704 2/30 Asignaciones de Variables Puntero Las variables puntero pueden apuntar a variables numéricas de caracter, arrays, funciones o a otras variables puntero. Los punteros, como cualquier otra variable, deben ser declarados. tipo *ptvar; A una variable puntero se le puede asignar la dirección de una variable ordinaria pv = &v; A una variable puntero se le puede asignar la dirección de otra variable puntero, siempre que ambas sean «apunten» al mismo tipo de datos pv = px; A una variable puntero se le puede asignar un valor nulo pv = NULL; [Curso de programación en C] - Punteros 3/30 Operador Dirección & Si v es una variable que representa un determinado dato, el compilador asignará celdas de memoria para esta variable. Si se conoce la localización (dirección) de la primera celda de memoria, entonces es posible accesar al dato. int v; 123 1974 La dirección de memoria de v se determina mediante la expresión &v, donde & es un operador unario, llamado el operador dirección. &v → 1974 [Curso de programación en C] - Punteros 4/30 Operador Indirección * Para asignar el valor de &v a la variable pv, ésta debe ser de tipo puntero, ya que «apunta» a la posición de memoria donde se aloja v. int v; 123 1974 XXXXX int *pv; pv = &v; pv → 1974 El dato representado por v puede ser accedido mediante la expresión *pv, donde * es un operador unario, llamado el operadar indirección que opera sólo sobre una variable puntero. *pv → 123 *pv y v representan el mismo dato [Curso de programación en C] - Punteros 5/30 Ejemplo de los Operadores Dirección e Indirección Operadores dirección (&) e indirección (*) #include <stdio.h> int main( ){ ___int v = 5; ___int *pv; /* puntero a un entero */ ___pv = &v; /* asigna dirección de v a pv */ ___printf("v = %d\t &v = %p\n",v, &v); ___printf("*pv = %d\t pv = %p\n",*pv, pv); ___return 0; } *pv = 5 *pv = 5 [Curso de programación en C] - Punteros &v = 0xbfcddab8 pv = 0xbfcddab8 6/30 Memoria Dinámica La función malloc sirve para solicitar un bloque de memoria del tamaño suministrado como parámetro. Devuelve un puntero a la zona de memoria concedida. Si malloc es incapaz de conceder el bloque (e.g. no hay memoria suficiente), devuelve un puntero nulo. void* malloc(unsigned numero_de_bytes); La función malloc devuelve un puntero inespecífico, en C estos punteros sin tipo se declaran como void*. Un puntero void* puede convertirse a cualquier otra clase de puntero. Cuando una zona de memoria reservada con malloc ya no se necesita, puede ser liberada mediante la función free. [Curso de programación en C] - Punteros 7/30 Array de Enteros con Memoria Dinámica Si x es un array unidimensional de 10 elementos enteros, es posible definir x como una variable puntero en vez de como un array. int *x; Debido a que x no tiene asignado automáticamente un bloque de memoria cuando se define como una variable puntero, se puede usar la función de biblioteca malloc para asignar suficiente memoria para x como sigue: x = (int *) malloc(10 * sizeof(int)); [Curso de programación en C] - Punteros 8/30 Operador ++ Sobre Variables Puntero Cuando se utiliza el operador ++ sobre una variable puntero, se puede modificar el valor del dato o la dirección al cual «apunta». Operador ++ en una variable puntero #include <stdio.h> int main( ){ ___int v = 5; ___int *pv; /* puntero a un entero */ ___pv = &v; /* asigna dirección de v a pv */ ___printf("v = %d\t &v = %p\n",v, &v); ___printf("*pv = %d\t pv = %p\n",*pv, pv); ___++*pv; /* dato al cual apunta */ ___++pv; /* dirección de memoria a la cual «apunta» */ ___printf("*pv = %d\t pv = %p\n",*pv, pv); ___return 0; } *pv = 5 *pv = 5 *pv = 6 &v = 0xbfcddab8 pv = 0xbfcddab8 pv = 0xbfcddabc [Curso de programación en C] - Punteros 9/30 Precedencia de los operadores ++ * en variables de tipo puntero #include<stdio.h> #include<stdlib.h> int main( ){ ___int *pv, i; ___pv = (int*) malloc (3 * sizeof(int)); ___pv[0] = 10; pv[1] = 15; pv[2] = 20; ___imprimir(3,pv); ___printf(“ %d\n”,++*pv); imprimir(3,pv); ___printf(“ %d\n”,*pv++); imprimir(3,pv); ___printf(“ %d\n”,*++pv); imprimir(3,pv); ___free(pv-=2); ___return 0; } void imprimir(int n, int *p ){ ___int i; ___printf(“pv = %p\n”,p) ___for(i=0; i<n; ++i) printf(“ %d\t”,p[i]); ___printf(“\n\n”); } [Curso de programación en C] - Punteros 10/30 Precedencia de los operadores ++ * en variables de tipo puntero #include<stdio.h> #include<stdlib.h> int main( ){ ___int *pv, i; ___pv = (int*) malloc (3 * sizeof(int)); ___pv[0] = 10; pv[1] = 15; pv[2] = 20; ___imprimir(3,pv); ___printf(“ %d\n”,++*pv); imprimir(3,pv); ___printf(“ %d\n”,*pv++); imprimir(3,pv); ___printf(“ %d\n”,*++pv); imprimir(3,pv); ___free(pv-=2); ___return 0; } void imprimir(int n, int *p ){ ___int i; ___printf(“pv = %p\n”,p) ___for(i=0; i<n; ++i) printf(“ %d\t”,p[i]); ___printf(“\n\n”); } [Curso de programación en C] - Punteros salida pv = 0x88f2008 10 15 20 11 pv = 0x88f2008 11 15 20 11 pv = 0x88f200c 15 20 135153 20 pv = 0x88f2010 20 135153 0 10/30 Punteros y Arrays Unidimensionales El nombre de un array es un puntero al primer elemento, por tanto, si x es un array unidimensional, la dirección del primer elemento se puede expresar como &x[0] o simplemente x. float x[4]; XX 0.4 2810 0.2 2814 0.1 2818 0.3 2822 &x[0] → 2810 x → 2810 [Curso de programación en C] - Punteros 11/30 Punteros y Arrays Unidimensionales El nombre de un array es un puntero al primer elemento, por tanto, si x es un array unidimensional, la dirección del primer elemento se puede expresar como &x[0] o simplemente x. float x[4]; XX 0.4 2810 0.2 2814 0.1 2818 0.3 2822 &x[0] → 2810 x → 2810 modificarprobabilidades(x); modificarprobabilidades(&x[0]); void modificarprobabilidades(float x[]){ ___instrucciones; } [Curso de programación en C] - Punteros 11/30 Punteros en Arrays Unidimensionales La dirección del segundo elemento del array se puede escribir como &x[1] o como (x + 1). Por tanto, la dirección del elemento i-ésimo del array se puede expresar &x[i] o como (x + i). float x[4]; XX 0.4 2810 0.2 2814 0.1 2818 0.3 2822 &x[2] → 2818 x + 2 → 2818 [Curso de programación en C] - Punteros 12/30 Punteros en Arrays Unidimensionales La dirección del segundo elemento del array se puede escribir como &x[1] o como (x + 1). Por tanto, la dirección del elemento i-ésimo del array se puede expresar &x[i] o como (x + i). float x[4]; XX 0.4 2810 0.2 2814 0.1 2818 0.3 2822 &x[2] → 2818 x + 2 → 2818 ¿Cuál es la diferencia? *x + 2 *(x + 2) [Curso de programación en C] - Punteros 12/30 Transformar un Puntero a una Posición de Arreglo strchr es una función de la biblioteca string.h que localiza un carácter en una cadena, buscando desde el principio y retorna un puntero en la primer ocurrencia de ese caracter. Obtener posición del arreglo a partir de un puntero #include <stdio.h> #include <string.h> int main(){ ___char cadena[] = “Hola Mundo”; ___char *pch; ___printf (“Localizando el caracter ’M’ en %s\n”, cadena); ___pch = strchr(cadena,’M’); ___if(pch! = NUll) ______printf(“Índice = %d\n”,pch-cadena); ___return 0; } [Curso de programación en C] - Punteros 13/30 Modificación de Valores con Punteros Si se desea se puede asignar el valor de un elemento del array a otro mediante un puntero 34 1974 56 1976 78 1978 95 1980 int numero[4] = {34, 56, 78, 95}; int *p1; p1 = &numero[1]; numero[2] = *p1; p1 = numero + 1; *(numero + 3) = *p1; [Curso de programación en C] - Punteros 14/30 Operaciones con Punteros A una variable puntero se le puede sumar o restar un valor entero, pero el resultado debe ser interpretado cuidadpsamente. Si px es una variable puntero que representa la dirección de x, se pueden escribir expresiones como ++px, −−px, (px + 3), (px + i) y (px - i), donde i es una variable entera. Cada expresión representará una dirección localizada a cierta distancia de la posición original. La distancia es el resultado del producto de la cantidad entera por el número de bytes que ocupa cada elemento al cual apunta px. [Curso de programación en C] - Punteros 15/30 Operaciones con Punteros Las comparaciones de variables puntero pueden ser de utilidad cuando ambas variables «apunten» a elementos de un mismo array. Las comparaciones posibles son <, >, <=, >=, ! =. Además, una variable puntero puede ser comparada con cero (NULL). float x[4]; XX 0.4 2810 0.2 2814 0.1 2818 0.3 2822 px = &x[1]; py = &x[3]; [Curso de programación en C] - Punteros 16/30 Operaciones con Punteros Las comparaciones de variables puntero pueden ser de utilidad cuando ambas variables «apunten» a elementos de un mismo array. Las comparaciones posibles son <, >, <=, >=, ! =. Además, una variable puntero puede ser comparada con cero (NULL). float x[4]; XX 0.4 2810 0.2 2814 0.1 2818 0.3 2822 px = &x[1]; py = &x[3]; Algunas comparaciones válidas: (px (px (px (px < py) != py) > py) == NULL) [Curso de programación en C] - Punteros (2814 (2814 (2814 (2814 < 2822) != 2822) > 2822) == NULL) 16/30 Operaciones Permitidas con Punteros - Resumen Operación Asignar la dirección de una variable ordinaria Asignar el valor de otra variable puxntero Asignar un valor nulo (cero) Sumar o restar una cantidad entera Una variable puntero puede ser restada de otra Comparar 2 punteros (si «apuntan» al mismo tipo) Ejemplo pv = &v; pv = px; pv = NULL; pv + 3; ++pv; −−pv; px - pv; (px < py) (px == py) Una variable puntero no puede ser multiplicada por una constante, no se pueden sumar dos punteros y así sucesivamente. A una variable ordinaria no se le puede asignar una dirección arbitraria, e.g. &x = pv; &x = NULL; &x = &y; [Curso de programación en C] - Punteros 17/30 Punteros de Arrays Un array bidimensional es una colección de arrays unidimensionales. Por tanto, se puede definir un array bidimensional como un puntero a un grupo de arrays unidimensionales contiguos, de esta forma, se puede escribir una declaración de array bidimensional como: tipo (*ptvar)[expresión2]; en vez de: tipo array[expresión1][expresión2]; [Curso de programación en C] - Punteros 18/30 Punteros a Arrays Bidimensionales de Enteros Si x es un array bidimensional de enteros con 10 filas y 20 columnas, x se puede declarar como: int (*x)[20]; en vez de int x[10][20]; [Curso de programación en C] - Punteros 19/30 Punteros a Arrays Bidimensionales de Enteros Si x es un array bidimensional de enteros con 10 filas y 20 columnas, x se puede declarar como: int (*x)[20]; en vez de int x[10][20]; De esta forma x apunta al primero de los arrays, i.e. la primer fila (fila 0). Entonces, (x + 1) apunta al segundo array que es la segunda fila (fila 1) y así sucesivamente, [Curso de programación en C] - Punteros 19/30 Arrays de Punteros Un array multidimensional puede ser expresado como un array de punteros. Cada puntero indica el principio de un array de dimensión (n-1). En términos generales, un array bidimensional se puede definir como un array unidimensional de punteros escribiendo: tipo *ptvar[expresión1]; El nombre del array precedido por un asterisco no está encerrado entre paréntesis en este tipo de declaración. Así la regla de precedencia de derecha a izquierda asocia el primer par de corchetes con array. El asterisco que lo precede establece que el array contendrá punteros. [Curso de programación en C] - Punteros 20/30 Acceso a un Elemento de una Matriz Usando Punteros Suponga que x es un array bidimensional de 10 filas y 20 columnas, x se puede declarar como: int *x[10]; [Curso de programación en C] - Punteros 21/30 Acceso a un Elemento de una Matriz Usando Punteros Suponga que x es un array bidimensional de 10 filas y 20 columnas, x se puede declarar como: int *x[10]; El elemento en la fila 2, columna 5, puede ser accedido escribiendo x[2][5] o *(x[2] + 5). [Curso de programación en C] - Punteros 21/30 Memoria Dinámica - Arrays de Punteros Ejemplo 1 #include <stdio.h> #include <stdlib.h> #define MAXFIL 10 int main(){ ___int fila, nc; ___int *a[MAXFIL]; ___printf(“¿Cuántos elementos? ”); ___scanf(“ %d”,&nc); ___for(fila = 0; fila < MAXFIL; ++fila) ______a[fila] = (int*) malloc(nc * sizeof(int)); . ___. . } [Curso de programación en C] - Punteros 22/30 Memoria Dinámica - Arrays de Punteros Ejemplo 2 int **MemoryMatrix(int nrows, int ncols){ ___int **matrix, i; ___matrix = (int**) malloc(nrows * sizeof(int*)); ___if(!matrix){ ______fprintf(stderr,“Error in Memory Matrix!\n”); ______exit(-1); ___} ___for(i = 0; i < nrows; i++){ ______matrix[i] = (int*) malloc(ncols * sizeof(int)); ______if(!matrix[i]){ _________fprintf(stderr,“Error in Memory Matrix!\n”); _________exit(-1); ______} ___} ___return matrix; } [Curso de programación en C] - Punteros 23/30 Paso de Punteros a una Función El «paso» de punteros a una función se conoce como pasar argumentos por referencia. Cuando un argumento se pasa por referencia, la dirección o posición del dato es pasada a la función. Un prototipo de función por referencia es el siguiente: tipo nombrefuncion(tipo *arg1 , ..., tipo *argn ); También se pueden omitir los nombres de los argumentos: tipo nombrefuncion(tipo *, ..., tipo *); [Curso de programación en C] - Punteros 24/30 Paso de Parámetros por Referencia void PasoValor(int n); /* Prototipo de función */ void PasoReferencia(int *n); /* Prototipo de función */ _ int main(){ ___int num = 10; ___printf(“Antes de funciones num = %d\n”, num); ___PasoValor(num); ___printf(“Después de paso por valor num = %d\n”, num); ___PasoReferencia(&num); ___printf(“Después de paso por referencia num = %d\n”, num); ___return 0; } _ void PasoValor(int n){ ___n = 0; ___return; } _ void PasoReferencia(int *n){ ___*n = 0; ___return; } [Curso de programación en C] - Punteros 25/30 Paso de Elementos de Arrays a Funciones por Referencia Cuando se pasa el elemento de un array a una función se pasa una «copia», por lo que si éste se modifica en la función el elemento original del array no es alterado, sin embargo, si se pasa por referencia, el valor original del elemento del arreglo puede ser modificado. Paso por referencia en los elementos de un array #include<stdio.h> void resetearpromedio(float *pf){ ___*pf = 0.0; ___return; } int main(){ ___int i; ___float promedio[5]; . ___. . ___resetearpromedio(&porcentajes[i]); . ___. . } [Curso de programación en C] - Punteros 26/30 Reordenación de una Cadena de Caracteres #include <stdio.h> #include <stdlib.h> #include <string.h> void reordenar(int n, char *x[]); int main(){ ___int i, n = 0; ___char *x[10]; ___printf(“Introducir una cadena en una nueva línea:\n”); ___printf(“Escribir \”FIN\” para terminar\n\n”); ___do{ ______x[n] = (char*) malloc (12 * sizeof(char)); ______printf(“cadena %d: ”, n + 1); ______scanf(" %s", x[n]); ___}while(strcmp(x[n++],“FIN”)&&n<11); ___reordenar(−−n, x); ___imprimir(n, x); } [Curso de programación en C] - Punteros 27/30 Reordenación de una Cadena de Caracteres void reordenar(int n, char *x[]){ ___char *temp; ___int i, elem; ___for(elem = 0; elem < n - 1; ++elem) ______for(i = elem + 1; i < n; ++i) _________if(strcmp(x[elem], x[i]) > 0){ ____________/* intercambiar las dos cadenas */ ____________temp = x[elem]; ____________x[e1em] = x[i]; ____________x[i] = temp; _________} ___return; } void imprimir(int n, char *x[]){ ___for(i = 0; i < n; ++i) ______printf("\ncadena %i: %s", i + 1, x[i]); } [Curso de programación en C] - Punteros 28/30 Ejercicios para aplicar los temas Los CAST ORE S son una casa club formada por 4 equipos (ei | i = [1 − 4]), donde cada equipo tiene de 2 a 3 miembros, i.e |ei | ∈ {2, 3}. Todos los miembros de cada equipo realizan la venta de productos, por lo que generan “utilidades” o “deudas” (representadas con el signo negativo). Cada principio de mes la casa club se reune y seleccionan un representante de cada equipo para realizar las aportaciones, de esta forma, el total de combinaciones diferentes para las aportaciones está dada por la ecuación: 4 Y |ei | i=1 Al club le interesa conocer todas las combinaciones de representantes de equipo que generan tanto el mínimo como el máximo de utilidades. [Curso de programación en C] - Punteros 29/30 Ejercicios para aplicar los temas (Punteros) S7_1 Codificar un programa con las siguientes características: Generar un vector ε donde se especifique la cantidad de miembros de cada equipo ei . Generar una matriz A de 4 filas, donde cada fila ai tiene un apuntador a un arreglo de flotantes tamaño |ei | que contiene las aportaciones de los miembros de cada equipo. Generar un vector υ el cual irá evaluando TODAS las combinaciones (índices) de los representantes de cada equipo. Utilizar una función de evaluación que reciba las 4 aportaciones de los representantes de equipo y calcule las utilidades. Imprimir todas las combinaciones de representantes que generen el mínimo y máximo de utilidades, bajo el siguiente patrón: Utilidad Mínima|Máxima: $ Miembro_e1 ($), Miembro_e2 ($), Miembro_e3 ($), Miembro_e4 ($) [Curso de programación en C] - Punteros 30/30 Ejercicios para aplicar los temas Ejemplo de entrada: ./Castores -e1 Hugo 300 Paco 200 Luis 100 -e2 Daisy 0 Mimi 500 -e3 Aurora -20.5 Ariel 10 -e4 Adam 20 Erick 50 Ejemplo de salida: Utilidad Mínima: $99.5 Luis($100.0), Daisy($0.0), Aurora($-20.5), Adam($20) Utilidad Máxima: $860.0 Hugo($300.0), Mimi($500.0), Ariel($10.0), Erick($50.0) [Curso de programación en C] - Punteros 31/30