Elementos esenciales de las funciones en C++

C++Beginner
Practicar Ahora

Introducción

En este laboratorio, aprenderás las funciones en C++. Aprenderás cómo definir y llamar a funciones, y cómo pasar argumentos a funciones.

Vista previa del contenido

A veces, una cierta porción de código tiene que utilizarse muchas veces. Es mejor ponerlos en una "subrutina" - función, y "llamar" a esta función muchas veces, lo que facilita la mantenibilidad y la comprensión.

En el uso de una función participan dos partes: un llamador que llama a la función, y la función llamada. El llamador pasa argumento(s) a la función. La función recibe estos argumento(s), realiza las operaciones programadas dentro del cuerpo de la función y devuelve un resultado al llamador.

Usando funciones

Supongamos que necesitamos calcular el área de un círculo muchas veces. Es mejor escribir una función llamada getArea() y reutilizarla cuando sea necesario.

/* Función de prueba */
#include <iostream>
using namespace std;
const int PI = 3.14159265;

// Prototipo de función (Declaración de función)
double getArea(double radio);

int main() {
   double radio1 = 1.1, area1, area2;
   // llamar a la función getArea()
   area1 = getArea(radio1);
   cout << "el área 1 es " << area1 << endl;
   // llamar a la función getArea()
   area2 = getArea(2.2);
   cout << "el área 2 es " << area2 << endl;
   // llamar a la función getArea()
   cout << "el área 3 es " << getArea(3.3) << endl;
}

// Definición de función
// Devuelve el área de un círculo dado su radio
double getArea(double radio) {
   return radio * radio * PI;
}

Salida:

el área 1 es 3.63
el área 2 es 14.52
el área 3 es 32.67
descripción de la imagen
descripción de la imagen

En C++, debes declarar un prototipo de función (antes de usar la función) y proporcionar una definición de función, con un cuerpo que contenga las operaciones programadas.

La sintaxis para la definición de función es la siguiente:

tipoDeRetorno nombreDeLaFunción ( listaDeParámetros ) {
   cuerpoDeLaFunción ;
}

Un prototipo de función le dice al compilador la interfaz de la función, es decir, el tipo de retorno, el nombre de la función y la lista de tipos de parámetros (el número y tipo de parámetros). Ahora se puede definir la función en cualquier lugar del archivo. Por ejemplo,

// Prototipo de función - colocado antes de usar la función.
double getArea(double);  // sin el nombre del parámetro
double getArea(double radio);  // los nombres de los parámetros se ignoran, pero sirven como documentación

El tipo de retorno "void"

Si no es necesario devolver un valor al llamador, se puede declarar su tipo de valor de retorno como void. En el cuerpo de la función, se puede usar una instrucción "return;" sin un valor de retorno para devolver el control al llamador.

Parámetros reales vs. Parámetros formales

En el ejemplo anterior, la variable (double radio) declarada en la firma de getArea(double radio) se conoce como parámetro formal. Su ámbito está dentro del cuerpo de la función. Cuando la función es invocada por un llamador, el llamador debe suministrar los llamados parámetros reales (o argumentos), cuyo valor se utiliza luego para el cálculo real. Por ejemplo, cuando la función es invocada a través de "area1 = getArea(radio1)", radio1 es el parámetro real, con un valor de 1.1.

Ámbito de las variables locales de la función y los parámetros

Todas las variables, incluyendo los parámetros de la función, declaradas dentro de una función solo son disponibles para la función. Se crean cuando se llama a la función y se liberan (destruyen) después de que la función devuelva. Se les llama variables locales porque son locales a la función y no están disponibles fuera de la función.

Argumentos predeterminados

C++ introduce los llamados argumentos predeterminados para las funciones. Estos valores predeterminados se utilizarán si el llamador omite el argumento real correspondiente al llamar a la función. Los argumentos predeterminados se especifican en el prototipo de la función y no se pueden repetir en la definición de la función. Los argumentos predeterminados se resuelven según sus posiciones. Por lo tanto, solo se pueden utilizar para sustituir los argumentos posteriores para evitar ambigüedades. Por ejemplo,

/* Función de prueba con argumentos predeterminados */
#include <iostream>
using namespace std;

