AnteriorPosterior

9. Funciones (1: creación)

Por: Nacho Cabanes
Actualizado: 20-04-2019 10:29
Tiempo de lectura estimado: 25 min.

 

C++

9. Funciones

9.1. Cómo evitar trabajo repetitivo

En muchos casos, nos encontraremos con tareas que tenemos que repetir varias veces en distintos puntos de nuestro programa. Si tecleamos varias veces el mismo fragmento de programa no sólo tardaremos más en escribir: además el programa final resultará menos legible, será más fácil que cometamos algún error alguna de las veces que volvemos a teclear el fragmento repetitivo, o cuando decidamos hacer una modificación y olvidemos hacerla en alguno de los fragmentos. Por eso, conviene evitar que nuestro programa contenga código repetitivo. Una de las formas de evitarlo es descomponerlo en bloques que llamaremos "funciones".

Para ver la necesidad de funciones en un caso sencillo, vamos a comenzar por crear un programa que escriba 3 textos, y subraye cada uno de ellos con 20 guiones:

// Introducción a C++, Nacho Cabanes
// Ejemplo 09.01:
// Primer acercamiento a las funciones: fuente repetitivo
 
#include <iostream>
using namespace std;
 
int main() 
{
    int i; // Para repetir con "for"
 
    cout << " Primer ejemplo" << endl;
    for (i=0; i<20; i++)
        cout << "-";
    cout << endl;
 
    cout << " Segundo ejemplo" << endl;
    for (i=0; i<20; i++)
        cout << "-";
    cout << endl;
 
    cout << " Tercer ejemplo" << endl;
    for (i=0; i<20; i++)
        cout << "-";
    cout << endl;
 
    return 0;
}

Funciona, pero es muy repetitivo. Sería un poco más elegante si creamos una "función" llamada "subrayar", que de esos pasos repetitivos, de modo que podríamos resscribirlo así:

// Introducción a C++, Nacho Cabanes
// Ejemplo 09.02:
// Segundo ejemplo de funciones: un procedimiento
 
#include <iostream>
using namespace std;
 
void subrayar() 
{
    int i; // Para repetir con "for" 
    for (i=0; i<20; i++)
        cout << "-";
    cout << endl;
}
 
int main() 
{    
    cout << " Primer ejemplo" << endl;
    subrayar();
 
    cout << " Segundo ejemplo" << endl;
    subrayar();
 
    cout << " Tercer ejemplo" << endl;
    subrayar();
 
    return 0;
}

Ahora nuestro programa tiene dos "bloques": uno de ellos es "main", el cuerpo del programa, que ya cononcíamos; el otro bloque se llama "subrayar" y se encarga de hacer la tarea repetitiva y que ayuda a que el cuerpo del programa sea más legible que antes. La palabra "void" que aparece al principio indica que no se trata de una función matemática que devuelva un resultado numérico, sino de un "procedimiento" o subrutina, que realiza ciertos pasos pero no devuelve ningún resultado.

Ejercicios propuestos:

  • (9.1.1) Crea una función "saludar", que escriba el texto "Hola" en pantalla.
  • (9.1.2) Crea una función "escribirTabla5", que escriba en pantalla la tabla de multiplicar del número 5.

9.2. Parámetros de una función

Es muy frecuente que nos interese además indicarle a nuestra función ciertos datos con los que queremos que trabaje. Por ejemplo, podemos mejorar la función "subrayar", de modo que no escriba siempre 20 guiones, sino la cantidad que le indiquemos:

// Introducción a C++, Nacho Cabanes
// Ejemplo 09.03:
// Tercer ejemplo de funciones: parámetros
 
#include <iostream>
using namespace std;
 
void subrayar(int cantidad) 
{
    int i; // Para repetir con "for" 
    for (i=0; i<cantidad; i++)
        cout << "-";
    cout << endl;
}
 
int main() 
{    
    cout << " Primer ejemplo" << endl;
    subrayar(16);
 
    cout << " Segundo ejemplo" << endl;
    subrayar(17);
 
    cout << " Tercer ejemplo" << endl;
    subrayar(16);
 
    return 0;
}

