martes, 10 de enero de 2017

FUNCIONES

CAPÍTULO 5 FUNCIONES 






Cuando tratamos de resolver un problema, resulta muy útil utilizar la filosofía de
“divide y vencerás”. Esta estrategia consiste en dividir nuestro problema en otros más
sencillos
Cuando realizamos un programa, por ejemplo, en el que se repetirán varias
instrucciones pero con distintos valores que definan los resultados, podemos
construir el programa a base de funciones. Una función es un bloque de
instrucciones a las que se les asigna un nombre. Entonces, cada que necesitemos
que se ejecuten esa serie de instrucciones, haremos una invocación a la función.
Ya hemos hecho uso de algunas funciones, en el capítulo anterior usamos cos y
sin, que están presentes en la librería math.h. Así no nos preocupamos por todo lo
que haga esa función para devolver un resultado, lo único que nos preocupó fue que
datos teníamos que mandar y los que íbamos a recibir.
PROTOTIPOS
El prototipo de una función se refiere a la información contenida en la declaración de
una función. Una función debe de estar definida o al menos declarada antes de hacer
uso de ella.
56
Cuando se declara una función debe de especificarse el tipo de dato que va a
devolver, el nombre de la función, y los parámetros. La siguiente declaración:
int suma(int a, int b);
Especifica una función que devuelve un tipo de dato entero, tiene por nombre suma,
y al invocarla, recibe 2 parámetros de tipo entero.
Esta declaración debe de escribirse antes de la función main, y su definición puede
escribirse después de ésta.
Por ejemplo:
#include <iostream.h>
int suma(int x, int y);
int main(){
    int dato1,dato2;
    cout<<"calculare la suma de 2 numeros"<<endl;
    cout<<"escribe el dato 1 y luego el dos"<<endl;
    cin>>dato1;
    cin>>dato2;
    cout<<"la suma es: "<<suma(dato1,dato2)<<endl;
    cin.ignore();
    cin.get();
    return 0;
}
int suma(int a, int b){
    return a+b;
}
Observamos que primeramente declaramos la función suma para usarla en el bloque
main, y al final del código la definimos. Podíamos haber escrito la función completa
antes del main.
La función main también tiene un prototipo, y por lo tanto también puede recibir
datos.
57
int main ( int argc, char *argv[], char *envp[] )
El parámetro argc es un entero que contiene el número de argumentos pasados a
la función desde la línea de comandos. Es decir, almacena el número de argumentos
en la tabla argv.
El parámetro argv es una tabla de cadenas de caracteres (se verá más adelante),
cada cadena de caracteres contiene un argumento pasado en la linea de comandos,
la primera cadena contiene el nombre con el que fue invocado y las siguientes son
los demás argumentos.
El parámetro envp es una tabla de cadenas, que pasa información sobre una
variable de entorno del sistema operativo.
PASO DE ARGUMENTOS
Cuando hablamos de argumentos, nos referimos a los valores que aparecen en la
llamada a la función. En el ejemplo anterior los argumentos fueron “dato1” y “dato2”.
Y los parámetros son “a” y “b”, los que reciben los datos.
Notemos que el prototipo de la función tiene los nombres de los parámetros distintos
a los de la definición, esto no afectará el comportamiento del programa, pero se vería
mejor si fueran iguales. Lo que no puede cambiar es el tipo de datos que va a recibir.
En el caso del main, el paso de argumentos se realiza desde la línea de comandos,
como ejemplo ponemos el caso de MS-DOS con el comando dir. Si invocamos a este
comando escribimos dir /w /p; el argumento argc tiene el valor 3, y argv contiene 3
cadenas de caracteres: dir, /w, /p.
58
VALORES DE RETORNO
Una función puede regresar cualquier tipo de valor excepto tablas u otras funciones.
Esta limitación podría resolverse utilizando estructuras o punteros, pero se verá más
adelante.
MACROS
Una macro es una parte del código que puede parecer y actuar como una función, se
define después de las librerías mediante un #define.
Se denominan instrucciones de preproceso, porque se ejecutan al comienzo de la
compilación.
Sin embargo, no hay que abusar de ellas, porque podrían entorpecer el código y
hacer lento el programa.
#include <iostream.h>
#define mayor(a,b)   (a>b)? a: b
int main(){
    int a,b;
    cout<<"teclea 2 numeros distintos"<<endl;
    cin>>a;
    cin>>b;
    cout<<"el mayor de esos numeros es: "
        <<(mayor(a,b))<<endl;
    cin.ignore();
    cin.get();
    return 0;
}
59
Debemos tener cuidado si vamos a usar macros, las macros, al compilar el
programa, se sustituyen directamente en donde se invocan, es decir, que en este
ejemplo, es como si se introdujera directamente los condicionales dentro del cout.
Así, debemos tener cuidado en introducir bloques grandes de código en una macro
(algo nada recomendable), sobre todo, cuidado con los puntos y comas.
Podemos ver que las macros al igual que las funciones pueden tener parámetros, sin
embargo, tienen que escribirse con cuidado.
Por ejemplo, al hacer una macro de la siguiente manera:
#define curva(a) 1+2*a+a*a         
4
si en nuestro código colocamos una instrucción
curva(1+3)
se sustituye por
1+2*1+3+1+3*1+3
contrario a lo que esperaríamos si invocáramos curva(4), 13 en lugar de 25. Habría
que definir la macro como 1+2*(a)+(a)*(a).
El uso de macros hace más difícil la depuración de un programa, porque en caso de
haber errores en la macro, el compilador nos avisará que hay errores, pero en la
implementación de la macro. Además de que no realizará comprobaciones de tipo de
datos ni conversiones.
4 Ejemplo del libro Programación en C/C++, Alejandro Sierra Urrecho
60
En el lenguaje C, se acostumbra usar macros para definir constantes (porque a
diferencia de C++, ahí no existe el tipo const. Por ejemplo:
#define PI 3.141592
cambiará todas las apariciones de la palabra “PI” por el valor 3.141592.
RECURSIVIDAD
La recursividad se presenta cuando una función se invoca a si misma. Distintamente
a las iteraciones (bucles), las funciones recursivas consumen muchos recursos de
memoria y tiempo.
Una función recursiva se programa simplemente para resolver los casos más
sencillos, cuando se llama a una función con un caso más complicado, se divide el
problema en dos partes, la parte que se resuelve inmediatamente y la que necesita
de más pasos, ésta última se manda de nuevo a la función, que a su ves la divide de
nuevo, y así sucesivamente hasta que se llegue al caso base. Cuando se llega al
final de la serie de llamadas, va recorriendo el camino de regreso, hasta que por fin,
presenta el resultado.
Un ejemplo clásico para este problema es calcular el factorial de un número, (n!).
Conocemos los casos base, 0!=1, 1!=1. Sabemos que la función factorial puede
definirse como n!=n⋅n−1! . Entonces, 5! = 5*4! = 5*4*3! = 5*4*3*2! =
5*4*3*2*1! = 120.
Nuestro programa quedaría como sigue:
61
#include <iostream.h>
long factorial(long numero);    //prototipo
int main(){
    long numero;
    cout<<"número para calcular el factorial:"<<endl;
    cin>>numero;
    cout<<"el factorial es: "<<factorial(numero)<<endl;
    cin.ignore();
    cin.get();
    return 0;
}
long factorial(long numero){
     if(numero<=1)
                  return 1;
     else
         return numero*factorial(numero­1);
}
Nuestro programa llama a la función factorial con un número inicial, posteriormente,
esta función se llama a sí misma cada vez con número más pequeño hasta que llega
al caso base (numero<=1).
La recursividad es un tema importante en el mundo de la programación, la utilidad de
este tipo de funciones se aprovecha en el diseño de algoritmos de criptografía, de
búsqueda, entre otros. La implementación de la recursividad en nuestros programas
debe de evaluarse con mucho cuidado, debemos de tratar de evitar que se usen
funciones de complejidad exponencial, que se llamen a sí mismas una y otra vez sin
que se tenga un control claro.

No hay comentarios:

Publicar un comentario