// Prototipo de función - Especifique los argumentos predeterminados aquí
int fun1(int = 1, int = 2, int = 3);
int fun2(int, int, int = 3);

int main() {
   cout << fun1(4, 5, 6) << endl; // Sin argumentos predeterminados
   cout << fun1(4, 5) << endl;    // 4, 5, 3(predeterminado)
   cout << fun1(4) << endl;       // 4, 2(predeterminado), 3(predeterminado)
   cout << fun1() << endl;        // 1(predeterminado), 2(predeterminado), 3(predeterminado)

   cout << fun2(4, 5, 6) << endl; // Sin argumentos predeterminados
   cout << fun2(4, 5) << endl;    // 4, 5, 3(predeterminado)
   // cout << fun2(4) << endl;
   // error: argumentos insuficientes para la función 'int fun2(int, int, int)'
}

int fun1(int n1, int n2, int n3) {
   // no se pueden repetir los argumentos predeterminados en la definición de la función
   return n1 + n2 + n3;
}

int fun2(int n1, int n2, int n3) {
   return n1 + n2 + n3;
}

Salida:

15
12
9
6
15
12
descripción de la imagen

Sobrecarga de funciones

C++ introduce la sobrecarga de funciones (o polimorfismo de funciones), que te permite tener múltiples versiones del mismo nombre de función, diferenciadas por la lista de parámetros (número, tipo o orden de los parámetros). Las funciones sobrecargadas no se pueden diferenciar por el tipo de retorno (error de compilación). Se seleccionará la versión que coincida con la lista de argumentos del llamador para su ejecución. Por ejemplo,

/* Prueba de sobrecarga de funciones */
#include <iostream>
using namespace std;

void fun(int, int, int);  // Versión 1
void fun(double, int);          // Versión 2
void fun(int, double);          // Versión 3

int main() {
   fun(1, 2, 3);   // versión 1
   fun(1.0, 2);    // versión 2
   fun(1, 2.0);    // versión 3
   fun(1.1, 2, 3); // versión 1 - double 1.1 convertido a int 1 (sin advertencia)

   // fun(1, 2, 3, 4);
      // error: no se encuentra la función adecuada para la llamada 'fun(int, int, int, int)'
   // fun(1, 2);
      // error: la llamada a la función sobrecargada 'fun(int, int)' es ambigua
      // nota: los candidatos son:
      //    void fun(double, int)
      //    void fun(int, double)
   // fun(1.0, 2.0);
      // error: la llamada a la función sobrecargada 'fun(double, double)' es ambigua
}

void fun(int n1, int n2, int n3) {  // versión 1
   cout << "versión 1" << endl;
}

void fun(double n1, int n2) { // versión 2
   cout << "versión 2" << endl;
}

void fun(int n1, double n2) { // versión 3
   cout << "versión 3" << endl;
}

Salida:

versión 1
versión 2
versión 3
versión 1
descripción de la imagen

Funciones y matrices

También se puede pasar una matriz a una función. Sin embargo, también es necesario pasar el tamaño de la matriz a la función. Esto se debe a que no hay forma de determinar el tamaño de la matriz a partir del argumento de matriz dentro de la función llamada. Por ejemplo,

/* Función para calcular la suma de una matriz */
#include <iostream>
using namespace std;

// Prototipo de función
int sum(int array[], int size);    // También es necesario pasar el tamaño de la matriz
void print(int array[], int size);

// Controlador de prueba
int main() {
   int a1[] = {8, 4, 5, 3, 2};
   print(a1, 5);   // {8,4,5,3,2}
   cout << "suma es " << sum(a1, 5) << endl;  // suma es 22
}

// Definición de función
// Devuelve la suma de la matriz dada
int sum(int array[], int size) {
   int sum = 0;
   for (int i = 0; i < size; ++i) {
      sum += array[i];
   }
   return sum;
}

// Imprime los contenidos de la matriz dada
void print(int array[], int size) {
   cout << "{";
   for (int i = 0; i < size; ++i) {
      cout << array[i];
      if (i < size - 1) {
         cout << ",";
      }
   }
   cout << "}" << endl;
}

Salida:

{8,4,5,3,2}
suma es 22
descripción de la imagen

Pasaje por valor vs. Pasaje por referencia

Existen dos maneras en las que un parámetro puede ser pasado a una función: paso por valor vs. paso por referencia.

Paso por valor

En el paso por valor, se crea una "copia" del argumento y se pasa a la función. La función invocada trabaja en la "clonación" y no puede modificar la copia original. En C/C++, los tipos fundamentales (como int y double) se pasan por valor.

/* Los tipos fundamentales se pasan por valor a la función */
#include <iostream>
using namespace std;

// Prototipos de función
int inc(int number);

// Controlador de prueba
int main() {
   int n = 8;
   cout << "Antes de llamar a la función, n es " << n << endl; // 8
   int result = inc(n);
   cout << "Después de llamar a la función, n es " << n << endl;  // 8
   cout << "result es " << result << endl;                // 9
}

// Definiciones de función
// Devuelve number+1
int inc(int number) {
   ++number;  // Modifica el parámetro, sin efecto en el llamador
   return number;
}

Salida:

Antes de llamar a la función, n es 8
Después de llamar a la función, n es 8
result es 9
descripción de la imagen

Paso por referencia

Por otro lado, en el paso por referencia, se pasa una referencia de la variable del llamador a la función. En otras palabras, la función invocada trabaja en los mismos datos. Si la función invocada modifica el parámetro, la misma copia del llamador también se modificará. En C/C++, las matrices se pasan por referencia. C/C++ no permite que las funciones devuelvan una matriz.

/* Función para incrementar cada elemento de una matriz */
#include <iostream>
using namespace std;

// Prototipos de función
void inc(int array[], int size);
void print(int array[], int size);

// Controlador de prueba
int main() {
   int a1[] = {8, 4, 5, 3, 2};

   // Antes del incremento
   print(a1, 5);   // {8,4,5,3,2}
   // Incrementar
   inc(a1, 5);     // La matriz se pasa por referencia (teniendo un efecto secundario)
   // Después del incremento
   print(a1, 5);   // {9,5,6,4,3}
}

// Definiciones de función

// Incrementa cada elemento de la matriz dada
void inc(int array[], int size) {  // array[] no es const
   for (int i = 0; i < size; ++i) {
      array[i]++;  // efecto secundario
   }
}

// Imprime los contenidos de la matriz dada
void print(int array[], int size) {
   cout << "{";
   for (int i = 0; i < size; ++i) {
      cout << array[i];
      if (i < size - 1) {
         cout << ",";
      }
   }
   cout << "}" << endl;
}

Salida:

{8,4,5,3,2}
{9,5,6,4,3}
descripción de la imagen

Parámetros de función const

Utilice const siempre que sea posible al pasar referencias, ya que evita modificar inadvertidamente los parámetros y lo protege contra muchos errores de programación.

En una búsqueda lineal, la clave de búsqueda se compara con cada elemento del array linealmente. Si hay una coincidencia, devuelve el índice del elemento coincidente; de lo contrario, devuelve -1. La búsqueda lineal tiene una complejidad de O(n).

/* Buscar un array para la clave dada usando Búsqueda Lineal */
#include <iostream>
using namespace std;

int linearSearch(const int a[], int size, int key);

int main() {
   const int SIZE = 8;
   int a1[SIZE] = {8, 4, 5, 3, 2, 9, 4, 1};

   cout << linearSearch(a1, SIZE, 8) << endl;  // 0
   cout << linearSearch(a1, SIZE, 4) << endl;  // 1
   cout << linearSearch(a1, SIZE, 99) << endl; // 8 (no encontrado)
}

// Buscar el array para la clave dada
// Si se encuentra, devuelve el índice del array [0, size-1]; de lo contrario, devuelve size
int linearSearch(const int a[], int size, int key) {
   for (int i = 0; i < size; ++i) {
      if (a[i] == key) return i;
   }
   // a[0] = 1;
   // Esto resultará en un error, porque a[] es const, lo que significa solo lectura
   return -1;
}

Salida:

0
1
-1
descripción de la imagen

Pasaje por referencia a través de parámetros de 'Referencia'

