Está en la página 1de 12

Funciones Basicas

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.

Definiendo una función


<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


Una vez que en su programa se ha definido una función, esta puede ser llamada las
veces que sean necesarias. Para llamar a una función basta con hacer referencia a su
nombre y si la misma requiere de parámetros estos deberán indicarse dentro de
parentesis. Para llamar a una función que no requiera de parámetros se deberá indicar el
nombre de la misma seguida de parentesis vacios. Por ejemplo, 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 void
Bajo 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>...";
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.

Nota: observe que en la sintaxis para declarar funciones tipo puntero se debe de poner el
símbolo * despues del tipo y antes del nombre de la función que se está declarando.
Esto se puede ver en el programa, ya que la función binstr se declara como: char
*binstr(unsigned int);
// programa : funciones01.cpp
// autor : Wikipedia
#include <iostream.h>
#include <string.h>

using namespace std;


// declaración de prototipo
char *binstr(unsigned int);

// punto de prueba
int main()
{
int n = 128;
count << "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áticas


Dentro 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 defecto


Los 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 mundo" );

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.

// programa : funciones02.cpp
// autor : Oscar E. Palacios
#include <iostream.h>
#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;
count "decimal = " << n << ", binario = " << numstr(n, 2) <<
endl;
count "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 estructurados
Al 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:

// Parametro empleado pasado por valor


void ImprimeEmpleadoV( empleado e)
{
printf("Nombre: %s\n", e.nombre);
printf("Edad : %i\n", e.edad);
printf("Sexo : %c\n", e.sexo);
return;
}

// Parametro empleado pasado por referencia


void ImprimeEmpleadoR( empleado &e )
{
printf("Nombre: %s\n", e.nombre);
printf("Edad : %i\n", e.edad);
printf("Sexo : %c\n", e.sexo);
return;
}

// Parametro empleado pasado como puntero


void ImprimeEmpleadoP( empleado *e )
{
printf("Nombre: %s\n", e->nombre);
printf("Edad : %i\n", e->edad);
printf("Sexo : %c\n", e->sexo);
return;
}

Funciones sobrecargadas
C++, 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 ).

Nota: cuando en los programas se hace una llamada a una función sobrecargada, el
compilador determina a cual de las funciones invocar en base al tipo y número de
parámetros pasados a la función.

// programa : funciones03.cpp
// autor : Oscar E. Palacios
#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();
}
Funciones recursivas
Una función recursiva es aquella que contiene una llamada a si misma. Usted
probablemente se pregunte ¿cúal es el objetivo de que una función se llame a si
misma ?. Bueno, déjeme decirle que habrá ocasiones en las cuales el uso de ésta técnica
tiene sus ventajas. Por ejemplo, las personas que manejan el lenguaje de las
matemáticas saben que hay ciertos procesos que son repetitivos, tal es el caso del
procedimiento para obtener el factorial de un número. Así, procederemos a escibir un
programa y dentro del mismo la función Factorial, misma que servirá como ejemplo de
una función recursiva. factorial tomará un solo parámetro tipo Long (entero largo)
pasado por valor y regresará el factorial del número.

! ADVERTENCIA ! Las funciones recursivas deben tener una forma para


poder salir y así evitar que éstas se esten llamado de manera infinita, ya que ésto
ocasionaría un error conocido como desbordamiento de pila (Stack Overflow).

En el programa titulado funciones04.cpp que se verá enseguida, se crean dos funciones;


Factorial y Factorial2, con el objetivo de que el estudiante pueda observar la diferencia
entre la construcción de una función recursiva y otra igual pero no recursiva.

Nota: El factorial de un número se define como n! y significa "Multiplicar todos los


números comprendidos entre el 1 y n; inclusive, n > 0". Tambien se considera que el
factorial de 0 es igual a 1, y se define como: 0! = 1.

Ejemplo. Dado el número 5, calcular el factorial.


Solucion: 5! = 1 x 2 x 3 x 4 x 5 = 120
La formula general para calcular el factorial de un número n puede expresarse como: n
(n-1) (n-2)...(1), misma que será utilizada por la función recursiva factorial() del
siguiente programa.
// programa funciones04.cpp
// Autor: Oscar E. Palacios
#include <stdlib.h>
#include <iostream.h>

// factorial es una funcion recursiva.


