You are on page 1of 26

Captulo 10. Conceptos avanzados sobre funciones.

1. Introduccin.

2. Paso de parmetros por referencia.


2.1. Arrays como parmetros.
2.2. Parmetros de la funcin main(): argc y argv.

3. Una funcin puede retornar un puntero.


3.1. Reserva dinmica de memoria dentro de una funcin.

4. Punteros a funciones.

5. Recursividad.

6. Tipos de almacenamiento.

7. Creacin de un programa con varios ficheros fuentes.

8. Ficheros cabecera creados por el usuario.

1
1. Introduccin.
En un captulo anterior han sido tratados algunos conceptos bsicos
sobre las funciones. Una vez que se han explicado los arrays y los punteros,
ahora pueden ser detallados otros conceptos ms avanzados.

2. Paso de parmetros por referencia.


Recordemos que el envo de datos y la recepcin de resultados de una
funcin se llama paso de parmetros. Este puede realizarse de dos modos:

- paso por valor: un parmetro pasado por valor slo podr ser usado por la
funcin para recibir un dato de entrada a la misma.
- paso por referencia: un parmetro pasado por referencia podr ser usado
por la funcin tanto para recibir un dato de entrada a la misma como para
devolver un resultado de salida de la funcin, que lo podr usar el mdulo
llamante.

El paso por valor ya fue desarrollado anteriormente. Veamos ahora el


paso por referencia.

Cuando se usa paso por referencia, la funcin trabaja directamente


sobre la variable usada en la llamada a la funcin, la variable original
(parmetro actual), es decir que las modificaciones que se realicen sobre la
variable dentro de la funcin (parmetro formal) afectarn directamente a la
variable original, no se hace copia como ocurre en el paso por valor. Veamos
un ejemplo.

Ej. Clculo de la longitud de una circunferencia.

Pseudocdigo.
Inicio.
Radio
Longitud

Escribir Teclee radio:


Leer Radio
Calcular_Longitud(Radio, Ref. Longitud)
//Radio se pasa por valor
//Longitud se pasa por referencia

Escribir La longitud es: , Longitud


//Visualiza la longitud calculada en la funcin.
Fin.

Funcin Calcular_Longitud(R, Ref. L)


//R por valor, L por referencia
PI = 3.1416
L = 2 * PI * R //Al modificar L se modifica
FinFuncin //Longitud.

2
Como vemos, cuando no se indica lo contrario un parmetro est
pasado por valor. Si colocamos Ref. delante del parmetro ser pasado por
referencia. En ese ejemplo, la variable original es Longitud, que se pasa por
referencia. Esto implica que las modificaciones que la funcin realice sobre L,
se aplican realmente sobre Longitud. Se podra decir que L es simplemente
otro nombre de la variable Longitud. Por tanto, el paso por referencia puede ser
utilizado para pasar datos a una funcin y para que sta devuelva resultados.

En C, para pasar un parmetro por referencia debe usarse la direccin


de la variable, es decir un puntero a la misma, de modo que tanto el parmetro
actual como el formal sern punteros. La funcin conocer la direccin de
memoria de la variable original, por ello podr modificaciones sobre ella. No
debe olvidarse que los tipos de datos deben coincidir. Veamos el ejemplo
anterior en C.

Ej. Clculo de la longitud de una circunferencia.

#include <stdio.h>
#include <conio.h>
#define PI 3.1416

void CALCULAR_LONGITUD(int, float *);


//El segundo parmetro es por referencia,
//es un puntero a float.
void main(void)
{
int Radio;
float Longitud; //Variable de tipo float.
clrscr();
printf(Teclee el radio:);
scanf(%d, &Radio);fflush(stdin);
CALCULAR_LONGITUD(Radio, &Longitud);
//Longitud es por referencia, se
//pasa su direccin de memoria: &

printf(\nLa longitud es: %.4f, Longitud);


}

void CALCULAR_LONGITUD(int R, float *L)


{ //L es por referencia, es un puntero float
//que apunta a la variable Longitud: *

*L = 2 * PI * R; //Como L es puntero, hay que


//usarlo como tal: *L=...
}

Como vemos en ese ejemplo, para pasar un parmetro por referencia,


se pasa realmente la direccin de memoria de una variable (&Longitud). En esa
variable (Longitud) podr dejar la funcin algn resultado, para ser usado por el
mdulo llamante. L es un puntero que contiene la direccin de memoria de

3
Longitud; es decir que *L coincide con Longitud, por tanto la funcin est
trabajando directamente con la variable original Longitud, as si *L es
modificado en la funcin, se modifica Longitud.

As vemos como una funcin puede devolver un valor en su nombre,


usando la sentencia return(), pero tambin puede usar parmetros por
referencia para devolver otros valores.

Ej. Funcin que calcula la longitud, el rea y el volumen de una


circunferencia cuyo radio es tecleado.

#include <stdio.h>
#include <conio.h>
#define PI 3.1416

float CALCULOS (float, float *, float *);

void main(void)
{
float Radio, Longitud, Area, Volumen;
clrscr();
printf(Teclee el radio:);
scanf(%d, &Radio);fflush(stdin);
Volumen = CALCULOS(Radio, &Longitud, &Area);
//El volumen es devuelto con return, mientras que
//para la longitud y el rea se usa paso por
//referencia.
printf(\nLa longitud es: %.4f, Longitud);
printf(\nEl area es: %.4f, Area);
printf(\nEl volumen es: %.4f, Volumen);
}

float CALCULOS(float R, float *L, float *A)


{
float V;
*L = 2 * PI * R;
*A = PI * R * R;
V = (4 / 3) * PI * R * R * R;
return(V);
}

