Punteros - dsi.fceia.unr.edu.ar · Punteros y funciones •En lugar de resolver ejercicio 5 de...

Post on 24-Jul-2020

10 views 0 download

Transcript of Punteros - dsi.fceia.unr.edu.ar · Punteros y funciones •En lugar de resolver ejercicio 5 de...

Punteros

Punteros

• Se usan mucho en C en parte porque: – A veces constituyen la única forma de expresar

una operación • Reserva de memoria dinámica

• Forma alternativa al paso de argumentos – Por referencia con argumentos tipo punteros

– por lo general generan un código más compacto y eficiente de lo que puede obtenerse en otras formas • Permiten representar en forma eficiente estructuras de

datos sofisticadas (pilas, colas, listas, etc.)

Punteros y funciones

• C pasa los argumentos de funciones por valor, no existe forma directa para que la función que se invoca altere una variable que se pase como argumento real

• Por ejemplo una rutina de ordenamiento podría intercambiar 2 elementos desordenados con una función:

Punteros y funciones

Punteros y funciones

• La función anterior sólo cambia copias de a y b! No sirve

• La forma de obtener los resultados deseados: el programa invocante pase punteros a los valores que se intercambian o sea,

• copias de las direcciones de memoria de variables del mismo tipo que el puntero

Punteros y funciones

Punteros y funciones

• Aplicaciones: poder devolver más de un valor • int scanf(const char *formato, arg1, arg2, …, argn);

int edad;

double altura;

scanf(“%d %lf”,&edad,&altura);

• Sirven para pasar punteros a objetos de datos grandes, evitando la sobrecarga de pasar una copia de los objetos mediante llamadas por valor.

Punteros y funciones

• En lugar de resolver ejercicio 5 de práctica 1, intente escribir una función void que, cuando la invoque, le pase como información una x cantidad de segundos, desglose esa cantidad de segundos en las horas, minutos y segundos que contiene. Mostrar el resultado por pantalla

• Ejemplo de salida por pantalla: En 115400 segundos hay: 32 horas, 3 minutos y 20 segundos

• En el capítulo de estructuras (registros) veremos otra posibilidad de resolver este ejercicio

Punteros y funciones #include <stdio.h> void conversor(int*h, int*m, int*s);//sin espacio para indentar int main(){ int segundos, minutos, horas, auxiliar; setbuf(stdout,0); printf("Ingrese un nro.entero de segundos para convertir a horas: "); scanf("%d",&segundos); auxiliar=segundos;//conversor cambia el valor inicial de segundos! conversor(&horas, &minutos, &segundos); printf("En %d segundos hay: %d horas, %d minutos y %d segundos\n", auxiliar, horas, minutos, segundos); return 0;} void conversor(int*h, int *m, int *s){ int resto; *h=*s/3600; //división entera resto=*s%3600;//* tiene mayor precedencia que / y % *m=resto/60; *s=resto%60;}

Punteros y funciones

• Cuando se pasa a una función un argumento que es un nombre de un arreglo, lo que se pasa es la dirección del elemento inicial (índice 0)

• Dentro de la función que se invoca, este argumento es una variable local y por tanto, un parámetro de nombre de arreglo es un puntero, o sea, una variable que contiene una dirección

• En general, cuando se escriben funciones para manejo de strings se prefiere la notación de punteros, en lugar de char [ ]

Punteros y funciones

Punteros y funciones • Puesto que s es un puntero es perfectamente legal

incrementarlo;

• s++ no tiene efecto alguno sobre la cadena de caracteres pasada como argumento real a strlen, sólo incrementa la copia privada del puntero de strlen

• Prueben realizar un printf (“%s”) del string con el cual invocaron a strlen y, efectivamente verán el string que pasaron como argumento

Punteros y funciones • Llamadas como estas, funcionan:

– strlen(“hola mundo”);//constante de cadena

– char simpleArray[] = "simple string";

strlen (simpleArray);

– char *simplePtr = (char*)malloc(strlen("simple string")+1);

strcpy(simplePtr, "simple string");

/* nunca use sizeof para determinar longitud de 1 string (*char)*/

-char command[16];

printf("Enter a Command: ");

scanf("%s", command);/*toma hasta un blanco,

mejor usar gets o más seguro fgets*/

strlen(command);

Paso de argumento puntero a constante

• El paso de argumentos punteros a constante es una técnica común en C

