UNIDAD 5 Funciones

Funciones

Definiendo una función


#include <iostream>
#include <string.h>
 
using namespace std;
 
// declaración de prototipo
char *binstr(unsigned int);
 
// punto de prueba
int main()
{
    int n = 128;
    cout << "decimal = " << n << ",  binario = " << binstr(n) << endl;
    cin.get();
}
 
// definición de función binstr()
// nota: esta funcion requiere de la librería estándar string
char *binstr(unsigned int n)
{
    static char buffer[65];
    int i = 0;
 
    strcpy(buffer, "0");
 
    if (n > 0) {
        while (n > 0) {
            buffer[i] = ( n & 1 ) + '0';
            i++;
            n >>= 1;
        }
        buffer[i] = '\0';
        strrev(buffer);
     }    // fin (n > 0)
    return buffer;
}
Variables estáticas y automáticasDentro de una función, las variables y/o constantes pueden ser declaradas como: 
auto (por omisión) o como static. Si una variable dentro de una función es 
declarada como estática significa que la misma retendrá su valor entre las 
llamadas a la función. Por otro lado, la variables automáticas pierden su valor 
entre las llamadas. En el programa anterior puede verse que el arreglo de 
caracteres (buffer[65]) se ha declarado como estático para garantizar que dicho 
buffer retenga los datos aún despues de que la función termine. En el mismo 
ejemplo, si el buffer no se declara como estático, el contenido del mismo podría 
ser destruido al salir de la función y los resultados obtenidos podrían ser no 
deseados.

Parámetros constantes
Los parámetros usados por una función pueden declararse como constantes ( const ) 
al momento de la declaración de la función. Un parámetro que ha sido declarado 
como constante significa que la función no podrá cambiar el valor del mismo 
( sin importar si dicho parámetro se recibe por valor o por referencia ).
Ejemplo:
int funcionX( const int n );
void printstr( const char *str );
Parámetros con valor por defectoLos parámetros usados por una función pueden declararse con un valor por defecto. 
Un parámetro que ha sido declarado con valor por defecto es opcional a la hora de 
hacer la llamada a la función. Ejemplo: Dada la función:

void saludo( char* mensaje = "Hola sudafrica 2010" );
la misma puede ser invocada como:

saludo();  // sin parámetro
saludo("Sea usted bienvenido a C++"); // con parámetro
Para ver un ejemplo más, vamos a considerar el caso de la función binstr() del 
programa funciones01. Ahora, vamos modificar dicha función, salvo que esta 
ocasión nos interesa que la misma sirva para convertir números decimales en 
cadenas numéricas y cuya base de conversión sea pasada como parámetro. Es decir, la
función de la que estamos hablando podrá convertir números decimales a: binario, 
octal, decimal, hexadecimal, etc.; y la única condición será que la base indicada 
esté entre el 2 y el 36, inclusive.

Nota: Ya que la función servirá para convertir números a cualquier representación 
la nombraremos como numstr() en lugar de binstr(). Si la función es invocada sin 
el parámetro base regresará una cadena de digitos decimales.

#include <iostream>
#include <stdlib.h>
 
using namespace std;
 
// declaración de prototipo
char *numstr(unsigned int, const int base = 10);
 
// punto de prueba
int main()
{
    int n = 128;
    cout << "decimal = " << n << ",  binario = " << numstr(n, 2) << endl;
    cout << "decimal = " << n << ",  octal.. = " << numstr(n, 8) << endl;
    cin.get();
}
 
// definición de función numstr()
// nota: esta funcion requiere de la librería stdlib.h
char *numstr(unsigned int n, const int base)
{
    static char buffer[65];
    itoa(n, buffer, base);
    return buffer;
}
 
Parámetros de tipo puntero
Anteriormente se mencionó que en C++ los parámetros a una función pueden pasarse 
por valor o por referencia, al respecto, podemos agregar que los parámetros 
también pueden pasarse como punteros. El paso de parámetros de punteros es 
bastante parecido al paso de parámetros por referencia, salvo que el proceso de 
los datos dentro de la función es diferente. Por ejemplo, las funciones:
 
void referencia( int &X ) { X = 100; }
void puntero( int *X ) { *X = 100; }
ambas reciben un puntero o referencia a un objeto de tipo entero, por lo tanto 
cualquiera de las funciones del ejemplo puede cambiar el valor de la variable 
entera apuntada por X, la diferencia radica en la forma en que cada una de las 
mismas lleva cabo la tarea. Si en la función puntero() en lugar de usar *X = 100; 
se usara X = 100; se le asignaría 100 al puntero X, más no al objeto apuntado 
por X, y esto podría ser la causa de que el programa se terminara de manera 
abrupta.
 