2.2. Arrays como parmetros.


Cuando se pasa una componente o elemento de un array como
parmetro, no hay diferencia a lo visto hasta ahora, se hace como con otra
variable cualquiera. Una componente es una variable ms y se puede pasar
por valor o por referencia. Veamos un ejemplo:

Ej. Funcin que suma los dos elementos de un array, dejando el


resultado en el segundo elemento.

4
#include <stdio.h>
#include <conio.h>

void SUMAR(int, int *);

void main(void)
{
int arr[2];

clrscr();
printf(\n Elemento 0:);
scanf(%d, &arr[0]);fflush(stdin);
//Podra usarse arr en lugar de &arr[0].
printf(\n Elemento 1:);
scanf(%d, &arr[1]);fflush(stdin);
//Podra usarse arr+1 en lugar de &arr[1].
SUMAR(arr[0], &arr[1]);
//La segunda componente se pasa por referencia.
//Podra usarse arr+1 en lugar de &arr[1].
printf(\nResultado de la suma:%d, arr[1]);
}

void SUMAR(int c0, int *c1)


{
*c1 = *c1 + c0; //c1 es puntero a la 2 componente.
}

Por otra parte, cuando se desea pasar un array completo como


parmetro a una funcin, no una sola componente, se usar en la llamada a la
funcin el nombre del array, sin indicar ninguna componente entre corchetes.
Pero en ese caso, no debe olvidarse que el nombre de un array es un puntero
al primer elemento del array, es decir en la funcin se crea una copia de ese
puntero, que apuntar al array original, cuando la funcin finalice ese puntero
se destruye. Esto significa que un array completo siempre se pasa por
referencia, ya que se pasa un puntero al comienzo del array. La funcin puede
modificar el array original, no utiliza una copia de dicho array, por lo que hay
que ser cuidadoso para que la funcin no realice modificaciones no deseadas
sobre el array.

Cuando un parmetro formal de una funcin es un array, puede


declararse de tres modos:

- Como array del tamao que tenga el original, el parmetro actual.


- Como array sin tamao, ya que el tamao lo recoge el parmetro actual.
- Como puntero al tipo de datos del array.

Veamos un ejemplo:

Ej.
#include <stdio.h>

5
#include <conio.h>

void PRODUCTO(int arr[4]); //arr es array de tamao 4.


//Podra usarse: int arr[] o int [], sin tamao.
//Tambin: int *arr o int *, como puntero.

void main(void)
{
int arr[4] = {1,2,3,4};
//Array original de tamao 4.

clrscr();
PRODUCTO(arr); //Pasa el array completo.
}

void PRODUCTO(int arr[4]) //arr es array de tamao 4.


//Podra usarse: int arr[] sin tamao.
//Tambin: int *arr como puntero.
{
int i;
long prod;
for (i=0, prod=1; i < 4; i++)
prod *= arr[i];
//Podra usarse: *(arr+i) en lugar de arr[i].
printf(\n El producto es: %ld, prod);
}

En ese ejemplo, la variable arr dentro de la funcin es un puntero que se


crea al comenzar la funcin. En dicho puntero se copia la direccin de la
variable usada en la llamada, es decir arr del bloque main. Por tanto, dicho
puntero apunta al inicio del array original. Veamos un ejemplo en el que la
funcin modifica un array declarado en main.

Ej. Funcin que carga un array de main.

#include <stdio.h>
#include <conio.h>
void CARGA (int *arr);
void main(void)
{
int arr[4];
clrscr();
CARGA(arr);
}

void CARGA(int *arr)


//arr es un puntero creado en la funcin y apunta al
//array arr[4] de main.
{
int i;
for (i=0; i < 4; i++)
6
{
scanf(%d, arr+i); fflush(stdin);
}//El dato tecleado se guarda en el array original.
}//Al finalizar la funcin se destruye el puntero arr,
//pero el array original se ha cargado previamente.

2.3. Parmetros de la funcin main(): argc y argv.


El bloque main de un programa en C es una funcin, la funcin principal,
y puede tener dos parmetros, que se recogen de la lnea donde se ejecute el
programa, desde el prompt del sistema operativo:

C:\>PROG <CADENA_1> <CADENA_2> ... <CADENA_N>

En este caso, la funcin principal main se define como sigue:

void main(int argc, char *argv[])

Como se ve, los dos parmetros son:

- int argc: es un parmetro numrico entero, donde se recoge el nmero


de cadenas de caracteres tecleadas al ejecutar el programa, incluyendo el
nombre del programa.

- char *argv[]: es un array de punteros a caracteres; cada puntero


apuntar a una de las cadenas tecleadas al ejecutar el programa,
incluyendo el nombre del programa, por tanto argv[0] apunta a la cadena
que contiene el nombre del programa.

El programa podr usar esos parmetros en sus instrucciones para


cualquier tarea. Veamos un ejemplo:

Ej. Programa que al ejecutarlo obliga a teclear una clave despus del
nombre del programa (la clave es CLAVE).

#include <stdio.h>
...
void main(int argc, char *argv[])
{
//Declaraciones
...
if (argc == 2)
if (strcmp(CLAVE, argv[1]) == 0)
printf(Clave correcta. Puede continuar.);
else
{
printf(Clave incorrecta.);
exit(0); //Finaliza la ejecucin.
}
else
7
printf(Debe teclear C:\>Nom_Prog Clave);
...
}

En ese ejemplo, si el programa se llama ENTRADA, para ejecutarlo


correctamente habra que teclear:

C:\>ENTRADA CLAVE

