Universidad de Santiago

Anuncio
Tema 4: Punteros
Universidad de Santiago – Departamento de Ingeniería Informática
1
PUNTEROS Y ESTRUCTURAS DE DATOS DINÁMICAS
Dato: Porción de información manejable por el computador.
Tipos de datos: Formados por el conjunto de espacio de representación
y conjunto de operaciones asociadas.
ü Simples
ü Caracter
ü Entero
ü Real
ü Puntero
Estructurados o compuestos:
Estáticos: Su espacio en memoria es reservado en tiempo de
compilación.
Dinámicos: Su espacio en memoria es reservado en tiempo de
ejecución.
Contiguos: Se guardan en memoria de forma consecutiva (vectores,
matrices, arrays...).
Enlazados: No se guardan en memoria de forma consecutiva (listas,
árboles, grafos...).
TIPOS ESTÁTICOS
Se almacenan de forma estática en la memoria del ordenador,
destinando memoria para este tipo de variables en tiempo de
compilación. La dirección de memoria asignada se puede conocer con el
operador de dirección &.
Ventajas
Universidad de Santiago – Departamento de Ingeniería Informática
2
- Sencillos de manejar y no dan problemas en tiempo de ejecución.
Desventajas
- No pueden crecer o menguar durante la ejecución del programa (hay
que hacer estimaciones previas a la codificación).
- Las zonas de memoria asignadas a estas variables son fijas (no son
adecuadas para representar listas, árboles, etc.).
LOS PUNTEROS
Es una variable de tipo simple, que almacena como valor una
dirección de memoria, que suele estar ocupada por otro dato diferente.
El puntero, pués, apunta o referencia a otro dato.
Se declaran mediante el operador * y el tipo de dato al que
apuntará (para que el compilador sepa cómo leer los datos de las
dirección apuntadas). También es posible usar punteros sin tipo definido
(void *) indicando luego durante la asignación a qué tipo de dato
apuntan.
Pueden usarse arrays de punteros o punteros a/dentro de
estructuras. Los punteros que no apuntan a ningún sitio deben
direccionarse a NULL (0x0000 en C) y se representan mediante una
raya que los tacha.
Acceso al contenido del puntero:
Se puede conocer el contenido de la variable a la que referencia
un puntero mediante el operador de contenido *, es decir, se puede
acceder al dato apuntado por el puntero, tanto para lectura como para
escritura:
Universidad de Santiago – Departamento de Ingeniería Informática
3
Ejemplo:
int *punt;
int var;
punt = &var;
*punt = 10;
/* hacemos que punt apunte a var */
/* estamos cambiando el valor de var */
Así pues:
punt
= Valor guardado dentro del puntero punt (dirección).
&punt
= dirección de memoria donde punt guarda sus valores.
*punt
= valor almacenado en la posición de memoria cuya
dirección se guarda en punt.
USO DE PUNTEROS
A). Para referenciar a variables simples:
Apuntado a variables de tipo simple:
int *punt;
int numero
punt = №
*punt = 123;
/ *hacemos numero=123*/
B). Paso por referencia de variables simples:
Consiste en usar punteros como parámetros:
/*-- Llamar con ModificaVariable(&var, valor); --- */
void ModificaVariable( int *a, int valor )
{
*a = valor;
}
C). Para referenciar a variables compuestas:
Universidad de Santiago – Departamento de Ingeniería Informática
4
Consiste en la utilización de punteros para referenciar a variables
compuestas como vectores y registros. En este caso se puede acceder
mediante el puntero a la totalidad de campos de la estructura:
float vect[4] = { 1, 2, 3, 4};
struct pieza nueva = {234, "bobina"};
float *pf;
struct pieza *psp;
pf = vect;
printf("%f = %f", *pf, pf[0]);
psp = &nueva;
printf("%d", psp->codigo );
NOTA: El operador flecha -> sustituye a * y ()
psp->codigo = (*psp).codigo
D). Paso por referencia de variables compuestas:
Para simulación de paso por referencia de variables compuestas:
void funcion1( float v[], struct fecha *actual)
{
v[0] = v[1]+5;
actual->mes = 5;
}
void main()
{
struct fecha hoy;
float vector[4]={1,2,3,4};
funcion1(vector, &hoy );
}
Universidad de Santiago – Departamento de Ingeniería Informática
5
E). Punteros que referencian a punteros:
Los punteros pueden referenciar a otros punteros. Para ello se declara
la variable de tipo puntero a puntero (**):
int a = 7;
int *pi;
int **ppi;
enteros.
// entero, puntero a entero y puntero a puntero de
pi = &a;
// pi referencia a "a"
ppi = &pi ;
// ppi referencia a pi
printf("El valor de a es %d", **pi );
F). Paso por referencia de punteros:
void main( void )
{
int a=3, *pi;
pi = &a;
// pi = dirección de la variable "a".
funcion1( pi, &pi );
}
void funcion1( int *p_entero, int **p_puntero )
{
*p_entero = 5;
p_entero = NULL;
pero no el de "pi".
*p_puntero = NULL;
// cambia el valor de "a"
// cambia el valor de "p_entero"
// cambia el valor de "pi"
}
USO DE PUNTEROS EN LA CREACIÓN DE VARIABLES DINÁMICAS
#include <alloc.h>
Los punteros también se utilizan en la reserva dinámica de
memoria, utilizándose para la creación de estructuras cuyo tamaño se
Universidad de Santiago – Departamento de Ingeniería Informática
6
decide en tiempo de ejecución (son creadas y destruidas cuando el
programa las necesite), adecuado para programas donde no se pueda
hacer una estimación inicial eficiente de necesidades de memoria.
El lenguaje C utiliza para la reserva dinámica de memoria una
zona de espacio diferente del segmento de datos y de la pila del
programa, llamada montículo (heap), para que ésta no depende de los
movimientos de la pila del programa y se puedan reservar bloques de
cualquier tamaño (otros lenguajes usan space-lists).
a). La función malloc():
void * malloc( size );
Reserva un bloque de memoria de tamaño "size" bytes y devuelve
la dirección de comienzo de dicha zona (sin valor inicial dentro de ella).
Esta
dirección
suele
asignarse
a
una
variable
de
tipo
puntero
previamente declarada. En caso de error o de no existir tanta memoria
disponible, malloc devuelve NULL:
float *p;
p = (float *) malloc( sizeof(float));
*p = 5.0;
b). La función calloc():
void * calloc( size_t n, size_t size );
Reserva un bloque de memoria para ubicar n elementos contiguos
de "size" bytes cada uno (ej: un vector).
int *p;
Universidad de Santiago – Departamento de Ingeniería Informática
7
p = (int *) calloc( 5, sizeof(int));
p[3] = 2;
c). La función realloc():
void * realloc( void *pant, size_t size );
Cambia el tamaño de un área de memoria reservada con
anterioridad, a un tamaño de "size" bytes contiguos. Si size es mayor
que el anterior tamaño, realloc() busca una nueva zona, copia allí los
datos y destruye la zona anterior. Si size es menor, truncará el bloque
actual al nuevo tamaño.
int *p, *nuevo
p = (int *) calloc( 5, sizeof(int));
nuevo = realloc(p, 10);
d). La función free():
void free( void *punt );
Libera la memoria asignada previamente con malloc() o calloc() al
puntero punt, evitando que los bloques de memoria permanezcan
ocupados hasta la salida del programa al liberar estos bloques para su
posterior utilización. Tras el uso de free es recomendable anular el
puntero poniéndolo a NULL. Free() no puede usarse para liberar la
memoria de las variables globales o locales (están en el segmento de
datos o pila, no en el montículo).
float *p;
p = (float *) malloc( sizeof(float));
Universidad de Santiago – Departamento de Ingeniería Informática
8
// libera la zona apuntada por p
free(p);
ARITMÉTICA DE PUNTEROS
Aparte de las operaciones de asignación (=) y de comparación
(==), sobre los punteros están definidas las operaciones aritméticas (-,-,+,++), aunque de un modo diferente a como están definidas para los
otros tipos. Estas operaciones sólo pueden usarse cuando los punteros
apuntan
a
matrices
o
vectores
(
de
cualquier
tipo).
Cuando
incrementamos o decrementamos un puntero en una cantidad "n", el
compilador
da
como
resultado
la
dirección
situada
a
n*sizeof(tipo_base_del_array) bytes de la dirección original. Es decir, al
sumar 1 a un puntero, estamos accediendo al siguiente elemento del
vector, como puede verse en el siguiente ejemplo:
int *p;
int vector[4]={ 5,6,1,4};
p = &vector[0];
p++;
// p apunta a vector[1]
p=p+2;
// p apunta a vector[3]
p=p-1;
// p apunta a vector[2]
PUNTEROS Y ARRAYS
Existe una gran relación entre los punteros y los arrays estáticos, ya
que en realidad estos últimos son punteros encubiertos que apuntan al
elemento inicial del vector. Al declarar un vector, el nombre del mismo
es un puntero a vector[0]. Al apuntar a un vector podemos utilizar
sobre él las operaciones del apartado anterior:
int *p, a;
Universidad de Santiago – Departamento de Ingeniería Informática
9
int vector[4]={ 5,6,1,4};
p = vector;
p[3] = 10;
a *(p+3)=10)
// escribimos 10 en vector[3] (equivale
p++;
// p apunta a vector[1]
a = *(p+2) + *(vector+1);
p=p+2;
// a vale 1+6=7
// p apunta a vector[3]
p = vector+2;
// p apunta a vector[2]
VECTORES DINÁMICOS
Son colecciones de datos del mismo tipo almacenados en posiciones
contiguas de memoria, pero definidos mediante memoria dinámica, y
que son liberados cuando no se necesitan (mediante free()).
int *p;
p = (int *) calloc( 5, sizeof(int));
Se puede indexar este vector de igual forma que los estáticos:
a) Mediante índice:
p[0] = primer elemento = *p;
(ej: p[5] = 32 )
p[1] = segundo elemento (etc...).
b) Mediante aritmética de punteros:
*p
= primer elemento = *p;
*(p+1) = segundo elemento (etc...).
(ej: *(p+5) = 32 )
Para recorrer un vector dinámico se hace de igual forma que mediante
vectores estáticos:
int n = 10;
Universidad de Santiago – Departamento de Ingeniería Informática
10
int *p;
p = (int *) calloc(n, sizeof(int));
for(i=0; i<n; i++)
q[i] = 1;
/* o bien *(q+i)=1 */
o bien:
#define n = 10;
int *p, *q;
q = p;
for(i=0; i<n; i++)
{
*q = 1;
q++;
}
Es recomendable mantener siempre una referencia (puntero) al
inicio del vector para poder volver al principio ( y liberarlo) en cualquier
momento, así como una variable que almacene la cantidad de
elementos del vector:
MATRICES DINÁMICAS
En las matrices dinámicas, a diferencia de las estáticas, no todos
los elementos están guardados en posiciones consecutivas de memoria.
La matriz dinámica se define como un vector de punteros a vectores de
datos (ej: 5x3):
// asignación:
int *matriz[5];
for( i=0; i<5; i++ )
matriz[i] = (int *) calloc( 3, sizeof(int));
// liberación:
for( i=0; i<5; i++ )
free(matriz[i]);
Universidad de Santiago – Departamento de Ingeniería Informática
También
puede
crearse
definiendo
11
el
vector
de
punteros
dinámicamente (mejor forma):
// asignación:
int **matriz;
matriz = (int **) calloc(5,sizeof(int *));
for( i=0; i<5; i++ )
matriz[i] = (int *) calloc( 3, sizeof(int));
// liberación:
for( i=0; i<5; i++ )
free(matriz[i]);
free(matriz);
Métodos de acceso al elemento (i,j):
a). Por índices:
matriz[i][j];
b). Por aritmética:
*(*(matriz+i)+j);
c). Mezcla de ambos:
*(matriz[i]+j),
(*(matriz+i))[j];
ERRORES CON PUNTEROS
a). Asignación correcta e incorrecta de valores:
int *p, *q;
int numero, copia;
numero=15;
p = №
p = numero;
// correcto: p apunta a numero
// incorrecto, p apunta la dirección 15.
// -> Apunta a zonas incontroladas.
*p = 5;
// correcto, numero ahora vale 5
p = 5;
// incorrecto, p apunta a la dirección 5.
q = (int *) malloc(sizeof(int));
*q = *p;
// correcto, pedimos memoria para otro entero,
Universidad de Santiago – Departamento de Ingeniería Informática
12
// y almacenamos numero allí
p=q;
// correcto, p apunta a q, es decir,
// a la nueva memoria.
copia = *p;
q = p;
// Incorrecto: si perdemos la referencia a q,
// ya no podremos liberar la memoria asignada.
b). Punteros no inicializados o apuntando a zonas de memoria no
controladas:
char *letra, mi_letra;
mi_letra = *letra;
//incorrecto: mi_letra tendrá un valor desconocido
// (letra no inicializado).
*letra = 'A';
// incorrecto: letra no inicializado, estamos
// escribiendo en algun lugar de la memoria.
(etc...)
free(puntero);
puntero=NULL,
*puntero = 100;
// incorrecto, puntero estaba apuntando a NULL.
// (Punteros sueltos).
También es incorrecto, y peligroso, en los arrays y punteros que
apunta a arrays, utilizar como índice valores que se salgan del rango del
vector.
float vector[10];
vector[-2] =
etc...
// incorrecto, igual que vector[11], p+11,
Descargar