Parámetros estructuradosAl igual que cualquier otro tipo los parámetros de tipo estruturado pueden pasarse 
por valor o por referencia, sin embargo, podría ser que si una estructura es pasada
por valor el compilador mostrara una advertencia ( warning ) indicando que se 
pasado por valor una estructura, puesto que el paso de estructuras por valor es 
permitido usted puede ignorar la advertencia, pero lo mejor es pasar estructuras 
por referencia. Si una estructura es pasada por valor y si esta es muy grande 
podria ser que se agotara la memoria en el segmento de pila ( Stack Segment ), 
aparte de que la llamada a la función sería más lenta.

Para ver un ejemplo, consideremos el caso del siguiente tipo estructurado:
struct empleado {
    char nombre[32];
    int  edad;
    char sexo; };
Ahora, pensemos que deseamos escribir una función para imprimir variables del tipo empleado. Así, la función puede escribirse de las tres maneras siguientes:
void ImprimeEmpleadoV( empleado e)
{
 cout << "Nombre: " << e.nombre << endl;
 cout << "Edad: " << e.edad << endl;
 cout << "Sexo: " << e.sexo << endl;
 
}
 
// Parametro empleado pasado por referencia
void ImprimeEmpleadoR( empleado &e )
{
 cout << "Nombre: " << e.nombre << endl;
 cout << "Edad: " << e.edad << endl;
 cout << "Sexo: " << e.sexo << endl;
 
}
 
// Parametro empleado pasado como puntero
void ImprimeEmpleadoP( empleado *e )
{
 cout << "Nombre: " << e->nombre << endl;
 cout << "Edad: " << e->edad << endl;
 cout << "Sexo: " << e->sexo << endl;
}
Funciones sobrecargadasC++, a diferencia del C estándar, permite declarar funciones con el mismo nombre y
a esto se conoce como sobrecarga de funciones. Las funciones sobrecargadas pueden
coincidir en tipo, pero al menos uno de sus parámetros tiene que ser diferente. En
todo caso, si usted trata de declarar funciones sobrecargadas que coincidan en 
tipo y número de parámetros el compilador no se lo permitirá. Para poner un ejemplo
vamos a considerar el caso de dos funciones cuyo nombre será divide, ambas regresarán 
el cociente de dos números, salvo que una de ellas operará sobre números enteros 
y la otra lo hará sobre números reales ( de punto flotante ).

#include <iostream.h>
#include <stdlib.h>
 
using namespace std;
 
// divide enteros
int divide(int a, int b)
{
    cout << "división entera" << endl;
    return ( (b != 0) ? a/b : 0);
}
 
// divide reales
double divide(double a, double b)
{
    cout << "división real" << endl;
    return ( (b != 0) ? a/b : 0);
}
// punto de prueba
int main()
{
    cout << divide(10, 3) << endl;
    cout << divide(10.0, 3.0) << endl;
    cin.get();
}
Número variable de parámetros
En C,C++ se pueden crear funciones que operen sobre una lista variable de parámetros,
es decir, en donde el número de parámetros es indeterminado. En esta sección se 
mostrará un ejemplo de la manera en que podemos crear funciones para manejar tales 
asuntos, y para ello haremos uso de tres macros soportadas por C++:
 
  1. va_list puntero de argumentos
  2. va_start inicializar puntero de argumentos
  3. va_end liberar puntero de argumentos
La sintaxis que usaremos para declarar funciones con lista de parámetros variables es:
1) tipo nombrefuncion(...)
2) tipo nombrefuncion(int num, ...)
donde:
 
  1. tipo es el tipo regresado por la función
  2. nombrefuncion es el nombre de la función
  3. int num es el número de parámetros que la función procesará
  4. ... esta notación se emplea para indicar que el número de parámetros es
variable.
Nota: observe que la primera forma de declaración es realmente variable el número 
de parámetros a procesar y en estos casos se debe establecer el mecanismo para 
determinar cuando se ha procesado el último de los argumentos, en el segundo tipo 
de declaración el número total de parámetros a procesar es igual al valor del 
parámetro num.
 
En el siguiente programa, por ejemplo, se define una función ( printstr ) que 
despliega una lista variable de cadenas de caracteres.
 
#include <iostream.h>
#include <stdarg.h>
 