Como se han tecleado dos cadenas, el parmetro argc valdr 2, el


parmetro argv tendr dos componentes: argv[0] valdr ENTRADA y argv[1]
valdr CLAVE.

3. Una funcin puede retornar un puntero.


Como ya se ha explicado, una funcin puede devolver en su nombre un
valor cualquiera, a travs de la sentencia return(). Esto incluye a los punteros,
es decir tambin se puede devolver un puntero en la sentencia return(puntero),
ya que un puntero es otro tipo de variable ms. Para ello simplemente debe
colocarse un asterisco delante del nombre de la funcin en su declaracin y en
su definicin:

<tipo> *<nombre_funcin> (...)

En ese caso, la funcin devuelve la direccin de memoria (un puntero)


de algn dato, del tipo que se haya indicado en <tipo>. Este concepto se utiliza
principalmente en asignacin dinmica de memoria dentro de una funcin,
como vamos a ver a continuacin.

3.1. Reserva dinmica de memoria dentro de una funcin.


La memoria reservada de forma dinmica desde dentro de una funcin
no se destruye cuando finaliza la funcin, como s ocurre con las variables
locales de la misma. Esto significa que esa memoria dinmica puede ser
utilizada por el main o por otras funciones, siempre que conozcan la direccin
de memoria que ocupa.

En este sentido a veces se comete un error, que consiste en pasar un


puntero como parmetro a una funcin para que lo utilice en la reserva
dinmica. La funcin crear un puntero que ser una copia del puntero original
usado en la llamada. La reserva dinmica se realiza con el puntero copia, que
ser destruido al finalizar la funcin, por lo que el puntero original utilizado en la
llamada no apuntar a la memoria reservada.

Ej.

#include <stdio.h>
#include <stdlib.h>
#include <conio.h>
void DINAMICA (char *nombre);
void main(void)
{
8
char *nombre;
clrscr();
DINAMICA(nombre); //nombre es el puntero original.
printf(Nombre tecleado: %30s, nombre); //ERROR!!!
//No se visualiza el nombre tecleado en la funcin.

free(nombre); //No libera memoria, ya que con este


//puntero nombre no se ha ejecutado ningn malloc.
}
void DINAMICA(char *nombre)
{ //Este nombre es puntero copia.
nombre = (char *)malloc(30*sizeof(char));
scanf(%29[^\n], nombre); fflush(stdin);
}//Al finalizar la funcin se pierde el nombre
//tecleado, ya que se destruye este puntero nombre.

Para realizar la tarea anterior sin cometer el error comentado, lo que


suele hacerse es retornar la direccin de la memoria dinmica con la sentencia
return(), es decir el valor que devuelve la funcin ser un puntero.

Ej.

#include <stdio.h>
#include <stdlib.h>
#include <conio.h>
char * DINAMICA (void); //Retorna un puntero.
void main(void)
{
char *nombre;
clrscr();
nombre = DINAMICA(); //El puntero nombre se guarda
//la direccin que retorna la funcin.
printf(Nombre tecleado: %30s, nombre);
free(nombre); //Libera la memoria reservada en la
//funcin.
}

char * DINAMICA(void)
{
char *nombre; //Puntero local de la funcin.
nombre = (char *)malloc(30*sizeof(char));
scanf(%29[^\n], nombre); fflush(stdin);
return(nombre); //Retorna la direccin de memoria
} //dinmica, donde se ha cargado el nombre.

Por otra parte, es importante tener en cuenta otro detalle. Como se ha


dicho antes, la memoria dinmica reservada dentro de una funcin no se
destruye al finalizar sta. Esto implica que si esa memoria no se va a utilizar
fuera de la funcin, no debe olvidarse ejecutar el correspondiente free(puntero)
dentro de la funcin, para liberar la memoria ocupada.

9
4. Punteros a funciones.
Una caracterstica particularmente confusa pero muy potente del C es el
puntero a funcin.

Las funciones, aunque no son variables, ocupan de igual modo


posiciones fsicas de memoria y, por tanto, existir una primera posicin donde
comienzan sus instrucciones, a la que se le puede asignar un puntero.

La direccin de memoria donde comienza una funcin es la entrada a la


funcin. Si un puntero contiene esa direccin de entrada, se podr realizar una
llamada a la funcin a travs de ese puntero, en lugar de usar el nombre de la
funcin, como hemos hecho hasta ahora.

La declaracin de un puntero a una funcin es la siguiente:

tipo ( *puntero ) ();

donde tipo es el tipo de datos que devuelve la funcin a travs de la


sentencia return.

La asignacin de la direccin de entrada de una funcin a un puntero se


hace de la forma:

puntero = nombre_funcin;

Es decir que el nombre de una funcin sin parntesis ni parmetros nos


devuelve la direccin de entrada de la funcin, por tanto es un puntero, al igual
que el nombre de un array sin corchetes ni ndices nos devuelve la posicin de
memoria donde comienza el array.

Despus de haber realizado la asignacin, se puede hacer la llamada a


la funcin usando el puntero del modo siguiente:

( *puntero ) ( param_1, param_2, ...);


// La llamada normal a la funcin sera:
nombre_funcion(param_1, param_2, ...);

En la llamada, param_1, param_2, etc. son los parmetros que tiene


definidos la funcin. Si la funcin devuelve un valor (tiene return), esa llamada
puede estar incluida en una expresin, como ocurre cuando se usa el propio
nombre de la funcin para llamarla.

<variable> = ( *puntero ) ( param_1, param_2, ...);

Ej. Programa que calcula la potencia de un nmero tecleado elevado a


otro tambin tecleado, llamando a una funcin usando puntero a funcin.