Como se puede ver, los parámetros se indican con un formato muy parecido al de la declaración de una variable: primero el tipo y luego el nombre de archivo. La diferencia con la declaración de variables es que no hay un "punto y coma" al final, y que si necesitamos varios parámetros, habrá que indicar tanto el tipo de datos como el nombre para todos ellos.

Ejercicios propuestos:

  • (9.2.1) Crea una función "saludarVariasVeces", que escriba el texto "Hola" en pantalla, tantas veces como se indique como parámetro.
  • (9.2.2) Crea una función "escribirTabla", que escriba en pantalla la tabla de multiplicar del número que se le indique como parámetro.
  • (9.2.3) Crea una función "escribirProducto", que escriba en pantalla la multiplicación (detalle y resultado) de los dos números que se le indiquen como parámetro. Por ejemplo, si se usa "escribirProducto(3,5)" en pantalla deberá aparecer "3 x 5 = 15".
  • (9.2.4) Crea una función "escribirGuiones" que escriba en pantalla tantos guiones ("-") como se indique como parámetro y no devuelva ningún valor.
  • (9.2.5) Crear una función "dibujarRectangulo", que reciba como parámetros la anchura y la altura del rectángulo a mostrar, y muestre en pantalla un rectángulo de ese tamaño, relleno de caracteres "#". Por ejemplo, para anchura 4 y altura 2 sería:

9.3. Valor devuelto por una función

También es habitual que queramos que nuestra función realice una serie de cálculos y nos "devuelva" el resultado de esos cálculos, para poderlo usar desde cualquier otra parte de nuestro programa. Por ejemplo, podríamos crear una función para elevar un número entero al cuadrado así:

// Introducción a C++, Nacho Cabanes
// Ejemplo 09.04:
// Cuarto ejemplo de funciones: valor devuelto
 
#include <iostream>
using namespace std;
 
int cuadrado(int n) 
{
    int resultado = n*n;
 
    return resultado;
}
 
int main() 
{    
    int numero;
 
    cout << "Dime un número: ";
    cin >> numero;    
    cout << "Su cuadrado es " << cuadrado(numero) << endl;
 
    return 0;
}

Como se puede observar, una función que devuelva un valor no será "void", sino "int" o de algún otro tipo, y deberá terminar con una orden "return".

Si una función puede devolver varios valores distintos, podrá tener varios "return" distintos. Por ejemplo, podemos crear una función que nos diga cual es el mayor de dos números reales así:

// Introducción a C++, Nacho Cabanes
// Ejemplo 09.05:
// Quinto ejemplo de funciones: varios "return"
 
#include <iostream>
using namespace std;
 
float mayor ( float n1, float n2 )
{
    if (n1>n2)
        return n1;
    else
        return n2;
}
 
int main()
{
    float numero1, numero2;
 
    cout << "Dime un número: ";
    cin >> numero1;
    cout << "Dime otro: ";
    cin >> numero2;
    cout << "El mayor es ";
    cout << mayor(numero1, numero2) << endl;
 
    return 0;
}

 

Como se puede intuir, el propio cuerpo del programa (main) es una función, y devuelve un valor, que es un número entero, que se puede leer desde fuera de nuestro programa, desde el sistema operativo. Lo habitual es devolver 0 si todo ha funcionado correctamente

int main() 
{
    ...
    return 0;
}

Y devolveríamos otro valor si hubiera habido algún problema durante el funcionamiento de nuestro programa (por ejemplo, si no hemos podido abrir algún fichero):

int main() 
{
    ...
    if ( fichero.fail() )
        return 1;
    ...
    return 0;
}

 

Este valor se podría comprobar desde el sistema operativo (o desde algún otro programa que hubiera lanzado al nuestro). Por ejemplo, en MsDos y Windows se lee con "IF ERRORLEVEL", así:

IF ERRORLEVEL 1 ECHO Ha habido un error en el programa