// Nota: el maximo valor para el parámetro n es 16
long factorial(long n)
{
if (n <=1) return(1);
else return( n * factorial(n - 1) );
}

// factorial2 es una funcion no recursiva.


// Nota: el maximo valor para el parámetro n es 16
long factorial2(long n)
{
long p, r;

r = 1;
if (n >1) {
for (p=1; p<=n; p++) r = r * p;
}
return r;
}
// punto de prueba
int main()
{
cout << "Demostración de una función recursiva" << endl << endl;

// llamada a la funcion recursiva


cout << "Factorial de 6 = " << factorial(6) << endl;

// llamada a la funcion no recursiva


cout << "Factorial de 6 = " << factorial2(6) << ednl;

system("pause");
return 0;
}

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.

// programa funciones05.cpp
// Autor: Oscar E. Palacios

#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.

// programa funciones06.cpp
// Autor: Oscar E. Palacios

#include <iostream.h>
#include <stdarg.h>

// 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;
}

Plantillas de función ( Templates )


Uno de los paradígmas de la programación es lo que se conoce como: Programación
Genérica. En ese sentido, las plantillas de funciones nos acercan al mencionado
paradígma puesto que podemos usar el mecanismo conocido como plantillas (en ingles,
Templates ). Las plantillas de funciones estan pensadas con la idea de crear 'moldes' en
donde el código para llevar a cabo determinadas tareas es básicamente el mismo y
donde la diferencia sería el tipo de datos sobre los que opera la función. Por ejemplo,
pensemos en la creación de una función que reciba un par de objetos y que intecambie
el valor de los mismos, es decir, si a la función se le pasan los parámetros ( X, Y ) el
valor de X se le asignara a Y, y el valor de Y se le pasará a X. Para comenzar, podemos
pensar en crear una función llamada Swap() para intercambiar un par de números
enteros y el código de la misma se vería más o menos como:

void Swap(int &a, int &b)


{
int temp = a;
a = b;
b = temp;
}

Ahora bien, si usted pone a prueba la función Swap() verá que la misma funciona
perfectamente, salvo que tiene la limitante de operar solamente con números de tipo int.
Luego, si quisieramos intercambiar objetos de tipo double, float, char, etc. ¿ sera que
debemos repetir el mismo código para cada uno de los tipos deseados ?. La respuesta a
dicha interrogante es No, ya que la mayoría de compiladores actuales para C++
permiten el use de plantillas.

Con el fin de poner un ejemplo, vamos a escribir un programa y en el mismo


declararemos la plantilla de función SwapGenerico(), misma que servirá para
intercambiar el valor de dos objetos de cualquier tipo.

// programa funciones06.cpp
// Autor: Oscar E. Palacios

#include <iostream.h>

template <class T> void SwapGenerico(T &a, T &b)


{
T temp = a;
a = b;
b = temp;
};

int main()
{
int i1 = 100;
int i2 = 5;
cout << "antes.. "<< "i1 = " << i1 << " i2 = " << i2 << endl;
SwapGenerico(i1, i2); // swap de números enteros
cout << "despues "<< "i1 = " << i1 << " i2 = " << i2 << endl;

cout << endl;


char c1 = 'a';
char c2 = 'b';
cout << "antes.. " << "c1 = " << c1 << " c2 = " << c2 << endl;
SwapGenerico(c1, c2); // swap de caracteres
cout << "despues " << "c1 = " << c1 << " c2 = " << c2 << endl;

cin.get();
return 0;
}
Otro problema
En el ejemplo anterior vimos que la plantilla SwapGenerico solucionó el problema de
intercambiar el contenido de una pareja de objetos primitivos ( char, int, float, double,
etc. ). Sin embargo, si usted pone a prueba la misma plantilla para hacer un Swap de
cadenas de caracteres, verá que el compilador le informará de un error, ya que a las
cadenas de caracteres no es posible aplicarles el operador de asignacíon (=). Pero, ya
que C++ lo permite, podemos escribir una función SwapGenerico sobrecargada y así el
compilador decidirá si usar la plantilla o usar la función de sobrecarga. Y de tal manera
abremos resuelto el problema. Así, puede agregar la siguiente función al programa visto
anteriormente.

void SwapGenerico(char *a, char *b )


{
char *tmp = new char[strlen(a)];
strcpy(tmp, a);
strcpy(a, b);
strcpy(b, tmp);
delete tmp;
}

También podría gustarte