• Por eficiencia en el uso de memoria utilizo un argumento de tipo puntero, pasándole la dirección de un dato pero, si no deseo que la función modifique dicho dato, a dicho argumento lo declaro como puntero a constante

Paso de argumento puntero a constante

• Muy común en las funciones declaradas en string.h

– int strcmp(const char *s1, const

char *s2);

Retornar un puntero

– Existen 2 formas de hacerlo, la primera:

– Dentro del cuerpo de la función se asigna memoria dinámicamente (por ejemplo, con malloc) y se retorna la dirección de comienzo del bloque de memoria asignado, a través del puntero que retorna la función (malloc, realloc o calloc).

– El invocante de dicha función es responsable de liberar ese bloque de memoria (free) cuando no lo necesite más

Retornar un puntero

• Importante: Nunca se debe retornar un puntero a una variable local.

• Este es un error muy común, cometido principalmente por no comprender cómo trabaja el stack de programa.

• Una vez que la función retorna al punto donde se la invocó, la dirección de memoria retornada (de la variable local) ya no es válida puesto que, el stack frame de dicha función es extraído del stack

Retornar un puntero #include <stdio.h>

#include <stdlib.h>//necesaria para malloc

int* allocateArray(int size, int value) {

int i;//asigna memoria y llena con un valor

int* arr = (int*)malloc(size * sizeof(int));

for(i=0; i<size; i++) {

arr[i] = value; //notación índices

}

return arr;}

void main(){

int i;

int* vector = allocateArray(5,45);

for(i=0; i<5; i++) {

printf("%d\n", vector[i]);//notación índices

}

free(vector);

}

Retornar un puntero

Punteros y arrays

• La correspondencia entre indexación y aritmética de punteros es muy estrecha

• Por definición el valor de una variable o expresión de tipo arreglo es la dirección del elemento 0 del arreglo. Así que, después de la asignación pa=&a[0];

• pa y a tienen valores idénticos. Puesto que, el nombre de un arreglo es sinónimo de la localidad del elemento inicial puede también escribirse como pa=a;

Punteros y arrays

• Una referencia a a[i] también puede escribirse como *(a+i).

• Al evaluar a[i] C la convierte inmediatamente a *(a+i); las 2 formas son equivalentes

• Al aplicar el operador & a ambas partes de esta equivalencia, se deriva que &a[i] y a+i son también idénticas; a+i es la dirección del i-ésimo elemento de a

• Si pa es un puntero, las expresiones pueden usarlo con un subíndice, pa[i] es idéntico a *(pa+i)

Punteros y arrays

1 4 -5 0 8

p

a[0] a[1] a[2] a[3] a[4]

p[0] p[1] p[2] p[3] p[4]

Punteros y arrays

Punteros y arrays

• En resumen, cualquier expresión de arreglo e índice es equivalente a una expresión escrita como un puntero y un desplazamiento (offset)

• Existe una diferencia entre un nombre de arreglo y un puntero, que debe tenerse en mente.

• Un puntero es una variable por esto pa=a; y pa++; son legales

• El nombre de un arreglo no es una variable, por tanto a=pa; y a++ son ilegales

Retornar un puntero

• Segunda forma: Pasar un objeto a través de un puntero como argumento de la función que lo modifica.

• El invocante es responsable de asignación y liberación de memoria dinámica

Retornar un puntero #include <stdio.h> #include <stdlib.h> //al pasar un array como argumento hay que pasar la dimensión void fillArray(int *arr, int size, int value) { int i; if(arr != NULL) { for(i=0; i<size; i++) { //buena práctica: si el puntero es NULL no se ejecuta ninguna //acción y el programa no termina anormalmente al invocar la función arr[i] = value; }}} int main(){ int* vector = (int*)malloc(5 * sizeof(int)); int i; //modifico contenido de vector, ahora tiene “basura” fillArray(vector,5,45); for(i=0; i<5; i++) { printf("%d\n", vector[i]); } free(vector); return 0;}

Para el ejercicio 6

#include <stdio.h> #include <stdlib.h> //la librería estándar no tiene una función para invertir strings char* reverse(char *str) { int j, largo=strLargo(str),i; //reservar un byte mas para el terminador nulo char* reversed = (char*) malloc( largo + 1); for (i = largo - 1, j=0; i>=0; i--, j++) { reversed[j] = str[i]; } reversed[j + 1] = '\0'; //no olvidar añadir terminador nulo al final return reversed; }