#include <stdio.h>
#include <conio.h>
long potencia(int, int);
10
void main(void)
{
int base, exponente;
long resultado;
long (*pf)(); //Declaracin de puntero a funcin
//que retorna un dato de tipo long.
clrscr();
printf(\n Base: );
scanf(%2d, &base); fflush(stdin);
printf(\n Exponente: );
scanf(%2d, &exponente); fflush(stdin);
pf = potencia; //El puntero pf apunta a la
//direccin de inicio de la funcin.
resultado = (*pf)(base, exponente);
//LLamada a la funcin usando el puntero. Se le
//pasan 2 parmetros int y retorna un dato long.

printf(\n Resultado: %ld, resultado);


}

// Potencia: baseexponente
long potencia(int base, int exponente)
{
int i = 1;
long resultado = 1;
for ( ; i <= exponente ; i++)
resultado *= base;
return(resultado);
}

Debe tenerse en cuenta que C++ (fichero fuente con extensin .CPP)
realiza un chequeo ms exhaustivo en el proceso de compilacin que C
(fichero fuente con extensin .C). Esto implica que en la declaracin de un
puntero a funcin, en C++ deben indicarse los tipos de los parmetros de la
funcin a la que va a apuntar dicho puntero, ya que en caso contrario se
producir el correspondiente error de compilacin.

tipo ( *puntero ) (); //En C.


tipo ( *puntero ) (tipo1, tipo2, ...); //En C++.

En el ejemplo anterior, el puntero habra que declararlo con dos tipos int
como parmetros, porque la funcin a la que va a apuntar tiene esos dos
parmetros, long potencia(int base, int exponente).

long (*pf)(); //En C.


long (*pf)(int, int); //En C++.

La ventaja de usar un puntero a funcin es que una misma llamada a


funcin no tiene que ejecutar siempre la misma funcin, ya que antes de
realizar esa llamada podemos asignar al puntero la funcin que queremos

11
ejecutar, de entre un conjunto de funciones. Se podra decir que es una
llamada a una funcin variable, no siempre a la misma como ocurrira si se
usara el nombre de la funcin en lugar de un puntero.

Ej. Calcular el sumatorio (1+2+3+...+N) o el factorial (1*2*3*...*N) de un


nmero N tecleado.

#include <stdio.h>
#include <conio.h>
#include <ctype.h>
long factorial(int);
long sumatorio(int);
void main(void)
{
int numero;
char operacion;
long resultado;
long (*pf)(); //Puntero a funcin de retorna long.
//En C++ sera: long (*pf)(int);
clrscr();
printf(\n S=Sumatorio F=Factorial Elija S/F:);
operacion = toupper(getche());
switch (operacion)
{
case S:
pf = sumatorio; //pf apunta a sumatorio.
break;
case F:
pf = factorial; //pf apunta a factorial
break;
default:
pf = NULL; //Operacin errnea.
}
if (pf != NULL) //Si es operacin correcta...
{
printf(\n Numero: );
scanf(%2d, &numero); fflush(stdin);
//La siguiente llamada a funcin con pf, est
//ejecutando factorial o sumatorio.
resultado = (*pf)(numero); //Llamada a funcin
printf(\n Resultado: %ld, resultado);
}
else
printf(\nOperacin errnea...);
}//Fin main.

long sumatorio(int numero) //1+2+3+...+numero


{
int i;
long resultado = 0;
for (i=0; i <= numero ; i++)
12
resultado += i;
return(resultado);
}
long factorial (int numero) //1*2*3*...*numero
{
int i;
long resultado = 1;
for (i=1; i <= numero ; i++)
resultado *= i;
return(resultado);
}

Incluso avanzando un paso ms, teniendo en cuenta que un puntero a


funcin es un tipo de variable ms, se podra usar dicho tipo de puntero como
parmetro de una funcin. Esto significa que una funcin puede ser parmetro
de otra.

Ej. El ejemplo anterior podra ser programado como sigue:

#include <stdio.h>
#include <conio.h>
#include <ctype.h>
long factorial(int);
long sumatorio(int);
void calcular(int, long (*)() );
//Un parmetro es punt. a funcin. En C++ sera:
// void calcular(int, long (*)(int) );
void main(void)
{
int numero;
char operacion;
long resultado;
long (*pf)(); //En C++: long (*pf)(int);
clrscr();
printf(\n S=Sumatorio F=Factorial Elija S/F:);
operacion = toupper(getche());
switch (operacion)
{
case S:
pf = sumatorio; //pf apunta a sumatorio.
break;
case F:
pf = factorial; //pf apunta a factorial
break;
default:
pf = NULL; //Operacin errnea.
}

if (pf != NULL) //Si es operacin correcta...


{
printf(\n Numero: );
13
scanf(%2d, &numero); fflush(stdin);
calcular(numero, pf);
//Al colocar ah pf es como pasar la funcin
//sumatorio o factorial como parmetro.
}
else
printf(\nOperacin errnea...);
}

//En C++: void calcular(int numero, long (*pf)(int))


void calcular(int numero, long (*pf)())
{
long resultado;
resultado = (*pf)(numero);
printf(\n Resultado: %ld, resultado);
}
long sumatorio(int numero)
{
int i;
long resultado = 0;
for (i=0; i <= numero ; i++)
resultado += i;
return(resultado);
}
long factorial (int numero)
{
int i;
long resultado = 1;
for (i=1; i <= numero ; i++)
resultado *= i;
return(resultado);
}

En ese ejemplo, la funcin calcular es muy general, ya que permitira


