PLE:Unidad5

=Punteros= La memoria está constituída por celdillas, cada una de las cuales tiene asignada una dirección única. Los punteros constituyen un tipo especial de datos que almacenan direcciones de memoria. Podemos emplear punteros para acceder al contenido de una determinada posición de memoria. Para declarar un puntero debemos especificar el tipo de dato hacia el que apunta además de utilizar el operador de indirección '*' delante de la variable de tipo puntero.

Ejemplo:

int *p1, n=1; char *p2, c='a'; ... p1=&n; p2=&c;

Veamos el contenido de meroria del fragmente de código anterior:

=El operador dirección '&'= El operador dirección (&) devuelve la dirección de memoria de una variable. Este operador puede ser utilizado para asignar una direccióna un puntero, permitiendo posteriormente el acceso al contenido apuntado por dicho puntero.

&p1 == 0012FDE0 &n == 0012FDE4 &p2 == 0012FDE6 &c == 0012FDEA

Mediante este operador asignamos valor a un puntero.

p1 = &n; p2 = &c;

Debemos tener en cuenta que puede ser necesaria una conversión de tipo, por que no se corresponda el tipo apuntado por el declarado para dicho puntero.

int *p1; char c; p1 = &c; // ERROR p1 = (int *) &c;

=El operador indirección '*'= El operador indirección (*) devuelve el contenido un puntero. Esto nos va a permitir acceder al contenido apuntado por dicho puntero.

Ejemplo:
 * p1 == 1
 * p2 == 'a'

Mediante este operador asignamos valor a la variable apuntada por el puntero.


 * p1 = 5;
 * p2 = 'b';

=Aritmética de punteros= Podemos utilizar los operadores de incremento y decremento para acceder a direcciones contiguas de meoria. Esto nos permite hacer recorridos secuenciales de una determinado trozo de memoria:

Se puede utilizar: ++, --, +, -

int *p, n1=8,n2=9,n3=10; p = &n1;

p++;            // p = 0012FDE6 p + = 1;        // p = 0012FDE8 p = p – 1;        // p = 0012FDE6 cout << *p        // Muestra 9 cout << *(p--) // Muestra 8

=Arrays y punteros= Los arrays son punteros constantes, lo que significa que una vez declarados, no pueden cambiar la dirección de memoria que le asigna el compilador:

int num[10]; // num es un puntero a num[0] int *p; ... p = &num[0]; // p = num;

Podemos aplicar aritmética de punteros a los arrays:

num == &num[0]; num + 1 equivale a &num[1]; num + 2 equivale a &num[2];

Podemos usar índices en variables de tipo puntero:

p = &num[0]; p[0] equivale a *(p); p[1] equivale a *(p + 1); p[2] equivale a *(p + 2); Tambien podemos acceder a los elementos de un array mediante punteros:


 * p equivale a vect[0], a *vect y a p[0]
 * (p+1) equivale a vect[1], a *(vect+1) y a p[1]
 * (p+2) equivale a vect[2], a *(vect+2) y a p[2]

Veamos varios ejemplo de acceso a los elementos de un array.

Acceso a los elementos del array mediante indice: //suma los N elementos del array a int a[N], suma, i, *p; for(i=0, suma=0; i<N; ++i) // forma 1 suma += a[i];

Acceso a los elementos del array mediante aritmética de punteros: int a[N], suma, i, *p; for(i=0, suma=0; i<N; ++i) // forma 2 suma += *(a+i);

Acceso mediante indice usando un puntero int a[N], suma, i, *p; for(p=a, i=0, suma=0; i<N; ++i) // forma 3 suma += p[i];

Acceso mediante aritmética de punteros usando un puntero int a[N], suma, i, *p; for(p=a, suma=0; p<&a[N]; ++p) // forma 4 suma += *p;

=Punteros genéricos= Podemos especificar punteros genéricos indicando que son de tipo void. Los punteros genéricos pueden apuntar a cualquier tipo de dato

Ejemplo

void *ptr; int n=1; char cadena="Hola"; ptr = cadena; ptr = &n;

=Puntero Nulo= El Puntero nulo "NULL" es utilizado para asignar un valor que apunta a ninguna parte. Puede ser utilizado para inicializar punteros.

void *ptr = NULL; Podemos crear punteros que apunten a otro puntero

int x, *p, **q; x = 2; p = &x; q = &p;

cout << **q;

De igual manera podemos crear un array de punteros

int *x[2], n1=1, n2=2; x[1] = &n1; x[2] = &n2; cout << *x[1]; cout << *x[2];