Retornar la dirección de un literal

• Ver la solución propuesta al ejercicio 4 donde se pide escribir una función que tome como argumento un entero positivo entre 1 y 7 y retorne un puntero a cadena con el nombre del día de la semana correspondiente al argumento

• char *dia_de_la_semana( int d );

• Si invoco la función con el valor 6, debe devolver el string “sábado”

Ejercicio 7

• Escribir una función que reciba como argumento un entero y retorne una cadena de caracteres en su representación decimal.

• Ídem para: representación octal, hexadecimal y binaria (genere una función por cada una de estas opciones)

Conversiones de string

• La función sprintf realiza las mismas conversiones que printf, pero almacena la salida en una cadena, pasada como 1º argumento (debe ser lo suficientemente grande), en lugar de hacerlo en la salida estándar

• int sprintf(char *cadena, char

*format, argv arg2, ...)

• Puede usarse para enviar datos (por ejemplo de un sensor) a través de un puerto serie

#include <stdio.h> int main() { char str[10]; /* MUST be big enough to hold all the characters of your number!! */ int i; i = sprintf(str, "%o", 15); printf("15 in octal is %s\n", str); printf("sprintf returns: %d\n\n", i); i = sprintf(str, "%d", 15); printf("15 in decimal is %s\n", str); printf("sprintf returns: %d\n\n", i); i = sprintf(str, "%x", 15); printf("15 in hex is %s\n", str); printf("sprintf returns: %d\n\n", i); return 0; }

Conversiones de string #include <stdio.h> #include <stdlib.h> //itoa no forma parte del estándar ANSI C int main() { char str[10]; /* MUST be big enough to hold all the characters of your number!! */ //el tercer argumento es la base (entre 2 y 36) printf("15 in binary is %s\n", itoa(15, str, 2)); printf("15 in octal is %s\n", itoa(15, str, 8)); printf("15 in decimal is %s\n", itoa(15, str, 10)); printf("15 in hex is %s\n", itoa(15, str, 16)); return 0; }

Ejercicio 6

• El siguiente código está basado en la propuesta planteada en el libro de Kernighan & Ritchie Ítem 3.6, válido para representar números entre 2 y 35

• Extraído de: http://www.strudel.org.uk/itoa/, char* version 0.4

Arrays de punteros

• Puesto que en sí mismo los punteros son variables pueden almacenarse en arreglos tal como los otros tipos de variables

• El uso más común de un array de punteros es el de formar arreglos de cadenas de caracteres (texto)

Arrays de punteros

• Cada entrada en el arreglo es un puntero al primer carácter de cada cadena

char* mensaje[4]={''Hola'',''Adios'',''Bye'',''Salut''};

• Cada cadena está almacenada en memoria como una cadena de caracteres terminada en ‘\0’.

• En el arreglo no están colocadas las cadenas, tan solo están almacenados los punteros a ellas (sus direcciones de comienzo)

Arrays de punteros

• Aunque el arreglo de punteros es de tamaño fijo, permite el acceso a cadenas de caracteres de cualquier longitud

• Los arreglos de punteros son una representación de datos que manejan de forma eficiente y conveniente líneas de texto de longitud variable

Arrays de punteros

Abcdefghi

xyz

Qwertyuiopasdfg

Abcdefghi

xyz

Qwertyuiopasdfg

Arrays de punteros

• Un ejemplo numérico de un vector de punteros estático donde cada elemento apunta a un único entero (inicializado con valores del 0 al 4). Ya veremos cómo crear arrays dinámicos de punteros int* arr[5]; int i; for(i=0; i<5; i++) { //creo espacio en el heap para el entero arr[i]

arr[i] = (int*)malloc(sizeof(int)); //arr[i] indica direcciones, es un puntero printf("%p ",arr[i]); //*arr[i] indica contenido de la dirección //indicada por arr[i] (desreferencia) //lleno con valores del 0 al 4 *arr[i] = i;

}

Punteros a punteros

• Un puntero a puntero (o doble puntero) es un puntero que contiene la dirección de memoria de otro puntero

Punteros a punteros

Punteros a punteros

Punteros a punteros

• Es una forma de indirección múltiple que puede llevarse al nivel que se desee (código difícil de leer y propenso a errores)