hacer cualquier operacin sobre un nmero, siempre que en su segundo
parmetro, pf, se le pase la direccin de la funcin que realiza dicha operacin.
Esta funcin debe retornar un dato de tipo long, para que se cumpla la
compatibilidad de tipos.

Se puede llegar an ms lejos, ya que puede declararse un array de


punteros a funciones, lo cual podra decirse que es un array de funciones.
Cada puntero del array puede apuntar a una funcin distinta. Se realizar la
llamada a una funcin u otra dependiendo del ndice del array.

<tipo> (*pf[tamao])(); //En C.


<tipo> (*pf[tamao])(tipo1, tipo2,...); //En C++.

En ese caso se define un array de punteros a funciones. El nmero de


componentes del array, es decir el nmero de punteros, es tamao. El ejemplo
anterior podra hacerse de la siguiente manera.

14
Ej.

#include <stdio.h>
#include <conio.h>
#include <ctype.h>
long factorial(int);
long sumatorio(int);
void main(void)
{
int numero, operacion;
long resultado;
long (*pf[2])(); //En C++: long (*pf[2])(int);
//Array de 2 punteros a funciones de tipo long.
clrscr();
pf[0] = sumatorio; //Un puntero apunta a sumatorio.
pf[1] = factorial; //Un puntero apunta a factorial.

printf(\n 0=Sumatorio 1=Factorial Elija 0/1:);


scanf(%d, &operacion); fflush(stdin);
//operacion indica qu puntero hay que usar, 0 o 1.
if (operacion >= 0 && operacion <= 1)
{
printf(\n Numero: );
scanf(%2d, &numero); fflush(stdin);
resultado = (*pf[operacion])(numero);
//Se llama a sumatorio o factorial segn el
//valor de operacion: pf[0] o pf[1].
}
else
printf(\nOperacion errnea...);
getch();
}//Fin main
//Las funciones sumatorio y factorial se escriben a
//continuacin, como en los ejemplos anteriores.
...

Lgicamente se pueden combinar los dos ltimos conceptos, es decir


que un puntero a funcin puede ser parmetro de una funcin y que puede
declararse un array de punteros a funciones. Con lo cual, una de las
componentes de un array de punteros a funciontes se puede pasar como
parmetro de una funcin. La funcin calcular anterior podra recibir como
segundo parmetro una componente del array pf previo. La funcin calcular no
cambia, slo se modifica la lnea donde se realiza la llamada:

calcular(numero, pf[operacion]);

5. Recursividad.

15
Se dice que una funcin es recursiva cuando se llama a s misma, es
decir entre sus instrucciones existe una llamada al propio nombre de la funcin.
El lenguaje C permite la creacin de funciones recursivas.

Generalmente una solucin recursiva a un problema puede sustituirse


por una solucin iterativa, obtenindose incluso mayor rapidez de ejecucin, ya
que la recursividad requiere varias llamadas a funciones, lo cual resulta ms
lento. Sin embargo, hay algoritmos que resueltos de forma iterativa son muy
complejos, mientras que de forma recursiva se obtienen cdigos mucho ms
cortos.

En una funcin recursiva debe existir siempre una condicin de


convergencia, es decir una situacin en la que la funcin no se vuelva a llamar
a s misma y devuelva un valor, para no entrar en un bucle infinito de llamadas.
En cada llamada sucesiva que se va haciendo a la funcin, se estar ms
cerca de la condicin de convergencia. Una vez que se alcanza esa condicin,
se realizan los retornos de todas las llamadas que han quedado en espera,
usando los valores que nos devuelva cada llamada. Desde este modo, se
regresar al punto de partida, donde fue llamada la funcin por primera vez.

Para que un problema puede ser resuelto de forma recursiva, debe


permitir su escritura del siguiente modo: Problema en situacin N = Problema
en situacin N-1 operado con algn otro elemento. Por ejemplo:

- Funcin potencia: An = An-1 * A. La condicin de convergencia se da


cuando n vale 1, cuya potencia vale A.

- Funcin factorial: A! = (A-1)! * A. La condicin de convergencia se da


cuando A vale 1, cuyo factorial vale 1.

- Funcin sumatorio: S(A) = S(A-1) + A. La condicin de convergencia se


da cuando A vale 1, cuyo sumatorio vale 1.

Ej. Calcular recursivamente An para valores enteros (potencia n de A):

#include <stdio.h>
#include <conio.h>
long potencia(int, int);
void main(void)
{
int base, exponente;
long resultado;
clrscr();
printf(\nBase......:);
scanf(%2d, &base);fflush(stdin);
printf(\nExponente.:);
scanf(%2d, &exponente);fflush(stdin);
resultado = potencia(base, exponente);
printf(\nResultado: %ld, resultado);
getch();
16
} //Fin main.
long potencia(int A, int n)
{
long pot;
if (n == 1) //condicin de convergencia.
pot = A;
else
pot = A * potencia( A, n-1 );
//Se llama a s misma.
return(pot);
}

Veamos la secuencia que se sigue con los valores A=7 y n=3:

1 Llamada 2Llamada 3Llamada:n=1


n no vale 1 n no vale 1 n=1. Cond.Converg.
potencia(7,3)
pot=7*potencia(7,2)
potencia(7,2)
pot=7*potencia(7,1)
potencia(7,1)
return(7)
pot = 7*7
return(49)
pot = 7 * 49
return(343)
(Devuelve 343 a main)

6. Tipos de almacenamiento.
El tipo de almacenamiento se especifica al declarar una variable o
funcin. En C existen 4 tipos de almacenamiento, que se aplican a los
siguientes elementos:

- auto: para variables locales.