=Estructuras dinámicas= Hasta la fecha hemos trabajado con estructuras estáticas. Estas se denominan así porque no cambian de tamaño a lo largo de la ejecución de un programa. Por contra las estructuras dinámicas si cambian de tamaño, lo cual les permite adaptarse a las necesidades de almacenamiento del programa en cada momento. Para crear estructuras dinámicas necesitamos usar mecanismos de asignación dinámica, además para acceder a las estructuras dinámicas necesitamos punteros.

Tipos de estructuras dinámicas:

=Asignación de memoria dinámica operador new y new[]= Para asignar memoria námica empleamos el operador new, que nos devuelve la dirección de la zona de memoria asignada.
 * Listas
 * Pilas
 * Colas
 * Árboles.

void main {   char *c; if ((c = (char *) malloc(80)) == NULL) {       cout << "Fallo en la asignación de memoria."; exit(1); }   cout << "Introduca una cadena: "; cin >> c;   cout << c;    free(c); }
 * 1) include 
 * 2) include 

=Liberación de memoria dinámica. delete y delete[]=

Desasigna la memoria asignada previamente. Debemos asegurarnos de liberar la memoria cuando ya no sea necesaria.

struct fecha {       int dia, mes,anyo; }; void main {   fecha *f; f = (fecha *) malloc(sizeof(fecha)); cout << "Día: "; cin >> f->dia; cout << "Mes: "; cin >> f->mes; cout << "Año: "; cin >> f->anyo; cout << f->dia << "/" << f->mes << "/" << f->anyo; free(f); }

=Listas enlazadas= Está formada por un conjunto de nodos enlazados en la que cada nodo contiene información del nodo y una referencia al siguiente elemento de la lista

_______     _______       _______        _______  |  Raiz |>|  Inf  |     |  Inf  |     |  Inf  | |_______|    |_______|     |_______|     |_______|                |  Sig  |     |  Sig  |     |  Sig  | |_______|>|_______|>|_______|> NULL

Una lista enlazada contiene un número variable de nodos. Cuando se insertan o eliminan nuevos elementos se deben actualizar los enlaces Los nodos se definen como estructuras:

struct nodo { struct nombre_estructura información; struct nodo *siguiente; };

Nodo de una lista doblemente enlazada:

struct nodo { struct nombre_estructura información; struct nodo *anterior; struct nodo *siguiente; };

Los nodos se crean asignándoles memoria dinámica

nodo *n; n = (nodo *) malloc(sizeof(nodo));

=Inserción de elementos=

1º. Asignamos memoria al nuevo nodo:

nuevo=(nodo *)malloc(sizeof(nodo));

_______      _______  | Nuevo |>|  Inf  | |_______|    |_______|                |  Sig  | |_______|

2º. Buscamos la posición en la que insertaremos el nodo:


 * Esta puede ser al principio, al final o en una posición intermedia. Vamos a suponer que se inserta al principio

_______      _______       _______       _______  |  Raiz |>|  Inf  |     |  Inf  |     |  Inf  | |_______|    |_______|     |_______|     |_______|   _______      |  Sig  |     |  Sig  |     |  Sig  | |NodoSig|>|_______|>|_______|>|_______|> NULL |_______|

3º. Enlazamos el nuevo nodo con el siguiente:

nuevo->siguiente = nodo_sig;

_______      _______       _______       _______      |  Raiz |>|  Inf  |     |  Inf  |     |  Inf  | |_______|    |_______|     |_______|     |_______|       _______      |  Sig  |     |  Sig  |     |  Sig  | |NodoSig|>|_______|>|_______|>|_______|> NULL |_______|         ^                         | _______       _______   |              |  Sig  |  | |_______|__|
 * Nuevo |>| Inf  |  |
 * _______|    |_______|  |

4º. Enlazamos el nodo anterior con el nuevo:

raiz = nuevo;

_______      _______       _______       _______       _______  |  Raiz |>|  Inf  |     |  Inf  |     |  Inf  |     |  Inf  | |_______|    |_______|     |_______|     |_______|     |_______|  |  Sig  |     |  Sig  |     |  Sig  |     |  Sig  | |_______|>|_______|>|_______|>|_______|> NULL

Ejemplo: Lista enlazada. Elementos insertados al inicio de la lista