Ejercicios propuestos:

  • (9.3.1) Crea una función "cubo", que devuelva el cubo (n3)) de un cierto número real. Crea un cuerpo de programa que te permita probarla.
  • (9.3.2) Crea una función "suma", que devuelva la suma de tres números enteros que se le indiquen como parámetros.
  • (9.3.3) Crea una función llamada "media", que reciba un array de 5 números reales y devuelva la media de esos 5 elementos.
  • (9.3.4) Crea una función "invertirCadena", que reciba una cadena como parámetro y devuelva el resultado de invertir las letras de esa cadena (por ejemplo, a partir de "hola" devolverá "aloh").
  • (9.3.5) Crea una función llamada “signo”, que reciba un número real, y devuelva un número entero con el valor: -1 si el número es negativo, 1 si es positivo o 0 si es cero.
  • (9.3.6) Crear una función que devuelva la primera letra de una cadena de texto. Probar esta función para calcular la primera letra de la frase “Hola”
  • (9.3.7) Crear una función que devuelva la última letra de una cadena de texto. Probar esta función para calcular la última letra de la frase “Hola”.

Ejercicios propuestos:

  • Crear una función que calcule el cubo de un número real (float). El resultado deberá ser otro número real. Probar esta función para calcular el cubo de 3.2 y el de 5.
  • Crear una función que calcule cual es el menor de dos números enteros. El resultado será otro número entero.

9.4. Variables locales y variables globales

El comportamiento será distinto según dónde esté declarada una variable. Si la variable está declarada dentro de una función, sólo será accesible por esa función y por ninguna otra (es una "variable local"). Si está declarada fuera de todas las funciones, será accesible y modificable por cualquier función (será una "variable global")).

// Introducción a C++, Nacho Cabanes
// Ejemplo 09.06:
// Sexto ejemplo de funciones: variables locales
 
#include <iostream>
using namespace std;
 
int n = 5;   // Variable global
 
void duplica(int n) 
{
    n = n * 2;  // Cuidado: variable local (parámetro)
}
 
int main() 
{    
    int x = 5;  // Variable local, no usada
 
    cout << "n vale " << n << endl;
    duplica(n);
    cout << "Ahora n vale " << n << endl;
 
    return 0;
}

¡Cuidado! Varias variables locales pueden tener el mismo nombre, o incluso una variable local y una global. Vamos a ver un ejemplo:

// Introducción a C++, Nacho Cabanes
// Ejemplo 09.07:
// Séptimo ejemplo de funciones: variables locales y globales
 
#include <iostream>
using namespace std;
 
int n = 7;
 
void duplica1()
{
    n = n * 2;
    cout << "En duplica1, n vale: " << n << endl;
}
 
void duplica2()
{
    int n = 5;
 
    n = n * 2;
    cout << "En duplica2, n vale: " << n << endl;
}
 
void duplica3(int n)
{
    n = n * 2;
    cout << "En duplica3, n vale: " << n << endl;
}
 
int main()
{
    cout << "En main, n vale: " << n << endl;
 
    duplica1();
    cout << "En main, n vale: " << n << endl;
 
    duplica2();
    cout << "En main, n vale: " << n << endl;
 
    duplica3(n);
    cout << "En main, n vale: " << n << endl;
 
    return 0;
}

¿Qué debemos hacer? En el mundo real, debemos intentar que las variables sean locales siempre que sea posible, para evitar que al modificar su valor estemos provocando efectos colaterales no deseados en algún otro punto del programa. En variables como el típico "i" que se usa para controlar un bucle "for" será claramente innecesario que sean globales, y, de hecho, lo habitual, como ya vimos, será declarar la variable dentro del propio "for", y así no existirá fuera de él:

for (int i=0; i<10; i++) ...

