Informática II - UNR...de las variables del arreglo a. El significado de “agregar 1 a un...
Transcript of Informática II - UNR...de las variables del arreglo a. El significado de “agregar 1 a un...
Informática II
Ingeniería Electrónica 2011
Punteros y arreglos
Un puntero es una variable que contiene la dirección de otra
Se usan mucho en C en parte porque: A veces constituyen la única forma de
expresar una operación En parte, debido a que, por lo general
generan un código más compacto y eficiente de lo que puede obtenerse en otras formas
Punteros y direcciones
Una computadora típica tiene un arreglo de celdas de memoria numeradas o direccionadas consecutivamente, que pueden manipularse individualmente o en grupos contiguos.
La declaración int *p; Indica que p es un puntero que apunta a un
entero. El valor de p es una dirección de comienzo de una
celda de memoria, el contenido almacenado en dicha dirección es de tipo int
Punteros y direcciones
int *p; puede leerse de dos formas: *p es un entero (* es el operador de
indirección o desreferencia), *p es una expresión
p es un int * (p es una variable) Ambas interpretaciones son
equivalentes
Punteros y direcciones
La sentencia p=&x; Indica que p apunta a x (asigna la
dirección de x a la variable p) El operador & sólo se aplica a objetos
que están en memoria: variables y elementos de arreglos. No puede aplicarse a expresiones, constantes o variables de tipo register
Punteros y direcciones
void main(){int x=1, y=2; int z[3]={1,2,3};int *p;//p puntero a enterop=&x;//p apunta ahora a xy=*p;//y ahora vale igual que x=1*p=0;// x ahora vale 0p=&z[0];//p ahora apunta a z[0]//podría haber escrito p=z;}
Punteros y direcciones
Si p apunta al entero x, *p puede presentarse en cualquier contexto donde x pueda hacerlo, así que (por ahora *p es una forma indirecta de referirme a x)
*p=*p+10;//ídem a x=x+10; Incrementa el *p (el valor de la variable a la
que apunta p, o sea el valor de x) en 10 Los operadores unarios * y & se ligan más
estrechamente que los aritméticos Así y=*p+1;//toma lo que apunta p, le agrega 1
y //asigna el resultado a y, ídem y=x+1;
Punteros y direcciones
*p+=1;//ídem a x+=1;ídem a *p=*p+1; ++*p;// ídem ++x;ídem *p=*p+1; (*p)++;//ídem x++;, los () son necesarios, sin
ellos la expresión incrementaría p en lugar de a lo que p apunta, debido a que los operadores unarios como * y ++ se asocian de derecha a izquierda
*p++;//ídem *(p++);aritmética de punteros
Punteros y direcciones
Puesto que los punteros son variables pueden emplearse sin desreferenciamiento. Por ejemplo, si q es otro puntero a entero: q=p;
Copia el contenido de p en q, así hace que q apunte a lo que p esté apuntando
Ojo! La declaración int *p, q; declara a p como puntero a int y q como
int
Punteros y argumentos de funciones
C pasa los argumentos de funciones por valor, no existe forma directa para que la función que se invoca altere una variable de la función que la llama.
Por ejemplo una rutina de ordenamiento podría intercambiar dos elementos desordenados con una función
Punteros y argumentos de funciones
Punteros y argumentos de funciones
La función anterior sólo cambia copias de a y b! No sirve
La forma de obtener los resultados deseados es: el programa invocador pase punteros a los valores que se intercambian (o sea direcciones de memoria de variables del mismo tipo que el puntero)
Punteros y argumentos de funciones
Punteros y argumentos de funciones
Aplicaciones: poder devolver más de un valor
int edad; double altura; scanf(“%d %lf”,&edad, &altura);
#include <stdio.h>
void conversor(int*h, int*m, int*s);
void main(){
int segundos, minutos, horas,auxiliar;
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);
}
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 arreglos
En C existe una fuerte relación entre punteros y arreglos, deben discutirse simultáneamente.
Cualquier operación que pueda lograrse por indexación de un arreglo también puede realizarse con punteros.
La versión con punteros por lo general es más rápida
Punteros y arreglos
La declaración int a[10]; define un arreglo a de tamaño 10, un bloque de 10 objetos consecutivos llamados: a[0], a[1], ..., a[9]
La notación a[i] se refiere al término i-ésimo elemento del arreglo a. Si pa es un puntero a entero declarado como:
int *pa; La asignación pa=&a[0]; hace que p apunte
al elemento 0 de a, o sea pa contiene la dirección de a[0]
Punteros y arreglos
Ahora la asignación x=*pa; copia el contenido de a[0] en x
Si pa apunta a un elemento en particular de un arreglo, entonces, por definición pa+1 apunta apunta al siguiente elemento, pa+i apunta i elementos después de pa, pa-i apunta a i elementos antes
Punteros y arreglos
Así si pa apunta a a[0] entonces *(pa+1) se refiere al contenido de a[1], pa+i es la dirección de a[i] y *(pa+i) es el contenido de a[i]
Esto es cierto sin importar el tipo o tamaño de las variables del arreglo a.
El significado de “agregar 1 a un puntero” y por extensión toda la aritmética de punteros es que, pa+1 apunta al siguiente objeto y pa+i apunta al i-ésimo objeto delante de pa
Punteros y arreglos
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 arreglos
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 delante 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 arreglos
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
Punteros y arreglos
Cuando un nombre de un arreglo se pasa a una función lo que se pasa es la localidad del elemento inicial.
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
Punteros y arreglos
/*strlen: regresa la longitud de s*/int strlen(char s[]){
int i=0;while(s[i]!=‘\0’)
++i; return i;}
Punteros y arreglos
Punteros y arreglos
Puesto que s es un puntero es perfectamente legal incrementarlo; s++ no tiene efecto alguno sobre la cadena de caracteres de la función que llamó a strlen, sólo incrementa la copia privada del puntero de strlen
Llamadas como estas, funcionan: strlen(“hola mundo”);//constante de cadena strlen(array);//char array[100];puntero o array? strlen(ptr); //char *ptr;más explícita la llamada
Punteros y arreglos
Puesto que los parámetros formales en una definición de función char s[]; y char *s; son equivalentes preferimos este último porque indica más explicitamente que el parámetro es un puntero.
Cuando un nombre de arreglo se pasa a una función ésta puede interpretar a su conveniencia que se ha manejado un arreglo o un puntero y manipularlo en consecuencia. Puede incluso manejar ambas notaciones si ello lo hace apropiado y claro
Punteros y arreglos
Es posible pasar parte de un arreglo a una función, pasando un puntero al inicio del subarreglo. Por ejemplo, si a es un arreglo f(&a[2]) y f(a+2) ambas pasan a f la dirección del subarreglo que se inicia en a[2].
Dentro de f la declaración de parámetros puede ser f(int arr[]){...} o f(int *arr){...}
En lo que concierne a f el hecho de que el parámetro se refiera a parte de un arreglo más grande no es de consecuencia
Punteros y arreglos
Si está seguro que los elementos existen, también es posible indexar hacia atrás en un arreglo, p[-1], p[-2],etc., son legítimos desde el punto de vista sintáctico y se refieren a elementos que preceden inmediatamente a p[0].
Por supuesto es ilegal hacer referencia a objetos que no estén dentro de los límites del arreglo
4 versiones equivalentes
#include <stdio.h> int main(){
int vector[10]={1,2,3,4,5,6,7,8,9,10};int i,suma=0;double promedio;for(i=0;i<10;i++)
suma+=vector[i];promedio=suma/(double)10;printf("El promedio es: %lf\n",promedio);
return 0;}
4 versiones equivalentes
#include <stdio.h> int main(){
int vector[10]={1,2,3,4,5,6,7,8,9,10};int i,suma=0;double promedio;for(i=0;i<10;i++)
suma+=*(vector+i);promedio=suma/(double)10;printf("El promedio es: %lf\n",promedio);
return 0;}
4 versiones equivalentes
#include <stdio.h> int main(){
int vector[10]={1,2,3,4,5,6,7,8,9,10};int i,*p,suma=0;p=vector;double promedio;for(i=0;i<10;i++)
suma+=*(p+i);promedio=suma/(double)10;printf("El promedio es: %lf\n",promedio);
return 0;}
4 versiones equivalentes
#include <stdio.h> int main(){
int vector[10]={1,2,3,4,5,6,7,8,9,10};int i,*p,suma=0;p=vector;double promedio;for(i=0;i<10;i++)
suma+=p[i];promedio=suma/(double)10;printf("El promedio es: %lf\n",promedio);
return 0;}
Aritmética de direcciones
Si p es un puntero a algún elemento de un arreglo entonces p++ incrementa p para apuntar al siguiente elemento y p+=i la incrementa para apuntar a i elementos delante de donde actualmente lo hace. Esas y otras construcciones semejantes son las formas más simples de aritméticas de punteros o de direcciones
Aritmética de direcciones
El lenguaje C es consistente y regular en su enfoque a la aritmética de direcciones; su integración de punteros, arreglos y aritmética de direcciones es uno de los aspectos que le dan fuerza
La aritmética de punteros es consistente, si p es un puntero a int (float), entonces p++ avanza al siguiente int (float)
Todas las manipulaciones de punteros automáticamente tiene en cuenta el tamaño (sizeof) de los objetos a los que apuntan
Aritmética de direcciones
Las operaciones válidas de punteros son asignación de punteros del mismo tipo, suma y resta de un puntero con un entero, resta o comparación de dos punteros a miembros del mismo arreglo y asignación o comparación con 0.
Toda otra aritmética de punteros es ilegal. No es legal sumar 2 punteros, multiplicarlos o dividirlos, enmascararlos o agregarles un float o un double o asignar un puntero de un tipo a otro tipo sin un cast
Aritmética de direcciones
Aritmética de direcciones
Aritmética de direcciones
Aritmética de direcciones
Aritmética de direcciones
En general un puntero puede inicializarse tal como cualquier otra variable aunque, normalmente los únicos valores significativos son 0 o una expresión que involucre la dirección de un dato previamente definido y de un tipo apropiado
Aritmética de direcciones
El lenguaje C garantiza que cero nunca es una dirección válida para datos
La constante simbólica NULL se emplea con frecuencia en lugar de 0 como un mnemónico para indicar más claramente que es un valor especial para un puntero; está definida en stdio.h
Aritmética de direcciones
Los punteros pueden compararse bajo ciertas circunstancias. Si p y q apuntan a miembros de un mismo arreglo, entonces relaciones como ==, !=, <,<=,etc, funcionan correctamente
p<q es verdadero si p apunta a un elemento que está antes en el arreglo de lo que está al que apunta q. Cualquier puntero puede compararse por su igualdad o desigualdad con 0. Pero está indefinido el comportamiento para la aritmética o comparaciones con punteros que no apuntan a miembros del mismo arreglo
Aritmética de direcciones
Como ya se vio un puntero y un entero pueden sumarse o restarse. La construcción p+n significa la dirección del n-ésimo objeto adelante del que apunta actualmente p. Esto es verdadero sin importar la clase de objeto al que apunta p.
n es escalada de acuerdo con el tamaño de los objetos a los que apunta p, lo cual está determinado por la declaración de p. Por ej. Si p es puntero a int que ocupa 4 bytes, la escala para el int será de 4
Aritmética de direcciones
La resta de punteros también es válida: si p y q apuntan a elementos del mismo arreglo y p<q entonces q-p+1 es el número de elementos desde p hasta q inclusive.
En strlen p-s da el número de caracteres que se avanza desde el comienzo de la cadena, es decir, su longitud
Punteros, arreglos y funciones
Punteros a caracteres y funciones
Una constante de cadena escrita como “Soy una cadena” es un arreglo de caracteres. En la representación interna el arreglo termina con el carácter nulo ‘/0’ de tal manera que los programas puedan encontrar el fin.
La longitud de almacenamiento es así uno más que el número de caracteres entre comillas
En printf(“hola mundo\n”); el acceso a la cadena de caracteres constante que se le pasa a printf el acceso a ella es a través de un puntero a char que apunta al inicio de la misma
Punteros a caracteres y funciones
El lenguaje C no proporciona ningún operador para procesar como una unidad una cadena de caracteres
Por ejemplo: char mensaje[]=“hola” //como array char *pmensaje=“hola”; //puntero mensaje es un arreglo suficientemente grande
como para contener el texto y el ‘\0’ que lo finaliza. Se pueden modificar caracteres individuales dentro del arreglo pero mensaje siempre se refiere a la misma localidad de almacenamiento
Punteros a caracteres y funciones
Por otro lado, pmensaje es un puntero inicializado para apuntar a una cadena constante, el puntero puede modificarse posteriormente para que apunte a otro lado, pero el resultado es indefinido si trata de modificar el contenido de la cadena
strcpy
strcpy
strcpy
Aquí hay punteros (los argumentos reales) convenientemente inicializados que se desplazan a lo largo del arreglo un carácter a la vez, hasta que el ‘\0’ (incluído) con que termina t se ha copiado a s
En la práctica strcpy no se escribiría así, los programadores expertos en C preferirían:
strcpy
strcpy
Esto traslada el incremento de s y t hacia adentro de la parte de prueba del lazo. El valor de *t++ es el carácter al que apunta t antes de incrementarse, el ++ postfijo no modifica t sino hasta después de que se ha tomado el carácter. En la misma forma, el carácter se almacena en la posición anterior de s antes de que s se incremente. También este carácter es el valor contra el cual se compara ‘\0’ para control del ciclo El efecto real es que los caracteres se copian de t a s, hasta el ‘\0’ inclusive
strcpy
strcpy
Finalmente como la comparación contra ‘\0’ es redundante se la modifica, puesto que la pregunta del lazo es simplemente si la expresión es 0
Debe dominarse este estilo puesto que es la que encontrará frecuentemente en programas escritos en C!!
strcmp
strcmp
Arreglos de punteros
Puesto que en sí mismo los punteros son variables pueden almacenarse en arreglos tal como otras variables
El uso más común es el de formar arreglos de cadenas de caracteres
Cada entrada en el arreglo es un apuntador al primer carácter de la cadena
Arreglos de punteros
Sea la declaración: 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 apuntadores
Arreglos de punteros
Aunque el arreglo es de tamaño fijo, permite el acceso a cadenas de caracteres de cualquier longitud
Los arreglos de apuntadores son una representación de datos que manejan de una forma eficiente y conveniente líneas de texto de longitud variable
Ordenar líneas de un texto
swap
/*swap, intercambia v[i] y v[j]*/void swap(char *v[], int i, int j){char *temp;//puntero a chartemp=v[i];//copia de punterosv[i]=v[j];v[j]=temp;}
2º ejemplo#include <stdio.h>#include <string.h> void main(void){ /* declaracion de variables*/ int i; int respuesta = 0; char ciudad[31]; char *lista[5] = {“Acapulco”, “Chilpancingo”, “Iguala”, “Zihuatanejo”, “Taxco”};//puedo no poner la dimensión 5 //lista[1]almacena un puntero a la 2º cadena de lista (almacena la dirección de //memoria de esa cadena) printf(“\nEscriba una de las cinco ciudades mas“); printf(“\nimportantes del Estado de Guerrero:\n“); gets(ciudad); for (i=0; i<5; i++)
if (!strcmp(lista[i], ciudad))respuesta = 1;
if (respuesta)printf(“\nCorrecto, felicidades\n”);
elseprintf(“\nLo siento, es incorrecto.\n”);
}
3º ejemplo
#include <stdio.h>#include <string.h>int diadelasemana(int m,int d, int y);int main(){
struct fecha{int dia;int mes;int año;char diasem[10];
};
3º ejemplo
struct fecha actual;char* meses[]={"enero","febrero","marzo","abril","mayo","junio","julio","agosto","setiembre","octubre","noviembre","diciembre"};char *diasemana[]={"domingo","lunes","martes","miercoles","jueves","viernes","sabado","domingo"};printf("ingrese en formato entero una fecha como:mm/dd/yyyy: ");scanf("%d/%d/%d",&actual.mes,&actual.dia,&actual.año);int d=diadelasemana(actual.mes,actual.dia,actual.año);strcpy(actual.diasem,diasemana[d]);printf("\nLa fecha completa es: %s %d de %s de %d\n",actual.diasem,actual.dia,meses[actual.mes-1],actual.año);
return 0;}
3º ejemplo
int diadelasemana(int m, int d, int y){ //sirve para año mayor a 1582//retorna un valor entre 0 y 6
int a=(14-m)/12;y-=a;m+=(12*a)-2;return (d+y+(y/4)-(y/100)+(y/400)+(31*m/12))%7;
}
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
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
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)
Gestión dinámica de memoria
Todos los elementos que forman parte de un programa en C se sitúan en memoria en zonas diferentes:
El segmento de código es un trozo fijo de memoria donde se ubica el código compilado del programa
Los objetos globales y los declarados static, se colocan en el segmento de datos y la gestión de su espacio de memoria se realiza en la fase de compilación-enlazado. Ellos ocupan esa zona de memoria mientras dura la ejecución del programa
Gestión dinámica de memoria
La asignación de memoria de los objetos locales, los ubicados en la pila (stack) o en los registros, se realiza en la fase de ejecución. Su ubicación es temporal, es decir, mientras permanece activo el bloque o función que incluye al objeto. Recuerde que los objetos locales tienen almacenamiento automático (auto) o register, explícita o implícitamente.
Gestión dinámica de memoria
El montón (heap) es un trozo de memoria extra donde se ubican los llamados objetos dinámicos. Un objeto dinámico se caracteriza porque su asignación de memoria se realiza en tiempo de ejecución, lo cual supone la creación dinámica del objeto.
Gestión dinámica de memoria
Mientras que la gestión de memoria estática en la pila o en los registros la realiza el propio sistema, la creación de objetos dinámicos es responsabilidad del programador, debiendo preocuparse además de su destrucción.
Destruir el objeto supone devolver la memoria usada para su almacenamiento cuando ya no se necesite
Gestión dinámica de memoria
Para asignar memoria dinámicamente se usan las funciones que trabajan con el puntero genérico void * y que están en el archivo de cabecera stdlib.h
void *calloc(size_t nobj, size_t size), calloc obtiene (reserva) espacio en memoria para alojar un vector de nobj objetos, c/u de tamaño size. Si no hay memoria disponible se devuelve NULL. El espacio reservado se inicializa a bytes de 0s. Obsérvese que calloc devuelve un (void *) y que para asignar la memoria que devuelve a un tipo Tipo_t hay que realizar el cast: (Tipo_T *)
Gestión dinámica de memoria
Ejemplo: char * c;c = (char *) calloc (40, sizeof(char)); void *malloc(size_t size), malloc funciona
de forma similar a calloc salvo que: no inicializa el espacio y es necesario saber el tamaño exacto de las posiciones de memoria solicitadas.
Gestión dinámica de memoria
El ejemplo anterior se puede reescribir:char * c;c = (char *) malloc (40*sizeof(char));
void *realloc(void *p, size_t size), realloc cambia el tamaño del objeto al que apunta p y lo hace de tamaño size. El contenido de la memoria no cambiará en las posiciones ya ocupadas. Si el nuevo tamaño es > que el antiguo, no se inicializan a ningún valor las nuevas posiciones. En el caso en que no hubiese suficiente memoria para “realojar” al nuevo puntero, se devuelve NULL y p no varía. El puntero que se pasa como argumento ha de ser NULL o bien un puntero devuelto por malloc(), calloc() o realloc().
Gestión dinámica de memoria
void free(void *p) free() libera el espacio de memoria al
que apunta p. Si p es NULL no hace nada. Además p tiene que haber sido “alojado” previamente mediante malloc(), calloc() o realloc().
Gestión dinámica de memoria
El siguiente código construye un vector de enteros del tamaño que indica el usuario por teclado, rellena el vector con los datos que introduce el usuario, amplia el tamaño del vector y sigue rellenando, y finalmente, libera el espacio de memoria ocupado por el vector. (De:studies.ac.upc.edu/EPSC/TCP/documentos/MemoriaDinamica.doc)
Gestión dinámica de memoria#include <stdio.h>#include <stdlib.h>void main (){ // declaramos un puntero a un entero
int *vector;int num_elem=0;int i, mas;
printf ("Escribe el numero de elementos del vector\n");scanf ("%d",&num_elem);
// Ahora se el tamaño inicial del vector. Reservo espacio de memoria vector = (int *) malloc (num_elem*sizeof (int));if (vector == NULL)
printf ("Operacion incorrecta");
Gestión dinámica de memoria
else{
// cargo el vector con los datos leidos del teclado
for (i=0; i<num_elem; i++){
printf ("Escribe siguiente elemento:\n ");
scanf ("%d",&vector[i]);
};
// Añado más elementos
printf ("¿Cuantos elementos quieres añadir?\n");
scanf ("%d",&mas);
// reajusto el tamaño del vector
vector = (int *) realloc (vector, (num_elem+mas)*sizeof (int));
Gestión dinámica de memoria
if (vector == NULL)
printf ("Operacion incorrecta");
else{
for (i=num_elem; i<num_elem+mas; i++){
printf ("Escribe siguiente elemento:\n ");
scanf ("%d",&vector[i]);
}
num_elem = num_elem + mas;
}}
// ya no necesito el vector y libero el espacio de memoria
free (vector);}
Arrays y asignación dinámica
A veces reservar memoria estática para arreglos (declarados suficientemente grandes aún a sabiendas de que no va a usarse toda la memoria reservada).
La gestión dinámica de memoria (arrays dinámicos) permite asignar memoria en tiempo de ejecución a medida que se vaya necesitando
Arrays y gestión dinámica de memoria
Para usar arreglos dinámicos: Se declara un puntero en vez de un arreglo
(inicialmente apunta a cualquier lado) Se pide la memoria necesaria para el arreglo
(calcularla) a partir del puntero declarado anteriormente (ahora apunta al comienzo de este trozo)
Debe asegurarse que efectivamente se dispone de ella
En caso afirmativo se procesa Se libera la memoria cuando no se necesite
En el ejemplo
La función malloc nos da la dirección de memoria donde comienza el espacio que ha reservado, y asignamos esa dirección al puntero vector. Fijate que la palabra malloc está precedida por (int *) para indicar simplemente que la función malloc retornará un apuntador a un entero.
En el ejemplo...
Si la función malloc ha tenido problemas para buscar el espacio de memoria (por ejemplo, no ha encontrado espacio suficiente) entonces malloc retorna el valor NULL. Debemos, por tanto, asegurarnos de que la operación de reserva de memoria ha funcionado bien antes de empezar a usar el vector.
Si la operación de reserva de memoria ha funcionado bien, entonces podemos ya usar el vector exactamente igual que si fuese un vector estático, con la diferencia de que ahora el vector ocupa exactamente el espacio que ha elegido el usuario una vez iniciada la ejecución del programa.
En el ejemplo...
int *vector;
0 n-1
vector ?
vector= (int *)malloc(n*sizeof (int));
vector
vector[3]=5;
0
vector n-1 3
5
vector[3]=5;
0
vector MAX-1 3
5
int vector[MAX]; 0
vector MAX-1
En el ejemplo
La figura compara el funcionamiento de los vectores estáticos y dinámicos. A la izquierda se muestra cómo al declarar un vector estático el computador automáticamente reserva espacio en memoria según el tamaño establecido en la declaración, y crea la variable que apunta a ese espacio, para poder acceder después durante le programa. A la derecha se muestra como, en el caso de los vectores dinámicos, primero se crea la variable que apuntará al vector, después se reserva el espacio justo que se necesite, se hace que la variable apunte al espacio y se accede al vector con normalidad.
En el ejemplo...
En la segunda parte del programa vemos qué hay que hacer en el caso de que necesitemos ampliar el tamaño del vector. Lo que haremos es usar la función realloc, a la que pasamos como parámetro el apuntador al inicio del vector cuyo tamaño queremos cambiar, y el nuevo tamaño que queremos que tenga ese vector. El computador buscará espacio para el nuevo vector y nos devolverá la dirección del sitio donde estará el vector. Como es lógico, las num_elem primeras posiciones del vector ampliado contendrán la misma información que había en el vector antes de la ampliación.
En el ejemplo...
Finalmente, cuando ya he terminado de trabajar con el vector, podemos llamar a la función free para liberar el espacio de memoria ocupado por el vector, de forma que ese espacio pueda ser usado para otras operaciones.
En algunas ocasiones, en lugar de hacer crecer el vector querrás reducir su tamaño. El mecanismo es el mismo: llamarás a la función realloc indicando el nuevo tamaño del vector, que será menor que el que tenía antes.
Matrices dinámicas
En algunos casos trabajaremos con matrices, y también querremos que se ajusten al tamaño estrictamente necesario. Veamos ahora cómo trabajar con matrices dinámicas (es un poco más complicado que el caso de los vectores)
Matrices dinámicas
matriz
Fila 0
Fila N-1
matriz[1,M-1]
Matrices dinámicas
La figura muestra la estructura que tiene una matriz dinámica de N filas y M columnas. La variable matriz es un apuntador a un vector de N posiciones. Cada posición contiene un apuntador a una de las filas de la matriz. El apuntador que hay en primera posición de ese vector apunta al espacio de memoria donde están los M elementos de la fila 0 de la matriz.
Ejemplo#include <stdio.h>#include <stdlib.h>void main (){
int **matriz;int N,M;int i,j;printf ("Escribe el numero de filas y el número de columnas\n");scanf ("%d %d",&N, &M);
// Ahora se el tamaño inicial de la matriz. // Reservo espacio para el vector de apuntadores a las //filas
matriz=(int **) malloc (N*sizeof (int *));if (matriz== NULL)printf ("Operacion incorrecta");else{
Ejemplo
// ahora reservo espacio para cada una de las filasfor (i=0; i<N; i++){
matriz[i]=(int *) malloc (M*sizeof (int));if (matriz[i]== NULL)
printf ("Operacion incorrecta");} // ahora le pido al usuario los datos de la matriz
for (i=0; i<N; i++)for (j=0; j<M; j++){
printf ("Elemento %d,%d: ",i,j);scanf ("%d",&matriz[i][j]);}
// cuando termino de trabajar con el vector, //libero el espacio de memoria
free (matriz);}}
Matrices dinámicas
Observa 1º la declaración de la variable matriz: un apuntador a un vector que contiene apuntadores a enteros. Por eso se escribe con dos * (apuntador a un apuntador).
El programa pide al usuario el tamaño de la matriz (N filas y M columnas), y reserva espacio para un vector de N apuntadores a enteros. El tamaño de cada uno de los elementos de ese vector de apuntadores es sizeof (int *) (el tamaño de un apuntador a entero).
Matrices dinámicas
Ahora lo que tiene que hacer es reservar el espacio necesario para cada una de las filas de la matriz. Para ello se ejecuta un bucle que hace tantas iteraciones como filas. En cada iteración reserva espacio para una fila (un vector de M números enteros), y pone el apuntador obtenido en la posición correspondiente del vector de apuntadores a filas (matriz[i])
Matrices dinámicas
Después de todo esto ya tenemos construida la matriz dinámica y podemos acceder a ella comos si fuese estática. Fíjate por ejemplo cómo accedemos a ella para poner los datos leídos del teclado