// despliega una lista de cadenas, la ultima debe ser NULL
void printstr(...)
{
   va_list ap;
   char    *arg;
   va_start(ap, 0);
   while ( (arg = va_arg(ap, char*) ) != NULL) {
      cout << arg;
   }
   va_end(ap);
}
 
int main()
{
    printstr("Hola, ", "Esta es\n", "una prueba\n", NULL);
    cin.get();
    return 0;
}
En el programa que se listará en seguida, se define la función suma(), misma que 
operará sobre listas de números enteros, la función devolverá la suma de dichos 
números.
#include <iostream>
#include <stdarg.h>
 
using namespace std;
 
// Esta función opera sobre una lista variable de números enteros
int suma( int num, ... )
{
    int total = 0;
    va_list argptr;
    va_start( argptr, num );
 
    while( num > 0 ) {
    total += va_arg( argptr, int );
    num--;
    }
 
    va_end( argptr );
    return( total );
}
 
int main()
{
    cout << suma(4, 100, 200, 300, 400) << endl;
    cin.get();
    return 0;
}
VARIABLES
Variables.
En lenguaje C/C++, una variable es un identificador.  El lugar donde se declara
una variable afecta mucho a la manera en que otras partes puedan usarla.
Existen 3 tipos de variables:
-          variables locales
-         variables globales
-        parámetros formales
 
Recursividad
Para algunos tipos de problemas, es útil tener funciones que se llamen a sí mismas. 
Una función recursiva  es una función que se llama a sí misma, ya sea directa o
indirectamente a través de otra función.  A veces llamada definición circular, la recursión es
el proceso de definir algo en términos de sí mismo.
Por ejemplo, una forma recursiva para definir un número entero es como los dígitos
0, 1, 2, 3, 4, 5, 6, 7, 8, 9, más o menos en los números enteros.  Por ejemplo, el número 15 es
el número 7 más el número 8; 21 es 9 más 12; 12 es 9 más 3, y así sucesivamente.
Para que un lenguaje de computadora sea recursivo, una función debe poder llamarse a sí
misma.  Un ejemplo sencillo es la función factorial ( ), que calcula el factorial de un entero
(5!= 1*2*3*4*5):
#include <iostream.h> 
long fact (int);
main( ) { 
     int num; 
     cout << "Introduzca un número entero: "; 
     cin >> num; 
     cout << "Su factorial es:", fact(num) << endl; //llamada por valor
     getch ( );
 } 
long fact (int n) { 
     if (n==1) return 1; 
     return n * fact (n-1); 
} 
 
como mandar una cadenada de caracteres a una 
funcion
a ver aki tienes un pekeño ejemplo, lo que haces es convertir el
primer y ultimo caracter d una cadena a mayuskulas:
 
Código:
#include<stdio.h> char *Convertir(char cad[]) { int i;    for ( i = 0 ; cad[i] != '\0' ; i++ )          if( i == 0 || cad[i-1]==' ' || cad[i+1]== ' ' || cad[i+1]=='\0')             if(cad[i] >= 'a' && cad[i] <= 'z') cad[i] -= 32;    return cad; } main() { char texto[80];    printf("\n\rINGRESE TEXTO :"); gets(texto);    printf("\n\r%s\n\r", Convertir(texto)); }
 