- register: para variables locales.
- extern: para variables globales y funciones.
- static: para variables locales, globales y funciones.

Como sabemos, las variables locales son las declaradas dentro de algn
bloque de programa, es decir dentro de dos llaves {...}, ya sea en el bloque
main o en cualquier otro. Mientras que las variables globales son las que se
declaran fuera de todos los bloques, generalmente antes del bloque main. Las
funciones en C estn declaradas a nivel global, es decir no existe una funcin
declarada dentro de otra, todas estn al mismo nivel. Esto implica que una
funcin puede ser llamada desde cualquier punto del programa, ya sea desde
dentro del main o desde otra funcin.

El tipo de almacenamiento auto se aplica slo a variables locales y es el


utilizado por defecto, es decir cuando no se indica ningn tipo al declarar las

17
variables. La variable existe solamente dentro del bloque donde se haya
declarado. Al finalizar dicho bloque, la variable ser destruida.

Ej.

void Funcion(void)
{
int Suma;
auto float Nota; //Tanto Suma como Nota son auto.
...
}
//Al finalizar el bloque, se destruyen esas variables.

El tipo de almacenamiento register se aplica slo a variables locales.


Una variable register funciona como si fuera auto, pero con la diferencia de que
ser almacenada en un registro interno del procesador, en lugar de guardarse
en la memoria RAM. Por ello, el acceso a la variable register ser mucho ms
rpido. En caso de que no haya registro internos libres, se utilizar la RAM.
Una variable register slo puede ser entera (int o char) o puntero.

Ej.

void Funcion(void)
{
register int Suma; //Se almacena en registro.
float Nota; //Es auto. Se almacena en RAM.
//El acceso a Suma es mucho ms rpido.
...
}
//Al finalizar el bloque, se destruyen esas variables.

El tipo de almacenamiento extern puede aplicarse a variables globales y


funciones. Un programa en C no siempre se escribe en un solo fichero fuente
(fichero.C), sino que puede desarrollarse con diferentes ficheros fuentes
(fichero1.C, fichero2.C, fichero3.C, etc.), que sern compilados y enlazados
generndose un solo fichero ejecutable (.EXE). Lgicamente, slo debe existir
un nico bloque main entre todos los ficheros fuentes. Cuando una variable
global o una funcin declaradas en un fichero fuente se necesita en otro fichero
fuente, se declarar en ese otro fuente con el tipo extern. Ms adelante se
explica en detalle cmo se crea un ejecutable a partir de varios fuentes.

Ej.

Fichero1.C
#include <stdio.h>
...
extern void Funcion(void);
//Funcin desarrollada en otro fuente.
int Clave;
//Clave es Variable global.

18
void main(void)
{
float Contador; //Variable local.
...
}

Fichero2.C
#include <stdio.h>
... //Aqu no hay main. Est en Fichero1.C.

extern int Clave;


//Clave est declarada en otro fuente: Fichero1.C.
void Funcion(void)
{
Clave = 123; //Se puede usar la variable Clave.
...
} //En Fichero2.C se desarrolla la funcin.

Finalmente el tipo de almacenamiento static puede aplicarse a variables


locales, globales y funciones. Cuando se aplica a una variable local, es decir
que est declarada dentro de un bloque, implica que est variable se crea la
primera vez que se llame al bloque, pero no se destruye cuando dicho bloque
finaliza, sino que se mantiene en memoria. Cuando se realice una nueva
llamada a ese bloque, la variable static mantiene el valor que tena de la
llamada anterior. Si al declarar la variable static no se inicializa, se le asigna
automticamente un 0. En caso de que se inicialice, esta inicializacin slo se
ejecuta la primera vez que se llama al bloque.

Ej.

#include <stdio.h>
...
void Funcion(void);
void main(void)
{
...;
Funcion(); //Primera llamada: Contador comienza
... //con el valor 10 y acaba con 15.
Funcion();
//Segunda llamada: Contador comienza con el valor
//15, de la llamada anterior, y acaba con 20.
}
void Funcion(void)
{
static int Contador = 10; //Var.local y static.
//Inicializar a 10 se ejecuta en la primera llamada
...
Contador = Contador + 5;
}

19
Por otra parte, cuando se utiliza static en una variable global o funcin
(las funciones son todas globales) indicar que dicha variable global o funcin
slo podr ser utilizada en el fichero fuente donde est declarada. Obviamente,
esto tiene sentido cuando se utilizan varios fuentes para crear un ejecutable.
Por ejemplo, si en un fichero fuente se declara la funcin Funcion(), en otro
fichero fuente no podr existir una funcin con ese mismo nombre, Funcion().
Pero utilizando el tipo static s podr existir. Igual ocurre con variables globales.

Ej.

Fichero1.C
#include <stdio.h>
...
static void Funcion(void);
//Esta Funcion() slo se usar en este Fichero1.C
int Clave;
//Variable global. Se podr usar en otros fuentes.
void main(void)
{
Funcion();//Se llama a Funcion() de este Fichero1.C
} //Visualiza un 1.
void Funcion(void)
{
Clave = 1; //Usa Clave de este fuente: Fichero1.C
printf(\n %d, Clave);
}

Fichero2.C
#include <stdio.h>
...
static int Clave;
//Declara Clave global en Fichero2.C para ser
usada //slo en este Fichero2.C. Es una variable
distinta de //Clave de Fichero1.C aunque tengan el
mismo nombre.
void Funcion(void)
{
Clave = 2; //Usa Clave de este fuente Fichero2.C
printf(\n %d, Clave);
}
//Esta Funcion() puede ser usada en otros fuentes. Es
//una funcin distinta de la de Fichero1.C, aunque
//tengan el mismo nombre.

