TEMA 4 : Punteros y funciones 

4.0 Introducción

        En  este tema  estudiaremos el  tipo de  dato más importante
dentro del  lenguaje C. Los punteros.  Absolutamente todos los datos
en  C pueden  ser tratados  como punteros  y por  ello este lenguaje
proporciona una serie de  importantes herramientas para trabajar con
ellos.

        Además  introduciremos  el   concepto  de  función  asociado
estrechamente  a la  llamada  programación  modular que  nos permite
crear un programa  mucho más claro y fácil de  corregir a la hora de
encontrar errores.


4.1 Punteros

4.1.1 ¿ Qué son los punteros ?

        Como  su nombre  indica un  puntero es  algo que  apunta, es
decir,  nos indica  dónde se  encuentra una  cierta cosa. Supongamos
(como otras  tantas veces) que disponemos  de un gran archivo  en el
que   almacenamos   informes.   Este   fichero   está   dividido  en
compartimientos,  cada uno  de los  cuales contiene  uno de nuestros
informes (esto sería  equivalente a las variables con  las que hemos
trabajado hasta ahora -informes-, la cuales contienen información, y
el archivo  representa la memoria  de nuestro ordenador,  obviamente
las  variables  se  almacenan  en  la  memoria).  Sin  embargo otros
compartimientos no contienen informes, sino  que lo que contienen es
una nota que nos dice dónde está ese informe.

        Supongamos que como máximo trabajamos con tres informes a la
vez,  digamos que  no nos  gusta leer  demasiado, y  reservamos, por
tanto, tres  compartimientos en los indicamos  en que compartimiento
se encuentran esos tres  informes. Estos tres compartimientos serían
nuestros  punteros y  como ocupan  un compartimiento  en el  archivo
(nuestra  memoria)  son  realmente  variables,  pero  variables  muy
especiales. Estas variables punteros  ocupan siempre un tamaño fijo,
simplemente  contienen  el  número  de  compartimiento  en el que se
encuentra la información. No contienen la información en sí.

        Si  en nuestro  archivo  pudiésemos  almacenar un  máximo de
20.000 hojas,  esta sería la  capacidad de nuestra  memoria (unos 19
Kilobytes).  Estas hojas  de nuestros  informes las  agruparíamos de
distintas formas. Quizá un informe sólo ocupe 5 páginas mientras que
otro puede ocupar 100. Podemos ver  esto como los distintos tipos de
datos  del C,  es lógico   pensar que  necesitamos más  espacio para
almacenar un  número real que uno  entero o que una  matriz de 20x20
elemento. Estos son nuestro informes en nuestro archivo. Sin embargo
los  punteros  siempre  ocupan  lo  mismo,  en  nuestro  ejemplo nos
llegaría  con   una  página  para  poder   escribir  el  número  del
compartimiento en el que se encuentra el inicio del informe.

        Así  en nuestro  supuesto de  que sólo  trabajemos con  tres
informes a la vez, dispondríamos  de tres compartimientos en los que
indicaríamos  dónde se  encuentran esos  informes que  buscamos y de
esta forma cuando terminemos con ellos y deseemos trabajar con otros
sólo tendremos que cambiar el contenido de esos tres compartimientos
diciendo donde se  encuentran los nuevos informes. De  esta forma no
es necesario reservar unos compartimientos  para trabajar y cada vez
que  cambiemos   de  trabajo  llevar   los  informes  viejos   a  su
compartimiento  anterior  y  traer   los  nuevos  informes  a  estos
compartimientos.

        Esto  es lo  que en  programación se  conoce como referencia
indirecta o  indireción. Accedemos a  la información a  través de un
puntero que  nos dice dónde  se encuentra ésta.  Y a grandes  rasgos
ésto son los punteros, referencias  indirectas a datos en la memoria
del ordenador.

        Los  punteros  en  C  son  muy  importantes  puesto  que  su
utilización es básica para  la realización de numerosas operaciones.
Entre  ellas:  paso  de  parámetros  que  deseamos sean modificados,
tratamiento de  estructuras dinámicas de  datos (ésto es,  variables
que no  se declaran en el  programa y se crean  durante la ejecución
del programa), cadenas de caracteres ...


4.1.2 Operadores que actúan sobre punteros.

        El  lenguaje  C   proporciona  dos  operadores  relacionados
directamente con los punteros. El primero de ellos es el operador &.
Ya  hemos visto  este operador  antes en  las llamadas  a la función
scanf, posteriormente explicaremos por que la función scanf necesita
ser llamada con el operador &.

        El operador &, es un  operador unario, es decir, actúa sobre