Ordenaciones en Arreglos La importancia de mantener nuestros arreglos ordenados radica en que es mucho más rápido tener acceso a un dato en un arreglo ordenado que en uno desordenado. Existen muchos algoritmos para la ordenación de elementos en arreglos, enseguida veremos algunos de ellos. a)Selección Directa Este método consiste en seleccionar el elemento más pequeño de nuestra lista para colocarlo al inicio y así excluirlo de la lista. Para ahorrar espacio, siempre que vayamos a colocar un elemento en su posición correcta lo intercambiaremos por aquel que la esté ocupando en ese momento. El algoritmo de selección directa es el siguiente: i <- 1 mientras i<= N haz         min <-i         j <- i + 1         mientras j <= N haz                 si arreglo[j] < [min] entonces                         min <-j                 j <- j + 1         intercambia(arreglo[min],arreglo[i])         i <- i +1 b)Ordenación por Burbuja Es el método de ordenación más utilizado por su fácil comprensión y programación, pero es importante señalar que es el más ineficiente de todos los métodos . Este método consiste en llevar los elementos menores a la izquierda del arreglo ó los mayores a la derecha del mismo. La idea básica del algoritmo es comparar pares de elementos adyacentes e intercambiarlos entre sí hasta que todos se encuentren ordenados. i <- 1 mientras i < N haz         j <- N         mientras j > i haz                 si arreglo[j] < arreglo[j-1] entonces                         intercambia(arreglo[j],arreglo[j-1])                 j < j - 1         i <- i +1 c)Ordenación por Mezcla Este algoritmo consiste en partir el arreglo por la mitad, ordenar la mitad izquierda, ordenar la mitad derecha y mezclar las dos mitades ordenadas en un array ordenado. Este último paso consiste en ir comparando pares sucesivos de elementos (uno de cada mitad) y poniendo el valor más pequeño en el siguiente hueco. procedimiento mezclar(dat,izqp,izqu,derp,deru) inicio         izqa <- izqp         dera <- derp         ind <- izqp         mientras (izqa <= izqu) y (dera <= deru) haz                 si arreglo[izqa] < dat[dera] entonces                         temporal[ind] <- arreglo[izqa]                         izqa <- izqa + 1                 en caso contrario                         temporal[ind] <- arreglo[dera]                         dera <- dera + 1                 ind <- ind +1         mientras izqa <= izqu haz                 temporal[ind] <- arreglo[izqa]                 izqa <- izqa + 1                 ind <- ind +1         mientras dera <= deru haz                 temporal[ind] <=dat[dera]                 dera <- dera + 1                 ind <- ind + 1         para ind <- izqp hasta deru haz                 arreglo[ind] <- temporal[ind] fin Búsquedas en Arreglos Una búsqueda es el proceso mediante el cual podemos localizar un elemento con un valor especifico dentro de un conjunto de datos. Terminamos con éxito la búsqueda cuando el elemento es encontrado. A continuación veremos algunos de los algoritmos de búsqueda que existen. a)Búsqueda Secuencial A este método tambien se le conoce como búsqueda lineal y consiste en empezar al inicio del conjunto de elementos , e ir atravez de ellos hasta encontrar el elemento indicado ó hasta llegar al final de arreglo. Este es el método de búsqueda más lento, pero si nuestro arreglo se encuentra completamente desordenado es el único que nos podrá ayudar a encontrar el dato que buscamos. ind <- 1 encontrado <- falso mientras no encontrado y ind < N haz si arreglo[ind] = valor_buscado entonces encontrado <- verdadero en caso contrario ind <- ind +1 b)Búsqueda Binaria Las condiciones que debe cumplir el arreglo para poder usar búsqueda binaria son que el arreglo este ordenado y que se conozca el numero de elementos. Este método consiste en lo siguiente: comparar el elemento buscado con el elemento situado en la mitad del arreglo, si tenemos suerte y los dos valores coinciden, en ese momento la búsqueda termina. Pero como existe un alto porcentaje de que esto no ocurra, repetiremos los pasos anteriores en la mitad inferior del arreglo si el elemento que buscamos resulto menor que el de la mitad del arreglo, o en la mitad superior si el elemento buscado fue mayor. La búsqueda termina cuando encontramos el elemento o cuando el tamaño del arreglo a examinar sea cero. encontrado <- falso primero <- 1 ultimo <- N mientras primero <= ultimo y no encontrado haz mitad <- (primero + ultimo)/2 si arreglo[mitad] = valor_buscado entonces encntrado <- verdadero en caso contrario si arreglo[mitad] > valor_buscado entonces ultimo <- mitad - 1 en caso contrario primero <- mitad + 1 c)Búsqueda por Hash La idea principal de este método consiste en aplicar una función que traduce el valor del elemento buscado en un rango de direcciones relativas. Una desventaja importante de este método es que puede ocasionar colisiones. funcion hash (valor_buscado) inicio hash <- valor_buscado mod numero_primo fin inicio <- hash (valor) il <- inicio encontrado <- falso repite si arreglo[il] = valor entonces encontrado <- verdadero en caso contrario il <- (il +1) mod N hasta encontrado o il = inicio  

Una función es un conjunto de líneas de código que realizan una tarea específica y puede retornar un valor. Las funciones pueden tomar parámetros que modifiquen su funcionamiento. Las funciones son utilizadas para descomponer grandes problemas en tareas simples y para implementar operaciones que son comúnmente utilizadas durante un programa y de esta manera reducir la cantidad de código. Cuando una función es invocada se le pasa el control a la misma, una vez que esta finalizó con su tarea el control es devuelto al punto desde el cual la función fue llamada.

<tipo> [clase::] <nombre> ( [Parámetros] )
{
    cuerpo;
}