Fichero3.C
#include <stdio.h>
...
int Clave; //Dara ERROR, ya que Clave ya est usada
//en Fichero1.C
extern int Clave; //Usara Clave de Fichero1.C
extern void Funcion(void); //Funcin externa.
20
void Funcion2(void)
{
Funcion();
//Llama a Funcion de Fichero2.C. Visualiza un 2.
printf(\n %d, Clave);
//Usa Clave de Fichero1.C. Visualiza un 1.
}

7. Creacin de un programa con varios ficheros fuentes.


Para crear un programa (.EXE) a partir de varios ficheros fuentes se
puede utilizar la orden BCC desde el smbolo de sistema, de la forma:

C:\>BCC Fichero1.C Fichero2.C Fichero3.C ...

El nombre del fichero ejecutable ser el nombre del primero fichero


fuente escrito, Fichero1 en este caso.

Esa misma tarea puede llevarse a cabo sin salir del IDE, con la opcin
Project. Para crear un proyecto nuevo se elige Open, se introduce una ruta y un
nuevo nombre de proyecto, con lo que se abre la ventana de nuevo proyecto.
Con la tecla <Ins> se abrir una nueva ventana para seleccionar los ficheros
fuentes que formarn el proyecto. Una vez aadidos todos los fuentes, con la
tecla <Esc> se regresa a la ventana de proyecto, en la que aparecen los
nombres de todos los fuentes aadidos. Con la tecla <Supr> (o <Del>) se
pueden quitar fuentes previamente aadidos al proyecto. Finalmente con la
tecla <F10> se activa el men que permite compilar, enlazar y ejecutar. El
nombre del ejecutable ser el dado al proyecto. La opcin Open citada
anteriormente tambin permite abrir un proyecto ya existente, con lo cual se
podr modificar la lista de fuentes que lo forman, siguiente el mismo mtodo.

8. Ficheros cabecera creados por el usuario.


El C proporciona una serie de ficheros cabecera que nos permiten
utilizar una gran cantidad de funciones en nuestros programas. Estos ficheros
tienen extensin .h y se incluyen en un programa a travs de la directiva
#include.

Un fichero cabecera creado por el usuario suele utilizarse para escribir


en l declaraciones de variables globales y de funciones, para poder ser
referenciadas externamente desde otros ficheros fuentes. Aunque realmente en
un fichero cabecera puede incluirse cualquier cdigo fuente, no slo
declaraciones.

Si el fichero .h se coloca entre los smbolos < > (por ejemplo, #include
<stdio.h>) el compilador buscar dicho fichero en la carpeta introducida en el
campo Include Directories. Este campo se halla dentro de Options ms
Directories del men del IDE. Cuando el fichero .h se escribe entre comillas
(por ejemplo, #include micabec.h), el compilador lo buscar primero en la
carpeta actual de trabajo y despus en la carpeta definida en Include
Directories del IDE. Tambin puede indicarse entre las comillas la ruta
completa donde se encuentra el fichero cabecera (por ejemplo, #include
21
C:\CPP\CABECERA\micabece.h), con lo cual el compilador buscar en la ruta
indicada.

Ej.
Fichero1.C (fichero fuente)
#include <stdio.h>
#include micabec.h
void main(void)
{
Funcion1(); //Llamadas a 2 funciones no declaradas
Funcion2(); //en este fichero fuente.
Clave = 123; //Variable global.
}
void Funcion1(void) //Definicin de Funcion1.
{...}

void Funcion2(void) //Definicin de Funcion2.


{...}

micabec.H (fichero cabecera)


void Funcion1(void); //Declaracin de funciones.
void Funcion2(void);
int Clave; //Declaracin de variable global.

En el ejemplo anterior, se ha incluido en el fichero cabecera micabec.h


solamente las declaraciones de las funciones, pero tambin podran haberse
incluido en l las definiciones, y as no escribirlas en el fuente Fichero1.C. En
tal caso, no ser necesario escribir las declaraciones, ya que una declaracin
de una funcin se utiliza cuando se va realizar la llamada antes de la definicin
de la funcin. Pero en este caso la definicin (en micabec.h) est escrita antes
de la llamada (en main de Fichero1.C).

Ej.
Fichero1.C (fichero fuente)
#include micabec.h
void main(void)
{
Funcion1(); //Llamadas a 2 funciones no declaradas
Funcion2(); //en este fichero fuente.
Clave = 123; //Variable global.
}
micabec.H (fichero cabecera)
#include <stdio.h>
int Clave; //Declaracin de variable global.
void Funcion1(void) //Definicin de Funcion1.
{
...
}
void Funcion2(void) //Definicin de Funcion2.
{
...
22
}

Como se observa en el ltimo ejemplo, los ficheros cabecera puede


anidarse, colocando un include dentro de un fichero .h. En ese caso micabec.h
contiene #include <stdio.h>, con lo cual este include no hay que escribirlo en el
fichero fuente Fichero1.C.

23
EJERCICIOS - CAPITULO 10:

Realizar los siguientes algoritmos en pseudocdigo y en C.

1. Funcin que intercambie los valores de dos variables float. Ponerle clave al
programa en el momento de ejecutarlo.

2. Funcin que calcula y devuelve la suma y el producto de dos nmeros


tecleados en el bloque main.

3. Funcin que carga en un array 10 nmeros enteros desde teclado. Otra


funcin debe devolver el valor y la posicin del nmero mayor. Modificar
esta funcin para que devuelva un puntero al nmero mayor.

4. Funcin que cuenta cuntas vocales tiene la cadena tecleada despus del
nombre del programa, al ejecutarlo.

5. Funcin que devuelve la direccin de memoria del nmero mayor de dos


tecleados en el bloque main.

6. Funcin que permite teclear cualquier dato, pasndole como parmetros el


nmero mximo de caracteres permitido, la fila y columna de pantalla donde
teclear y el tipo de dato a teclear, es decir cadena, nmero entero o nmero
real. Slo permitir los caracteres adecuados para cada tipo. Debe controlar
la tecla BackSpace (8). La funcin devuelve los caracteres tecleados en una
cadena, que podr ser convertida con las funciones atol y atof a long y float
respectivamente.

7. Funcin que visualiza un men en pantalla, en la posicin pasada como


parmetro y devuelve la opcin elegida. Las opciones del men se le pasan
usando un array de punteros a cadenas, que sern cargadas en el bloque
main.

8. Realizar el ejecicio 2 del tema 7 (Visualizar un men con las 4 opciones:


1)Longitud circunferencia: 2r; 2)Area del crculo: r2; 3)Volumen de la
esfera: 4/3r3; 4)Salir. El programa finaliza al elegir opcin 4. Realizar una
funcin para cada una de las tres primeras opciones. Se teclear el radio y
se visualizar el resultado en el bloque main) con punteros a funciones.
Despus con un array de punteros a funciones. Y finalmente, con una
funcin que recibe como parmetro el puntero a la funcin que se desea
ejecutar.