struct nodo{ int n; nodo *ps; }; void main {   int num; nodo *raiz=NULL,*nuevo=NULL; cout << "\nIntroduce un numero a insertar:"; cin >> num; while(num != 0) {       // insertamos el nodo al principio nuevo=(struct nodo *)malloc(sizeof(nodo)); nuevo->n = num; nuevo->ps = raiz; raiz = nuevo; // Leemos el siguiente número cout << "\nIntroduce un numero a insertar:"; cin >> num; }   // imprimir lista nuevo=raiz; while(nuevo!=NULL) {       cout << nuevo->n << " "; nuevo = nuevo->ps; } }

Ej: Modificamos el código anterior para insertar al final de la lista.

while(num != 0) {       // insertamos el nodo al principio nuevo=(struct nodo *)malloc(sizeof(nodo)); nuevo->n = num; // Buscamos el último nodo if(raiz == NULL){ nuevo->ps = NULL; raiz = nuevo; }else { ultimo = raiz; while(ultimo->ps != NULL){ ultimo = ultimo->ps; }              nuevo->ps = ultimo->ps; ultimo->ps = nuevo; }   // Leemos el siguiente número cout << "\nIntroduce un numero a insertar:"; cin >> num; }

Ej: Modificamos el código anterior para insertar elementos en orden.

while(num != 0) {   // insertamos el nodo al principio nuevo=(struct nodo *)malloc(sizeof(nodo)); nuevo->n = num; // Buscamos el nodo anterior if(raiz==NULL || raiz->n >= num){ nuevo->ps = raiz; raiz = nuevo; }else { anterior = raiz; while(anterior->ps != NULL && anterior->ps->n < num){ anterior = anterior->ps; }              nuevo->ps = anterior->ps; anterior->ps = nuevo; }   // Leemos el siguiente número cout << "\nIntroduce un numero a insertar:"; cin >> num; }

Ejemplo: Función que inserta un nodo en orden:

void inserta_en_lista(nodo **raiz, int n) { nodo *nuevo, *ant=*raiz; // Primero creamos el nodo nuevo nuevo=(struct nodo *)malloc(sizeof(NODO_L)); nuevo->numero=n; if(*raiz==NULL || ant->numero >= n) //Insertamos al principio {       nuevo->ps = *raiz; *raiz = nuevo; }   else //Buscamos posicion {       while(ant->ps!=NULL && ant->ps->numerops; }       nuevo->ps = ant->ps; ant->ps = nuevo; } }

=Borrado de elementos en una lista enlazada=

1º. Buscamos la posición del nodo a eliminar: Esta puede ser al principio, al final o en una posición intermedia. Vamos a suponer que se elimina el nodo que se encuentra al principio de la lista

nodo = raiz;

_______      _______       _______       _______  |  Raiz |>|  Inf  |     |  Inf  |     |  Inf  | |_______|    |_______|     |_______|     |_______|   _______      |  Sig  |     |  Sig  |     |  Sig  | | Nodo |>|_______|>|_______|>|_______|> NULL |_______|

2º. Enlazamos el nodo anterior con el siguiente:

raiz = nodo->siguiente;

______________           |              |   _______  |    _______   |   _______       _______  |  Raiz |_|   |  Inf  |  |->|  Inf  |     |  Inf  | |_______|    |_______|     |_______|     |_______|   _______      |  Sig  |     |  Sig  |     |  Sig  | | Nodo |>|_______|>|_______|>|_______|> NULL |_______|

3º. Liberamos memoria del nodo a borrar: free(nodo);

_______                    _______       _______  |  Raiz |-->|  Inf  |     |  Inf  | |_______|                  |_______|     |_______|                              |  Sig  |     |  Sig  | |_______|>|_______|> NULL

=Ejemplo: Función que elimina un nodo de una lista ordenada de números=

void borra_en_lista(nodo **raiz, int n) { nodo *pos=*raiz; nodo *ant=pos; if(*raiz!=NULL) {       while(pos!=NULL && pos->numerops; }       if(pos!=NULL && pos->numero==n) // lo hemos encontrado {           if(pos!=*raiz) ant->ps=pos->ps; else *raiz=pos->ps; free(pos); }   } }

=Ejemplo de recorrido de una lista:=

struct nodo{ int n; nodo *ps; };

void imprime_lista(nodo *pl) {   clrscr; while(pl!=NULL) {       printf("%d,",pl->numero); pl=pl->ps; }   printf("\nPulse una tecla para continuar"); getch; }

=Tipos de lista=


 * Lista Simplemente enlazada.

_______      _______       _______       _______  |  Raiz |>|  Inf  |     |  Inf  |     |  Inf  | |_______|    |_______|     |_______|     |_______|  |  Sig  |     |  Sig  |     |  Sig  | |_______|>|_______|>|_______|> NULL


 * Lista circular.

_______      _______       _______       _______     |  Raiz |>|  Inf  |     |  Inf  |     |  Inf  | |_______|    |_______|     |_______|     |_______|     |  Sig  |     |  Sig  |     |  Sig  | >|_______|>|_______|>|_______||
 * _____________________________________________|
 * _____________________________________________|