Ejercicios propuestos :

  • (9.4.1) Crear una función “pedirEntero”, que reciba como parámetros el texto que se debe mostrar en pantalla, el valor mínimo aceptable y el valor máximo aceptable. Deberá pedir al usuario que introduzca el valor tantas veces como sea necesario, volvérselo a pedir en caso de error, y devolver un valor correcto. Probarlo con un programa que pida al usuario un año entre 1800 y 2100.
  • (9.4.2) Crear una función “esPrimo”, que reciba un número y devuelva el valor 1 si es un número primo o 0 en caso contrario.
  • (9.4.3) Crear una función que reciba una cadena y una letra, y devuelva la cantidad de veces que dicha letra aparece en la cadena. Por ejemplo, si la cadena es "Barcelona" y la letra es 'a', debería devolver 2 (aparece 2 veces).
  • (9.4.4) Crear una función que reciba un número cualquiera y que devuelva el resultado de la suma de sus dígitos. Por ejemplo, si el número fuera 123 la suma sería 6.
  • (9.4.5) Crear una función que reciba una letra y un número, y escriba un “triángulo” formado por esa letra, que tenga como anchura inicial la que se ha indicado. Por ejemplo, si la letra es * y la anchura es 4, debería escribir
****  
***  
**  
*

9.5. El orden importa

En general, una función debe estar declarada antes de usarse. Por ejemplo, este fuente daría un error en muchos compiladores, porque dentro de "main" intentamos usar algo llamado "duplica", que no se ha mencionado antes:

// Introducción a C++, Nacho Cabanes
// Ejemplo 09.08:
// Función desordenada puede no compilar
 
#include <iostream>
using namespace std;
 
int main() 
{
    float n = 5;
    cout << "n vale " << n << endl;
    n = duplica(n);
    cout << "Ahora n vale " << n << endl;
 
    return 0;
}
 
float duplica(float n) 
{
    return n * 2;
}

La forma de evitarlo es colocar la definición de las funciones antes de usarlas (si se puede) o bien incluir al menos su "prototipo", la cabecera de la función sin incluir los detalles de cómo trabaja internamente, así:

// Introducción a C++, Nacho Cabanes
// Ejemplo 09.09:
// Prototipo de la función antes de main, para que 
//   compile sin problemas
 
#include <iostream>
using namespace std;
 
float duplica(float n);
 
int main() 
{
    float n = 5;
    cout << "n vale " << n << endl;
    n = duplica(n);
    cout << "Ahora n vale " << n << endl;
 
    return 0;
}
 
float duplica(float n) 
{
    return n * 2;
}

Como curiosidad, si no declaramos la función ni su prototipo antes de "main", más de un compilador dará por sentado que es "int", de modo que este otro fuente sí compilaría correctamente en algunos sistemas:

// Introducción a C++, Nacho Cabanes
// Ejemplo 09.08b:
// Función desordenada puede no compilar
//   (Variante con "int")
 
#include <iostream>
using namespace std;
 
int main() 
{
    int n = 5;
    cout << "n vale " << n << endl;
    n = duplica(n);
    cout << "Ahora n vale " << n << endl;
 
    return 0;
}
 
int duplica(int n) 
{
    return n * 2;
}

9.6. Modificar el valor de un parámetro

Como ya hemos visto, cuando un dato se recibe como parámetro, los cambios que hagamos en su valor no se reflejan al salir de la función. Esto se debe a que realmente se trabaja sobre "una copia" de ese dato, no sobre el dato original. Esa forma de trabajar se llama "pasar parámetros por valor".

Pero en alguna ocasión nos puede interesar que sí se pueda modificar el valor de un parámetro, lo que llamaremos "pasar un parámetro por referencia". Hay dos casos especialmente frecuentes:

  • Cuando tengamos que devolver más de un valor. Sabemos cómo hacer que una función devuelva un resultado, pero si tiene que devolver dos (por ejemplo, las dos soluciones de una ecuación de segundo grado), esto no se puede conseguir de la forma convencional. Hay dos alternativas para conseguirlo: la "mala" es devolver un array que contenga ambos valores; la "buena" es que esos dos datos se devuelvan como parámetros modificados.
  • También puede ocurrir que estemos pasando datos muy grandes como parámetros (un array o un struct de gran tamaño, por ejemplo). En ese caso, el pasar los datos "por referencia" hace que el programa sea ligeramente más rápido, porque evitamos hacer una copia de esos datos de gran tamaño. A cambio, existe el riesgo de modificar los datos sin querer.