9. Funcin que carga, usando un array de punteros (mx. 50) y asignacin


dinmica de memoria, las notas de cada alumno, hasta responder N a
Otro alumno (S/N)?. El nmero de notas de cada alumno ser tecleado
para crear un array dinmico, en cuya primera componente se guarda ese
nmero de notas y en el resto de componentes se guardan las notas
tecleadas. Despus, otra funcin calcular y devolver la nota media del
alumno tecleado en el bloque main, donde tambin se visualizar la nota
media, hasta que el nmero de alumno tecleado sea 0.
24
10. Programa que devuelve el resultado de la expresin X 1 + X2 + ... + XN de
forma recursiva, donde X y N son enteros tecleados en el bloque main (la
funcin pow(X,N) devuelve XN). Aplicar tambin recursividad a la expresin:
11 + 22 + ... + NN.

11. Teclear una serie de nmeros hasta que se teclee un cero. Para cada
nmero tecleado visualizar su factorial: N! = 1 * 2 * 3 * ...* (N-1) * N,
calculado de forma recursiva.

12. Teclear una serie de parejas de nmeros hasta que se responda N a la


pregunta Teclear nmeros (S/N)?. Para cada pareja de nmeros visualizar
la potencia del primero elevado al segundo, teniendo en cuenta que:
XY = X * X *...* X (Y veces). Debe realizarse utilizando recursividad.

13. Invertir una cadena con una funcin recursiva.

14. Programa para hacer apuestas de lotera primitiva. Debe crearse el men
siguiente:
LOTERIA PRIMITIVA:
1. Establecer coste/apuesta.
2. Apuesta manual
3. Apuesta automtica
4. Crear apuesta premiada.
5. Consultar nmero de aciertos.
6. Salir.

La opcin Establecer coste por apuesta simplemente solicitar el


importe de una apuesta. Tanto la apuesta manual como automtica debe
solicitar el nmero de apuestas que se desea hacer y a continuacin se debe
crear un array dinmico de 7 elementos (6 nmeros y el reintegro) para cada
una de las apuestas, usando un array de punteros (mx. 100 apuestas). El
caso manual permitir rellenar cada array introduciendo los 6 nmeros desde
teclado, excepto el reintegro que ser un valor aleatorio (funcin random). El
caso automtico rellena los arrays de forma aleatoria. En una misma apuesta
no puede aparecer 2 veces el mismo nmero. El reintegro debe estar entre 0 y
9, mientras que los 6 nmeros entre 1 y 49. Al hacer apuestas debe
visualizarse el importe total a pagar. La opcin Crear apuesta premiada
generar aleatoriamente una lista de 6 nmeros, el complementario y el
reintegro que ser la combinacin premiada. La opcin 5 visualizar el nmero
de aciertos de las apuestas realizadas con la opcin 2 o 3.

25
EJERCICIOS ADICIONALES DE REPASO:

15. Programa que est en bucle hasta que se responda N a la pregunta


Desea teclear otra cadena (S/N)?. Cada vez que se responda S debe
llamarse a una funcin para teclear una cadena. Desde main se llamar a
otra funcin para que deje en la misma cadena slo las letras y los
nmeros, quitando signos de puntuacin, blancos, etc., sin usar otra cadena
auxiliar. Visualizar cada cadena en main.

16. Mantener una lista de espera con un array dinmico con el tamao justo
para las personas que hay en la lista de espera. La informacin que se
guarda en el array ser el DNI. Debe usarse un men con las opciones: 1.
Apuntar a la lista; 2. Avisar (Borrar el primero de la lista); 3. Eliminar (Borrar
un DNI que puede no ser el primero); 4. Visualizar la lista completa; y 5.
Salir. Deben hacerse las siguientes funciones: una funcin recibe un DNI
como parmetro y devuelve si est en la lista y en qu posicin.; una
funcin visualiza el men y pide teclear opcin hasta que sea correcta
(entre 1 y 5), devolviendo la opcin tecleada; una funcin para cada opcin
del men. Debe recordarse que para aumentar o reducir el tamao del array
dinmico, debe crearse uno nuevo con el nuevo tamao(malloc), copiar el
viejo en el nuevo y despus eliminar el viejo (free).

26

You might also like