Documentos de Académico
Documentos de Profesional
Documentos de Cultura
Punteros en C
Punteros en C
www.detodoprogramacion.com
ii
www.detodoprogramacion.com
Indice general
Prlogo
o
1. Fundamentos
1.1. Qu es un puntero . . . . . . . . . . . . . . . . . . . .
e
1.2. Valor inicial de un puntero . . . . . . . . . . . . . . . .
1.3. Punteros a punteros . . . . . . . . . . . . . . . . . . . .
1.4. Aritmtica de punteros . . . . . . . . . . . . . . . . . .
e
1.5. Punteros a bloques . . . . . . . . . . . . . . . . . . . .
1.6. Punteros a estructuras y estructuras auto-referenciadas
1.7. Punteros a funciones . . . . . . . . . . . . . . . . . . .
1.8. Declaraciones complejas . . . . . . . . . . . . . . . . .
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
1
. 1
. 2
. 4
. 5
. 6
. 6
. 8
. 11
. . . . . . . . . . . . .
. . . . . . . . . . . . .
. . . . . . . . . . . . .
. . . . . . . . . . . . .
y ordenacin de listas
o
. . . . . . . . . . . . .
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
19
19
20
20
25
26
28
4. Arboles
31
4.1. Introduccin . . . . . . . . . . . . . . . . . . . . . . . . . . . . 31
o
4.2. Insercin de nodos . . . . . . . . . . . . . . . . . . . . . . . . 33
o
4.3. Encontrar y eliminar nodos . . . . . . . . . . . . . . . . . . . 36
5. Punteros en otros contextos
41
5.1. Concepto . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 41
iii
www.detodoprogramacion.com
INDICE GENERAL
iv
5.2. FAT e i-nodos . . . .
5.2.1. FAT . . . . .
5.2.2. i-nodos . . . .
5.3. Gestin de memoria .
o
5.4. Conclusin . . . . . .
o
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
www.detodoprogramacion.com
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
41
42
43
45
46
Prlogo
o
Estas notas estn dirigidas a mis alumnos de la asignatura de Perifricos,
a
e
de las titulaciones de Informtica en la EPS de la Universidad de Alicante.
a
Aunque dicha asignatura no trata ni de programacin, ni de estructuras de
o
datos ni de algor
tmica, cada vez se ha hecho ms evidente en los ultimos aos
a
n
la necesidad de estas notas con vistas a la realizacin de las prcticas de la
o
a
asignatura, que por tratar de cuestiones de bajo nivel requieren el manejo de
puertos, punteros, operaciones de bits y algunas pocas cosas ms que tienden
a
a quedar relegadas debido a la evolucin de la programacin hacia lenguajes
o
o
de ms alto nivel. Aqu he pretendido cubrir las posibles lagunas por lo que
a
www.detodoprogramacion.com
0. Prlogo
o
vi
www.detodoprogramacion.com
Cap
tulo 1
Fundamentos
1.1.
Qu es un puntero
e
www.detodoprogramacion.com
1. Fundamentos
p=&i;
el operador & devuelve la direccin de la variable i, que en nuestro caso
o
es m. De manera que la variable p va a contener el valor m. Se dice que
p ((apunta)) a i, ya que indica en qu lugar se encuentra i. A su vez, p
e
se encuentra tambin en algn lugar. Puesto que p apunta a un lugar de
e
u
memoria donde est almacenado el valor 10, el ((valor apuntado)) por p es
a
precisamente 10. Este valor apuntado se escribe como
*p
1.2.
despus de hacer
e
j=20
sabemos que la variable j contiene el valor 20, de modo que la expresin
o
j+4 vale 24. De la misma manera, cuando declaramos un puntero como
int *p;
no podemos suponer que contiene ningn valor en concreto, es decir,
u
que apunta a ninguna direccin en particular. Pues bien, existen tres formas
o
distintas de asignar un valor a un puntero:
1. Llamando al gestor de memoria, que buscar un hueco libre y dea
volver su direccin. As al escribir
a
o
,
int *p=(int *)malloc(sizeof(int));
www.detodoprogramacion.com
www.detodoprogramacion.com
1. Fundamentos
1.3.
Punteros a punteros
Los punteros apuntan a variables. Pero un puntero es una clase de variable, luego nada impide que un puntero apunte a otro. As que podemos
e
#include <stdio.h>
main()
{
int j=10;
int *p=(int *)&j;
int **q=(int **)&p;
int ***r=(int ***)&q;
printf("%d %d %d\n",*p,**q,***r);
return;
}
p es un puntero que apunta a un entero; q es un puntero que apunta a
p, y es por tanto un puntero a un puntero; r es un puntero que apunta a
q; *p, **q y ***r son tres formas distintas de referirnos al mismo valor 10
almacenado en j.
En este punto, dado un puntero p declarado como
int *p;
y apuntado ya algn lugar, por ejemplo
u
int j=10;
....
p=(int *)&j;
no ha de haber dicultad en comprender la diferencia que existe entre p,
&p y *p. p es una variable, que contiene un nmero, que es una direccin.
u
o
As que
printf("%p",p);
imprimir el contenido de la variable p de la misma forma que
a
printf("%d",j);
imprime el contenido de la variable j. Puesto que p es una variable como
otra cualquiera, &p indica su direccin. No la direccin del lugar al que apunta
o
o
p, sino la direccin de la misma p. Finalmente, *p proporciona el contenido
o
que se haya almacenado en la direccin a la que apunta p.
o
www.detodoprogramacion.com
1.4.
Aritmtica de punteros
e
www.detodoprogramacion.com
1. Fundamentos
1.5.
Punteros a bloques
1.6.
www.detodoprogramacion.com
struct libro{
char *titulo;
char *autor;
int paginas;
int precio;
};
De esta forma, es posible agrupar distintos tipos de datos (en este caso,
dos enteros y dos punteros a carcter) relacionados lgicamente entre s La
a
o
.
declaracin anterior dene un nuevo tipo de dato, de nombre libro, de tal
o
forma que ahora pueden declararse variables de ese tipo:
struct libro biblioteca[1000];
Es posible tambin declarar variables del nuevo tipo antes de cerrar la
e
declaracin, como en
o
struct libro{
char *titulo;
char *autor;
int paginas;
int precio;
}un_libro,una_biblioteca[200];
en que se declara una variable del tipo struct libro y un array de
variables del mismo. El acceso a los campos de la estructura se hace mediante
el operador .. Por ejemplo:
un_libro.paginas=372;
un_libro.precio=24;
Pero la estructura anterior contiene tambin punteros, en este caso a
e
carcter, que no requieren ningn tratamiento especial:
a
u
un_libro.titulo=(char *)malloc(50*sizeof(char));
strcpy(un_libro.titulo,"Aurelius Augustinus");
Esto abre la interesante posibilidad de que una estructura de un tipo
contenga punteros a estructuras del mismo tipo, lo que permite crear listas.
Consideremos por ejemplo:
www.detodoprogramacion.com
1. Fundamentos
struct libro{
struct libro *siguiente;
char *titulo;
char *autor;
int precio;
int paginas;
}*lista;
Ahora lista es un puntero (al que no se ha asignado ningn valor, y por
u
tanto no puede an usarse) a una estructura que contiene, entre otras cosas,
u
un puntero a otra estructura del mismo tipo. As
:
lista=(struct libro *)malloc(sizeof(struct libro));
reserva un bloque de memoria suciente para contener un libro y apunta
la variable lista a ese bloque. Para acceder a los campos de estructuras
apuntadas se usa el operador ->:
lista->precio=20;
lista->paginas=400;
lista->autor=(char *)malloc(50*sizeof(char));
Si hubisemos declarado variables tipo struct libro estticas, cuntas
e
a
a
de ellas habr
amos declarado? Si pocas, pudieran no ser sucientes. Si muchas,
pudieramos estar desperdiciando espacio. Pero al incluir en cada estructura
un puntero a otra del mismo tipo, podemos construir una lista, reservando
exactamente la cantidad de memoria que precisamos: ni ms ni menos. As
a
,
si despus de haber almacenado el primer libro es preciso almacenar otro,
e
creamos un nuevo nodo:
lista->siguiente=(struct libro *)malloc(sizeof(struct libro));
Dedicaremos ms adelante un cap
a
tulo a la creacin y manipulacin de
o
o
listas.
1.7.
Punteros a funciones
www.detodoprogramacion.com
e
n
lenguaje puede contener del orden de un centenar de cdigos distintos,
o
as que la estructura obvia:
if (codigo==1){
/* acciones */
} else
if (codigo==2){
/* acciones */
} else
if (codigo==3){
/* acciones */
} ....
puede contener unos centenares de l
neas. El cdigo no es ni elegante
o
ni prctico, pues, por trmino medio, habrn de evaluarse la mitad de
a
e
a
www.detodoprogramacion.com
10
1. Fundamentos
www.detodoprogramacion.com
11
main()
{
f[0]=suma;
f[1]=prod;
printf("%d %d\n",(*f[0])(2,3),(*f[1])(2,4));
return;
}
1.8.
Declaraciones complejas
e
1. int a[10];
2. int **b;
3. int *c[10];
4. int (*d)[10];
5. int *e[10][20];
6. int (*f)[10][20];
En el primer caso tenemos un vector de diez enteros; en el segundo, un
puntero a un puntero a entero; en el tercero, un vector de diez punteros a
entero; en el cuarto, un puntero a un vector de diez enteros; en el quinto, un
vector de diez punteros a vector de veinte enteros; en el sexto, un puntero a
un vector de 1020 enteros. La comprensin de las declaraciones anteriores
o
pasa por saber el orden en que se aplican los modicadores. Para ello se
tendrn en cuenta las tres reglas siguientes:
a
1. La prioridad de un modicador es tanto mayor cuanto ms cerca se
a
encuentre del nombre al que modica.
2. Los modicadores () y [] tienen mayor prioridad que *.
3. Los parntesis agrupan partes de la declaracin otorgndoles mayor
e
o
a
prioridad.
www.detodoprogramacion.com
12
1. Fundamentos
Por ejemplo, para interpretar el tipo de int *e[10][20] vemos que los
modicadores ms cercanos son * y [10], pero, por la segunda regla, [10]
a
tiene mayor prioridad, de manera que e es un vector de diez punteros (ya
que tras el [10] es *, por cercan quien tiene prioridad) a vectores de
a,
20 enteros. En el caso de la declaracin int (*f)[10][20] los parntesis
o
e
otorgan la mxima prioridad a *, de manera que f es un puntero, a un
a
vector de 1020 enteros. Aplicamos las mismas reglas para interpretar las
declaraciones siguientes:
1. char *g();
2. char (*h)();
3. char *i()[10];
4. char *j[10]();
En el primer caso, tenemos una funcin (los parntesis tienen mayor prio
e
oridad) que devuelve un puntero a carcter; en el segundo caso es el * quien
a
tiene mayor prioridad, al estar asociado mediante parntesis al nombre que
e
se declara, de forma que tenemos un puntero, a una funcin que devuelve
o
un carcter; en el tercer caso tenemos una funcin que devuelve un puntero
a
o
a un vector de 10 caracteres; nalmente, tenemos un vector de 10 punteros
a funcin que devuelve un carcter. Con esto es suciente, y reconozcamos
o
a
que son poco probables (y en todo caso el programador preferir evitarlas)
a
declaraciones como
char (*(*x())[])();
donde x es una funcin que devuelve un puntero a un vector de punteros
o
a funcin que devuelve char, o como
o
char (*(*z[10])())[5];
donde z es un vector de 10 punteros a funcin que devuelve puntero a
o
vector de cinco elementos de tipo char.
www.detodoprogramacion.com
Cap
tulo 2
Punteros, cadenas y matrices
2.1.
Cadenas de caracteres
www.detodoprogramacion.com
14
www.detodoprogramacion.com
15
#include <stdio.h>
#define MAX 20
char *p[MAX];
void divide(char *cadena)
{
int j;
char *r=cadena;
for(j=0;j<MAX;++j) p[j]=NULL;
j=0;
while (1){
while (*r== ) ++r;
if (*r==\0) return;
p[j]=r;
++j;
while (*r!= ){
if (*r==\0) return;
++r;
}
*r=\0;
++r;
}
}
main()
{
int j=0;
char cadena[]="defende nos in proelio";
divide(cadena);
while (p[j]!=NULL){
printf("\n%2d /%s/",j,p[j]);
++j;
}
printf("\n");
return;
}
El funcionamiento de divide() consta esencialmente de un bucle principal que se ejecuta en tanto en cuanto no se alcance el nal de la cadena.
Dentro de l, estn contenidos dos bucles secundarios. El primero salta ese
a
pacios en blanco hasta encontrar uno distinto, que ser el de comienzo de
a
una palabra. Entonces, se hace apuntar all uno de los punteros del vector de
www.detodoprogramacion.com
16
2.2.
Matrices
www.detodoprogramacion.com
17
2.2. Matrices
int v[2][3]={1,2,3,4,5,6};
int *w=&(v[0][0]);
int j;
for(j=0;j<6;++j){
printf("\n%d %d\n",v[j/3][j%3],*(w+j));
}
}
Ahora hacemos apuntar w al primer elemento de la matriz. El bucle nos
demuestra que los elementos se encuentran en memoria por las, y una la a
continuacin de la anterior. Sin embargo, ya no podemos referirnos al elemeno
to j mediante v[j] sino que, siendo v una matriz, es preciso especicar la
y columna. En el ejemplo anterior, de la posicin lineal respecto al principio
o
de la matriz, tal como se almacena en memoria, obtenemos la la y columna
correspondientes. A la inversa, dada una la y columna es posible obtener el
desplazamiento lineal:
#include <stdio.h>
main()
{
int v[2][3]={1,2,3,4,5,6};
int *w=&(v[0][0]);
int j,k;
for(j=0;j<2;++j){
for(k=0;k<3;++k){
printf("\n%d %d\n",v[j][k],*(w+3*j+k));
}
}
}
www.detodoprogramacion.com
18
www.detodoprogramacion.com
Cap
tulo 3
Listas
3.1.
Introduccin
o
Las listas son las estructuras de datos ms verstiles que existen. Una
a
a
lista puede representar, obviamente, a una lista. Pero tambin a una pila, un
e
a
rbol, un vector, una matriz. Esta versatilidad es la que inspir al lenguao
je LISP. No decimos claro est que la implementacin mediante listas de
a
o
cualquier estructura de datos sea la forma ms adecuada. Por ejemplo, repa
resentar mediante listas una matriz es ineciente. Lo que queremos es poner
de maniesto esta versatilidad.
Y las listas se construyen mediante punteros. Los punteros hacen de enlace
entre un elemento de la lista y el siguiente y/o el anterior. A cada elemento
de la lista le llamaremos ((nodo)). As que cada nodo ha de contener cierta
www.detodoprogramacion.com
20
3. Listas
3.2.
Iniciar la lista
3.3.
nuevo nodo a los nodos siguiente y anterior (que no existen todav Puede
a).
suceder, en segundo lugar, que el nodo a continuacin del cual queremos reo
alizar la insercin sea el ultimo, en cuyo caso ser x->sig==NULL. En este
o
a
caso: creamos un nuevo nodo, lo enlazamos hacia atrs con el nodo actual y
a
establecemos a NULL su enlace hacia adelante. Finalmente, puede suceder que
el nodo actual tenga uno anterior y otro siguiente. En ese caso, nos servimos
www.detodoprogramacion.com
21
a
no ser preciso localizar. El otro extremo de la lista tampoco se perder, ya
a
a
que el nodo por donde comenz a construirse la lista se identica fcilmente
o
a
por la condicin de que x->ant==NULL. De la misma forma escribimos una
o
funcin que inserta un nodo en la posicin anterior al actual:
o
o
struct nodo *L_inserta_antes(struct nodo *x)
{
struct nodo *y;
www.detodoprogramacion.com
22
3. Listas
Figura 1
if (x==NULL){
x=(struct nodo *)malloc(sizeof(struct nodo));
x->ant=NULL;
x->sig=NULL;
} else
if (x->ant==NULL){
x->ant=(struct nodo *)malloc(sizeof(struct nodo));
x->ant->sig=x;
x->ant->ant=NULL;
x=x->ant;
} else
{
y=(struct nodo *)malloc(sizeof(struct nodo));
y->ant=x->ant;
y->sig=x;
x->ant->sig=y;
x->ant=y;
x=x->ant;
}
return(x);
}
Localizar los extremos de la lista a partir de un nodo dado es trivial:
struct nodo *L_inicio(struct nodo *x)
{
while (x->ant!=NULL)
x=x->ant;
www.detodoprogramacion.com
23
return(x);
}
struct nodo *L_fin(struct nodo *x)
{
while (x->sig!=NULL)
x=x->sig;
return(x);
}
Pero es hora ya de hacer algo util. En muchas ocasiones, las listas se
n
en una lista y luego imprime cada elemento:
#include <stdio.h>
main()
{
struct nodo *z=NULL;
z=L_carga_archivo(z,"listas.c");
www.detodoprogramacion.com
24
3. Listas
while (z->sig!=NULL){
printf("%s",z->cadena);
z=z->sig;
}
printf("%s",z->cadena);
return;
}
Para eliminar un nodo de la lista hemos de decidir qu hacer con el
e
puntero que apunta al nodo actual. Por analog con el comportamiento
a
de los editores de texto, y con el ejemplo mostrado anteriormente sobre un
archivo de texto, vamos a hacer la siguiente implementacin: si el nodo que se
o
desea eliminar es el primero de la lista, se elimina y se devuelve un puntero al
nodo siguiente, que pasa a ser el primero. Si el nodo a eliminar es el ultimo, se
www.detodoprogramacion.com
25
} else
if (x->ant==NULL){
x=x->sig;
free(x->ant->cadena);
free(x->ant);
x->ant=NULL;
} else
if (x->sig==NULL){
x=x->ant;
free(x->sig->cadena);
free(x->sig);
x->sig=NULL;
}
return(x);
}
Eliminamos la lista eliminando uno a uno los nodos hasta que no quede
ninguno:
void L_elimina_lista(struct nodo *x)
{
x=L_inicio(x);
while (x!=NULL)
x=L_elimina_nodo(x);
return;
}
3.4.
Fusin de listas
o
www.detodoprogramacion.com
26
3. Listas
NULL
NULL
Figura 2
Figura 3
3.5.
www.detodoprogramacion.com
27
return;
}
Una vez que sabemos cmo intercambiar dos nodos (los datos apuntados
o
por ellos), es fcil implementar algn algoritmo de ordenacin. Como estas
a
u
o
pginas tratan de punteros, no de algor
a
tmica, implementaremos el algoritmo
obvio de ordenacin por el mtodo de la burbuja:
o
e
void L_ordena(struct nodo *x)
{
struct nodo *a,*b,*i;
a=L_inicio(x);
b=L_fin(x);
while (a!=b){
i=a->sig;
while (i!=NULL){
if (strcmp(i->cadena,a->cadena)<0)
L_intercambia(a,i);
i=i->sig;
}
a=a->sig;
}
return(L_inicio(x));
}
El algoritmo de la burbuja es el ms sencillo, y se comprende de inmediato.
a
Tambin es el ms lento, por lo cual suele ser descartado en favor de otros
e
a
ms rpidos. Ahora bien, el mundo de la alta velocidad tiene sus peligros,
a a
pues se suele olvidar que la velocidad se compra con espacio (y a la inversa,
el espacio se paga con tiempo). Cuando el nmero de elementos a ordenar
u
es pequeo (hasta unos pocos miles de elementos), la diferencia entre usar
n
quicksort y burbuja es inapreciable. El primero es ms interesante cuanto
a
mayor es el nmero de elementos a ordenar. Pero si este nmero es muy alto,
u
u
quicksort puede fallar miserablemente. En nuestro sistema, ordena en unos 20
ms un vector de 15357 elementos. Pero 15358 hacen que el mismo programa
quede detenido. Suponemos que se agot el espacio en la pila por exceso
o
de llamadas recursivas, y el sistema operativo no puede o no sabe resolver
la situacin. Pero burbuja sigue funcionando perfectamente, si bien consume
o
aproximadamente un segundo y medio en ordenar los 15358 elementos. N.
Wirth 1 ofrece una versin iterativa de quicksort. Remitimos all al lector
o
www.detodoprogramacion.com
28
3. Listas
NULL
NULL
NULL
NULL
Figura 4
3.6.
Extraccin de sub-listas
o
Hay dos formas en que puede construirse una sub-lista a partir de una
lista dada. La primera es destructiva, y consiste simplemente en eliminar los
nodos que no cumplan la condicin deseada. Para ello ya disponemos de la
o
funcion que elimina un nodo dado. La segunda es no-destructiva, y procede
extrayendo los elementos de la lista que satisfacen la condicin deseada y
o
agregndolos a otra lista, la sub-lista que deseamos obtener. Aqu trataremos
a
de este segundo caso. A su vez, ste tiene dos variantes: en la primera, cada
e
nodo de la sub-lista apunta a un bloque nuevo, creado para copiar en l
e
los datos apuntados por el nodo correspondiente en la lista principal; en la
segunda, los nuevos nodos apuntan a los datos ya existentes, apuntados por
los nodos de la lista principal, tal y como muestra la Figura 4.
El problema de esta segunda opcin es que cuando sea preciso liberar las
o
listas, habr que seguir un procedimiento ad hoc para evitar querer liberar
a
dos veces los datos apuntados en comn por la lista y la sublista. En el
u
siguiente ejemplo, a partir de la lista x se construye la sub-lista s, de tal
www.detodoprogramacion.com
29
forma que esta ultima est constituida por las cadenas de longitud inferior a
a
20 apuntadas por la lista principal.
struct nodo *L_sublista(struct nodo *x, struct nodo *s)
{
/*
apunta mediante la sublista a los nodos que
contienen cadenas de longitud inferior a 20
*/
struct nodo *y;
y=L_inicio(x);
while (y!=NULL){
if (strlen(y->cadena)<20){
s=L_inserta_despues(s);
s->cadena=(char *)malloc(strlen(y->cadena)+1);
strcpy(s->cadena,y->cadena);
}
y=y->sig;
}
return(L_inicio(s));
}
www.detodoprogramacion.com
30
3. Listas
www.detodoprogramacion.com
Cap
tulo 4
Arboles
4.1.
Introduccin
o
Un rbol es una estructura jerrquica formada por nodos de los que parten
a
a
ramas que conducen a otros nodos. De cada nodo pueden partir dos o ms
a
ramas. Aqu nos limitaremos al caso en que de un nodo parten slo dos
o
ramas. Se habla entonces de rboles binarios. El concepto de rbol, ilustrado
a
a
en la Figura 6, nos parece lo sucientemente claro como para no dar una
descripcin formal, que de todas formas puede encontrarse en los textos al
o
uso. Advirtase, Figura 5, que la estructura del nodo de una lista es la misma
e
que la del nodo de un rbol, si bien las representamos, y las usamos, de
a
distinta forma. Por eso tambin a los punteros que enlazan unos nodos con
e
otros no los llamamos anterior y siguiente, o de cualquier forma similar, sino
izquierda y derecha, o similar. Hemos indicado los punteros NULL rellenando
en negro el rectngulo izquierdo o derecho (o ambos), por claridad en el
a
dibujo. Al nodo etiquetado con la letra R le llamamos ra Como cada
z.
nodo puede considerarse ra para la estructura que cuelga de l, se sigue
z
e
que los algoritmos que tratan con rboles son naturalmente recursivos. Dado
a
que la estructura de los nodos en rboles y listas es la misma, podr
a
amos
preguntarnos el motivo por el que, en el cap
tulo anterior, las listas no han
sido tratadas recursivamente. Simplemente, no era necesario, pero las listas
se prestan igualmente bien al tratamiento recursivo. Por otro lado, en la
implementacin de listas del cap
o
tulo anterior no es preciso mantener un
puntero al primer elemento de la lista, puesto que sta se puede recorrer en
e
un sentido u otro. Bastan un par de funciones para desplazarnos desde un
nodo cualquiera al primero o al ultimo. Sin embargo, en la implementacin
o
de este cap
tulo un nodo no tiene enlace con el nodo del que cuelga, as que
www.detodoprogramacion.com
4. Arboles
32
Figura 5
Si en el cap
tulo dedicado a listas ilustramos su uso con nodos que apuntan
a cadenas, en ste usaremos nodos que contienen nmeros enteros. Como el
e
u
tamao de un entero y el de un puntero es el mismo, no hay ventaja entre
n
apuntar al dato desde el nodo y guardar el dato en el mismo nodo, as que
declaramos
struct nodo{
struct arbol *izq;
struct arbol *der;
int valor;
};
y un rbol comienza siendo, simplemente
a
struct nodo *arbol=NULL:
Al contrario de lo que sucede con las listas, en que normalmente los nodos
se aaden secuencialmente (aunque luego puedan ejecutarse operaciones de
n
reordenacin), en los rboles necesariamente se ha de elegir dnde colocar
o
a
o
cada nuevo elemento. Por su naturaleza de estructura jerrquica, cuando es
a
preciso un rbol se tiene ya un criterio por el que se insertarn nuevos nodos.
a
a
Por ejemplo, dado el nodo ra que contiene un nmero, se puede decidir
z,
u
colocar los nmeros menores a ese en la rama izquierda, y los mayores a
u
la derecha. Si todos los niveles se encontrasen llenos, con punteros vac
os
slo en el ultimo nivel, el primer nivel contendr dos nodos, el segundo
o
a
cuatro, el tercero ocho, y el n, 2n nodos. De la misma forma, para alcanzar
un nodo del primer nivel slo tendr
o
amos que decidir, a partir del ra si
z,
viajar a izquierda o derecha. Para alcanzar un nodo del segundo nivel slo
o
son precisos dos saltos, tres para el tercer nivel y as sucesivamente.
www.detodoprogramacion.com
33
Figura 6
...
Figura 7
hay casos en que puede encontrarse totalmente desequilibrado, habiendo degenerado en una lista, como se muestra en la Figura 7. As que su utilidad
4.2.
Insercin de nodos
o
a
o,
www.detodoprogramacion.com
4. Arboles
34
www.detodoprogramacion.com
35
10
12
Figura 8
return;
}
Se presenta ahora el problema de recorrer el rbol e imprimir (o cualquier
a
otra operacin) los valores contenidos en los nodos. Aprovechamos nuevao
mente la estructura recursiva del rbol y escribimos la siguiente funcin:
a
o
void A_print_in(struct nodo **a)
{
if ((*a)->izq!=NULL){
A_print_in( &((*a)->izq));
}
printf("%d\n",(*a)->valor);
if ((*a)->der!=NULL){
A_print_in( &((*a)->der));
}
}
Si volvemos a la Figura 8 y hacemos una traza de la A print in(), vemos
que el resultado es la impresin en orden creciente de los valores contenidos
o
en el rbol. A este tipo de recorrido en profundidad del rbol se le llama
a
a
in-orden. Pero podemos elegir imprimir en primer lugar el valor del nodo y
luego ocuparnos de las ramas, y en ese caso hablamos de un recorrido en
pre-orden; o bien ocuparnos de las ramas en primer lugar, y nalmente del
nodo. Tenemos entonces un recorrido en post-orden
www.detodoprogramacion.com
4. Arboles
36
4.3.
www.detodoprogramacion.com
37
else
{
if (n < (*a)->valor)
A_buscar_rec(&((*a)->izq),n);
else
A_buscar_rec(&((*a)->der),n);
}
}
Apenas hay ventaja entre escribir la funcin de forma recursiva y hacerlo
o
de forma iterativa:
struct nodo *A_buscar_it(struct nodo **a, int n)
{
struct nodo *b=(*a);
while (b!=NULL){
if (b->valor==n)
return(b);
else
if (n< b->valor)
b=b->izq;
else
b=b->der;
}
return(b);
}
Es ms: la versin recursiva, tal y como la hemos escrito, no funciona. El
a
o
cdigo parece correcto, pero ilustra un error comn; a saber: el valor devuelto
o
u
por una funcin recursiva no es usado por la funcin que la invoca, excepto
o
o
en la primera instancia. Como en general una funcin recursiva se llama a
o
s misma varias veces, el valor devuelto por la segunda, tercera y sucesivas
www.detodoprogramacion.com
4. Arboles
38
if ((*a)==NULL)
busca=NULL;
return;
else
if ((*a)->valor==n)
busca=(*a);
return;
else
{
if (n < (*a)->valor)
A_buscar_rec2(&((*a)->izq),n);
else
A_buscar_rec2(&((*a)->der),n);
}
}
e
o
nodo padre, ya que ste es unico y ser necesario tenerlo en cuenta para
e
a
eliminar nodos. La implementacin es directa, tanto en forma recursiva (con
o
la salvedad comentada antes) como en forma iterativa. Como carece de sentido buscar el padre de un nodo que no existe, supondremos que el nodo
pertenece al rbol, devolviendo NULL si se trata del nodo ra o un puntero
a
z
al nodo padre, en otro caso.
struct nodo *busca_padre;
void A_buscar_padre_rec(struct nodo **a, struct nodo *x)
{
int n=x->valor;
if (*a==x){
busca_padre=NULL;
return;
} else
if (n < (*a)->valor){
if ((*a)->izq==x){
busca_padre=(*a);
return;
} else
A_buscar_padre_rec(&((*a)->izq),x);
} else
{
if ((*a)->der==x){
www.detodoprogramacion.com
39
busca_padre=(*a);
return;
} else
A_buscar_padre_rec(&((*a)->der),x);
}
}
El borrado de un nodo puede ser sencillo o dif segn donde est el nocil,
u
e
do. El caso ms simple se da cuando un nodo no tiene descendientes, es decir,
a
sus punteros izq y der se encuentran a NULL. En ese caso, basta eliminarlo y
poner a NULL el puntero que apuntaba a l. Es ms complejo cuando el nodo
e
a
es interior al rbol, y tiene tanto padre como hijos. Renunciamos a discutir
a
ese caso, ya que stas notas no son sobre algor
e
tmica, sino sobre punteros. No
obstante, existe una solucin muy sencilla, aunque poco eciente, que cono
siste en recorrer el rbol en pre-orden y reconstruirlo en otro lugar, omitiendo
a
el nodo que se desea eliminar. Una vez hecho, se procede al borrado del rbol
a
original. Esta operacin es fcil si se recorre el rbol en post-orden:
o
a
a
void A_borrar_arbol(struct nodo **a)
{
/* borrar en post-orden */
if ((*a)->izq!=NULL){
A_borrar_arbol( &((*a)->izq));
}
if ((*a)->der!=NULL){
A_borrar_arbol( &((*a)->der));
}
free(*a);
}
www.detodoprogramacion.com
4. Arboles
40
www.detodoprogramacion.com
Cap
tulo 5
Punteros en otros contextos
5.1.
Concepto
5.2.
FAT e i-nodos
www.detodoprogramacion.com
42
B 00000
11111
11111111111111
00000000000000 A
11111
00000
11111
00000
11111111111111
00000000000000
11111
00000
11111
00000
11111111111111
00000000000000
11111
00000
11111
00000
11111111111111
00000000000000
A
111111111
000000000
111111111
000000000
111111111
000000000
111111111
000000000
111111111
000000000
111111111
000000000
111111111
000000000
Figura 9
Figura 10
o sica de bloques.
5.2.1.
FAT
10.
Pero ms claramente se aprecia el mecanismo cuando un archivo no ocua
pa bloques consecutivos. Imaginemos que han quedado libres los bloques
{7,9,22,111,204}, y que van a ser usados para alojar un nuevo archivo. En
ese caso, en la entrada 7 de la FAT escribiremos un 9, en la entrada 9 un 22
y as sucesivamente, como muestra la Figura 11.
www.detodoprogramacion.com
43
22
111
204
22
111
204
Figura 11
Figura 12
o
o
dnde comienza la lista de bloques asignada a un archivo. Para eso existe el
o
directorio ra El directorio ra consiste en una serie de bloques reservados
z.
z
donde se escriben los nombres de los archivos y el primer bloque que ocupa
cada uno (entre otras informaciones que no vienen al caso, como tamao,
n
fecha, etc). Ahora bien, el directorio ra tiene un tamao limitado, luego
z
n
ah no pueden estar los nombres de todos los archivos de un volumen (disco).
5.2.2.
i-nodos
www.detodoprogramacion.com
44
FAT, es decir, leer las entradas correspondientes a todos los archivos que no
son el de nuestro inters. La solucin obvia ser colocar juntos todos los pune
o
a
teros correspondientes a un archivo dado. As la entrada del directorio ra
,
z
de un archivo contendr el nombre del mismo, tamao, fecha... y el nmero
a
n
u
del bloque donde estn contenidos los nmeros de los bloques que ocupa el
a
u
archivo. A este bloque que contiene los nmeros de bloques ocupados por
u
un archivo se le llama i-nodo. Puede suceder que un i-nodo no pueda contener todos los nmeros de bloque ocupados por un archivo, si ste es muy
u
e
grande. Por ejemplo, con bloques de 1K y enteros de 32 bits, un bloque i-nodo
puede contener 256 entradas correspondientes a otros tantos bloques. Esto
limitar el tamao de los archivos a 256K. Hay dos soluciones: la primera
a
n
consiste en tomar bloques de mayor tamao. Por ejemplo, con bloques de
n
8K el tamao mximo de archivo es de 16MB. Pero, si el tamao de bloque
n
a
n
es muy grande, puede desperdiciarse una cantidad inaceptable de espacio en
disco pues, por trmino medio, queda vac la mitad del ultimo bloque de cae
a
resevar las tres ultimas, de modo que la ultima sirva para indireccin triple
o
(contiene punteros a nodos, que contienen punteros a nodos, que contienen
punteros a nodos que contienen nmeros de bloque del archivo).
u
Un problema de este sistema es que no es posible saber qu nodos se
e
encuentran ocupados y qu nodos libres. Entonces, ser preciso reservar unos
e
a
cuantos bloques para implementar sobre ellos un mapa de bits. Si el disco
contiene n bloques de datos, ser preciso guardar en algn lugar una secuencia
a
u
de n bits. El bit x de esa secuencia a 1(0) indica que el bloque x de datos se
encuentra ocupado(libre).
www.detodoprogramacion.com
45
a bloque
Figura 14
5.3.
Gestin de memoria
o
o
un bloque por otra parte no es ms que pasarlo a la lista de bloques libres.
a
As que, en esencia, el problema que subyace es el de la implementacin
o
de listas sobre secuencias de bytes. En el caso sencillo de una lista simple
enlazada es suciente una pequea cabecera de un entero por cada elemento
n
de la lista, que indique el comienzo del elemento siguiente. De aqu se sigue
www.detodoprogramacion.com
46
11
25
16
Figura 15
8
6
10
4
7
9
12
6
4
9
10
7
12
Figura 16
5.4.
Conclusin
o
a
de alto nivel estn implementados haciendo un uso intensivo de los punteros.
a
Y si vamos algo ms abajo, al puntero no como tipo de dato en C sino como
a
entero que indica una posicin, los encontramos en otras partes de cualquier
o
sistema.
Los inconvenientes clsicos que se oponen a su uso, como prdidas de
a
e
www.detodoprogramacion.com
47
5.4. Conclusin
o
o
de Perifricos, aunque tangencialmente, ya en relacin con cualquier otra.
e
o
Explicit libellus.
www.detodoprogramacion.com