un sólo  operando. Este operando tiene  que ser obligatoriamente una
estructura direccionable,  es decir, que se  encuentre en la memoria
del ordenador. Estas estructuras  son fundamentalmente las variables
y las funciones,  de las que hablaremos  posteriormente. Decimos que
sólo  se puede  aplicar sobre  estructuras direccionables  porque su
función es  devolver la posición de  memoria en la que  se encuentra
dicha  estructura. En  nuestro ejemplo  nos indicaría  cual sería el
compartimiento en el que se encuentra el informe que le indiquemos.

        El segundo operador es el *. También se trata de un operador
unario como el anterior y su función  en este caso es la de permitir
el acceso  al contenido de la  posición indicada por un  puntero. En
nuestro  ejemplo el  operador *  nos permitiría  leer o  escribir el
informe  al que  apunta  uno  de nuestros  compartimientos punteros.
Además el  carácter * se  utiliza para declarar  punteros los cuales
como  ya  dijimos  tienen  que  ser  declarados  (tienen  su  propio
compartimiento en el archivo).

        Por  supuesto  el  operador  *  debe  ser  aplicado sobre un
puntero,   mientras  que   el  operador   &  sobre   una  estructura
direccionable  (variable  o  función).   Veamos  un  ejemplo  de  su
utilización:

        main ()
        {
        int x,y;    /* Variables de tipo entero */
        int *px;    /* Puntero a una variable de tipo entero */

        /* Leemos la dirección -compartimiento- de la variable
-informe- x mediante & y lo almacenamos en la variable puntero
px */
        px = &x;
        /* px contiene la dirección en la que se encuentra x */
        /* Utilizando el operador *, podemos acceder a su
información. *px representa ahora el valor de la variable x */
        *px = 10;       /* Ahora x contiene el valor 10 */
        y = 15;
        /* Si ahora hacemos que nuestro puntero apunte a la
variable y utilizando de nuevo el operador & */
        px = &y;
        /* El valor que ahora toma *px será el valor de y puesto
que es el compartimiento al que ahora estamos apuntando */
        *px = 125; /* Ahora y contiene el valor 125 */
        x = *px    /* Ahora x contiene también 125 */
        }

        Como  hemos  visto  en  este  ejemplo  es  exactamente igual
acceder a  una variable que  utilizar un puntero  que apunte a  ella
(hacemos  que apunte  a ella  mediante el  operador &)  junto con el
operador *.

        Pero  el lenguaje  C aún  ofrece otra  herramienta más  para
trabajar  con punteros.  Es lo   que se  suele llamar  aritmética de
punteros.  Este tema  lo trataremos  en profundidad  en el siguiente
apartado.


4.1.3 Punteros y matrices

        Ya  hemos hablado  de las  matrices en  el tema anterior. Se
trataba de un conjunto de un  número de terminado de variables de un
mismo tipo  que se referenciaban con  un nombre común seguido  de su
posición entre corchetes con relación  al primer elemento. Todas las
entradas de una matriz están consecutivas en memoria, por eso es muy
sencillo  acceder   al  elemento  que   queramos  en  cada   momento
simplemente  indicando su  posición. Sólo  se le  suma a la posición
inicial  ese   índice  que  indicamos.   Es  un  ejemplo   que  casa
perfectamente  con nuestro  ejemplo  de  los informes,  cada informe
podría  ser considerado  como una  matriz de  tantos elementos  como
páginas tenga el informe  y en los que cada uno de  ellos es un tipo
de datos llamado página.

        Las matrices  son realmente punteros  al inicio de  una zona
consecutiva  de los  elementos indicados  en su  declaración, por lo
cual podemos  acceder a la  matriz utilizando los  corchetes como ya
vimos o utilizando el operador *.

        elemento[i] <=> *(elemento +i)

        Como ya se  ha comentado todos los punteros  ocupan lo mismo
en memoria,  el espacio suficiente para  contener una dirección, sin
embargo cuando se  declaran es necesario indicar cual  es el tipo de
datos al que van a apuntar (entero, real, alguna estructura definida
por el  usuario). En nuestro  ejemplo tendríamos un  tipo de puntero
por cada tipo  de informe distinto, un puntero  para informes de una
página, otro puntero para informes de 2 páginas y así sucesivamente.
En principio esto es irrelevante por que una dirección de memoria es
una dirección de memoria, independientemente  de lo que contenga con
lo  cual  no  sería  necesario   declarar  ningún  tipo,  pero  esta
información es necesaria para  implementar la aritmética de punteros
que ejemplificaremos a continuación.

        Supongamos que  hemos definido un  tipo de datos  en nuestro