• Un arreglo de punteros no es más que un puntero a punteros.

• El nombre del arreglo es la dirección de la primer elemento del array que apunta a los demás a medida que se incrementa

Punteros a punteros

Punteros a punteros

Punteros a punteros

• En el ejemplo de array de punteros int main(){ int* arr[5]; int i; for(i=0; i<5; i++) { arr[i] = (int*)malloc(sizeof(int)); *arr[i] = i; printf("(arr+%d) es:%p, *(arr+%d) es: %p y **(arr+%d)es: %d\n",i,(arr+i), i,*(arr+i),i, **(arr+i)); }

Punteros a punteros

• arr es un array de punteros a enteros

• (arr+i) muestra la dirección donde está cada elemento de arr (como son punteros, las direcciones están separadas por 4 bytes)

• *(arr+i)=arr[i] muestra el contenido de cada elemento de arr que es un puntero, son direcciones de memoria devueltas por malloc

• **(arr+i)=* arr[i] son los enteros a los que apunta cada puntero del array arr[i]

Punteros a punteros

Ejercicio 8

• Se necesita contar las letras de un texto ingresado. El texto puede tener varias oraciones.

• Crear una rutina que almacene las letras y la cantidad de veces que aparecen.

• Generar un informe con el detalle, en caso de que no sean caracteres alfabéticos o númericos, mostrar su valor hexadecimal.

• Debe crear un vector de punteros que almacenen las frases y luego recorrerlo con un doble puntero.

Ejercicio 8

• Ejercicio extenso pero contiene muchas funciones de librería string

• Ver cómo se crea el array dinámico de punteros conteniendo frases, usando un puntero a puntero y malloc

• Investigar el uso de la función strtok de la librería string que sirve para separar strings en subcadenas delimitadas por determinados caracteres, por ejemplo extraer día mes y año de “12/10/1892” o, horas minutos y segundos de “23:34:20”, etc. Ver por ej.: https://parzibyte.me/blog/2018/11/13/separar-cadena-delimitadores-c-strtok/

Ejercicio 8, probar, analizar y mejorar!

Ejercicio 8, probar, analizar y mejorar!

Ejercicio 8, probar, analizar y mejorar!

Tabla de códigos ASCII: http://ascii.cl/es/

Matrices

• Este tipo de arrays (array de array) deben mapearse al espacio de direcciones unidimensional de la memoria principal

• int matrix[2][3]={{1,2,3},{4,5,6}};

Matrices

• Cada fila de una matriz es un array, por eso es un array de arrays

Matrices

Matrices dinámicas

• Cuando la matriz se declara: int matrix[2][5] = {{1, 2, 3, 4, 5},{6, 7, 8, 9, 10}};

• Se asignan espacios contiguos en memoria (como si fuese un vector de 10 elementos)

• Sin embargo, al usar malloc para asignar memoria dinámicamente correspondiente a una matriz, hay variantes en la forma en que es alojada en memoria. Veremos una, ver las demás en el apunte

• Puesto que un array multidimensional puede tratarse como un “array de array”, no hay razón para que el “array interno” (las columnas de las distintas filas) ocupen espacios contiguos.

• Cuando se utilizan índices para recorrer tal array, la naturaleza “no contigua” del mismo es transparente para el programador.

Matrices dinámicas

• Una forma de reservar memoria dinámicamente para una matriz donde no hay garantía de que los elementos de la matriz ocupen posiciones contiguas:

• en primer lugar se reserva espacio para el array “externo” (filas) y luego se reserva espacio para los elementos de cada fila (columnas) usando dos sentencias malloc separadas:

Matrices dinámicas

Matrices dinámicas

• Al usar malloc separados no se garantiza la contigüidad de la memoria, depende del gestor del heap.

int rows = 2;

int columns = 5,i;

int **matrix = (int **) malloc(rows * sizeof(int *));

for (i = 0; i < rows; i++) {

matrix[i]=(int*) malloc(columns*sizeof(int));

}

//ahora puedo usar la notación con 2 índices

Matrices dinámicas

Matrices dinámicas

• Para liberar la memoria asignada dinámicamente lo realizo en orden inverso! al que utilicé para reservar memoria,

• 1º libero el espacio reservado para las columnas de cada fila y luego el reservado para el array de las filas:

for(i=0;i<rows;i++)

free(matrix[i]);

free(matrix);