Puedes pasar un parámetro de tipo fundamental por referencia a través del parámetro de referencia denotado por &.

/* Prueba de paso por referencia para un parámetro de tipo fundamental
   a través de la declaración de referencia */
#include <iostream>
using namespace std;

int squareByValue (int number);        // Paso por valor
void squareByReference (int &number); // Paso por referencia

int main() {
   int n1 = 8;
   cout << "Antes de la llamada, el valor es " << n1 << endl;  // 8
   cout << squareByValue(n1) << endl;  // sin efecto secundario
   cout << "Después de la llamada, el valor es " << n1 << endl;   // 8

   int n2 = 9;
   cout << "Antes de la llamada, el valor es " << n2 << endl;  // 9
   squareByReference(n2);  // efecto secundario
   cout << "Después de la llamada, el valor es " << n2 << endl;   // 81
}

// Pasar el parámetro por valor - sin efecto secundario
int squareByValue (int number) {
   return number * number;
}

// Pasar el parámetro por referencia declarándolo como referencia (&)
// - con efecto secundario para el llamador
void squareByReference (int &number) {
   number = number * number;
}

Salida:

Antes de la llamada, el valor es 8
64
Después de la llamada, el valor es 8
Antes de la llamada, el valor es 9
Después de la llamada, el valor es 81
descripción de la imagen

2.8 Funciones matemáticas

C++ proporciona muchas funciones matemáticas comúnmente utilizadas en la biblioteca <cmath>

sin(x), cos(x), tan(x), asin(x), acos(x), atan(x):
   Toman un tipo de argumento y un tipo de retorno de float, double, long double.
sinh(x), cosh(x), tanh(x):
   Funciones hipertrigonométricas.
pow(x, y), sqrt(x):
   Potencia y raíz cuadrada.
ceil(x), floor(x):
   Devuelven el entero techo y piso del número de punto flotante.
fabs(x), fmod(x, y):
   Valor absoluto y módulo de punto flotante.
exp(x), log(x), log10(x):
   Funciones exponencial y logarítmica.

El encabezado cstdlib (importado de stdlib.h de C) proporciona una función rand(), que genera un número entero pseudoaleatorio entre 0 y RAND_MAX (inclusive).

/* Prueba de generación de números aleatorios */
#include <iostream>
#include <cstdlib>  // para rand(), srand()
#include <ctime>    // para time()
using namespace std;

int main() {
   // rand() genera un número aleatorio en [0, RAND_MAX]
   cout << "RAND_MAX es " << RAND_MAX << endl;   // 32767

   // Generar 10 números pseudoaleatorios entre 0 y 99
   //   sin sembrar el generador.
   // Obtendrás la misma secuencia, cada vez que ejecutes este programa
   for (int i = 0; i < 10; ++i) {
      cout << rand() % 100 << " ";   // necesita el encabezado <cstdlib>
   }
   cout << endl;

   // Sembrar el generador de números aleatorios con la hora actual
   srand(time(0));   // necesita los encabezados <cstdlib> y <ctime>
   // Generar 10 números pseudoaleatorios
   // Obtendrás una secuencia diferente en diferentes ejecuciones,
   //   porque la hora actual es diferente
   for (int i = 0; i < 10; ++i) {
      cout << rand() % 100 << " ";   // necesita el encabezado <cstdlib>
   }
   cout << endl;
}

Salida:

RAND_MAX es 2147483647
83 86 77 15 93 35 86 92 49 21
29 0 83 60 22 55 97 80 68 87
descripción de la imagen
- name: check if keyword exist
  script: |
    #!/bin/bash
    grep -i 'rand' /home/labex/Code/test.cpp
  error: Oops! We find that you didn't use "rand()" method in "test.cpp".
  timeout: 3

Resumen

Las ventajas de utilizar funciones son:

  1. Dividir y conquistar: construir el programa a partir de piezas o componentes simples y pequeños. Modularizar el programa en tareas autónomas.
  2. Evitar la repetición de código: es fácil copiar y pegar, pero es difícil mantener y sincronizar todas las copias.
  3. Reutilización de software: se pueden reutilizar las funciones en otros programas, empaquetándolas en código de biblioteca.