programa  que  fuese  página,  si  cada  página  puede  contener  80
caracteres de ancho por 25 de alto, podría ser algo como ésto:

        typedef char página[80][25];

        Y  supongamos  también  que   sólo  tenemos  tres  tipos  de
informes, de 1 página, de 5 páginas y de 25 páginas:

        typedef página  informe1;
        typedef página  informe2[5];
        typedef página  informe3[25];

        Y  en   nuestro  programa  principal   hemos  declarado  las
siguientes variables:

        main()
        {
        página     *punt_página;
        informe1   i1[10],*punt1;
        informe2   i3[5],*punt2;
        informe3   i4[15],*punt3;

        ....

        Por  tanto  disponemos  de  un  puntero  a  páginas  y  tres
punteros, uno para cada tipo de informe y tres matrices de distintos
tipos de informes  que nos permiten almacenar en  nuestro archivo un
máximo de  30 informes (10 de  1 página, 5 de  5 páginas y 15  de 25
páginas).

        Supongamos  que  en  el  programa  principal  se llenan esas
matrices  con  datos  (por  teclado  o  leyendo  de  un fichero, por
ejemplo) y realizamos las siguientes operaciones:

        punt_página = (página *)  &i4[0];
        punt3       = (informe3 *)&i4[0];

        Los  cast  (que  comentamos  en  el  tema  1) convierten las
direcciones  al  tipo  apropiado,  las  direcciones  que  contendrán
punt_página  y   punt3  serán  exactamente   iguales,  apuntarán  al
principio del primer informe de tipo3. Sin embargo punt_página es un
puntero de tipo página y punt3  es un puntero de tipo informe3, ¨qué
significa ésto?. Si ejecutásemos una instrucción como ésta:

        punt_página = punt_página + 5;

        punt_página pasaría a apuntar a  la quinta página del primer
informe de tipo  3 (i4[0]), puesto que punt_página  es un puntero de
paginas. Mientras que si la operación fuese:

        punt3 = punt3 + 5;

        punt3  pasaría  a  apuntar  a  el  quinto  informe de tipo 3
(i4[5]), puesto que punt3 es un  puntero a informes de tipo tres. Si
ahora realizásemos la operación:

        punt_página = (página *)punt3;

        Ahora punt  página apuntaría a la  primera página del quinto
informe  de tipo  3. En   esto consiste  la aritmética  de punteros,
cuando  se realiza  una operación  aritmética sobre  un puntero  las
unidades de ésta son el tipo que  se le ha asociado a dicho puntero.
Si el puntero es de tipo página  operamos con páginas, si es de tipo
informes operamos con informes. Es evidente que un informe de tipo 3
y una página tienen  distintos tamaños (un informe de  tipo 3 son 25
páginas por definición).

        Como  hemos visto  las  matrices  se pueden  considerar como
punteros  y  las  operaciones  con  esos  punteros  depende del tipo
asociado  al puntero,  además es  muy recomendable  utilizar el cast
cuando se realizan conversiones de un tipo de puntero a otro.


4.1.4 Punteros y cadenas de caracteres

        Como  su propio  nombre indica  una cadena  de caracteres es
precisamente  eso un  conjunto  consecutivo  de caracteres.  Como ya
habíamos comentado los caracteres  se codifican utilizando el código
ASCII  que asigna  un número  desde 0  hasta 255  a cada  uno de los
símbolos  representables  en  nuestro   ordenador.  Las  cadenas  de
caracteres utilizan el valor 0 ('\0')  para indicar su final. A este
tipo de codificación se le ha llamado  alguna vez ASCIIZ (la Z es de
zero).

        Las  cadenas  de  caracteres  se  representan entre comillas
dobles (") y  los caracteres simples, como ya  habíamos indicado con
comillas  simples (').  Puesto que  son un  conjunto consecutivo  de
caracteres la forma de definirlas es como una matriz de caracteres.

        char  identificador[tamaño_de_la_cadena];

        Y  por   ser  en  esencia  una   matriz  todo  lo  comentado
anteriormente para  matrices y punteros puede  ser aplicado a ellas.
Así  la  siguiente  definición  constituye  también  una  cadena  de
caracteres:

        char    *identificador;

        La diferencia  entre ambas declaraciones  es que la  primera
reserva una zona de memoria de tamaño_de_la_cadena para almacenar el
mensaje que deseemos mientras que la segunda sólo genera un puntero.
La  primer  por  tratarse  de  una  matriz  siempre tiene un puntero
asociado  al  inicio  del  bloque  del  tamaño especificado. Podemos
tratar  a  las  cadenas  como  punteros  a  caracteres (char *) pero
tenemos que recordar siempre que  un puntero no contiene información
sólo nos  indica dónde se encuentra  ésta, por tanto con  la segunda
definición  no  podríamos  hacer  gran  cosa  puesto  que no tenemos
memoria reservada  para ninguna información. Veamos  un ejemplo para
comprender   mejor   la   diferencia   entra   ambas  declaraciones.
Utilizaremos dos  funciones especiales de stdio.h  para trabajar con
cadenas. Estas son puts y gets  que definiríamos como un printf y un
scanf exclusivo para cadenas.

        #include <stdio.h>
        main()
        {
        char     cadena1[10];
        char     cadena2[10];
        char     *cadena;

        gets(cadena1); /* Leemos un texto por teclado y lo
almacenamos en cadena 1 */
        gets(cadena2); /* Idem cadena2 */

        puts (cadena1); /* Lo mostramos en pantalla */
        puts (cadena2);

        cadena = cadena1; /* cadena que sólo es un puntero ahora
apunta a cadena1 en donde tenemos 10 caracteres reservados por la
definición */

        puts (cadena);  /* Mostrara en pantalla el mensaje
contenido en cadena1 */
        cadena = cadena2; /* Ahora cadena apunta a la segunda
matriz de caracteres */
        gets(cadena);   /* Cuando llenos sobre cadena ahora
estamos leyendo sobre cadena2, debido al efecto de la instrucción
anterior */
        puts(cadena2); /* SI imprimimos ahora cadena2 la pantalla
nos mostrará la cadena que acabamos de leer por teclado */
        }

        En el programa vemos como utilizamos cadena que solamente es
un  puntero para  apuntar a  distintas zonas  de memoria  y utilizar
cadena1 o cadena2 como destino de nuestras operaciones. Como podemos
ver  cuando cambiamos  el valor  de cadena  a cadena1  o cadena2  no
utilizamos  el operador  de dirección  &, puesto  que como  ya hemos
dicho una matriz es en sí un puntero (si sólo indicamos su nombre) y
por tanto una matriz o cadena de caracteres sigue siendo un puntero,
con lo cual los dos miembros de la igualdad son del mismo tipo y por
tanto no hay ningún problema.


4.2 Funciones

4.2.1 Introducción

        Hasta  el momento  hemos utilizado  ya numerosas  funciones,
como printf o scanf, las cuales forman parte de la librería estándar
de entrada/salida  (stdio.h). Sin embargo el  lenguaje C nos permite
definir  nuestras propias  funciones,  es  decir, podemos  añadir al
lenguaje tantos comandos como deseemos.

        Las funciones  son básicas en  el desarrollo de  un programa
cuyo tamaño sea  considerable, puesto que en este  tipo de programas
es común que  se repitan fragmentos de código,  los cuales se pueden
incluir en  una función con  el consiguiente ahorro  de memoria. Por
otra parte el uso de funciones  divide un programa de gran tamaño en
subprogramas   más   pequeños   (las   funciones),   facilitando  su
comprensión, así como la corrección de errores.

        Cuando  llamamos   a  una  función   desde  nuestra  función
principal  main()  o  desde  otra  función  lo  que estamos haciendo
realmente  es  un  salto  o  bifurcación  al  código  que le hayamos
asignado,  en cierto  modo es  una forma  de modificar  el flujo  de
control del programa como lo hacíamos con los comandos while y for.


4.2.2 Definición de funciones

        Ya hemos visto cual es  la estructura general de una función
puesto que  nuestro programa principal,  main() no es  otra cosa que
una función. Veamos cual es el esquema genérico:

tipo_a_devolver identificador (tipo1 parámetro1, tipo2 ...)
        {
        tipo1   Variable_Local1;
        tipo2   Variable_Local2;
        ...

        Código de la función

        return valor del tipo valor a devolver;
        }

        Lo primero con lo que nos encontramos es la cabecera de la
función.   Esta   cabecera   está   formada   por   una  serie  de
declaraciones. En primer lugar el tipo_a_devolver.

        Todas las  funciones tienen la posibilidad  de devolver un
valor, aunque pueden no hacerlo.  Si definimos una función que nos
calcula el coseno de un  cierto ángulo nos interesaría que nuestra
función devolviese ese valor. Si  por el contrario nuestra función
realiza el proceso de borrar la pantalla no existiría ningún valor
que nos interesase conocer sobre  esa función. Si no se especifica
ningún  parámetro  el  compilador  supondrá  que  nuestra  función
devuelve un valor entero (int).

        A continuación nos encontramos  con el identificador de la
función, es decir, el nombre con  el que la vamos a referenciar en
nuestro  programas,  seguido  de  una  lista  de  parámetros entre
paréntesis y separados  por comas sobre los que  actuará el código
que escribamos para esa función. En el caso de la función coseno a
la que antes aludíamos, el parámetro sería el ángulo calculamos el
coseno  de un  cierto ángulo  que  en  cada llamada  a la  función
probablemente   sea  distinto.   Véase  la   importancia  de   los
parámetros,  si no  pudiésemos definir  un parámetro  para nuestra
función  coseno,  tendríamos  que  definir  una  función para cada
ángulo, en la que obviamente no indicaríamos ningún parámetro.

        A continuación nos encontramos el cuerpo de la función. En
primer  lugar declaramos  las  variables  locales de  esa función.
Estas  variables  solamente  podrán  ser  accedidas  dentro  de la
función,  esto  es,  entre  las  llaves  ({}).  Los nombres de las
variables  locales pueden  ser los  mismos en  distintas funciones
puesto  que sólo  son accesibles  dentro de  ellas. Así si estamos
acostumbrados  a  utilizar  una  variable  entera  llamada  i como
contador en nuestro bucles, podemos definir en distintas funciones
esta  variable y  utilizarla dentro  de cada  función sin que haya
interferencias entre las distintas funciones.

        Con respecto al código de  la función, pues simplemente se
trata  de un  programa como  todos los  que hemos  estado haciendo
hasta ahora.

        La  instrucción  return  del  final  puede  omitirse si la
función  no  devuelve  ningún  valor,  su  cometido es simplemente
indicar que  valor tomaría esa  función con los  parámetros que le
hemos pasado.  En otros lenguajes  las funciones que  no devuelven
valores se conocen como procedimientos.

        Veamos un ejemplo de definición de una función.

      int     busca_elemento (int *vector,int valor,int longitud)
      {
      int     i;

      for (i=0;i<longitud;i++)
              if (vector[i] == valor) break;

      return i;
      }

        Esta  función  busca  un  valor  en  un  vector de números
enteros y devuelve el índice dentro  de la matriz de la entrada de
ésta que lo  contiene. Puesto que devuelve el  índice de la matriz
supondremos  en  principio  un  valor  de  retorno entero para ese
índice. Los parámetros que debe  conocer la función son: la matriz
en la que buscar, el valor que  debemos buscar y la longitud de la
matriz. Podríamos  haber realizado una  función a medida  para que
utilizase una  matriz de un  número determinado de  elementos (int
vector[100],  por ejemplo)  y ahorrar  el parámetro  longitud, sin
embargo  con  la  definición   que  hemos  hecho  nuestra  función
funcionará con matrices de cualquier longitud de enteros.

        Hemos declarado además una variable local que es necesaria
para la realización del bucle actuando como contador y conteniendo
además el índice dentro de la matriz que buscamos. Si la entrada i
de nuestro  vector corresponde con  valor, salimos del  bucle y la
variable i contiene  ese valor de la entrada,  el cual es devuelto
por la función mediante la  instrucción return. Ahora veremos como
utilizaríamos esta función desde un programa:

        main ()
        {
        int     matriz1[20];
        int     matriz2[30];
        int     indice,dato;

        /* Aquí realizaríamos alguna operación sobre las
matrices como por ejemplo inicializarlas */

        indice = busca_elemento (matriz1,10,20);
        ....
        dato = 15;
        indice = busca_elemento (matriz2,dato,30);

        .....
        }

        Como  vemos  en  las  llamadas  a  nuestra función podemos
utilizar tanto variables o constantes como parámetros.


4.2.3 Más sobre funciones

        Cuando el  valor que retornan las  funciones no es entero,
es necesario  que el compilador  sepa de antemano  su tipo por  lo
cual es necesario añadir al comienzo del programa lo que se llaman
prototipos. Los  prototipos simplemente son  una predeclaración de
la función,  solo indican el  tipo que devuelve,  su nombre y  los
tipos de los parámetros, no  es necesario indicar un identificador
para los parámetros. Un prototipo para la función anterior sería:

        int busca_elemento (int *, int, int);

        Los  fichero  .h  que  se  incluyen  con  la directiva del
procesador #include, contienen entre otras cosas los prototipos de
las funciones a las que nos dan acceso.

        Para  finalizar con  las funciones  vamos a  explicar como
pasar  parámetros que  deseamos que  la función  modifique. Cuando
pasamos  parámetros a  una función  ésta realiza  una copia de los
valores de  éstos en una  zona de memoria  propia, con lo  cual la
función trabaja con  estas copias de los valores  y no hay peligro
de que se modifique la variable  original con la que llamamos a la
función, forzando de esta forma  a utilizar el valor retornado por
la función como parámetro. Sin embargo es posible que nos interese
que nuestra función nos devuelva más de una valor o que uno de los
parámetros con los que lo llamamos  se modifique en función de las
operaciones realizadas  por la función.  En este caso  tenemos que
pasar los parámetros como punteros.

        Cuando  pasamos  los  valores  como  punteros  la  función
realiza  una  copia  de  los  valores  de  los  parámetros  de las
funciones en su zona propia de memoria, pero en este caso el valor
que pasamos  no es un  valor en sí,  sino que es  una dirección de
memoria  en  la  que  se  encuentra  ese  valor  que  deseamos  se
modifique, es decir,  creamos un puntero que apunta  a la posición
que deseamos modificar, con lo cual tenemos acceso a esos valores.
Veamos un ejemplo típico de parámetros que deben modificarse, este
es la función swap(a,b) cuya misión es intercambiar los valores de
los dos  parámetros, es decir,  el parámetro a  toma el valor  del
parámetro b y viceversa. La primera codificación que se nos ocurre
sería ésta:

        swap (int a,int b)
        {
        int     t;

        t = a;
        a = b;
        b = t;
        }

        Y nuestro programa principal podría ser algo como ésto:

        main ()
        {
        int     c,d;

        c = 5;
        d = 7;

        swap (c,d);
        }

        Veamos que pasa en la memoria de nuestro ordenador.

        -Función main()
        -Espacio para la variable c (Posición de memoria x)
        -Espacio para la variable d (Posición de memoria y)
        -Inicialización de las variables
        -swap(c,d)
        -Fin de main()
        -Función swap
        -Código de la función swap
        -Espacio privado  para almacenar los  parámetros (Posición
de memoria z)

        En  este último  compartimiento es  dónde almacenamos  los
valores de  nuestros parámetros que  serán respectivamente 5  y 7.
Después  de la  ejecución de  swap  en  esta zona  de memoria  los
valores   están  intercambiados,   nuestro  parámetro   a  que  se
corresponde con  la variable c en  la llamada a swap  contendrá el
valor 7  y el parámetro b  correspondiente a d en  la función main
contendrá el valor 5. Esto es lo que se encuentra almacenado en la
zona privada de memoria de la  función. Con este esquema cuando la
función  swap termina  su ejecución  y se  devuelve el  control al
programa principal  main, los valores  de c y  d no han  cambiado,
puesto que  los compartimientos o posiciones  de memoria x e  y no
han  sido tocados  por la  función swap,  la cual  sólo ha actuado
sobre el compartimiento z.

        Si declaramos ahora nuestra función swap como sigue:

        swap (int *p1,int *p2)
        {
        int     t;

        t = *p1;    /*Metemos en t el contenido de p1 */
        *p1 = *p2;  /* Contenido de p1 = contenido de p2 */
        *p2 = t;
        }

        Tendremos el  mismo esquema de  nuestra memoria que  antes
pero en lugar  de almacenar en la zona privada  de la función swap
para los parámetros los valores 5  y 7 tenemos almacenados en ella
los  compartimientos  en  los  que  se  encuentran, ésto es, hemos
almacenado las posiciones x  e y en lugar de 5 y  7. De esta forma
accedemos mediante un  puntero a las variables c  y d del programa
principal que se encuentran en las posiciones x e y modificándolas
directamente así que al regresar al programa principal sus valores
se encuentran ahora intercambiados.

        En resumen,  cuando deseemos que una  función modifique el
valor  de uno  de los  parámetros con  los que  es llamada debemos
pasar  un  puntero  a  esa  variable  en  lugar  del  valor de esa
variable. Es evidente que si implementamos nuestra función de esta
forma,  los parámetros  jamás  podrán  ser constantes,  puesto que
difícilmente podríamos modificar el valor de una constante.