La forma de indicar que un parámetro es "por referencia" (que queremos permitir que su valor se pueda modificar) es incluir un símbolo "&" entre el tipo de la variable y el nombre de la variable:

// Introducción a C++, Nacho Cabanes
// Ejemplo 09.10:
// Modificar el valor de un parámetro (estilo C++)
 
#include <iostream>
using namespace std;
 
void duplica(int & x) 
{
    x = x * 2;
}
 
int main() 
{
    int n = 5;
    cout << "n vale " << n << endl;
    duplica(n);
    cout << "Ahora n vale " << n << endl;
 
    return 0;   
}

En cuanto al "sitio" en que se coloca ese símbolo, hay gente que prefiere escribir junto al tipo de datos: "int& x", hay quien prefiere que esté junto a la variable: "int &x" y hay quien prefiere dejar un espacio entre medias: "int & x". En los tres casos el comportamiento será el mismo.

En ocasiones, podemos encontrar fuentes que pasan parámetros por referencia usando la sintaxis de C, que es más enrevesada que la de C++. En C, el símbolo "&" se usa en la llamada a la función, mientras que en la cabecera de ésta se usa un "asterisco" (*) y en el cuerpo de la función se debe escribir también el nombre de la variable precedido por un asterisco (las razones exactas las veremos más adelante, cuando hablemos de memoria dinámica):

// Introducción a C++, Nacho Cabanes
// Ejemplo 09.10b:
// Modificar el valor de un parámetro (estilo C)
 
#include <iostream>
using namespace std;
 
void duplica(int *x) 
{
    *x = *x * 2;
}
 
int main() 
{
    int n = 5;
    cout << "n vale " << n << endl;
    duplica(&n);
    cout << "Ahora n vale " << n << endl;
 
    return 0;   
}

Para duplicar un valor no hace falta pasar parámetros por referencia, sino que nos habría bastado con devolver el nuevo valor. Un ejemplo real en el que sí se necesitarían parámetros por referencia es el de intercambiar los valores de dos variables. Se podría conseguir así:

// Introducción a C++, Nacho Cabanes
// Ejemplo 09.11:
// Intercambiar el valor de dos parámetros (estilo C++)
 
#include <iostream>
using namespace std;
 
void intercambia(int & x, int & y) 
{
    int auxiliar;
    auxiliar = x;
    x = y;
    y = auxiliar ;
}
 
int main() 
{
    int a = 5;
    int b = 12;
    cout << "a es " << a << " y b es "
        << b << endl;
 
    intercambia(a, b);
    cout << "Ahora a es " << a << " y b es "
        << b << endl;
 
    return 0;
}

Y con la sintaxis de C quedaría así:

// Introducción a C++, Nacho Cabanes
// Ejemplo 09.11b:
// Intercambiar el valor de dos parámetros (estilo C)
 
#include <iostream>
using namespace std;
 
void intercambia(int *x, int *y) 
{
    int auxiliar;
    auxiliar = *x;
    *x = *y;
    *y = auxiliar ;
}
 
int main() 
{
    int a = 5;
    int b = 12;
    cout << "a es " << a << " y b es "
        << b << endl;
 
    intercambia(&a, &b);
    cout << "Ahora a es " << a << " y b es "
        << b << endl;
 
    return 0;
}

Ejercicios propuestos:

  • (9.6.1) Crear un programa que resuelva ecuaciones de segundo grado, del tipo ax2 + bx + c = 0 El usuario deberá introducir los valores de a, b y c. Pista: la solución se calcula con
    x = +- raíz (b2 – 4·a·c) / 2·a Como hay dos soluciones x1 y x2, deberás crear una función que use parámetros por referencia para devolver sus valores. Si alguna de las soluciones no existe, devolverás un valor prefijado: -9999. Si no sabes cómo calcular raíces cuadradas, puedes aplazar este ejercicio hasta leer el siguiente apartado.

27445 visitas desde el 20-04-2019

AnteriorPosterior