Ejemplo de una función
Para comenzar, vamos a considerar el caso en el cual se desea crear la función cuadrado(), misma que deberá volver el cuadrado de un número real (de punto flotante), es decir, cuadrado() aceptará números de punto flotante y regresará una respuesta como número flotante.
Nota: aunque para la función que veremos el tipo de retorno coincide con el tipo de parámetro pasado, algunas veces las cosas pueden cambiar, es decir, no es obligatorio que una función reciba un parámetro de un tipo y que tenga que regresar una respuesta de dicho tipo.

// regresar el cuadrado de un número
double cuadrado(double n)
{
    return n*n;
}
 
Parámetros
 
Normalmente, las funciones operan sobre ciertos valores pasados a las mismas 
ya sea como constantes literales o como variables, aunque se pueden definir
funciones que no reciban parámetros. Existen dos formas en C++ de pasar 
parámetros a una función; por referencia o por valor. El hecho es que si en 
una declaración de función se declaran parámetros por referencia, a los mismos 
no se les podrá pasar valores literales ya que las referencias apuntan a 
objetos (variables o funciones) residentes en la memoria; por otro lado, si un 
parámetro es declarado para ser pasado por valor, el mismo puede pasarse como 
una constante literal o como una variable. Los parámetros pasados por referencia
pueden ser alterados por la función que los reciba, mientras que los parametros
pasados por valor o copía no pueden ser alterados por la función que los recibe
es decir, la función puede manipular a su antojo al parámetro, pero ningún 
cambio hecho sobre este se reflejará en el parámetro original.
 
Parametros por valor
La función cuadrado() (ver arriba) es un clásico ejemplo que muestra el paso de par
ámetros por valor, en ese sentido la función cuadrado() recibe una copia del parámetro 
n. En la misma función se puede observar que se realiza un calculo ( n*n ), sin embargo
el parámetro original no sufrirá cambio alguno, esto seguirá siendo cierto aún cuando 
dentro de la función hubiera una instrucción parecida a n = n * n; o n*=n;

Parametros por referencia
Para mostrar un ejemplo del paso de parámetros por referencia, vamos a retomar 
el caso de la función cuadrado, salvo que en esta ocasión cambiaremos ligeramente 
la sintaxis para definir la misma. Veamos:
// regresar el cuadrado de un número
double cuadrado2(double &n)
{
    n *= n;
    return n;
}
Al poner a prueba las funciones cuadrado() y cuadrado2() se podrá verificar que la primera de estas no cambia el valor del parámetro original, mientras que la segunda sí lo hace. Llamar a una función para llamar a la función cuadrado() vista anteriormente, podemos emplear:
cout << cuadrado(25);
cout << cuadrado(X);
R = cuadrado(X);  // guardar en R el cuadrado de X
Funciones voidBajo ciertas circunstancias se deseará escribir funciones que no regresen valor 
alguno (esto sería algo parecido a escribir procedures en Pascal) y para ello
podemos declarar a la función como void. La palabra reservada void es utilizada
para declarar funciones sin valor de retorno y también para indicar que una 
función específica no requiere de parámetros. Por ejemplo, la función pausa() que
se verá en seguida, no devolverá valor alguno y la misma no requiere de parámetros.
// esta función requiere de la librería iostream
void pausa(void)
{
    cout << "Por favor presione <Enter> HOLA...";
    cin.get();
    cin.ignore(255, '\n');  // rechazar caracteres introducidos antes de <Enter>
}
Notas: se debe de aclarar que el uso de la palabra void dentro de los parentesis es opcional al momento de declarar una función. Asi, la función pausa() podría haberse declarado como void pausa(), y la misma puede invocarse como: pausa();. Funciones anidadas A diferencia de Pascal, el lenguaje C,C++ no permite anidar funciones, sin embargo, dentro de una funcíon puede existir la llamada a una o más funciones declaradas previamente. Funciones de tipo puntero (*) En muchas ocasiones se desea que ciertas funciones regresen una referencia o puntero hacia un tipo ( sea éste estructurado o no) específico de dato en lugar de un valor específico. En tales casos, la función se deberá declarar como para que regrese un puntero. Por ejemplo, supongamos que deseamos crear una función para convertir un número entero en notación decimal a una cadena de caracteres en forma de números binarios, luego, la función mencionada podría escribirse para que reciba el número entero como parámetro y regrese un puntero a una cadena de caracteres conteniendo la conversión. Para ser más puntuales, vamos a escribir un programa en donde se verá la función binstr(), y cuyo objetivo será precisamente convertir números decimales en cadenas binarias.