Definir y Usar Funciones en C++

C++Beginner
Practicar Ahora

Introducción

En este laboratorio, te adentrarás en el mundo de las funciones en C++. Las funciones son una piedra angular de la programación, ya que te permiten organizar tu código en bloques manejables y reutilizables. Este laboratorio cubre una amplia gama de temas relacionados con las funciones, desde la creación de funciones con varios tipos de retorno hasta el aprovechamiento de conceptos avanzados como la recursión y la sobrecarga de funciones. Aprenderás a definir funciones, pasar parámetros por valor y por referencia, establecer valores de parámetros por defecto y comprender los prototipos de funciones. Además, descubrirás cómo devolver múltiples valores utilizando estructuras. Al final de este laboratorio, tendrás una base sólida para escribir código C++ modular, eficiente y bien estructurado.

Comenzaremos explorando funciones con diferentes tipos de retorno, como enteros, números de punto flotante, caracteres y cadenas. Luego aprenderás la diferencia crítica entre pasar parámetros por valor y por referencia, y cómo los valores de parámetros por defecto pueden agregar flexibilidad a tus funciones. También cubriremos la sobrecarga de funciones, que te permite definir múltiples funciones con el mismo nombre pero con diferentes listas de parámetros. Además, explorarás las funciones recursivas, una herramienta poderosa para resolver problemas llamándose a sí mismas. Verás cómo usar prototipos de funciones en archivos de encabezado para crear código más organizado. Finalmente, descubrirás cómo devolver múltiples valores de una función utilizando estructuras.

Este es un Guided Lab, que proporciona instrucciones paso a paso para ayudarte a aprender y practicar. Sigue las instrucciones cuidadosamente para completar cada paso y obtener experiencia práctica. Los datos históricos muestran que este es un laboratorio de nivel principiante con una tasa de finalización del 84%. Ha recibido una tasa de reseñas positivas del 97% por parte de los estudiantes.

Crear Funciones con Diferentes Tipos de Retorno

Las funciones son un concepto fundamental en la programación. Te permiten dividir programas grandes en piezas más pequeñas y manejables, haciendo que tu código sea más fácil de escribir, entender y mantener. Las funciones también pueden devolver valores, lo que permite pasar datos de vuelta a la parte del programa que las llama.

Aquí tienes la sintaxis básica de una función en C++:

return_type nombre_funcion(parametros) {
    // Cuerpo de la función
    return valor;
}
  • return_type: Especifica el tipo de dato del valor que la función devolverá. Si la función no devuelve ningún valor, se utiliza la palabra clave void.
  • nombre_funcion: Es el nombre que usarás para llamar a la función. Elige un nombre que describa claramente el propósito de la función.
  • parametros: Son los valores de entrada que pasas a la función. Son opcionales y puedes tener cero o más parámetros.
  • valor: Es el valor que la función devuelve. Debe coincidir con el return_type de la función. Si el return_type es void, omites la declaración return o simplemente usas return; sin un valor.

En este paso, aprenderás a crear funciones que devuelven diferentes tipos de datos. Esta capacidad es crucial para construir programas versátiles y funcionales.

Abre el WebIDE y crea un nuevo archivo llamado function_types.cpp en el directorio ~/project:

touch ~/project/function_types.cpp

Abre el archivo function_types.cpp en el editor y añade el siguiente código:

#include <iostream>
#include <string>

// Función que devuelve un entero
int addNumbers(int a, int b) {
    return a + b;
}

// Función que devuelve un double (número de punto flotante)
double calculateAverage(double x, double y) {
    return (x + y) / 2.0;
}

// Función que devuelve un carácter
char getGrade(int score) {
    if (score >= 90) return 'A';
    if (score >= 80) return 'B';
    if (score >= 70) return 'C';
    if (score >= 60) return 'D';
    return 'F';
}

// Función que devuelve una cadena (string)
std::string getStudentStatus(bool isEnrolled) {
    return isEnrolled ? "Active" : "Inactive";
}

int main() {
    // Demostración de diferentes tipos de retorno
    int sum = addNumbers(5, 3);
    std::cout << "Sum: " << sum << std::endl;

    double average = calculateAverage(10.5, 20.5);
    std::cout << "Average: " << average << std::endl;

    char grade = getGrade(85);
    std::cout << "Grade: " << grade << std::endl;

    std::string status = getStudentStatus(true);
    std::cout << "Student Status: " << status << std::endl;

    return 0;
}

Compila y ejecuta el programa:

g++ function_types.cpp -o function_types
./function_types

Salida de ejemplo:

Sum: 8
Average: 15.5
Grade: B
Student Status: Active

Puntos clave sobre los tipos de retorno de funciones:

  • Las funciones pueden devolver diferentes tipos de datos, proporcionando flexibilidad al código.
  • El return_type se especifica antes del nombre de la función, indicando qué tipo de datos enviará de vuelta la función.
  • La declaración return debe coincidir con el return_type declarado o el compilador informará un error.
  • Las funciones void no devuelven un valor, y se usa return; u omite la declaración de retorno si estás al final de una función void.
  • Puedes devolver tipos primitivos (como int, double, char), cadenas (strings) e incluso tipos definidos por el usuario.
  • Piensa en los tipos de retorno de las funciones como diferentes tipos de contenedores. Así como usarías una caja, una bolsa o una cesta para llevar diferentes artículos, las funciones usan diferentes tipos de retorno para enviar de vuelta diferentes tipos de datos.

Pasar Parámetros a Funciones por Valor

En este paso, aprenderás sobre cómo pasar parámetros a funciones por valor en C++. Cuando pasas un parámetro por valor, se crea una copia del argumento original, y esa copia se utiliza dentro de la función. Cualquier cambio realizado en el parámetro dentro de la función no afecta a la variable original fuera de la función.

Abre el WebIDE y crea un nuevo archivo llamado pass_by_value.cpp en el directorio ~/project:

touch ~/project/pass_by_value.cpp

Abre el archivo pass_by_value.cpp en el editor y añade el siguiente código:

#include <iostream>

// Función que demuestra el paso por valor
void modifyValue(int x) {
    // Esta modificación solo afecta a la copia local, no a la variable original
    x = x * 2;
    std::cout << "Inside function - Value of x: " << x << std::endl;
}

int main() {
    // Variable original
    int number = 10;

    // Imprimir valor original
    std::cout << "Before function call - Original value: " << number << std::endl;

    // Llamar a la función con la variable original
    modifyValue(number);

    // La variable original permanece sin cambios
    std::cout << "After function call - Original value: " << number << std::endl;

    return 0;
}

Compila y ejecuta el programa:

g++ pass_by_value.cpp -o pass_by_value
./pass_by_value

Salida de ejemplo:

Before function call - Original value: 10
Inside function - Value of x: 20
After function call - Original value: 10

Creemos otro ejemplo para ilustrar aún más el paso por valor:

touch ~/project/swap_values.cpp

Añade el siguiente código a swap_values.cpp:

#include <iostream>
#include <string>

// Función para intercambiar valores (no funcionará como se espera con el paso por valor)
void swapValues(int a, int b) {
    int temp = a;
    a = b;
    b = temp;
    std::cout << "Inside function - a: " << a << ", b: " << b << std::endl;
}

int main() {
    int first = 5;
    int second = 10;

    std::cout << "Before swap - first: " << first << ", second: " << second << std::endl;

    // Llamar a la función swap
    swapValues(first, second);

    // Las variables originales permanecen sin cambios
    std::cout << "After swap - first: " << first << ", second: " << second << std::endl;

    return 0;
}

Compila y ejecuta el segundo programa:

g++ swap_values.cpp -o swap_values
./swap_values

Salida de ejemplo:

Before swap - first: 5, second: 10
Inside function - a: 10, b: 5
After swap - first: 5, second: 10

Puntos clave sobre el paso por valor:

  • Se pasa una copia del argumento a la función, preservando el valor original.
  • Los cambios realizados en el parámetro dentro de la función no afectan a la variable original fuera de la función.
  • El paso por valor es adecuado para tipos de datos simples como int, char, float, etc., cuando no necesitas modificar el valor original.
  • Al pasar objetos grandes por valor, la copia puede consumir más memoria y tiempo, haciéndolo menos eficiente en comparación con el paso por referencia.

Puedes pensar en el paso por valor como hacer una fotocopia de un documento. El original permanece sin cambios, y puedes modificar la copia sin afectar al original.

Implementar Paso por Referencia Usando el Operador &

En este paso, aprenderás cómo pasar parámetros a funciones por referencia usando el operador & en C++. El paso por referencia permite proporcionar a la función acceso directo a la variable original, lo que significa que cualquier cambio realizado en el parámetro dentro de la función modificará directamente la variable original fuera de la función.

Abre el WebIDE y crea un nuevo archivo llamado pass_by_reference.cpp en el directorio ~/project:

touch ~/project/pass_by_reference.cpp

Abre el archivo pass_by_reference.cpp en el editor y añade el siguiente código:

#include <iostream>

// Función que demuestra el paso por referencia
void swapValues(int& a, int& b) {
    // Modifica directamente las variables originales
    int temp = a;
    a = b;
    b = temp;
}

// Función para modificar un valor usando referencia
void incrementValue(int& x) {
    // Incrementa directamente la variable original
    x++;
}

int main() {
    // Ejemplo de intercambio
    int first = 5;
    int second = 10;

    std::cout << "Before swap - first: " << first << ", second: " << second << std::endl;

    // Llamar a la función swap con referencias
    swapValues(first, second);

    std::cout << "After swap - first: " << first << ", second: " << second << std::endl;

    // Ejemplo de incremento
    int number = 7;

    std::cout << "Before increment: " << number << std::endl;

    // Llamar a la función increment con referencia
    incrementValue(number);

    std::cout << "After increment: " << number << std::endl;

    return 0;
}

Compila y ejecuta el programa:

g++ pass_by_reference.cpp -o pass_by_reference
./pass_by_reference

Salida de ejemplo:

Before swap - first: 5, second: 10
After swap - first: 10, second: 5
Before increment: 7
After increment: 8

Creemos otro ejemplo para demostrar el poder del paso por referencia:

touch ~/project/string_reference.cpp

Añade el siguiente código a string_reference.cpp:

#include <iostream>
#include <string>

// Función para modificar una cadena usando referencia
void appendText(std::string& text) {
    text += " - Modified";
}

int main() {
    std::string message = "Hello, World!";

    std::cout << "Original message: " << message << std::endl;

    // Modificar la cadena directamente usando referencia
    appendText(message);

    std::cout << "Modified message: " << message << std::endl;

    return 0;
}

Compila y ejecuta el segundo programa:

g++ string_reference.cpp -o string_reference
./string_reference

Salida de ejemplo:

Original message: Hello, World!
Modified message: Hello, World! - Modified

Puntos clave sobre el paso por referencia:

  • Usa & después del tipo para crear un parámetro de referencia (por ejemplo, int& a).
  • Modifica directamente la variable original, a diferencia del paso por valor que utiliza una copia.
  • Evita la sobrecarga de copiar objetos grandes, lo que lo hace más eficiente para tipos de datos complejos.
  • Útil cuando necesitas modificar el valor original o trabajar con tipos de datos complejos sin copiar.

Puedes pensar en el paso por referencia como darle a alguien el documento original en lugar de una fotocopia. Cualquier cambio que hagan afectará directamente al original.

Establecer Valores Predeterminados para Parámetros de Función

En este paso, aprenderás cómo establecer valores predeterminados para parámetros de función en C++. Los parámetros predeterminados te permiten asignar valores por defecto a los argumentos de la función, haciendo que tus funciones sean más versátiles y fáciles de usar. Cuando un llamador omite un parámetro con un valor predeterminado, se utiliza automáticamente el valor predeterminado.

Abre el WebIDE y crea un nuevo archivo llamado default_parameters.cpp en el directorio ~/project:

touch ~/project/default_parameters.cpp

Abre el archivo default_parameters.cpp en el editor y añade el siguiente código:

#include <iostream>
#include <string>

// Función con parámetros predeterminados
void greetUser(std::string name = "Guest", int age = 0) {
    std::cout << "Hello, " << name << "!" << std::endl;

    if (age > 0) {
        std::cout << "You are " << age << " years old." << std::endl;
    }
}

// Otro ejemplo con parámetros predeterminados para cálculo
double calculateArea(double length = 1.0, double width = 1.0) {
    return length * width;
}

int main() {
    // Llamar a la función sin argumentos (usa valores predeterminados)
    greetUser();

    // Llamar a la función con argumentos parciales
    greetUser("Alice");

    // Llamar a la función con todos los argumentos
    greetUser("Bob", 25);

    // Demostrar el cálculo de área con parámetros predeterminados
    std::cout << "\nArea calculations:" << std::endl;

    // Área predeterminada (1x1)
    std::cout << "Default area: " << calculateArea() << std::endl;

    // Área con un parámetro
    std::cout << "Area with length 5: " << calculateArea(5.0) << std::endl;

    // Área con ambos parámetros
    std::cout << "Area with length 5 and width 3: " << calculateArea(5.0, 3.0) << std::endl;

    return 0;
}

Compila y ejecuta el programa:

g++ default_parameters.cpp -o default_parameters
./default_parameters

Salida de ejemplo:

Hello, Guest!
Hello, Alice!
Hello, Bob!
You are 25 years old.

Area calculations:
Default area: 1
Area with length 5: 5
Area with length 5 and width 3: 15

Puntos clave sobre los parámetros predeterminados:

  • Los valores predeterminados se especifican en la declaración de la función, justo después del tipo del parámetro (por ejemplo, int age = 0).
  • Los parámetros con valores predeterminados deben estar al final de la lista de parámetros. No puedes tener parámetros no predeterminados después de parámetros predeterminados.
  • Puedes llamar a la función con menos argumentos, y los que falten usarán sus valores predeterminados.
  • Los valores predeterminados proporcionan flexibilidad y reducen la necesidad de múltiples sobrecargas de funciones que hacen lo mismo con entradas ligeramente diferentes.

Creemos otro ejemplo para demostrar parámetros predeterminados más complejos:

touch ~/project/student_info.cpp

Añade el siguiente código a student_info.cpp:

#include <iostream>
#include <string>

// Función con múltiples parámetros predeterminados
void printStudentInfo(
    std::string name = "Unknown",
    int age = 0,
    std::string grade = "N/A",
    bool isEnrolled = false
) {
    std::cout << "Name: " << name << std::endl;
    std::cout << "Age: " << age << std::endl;
    std::cout << "Grade: " << grade << std::endl;
    std::cout << "Enrollment Status: " << (isEnrolled ? "Enrolled" : "Not Enrolled") << std::endl;
}

int main() {
    // Diferentes formas de llamar a la función
    printStudentInfo();  // Todos los predeterminados
    std::cout << "\n";
    printStudentInfo("John");  // Solo nombre
    std::cout << "\n";
    printStudentInfo("Alice", 20);  // Nombre y edad
    std::cout << "\n";
    printStudentInfo("Bob", 22, "A", true);  // Todos los parámetros

    return 0;
}

Compila y ejecuta el segundo programa:

g++ student_info.cpp -o student_info
./student_info

Salida de ejemplo:

Name: Unknown
Age: 0
Grade: N/A
Enrollment Status: Not Enrolled

Name: John
Age: 0
Grade: N/A
Enrollment Status: Not Enrolled

Name: Alice
Age: 20
Grade: N/A
Enrollment Status: Not Enrolled

Name: Bob
Age: 22
Grade: A
Enrollment Status: Enrolled

Puedes pensar en los parámetros predeterminados como un buffet donde puedes elegir tomar todos los elementos o solo algunos, con algunos elementos rellenados automáticamente si no los especificas.

Crear Sobrecargas de Funciones con Diferentes Parámetros

En este paso, aprenderás sobre la sobrecarga de funciones en C++, una característica que te permite crear múltiples funciones con el mismo nombre, siempre y cuando tengan listas de parámetros diferentes (ya sea en el número de parámetros o en los tipos de parámetros). Esta característica hace que tu código sea más flexible y legible al permitirte realizar operaciones similares en diferentes tipos de datos o con un número variable de entradas utilizando el mismo nombre de función.

Abre el WebIDE y crea un nuevo archivo llamado function_overloads.cpp en el directorio ~/project:

touch ~/project/function_overloads.cpp

Abre el archivo function_overloads.cpp en el editor y añade el siguiente código:

#include <iostream>
#include <string>

// Sobrecargas de funciones para sumar diferentes tipos de números
int add(int a, int b) {
    std::cout << "Adding two integers" << std::endl;
    return a + b;
}

double add(double a, double b) {
    std::cout << "Adding two doubles" << std::endl;
    return a + b;
}

// Sobrecargas de funciones con diferente número de parámetros
int add(int a, int b, int c) {
    std::cout << "Adding three integers" << std::endl;
    return a + b + c;
}

// Sobrecarga de función con diferentes tipos de parámetros
std::string add(std::string a, std::string b) {
    std::cout << "Concatenating strings" << std::endl;
    return a + b;
}

int main() {
    // Llamando a diferentes funciones sobrecargadas
    std::cout << "Integer addition: " << add(5, 3) << std::endl;
    std::cout << "Double addition: " << add(5.5, 3.7) << std::endl;
    std::cout << "Three integer addition: " << add(1, 2, 3) << std::endl;
    std::cout << "String concatenation: " << add("Hello, ", "World!") << std::endl;

    return 0;
}

Compila y ejecuta el programa:

g++ function_overloads.cpp -o function_overloads
./function_overloads

Salida de ejemplo:

Adding two integers
Integer addition: 8
Adding two doubles
Double addition: 9.2
Adding three integers
Three integer addition: 6
Concatenating strings
String concatenation: Hello, World!

Creemos otro ejemplo para demostrar más sobrecarga de funciones:

touch ~/project/overloading_examples.cpp

Añade el siguiente código a overloading_examples.cpp:

#include <iostream>
#include <string>

// Funciones de impresión sobrecargadas
void print(int value) {
    std::cout << "Printing integer: " << value << std::endl;
}

void print(double value) {
    std::cout << "Printing double: " << value << std::endl;
}

void print(std::string value) {
    std::cout << "Printing string: " << value << std::endl;
}

// Función de cálculo sobrecargada
int calculate(int a, int b) {
    return a + b;
}

double calculate(double a, double b) {
    return a * b;
}

int main() {
    // Demostrando la sobrecarga de funciones
    print(42);
    print(3.14);
    print("Hello, Overloading!");

    std::cout << "Integer calculation: " << calculate(5, 3) << std::endl;
    std::cout << "Double calculation: " << calculate(5.5, 3.2) << std::endl;

    return 0;
}

Compila y ejecuta el segundo programa:

g++ overloading_examples.cpp -o overloading_examples
./overloading_examples

Salida de ejemplo:

Printing integer: 42
Printing double: 3.14
Printing string: Hello, Overloading!
Integer calculation: 8
Double calculation: 17.6

Puntos clave sobre la sobrecarga de funciones:

  • Las funciones pueden tener el mismo nombre siempre que tengan listas de parámetros diferentes (tipos diferentes o número diferente de parámetros).
  • El compilador de C++ decide qué función sobrecargada llamar basándose en los tipos y el número de argumentos proporcionados durante la llamada a la función.
  • La sobrecarga de funciones conduce a un código más legible e intuitivo, permitiéndote reutilizar nombres para acciones similares.
  • No puedes sobrecargar funciones si solo difieren por sus tipos de retorno. Los parámetros deben ser diferentes para que la sobrecarga funcione correctamente.

Puedes pensar en la sobrecarga de funciones como un control remoto universal que funciona con diferentes tipos de dispositivos. El mismo botón (nombre de función) hace cosas diferentes dependiendo del contexto (los argumentos que proporcionas).

Escribir Funciones Recursivas para el Cálculo del Factorial

En este paso, aprenderás sobre las funciones recursivas implementando un programa de cálculo de factorial. La recursión es una técnica de programación donde una función se llama a sí misma para resolver un problema. Para evitar bucles infinitos, las funciones recursivas deben tener un caso base donde detienen la llamada recursiva, junto con un caso recursivo para seguir llamándose a sí misma para trabajar hacia el caso base.

Abre el WebIDE y crea un nuevo archivo llamado factorial_recursive.cpp en el directorio ~/project:

touch ~/project/factorial_recursive.cpp

Abre el archivo factorial_recursive.cpp en el editor y añade el siguiente código:

#include <iostream>

// Función recursiva para calcular el factorial
unsigned long long calculateFactorial(int n) {
    // Caso base: el factorial de 0 o 1 es 1
    if (n == 0 || n == 1) {
        return 1;
    }

    // Caso recursivo: n! = n * (n-1)!
    return n * calculateFactorial(n - 1);
}

int main() {
    // Calcular el factorial para diferentes números
    int numbers[] = {0, 1, 5, 7, 10};

    for (int num : numbers) {
        unsigned long long result = calculateFactorial(num);
        std::cout << "Factorial of " << num << " is: " << result << std::endl;
    }

    return 0;
}

Compila y ejecuta el programa:

g++ factorial_recursive.cpp -o factorial_recursive
./factorial_recursive

Salida de ejemplo:

Factorial of 0 is: 1
Factorial of 1 is: 1
Factorial of 5 is: 120
Factorial of 7 is: 5040
Factorial of 10 is: 3628800

Creemos otro ejemplo con una función recursiva más detallada:

touch ~/project/factorial_steps.cpp

Añade el siguiente código a factorial_steps.cpp:

#include <iostream>

// Función recursiva con salida paso a paso
unsigned long long calculateFactorialWithSteps(int n, int depth = 0) {
    // Añadir sangría para visualización
    std::string indent(depth * 2, ' ');

    // Caso base: el factorial de 0 o 1 es 1
    if (n == 0 || n == 1) {
        std::cout << indent << "Base case: factorial(" << n << ") = 1" << std::endl;
        return 1;
    }

    // Caso recursivo con visualización
    std::cout << indent << "Calculating factorial(" << n << ")" << std::endl;

    // Llamada recursiva
    unsigned long long subResult = calculateFactorialWithSteps(n - 1, depth + 1);

    // Combinar resultados
    unsigned long long result = n * subResult;

    std::cout << indent << "factorial(" << n << ") = "
              << n << " * factorial(" << n-1 << ") = "
              << result << std::endl;

    return result;
}

int main() {
    int number = 5;

    std::cout << "Factorial calculation steps for " << number << ":" << std::endl;
    unsigned long long result = calculateFactorialWithSteps(number);

    std::cout << "\nFinal result: " << number << "! = " << result << std::endl;

    return 0;
}

Compila y ejecuta el segundo programa:

g++ factorial_steps.cpp -o factorial_steps
./factorial_steps

Salida de ejemplo:

Factorial calculation steps for 5:
Calculating factorial(5)
  Calculating factorial(4)
    Calculating factorial(3)
      Calculating factorial(2)
        Calculating factorial(1)
        Base case: factorial(1) = 1
      factorial(2) = 2 * factorial(1) = 2
    factorial(3) = 3 * factorial(2) = 6
  factorial(4) = 4 * factorial(3) = 24
factorial(5) = 5 * factorial(4) = 120

Final result: 5! = 120

Puntos clave sobre las funciones recursivas:

  • Las funciones recursivas se llaman a sí mismas: Una función se llama a sí misma para resolver subproblemas más pequeños del mismo tipo.
  • Caso Base: Una función recursiva debe tener un caso base, una condición que detiene las llamadas recursivas. Sin un caso base, la función se llamará a sí misma infinitamente, lo que provocará un error de desbordamiento de pila (stack overflow).
  • Caso Recursivo: El caso recursivo es donde la función se llama a sí misma para avanzar hacia el caso base.
  • Descomposición del Problema: La recursión descompone un problema complejo en subproblemas más pequeños y simples, lo que facilita su gestión.
  • Uso de la Pila: Cada llamada recursiva ocupa espacio en la pila de llamadas del programa. La recursión profunda puede provocar un desbordamiento de pila, así que ten en cuenta los límites de la pila de llamadas.
  • Problemas Adecuados: La recursión es especialmente adecuada para problemas que se pueden definir de forma natural en términos de subproblemas más pequeños y similares, como recorridos de árboles, algoritmos de divide y vencerás, y generación de fractales.

Puedes pensar en la recursión como un conjunto de muñecas rusas anidadas, donde cada muñeca contiene una versión más pequeña de sí misma, y la muñeca más interna representa el caso base que detiene el proceso de anidamiento.

Usar Prototipos de Funciones en Archivos de Cabecera

En este paso, aprenderás sobre los prototipos de funciones y cómo usar archivos de cabecera para organizar y declarar funciones en C++. Los prototipos de funciones declaran el nombre de la función, el tipo de retorno y los parámetros sin proporcionar la implementación (el cuerpo de la función). Al usar archivos de cabecera, separas la interfaz de una función (su declaración) de su implementación. Esta separación mejora la organización del código, facilitando el mantenimiento y la reutilización de funciones en múltiples partes de tu programa o en múltiples programas.

Abre el WebIDE y crea tres archivos en el directorio ~/project:

Primero, crea math_functions.h:

touch ~/project/math_functions.h

Añade el siguiente código a math_functions.h:

#ifndef MATH_FUNCTIONS_H
#define MATH_FUNCTIONS_H

// Prototipos de funciones para operaciones matemáticas
int add(int a, int b);
int subtract(int a, int b);
int multiply(int a, int b);
double divide(double a, double b);

#endif // MATH_FUNCTIONS_H

Los archivos .h se utilizan para declaraciones, que contienen prototipos de funciones y otras declaraciones pero no implementaciones. De esta manera, puedes declarar funciones sin implementarlas. Las directivas #ifndef, #define y #endif se llaman "include guards" (guardas de inclusión) y evitan inclusiones múltiples del archivo de cabecera, lo que puede causar errores.

A continuación, crea math_functions.cpp:

touch ~/project/math_functions.cpp

Añade el siguiente código a math_functions.cpp:

#include "math_functions.h"

// Implementaciones de funciones
int add(int a, int b) {
    return a + b;
}

int subtract(int a, int b) {
    return a - b;
}

int multiply(int a, int b) {
    return a * b;
}

double divide(double a, double b) {
    // Comprobar división por cero
    if (b == 0) {
        return 0;
    }
    return a / b;
}

Este archivo .cpp contiene las implementaciones reales de las funciones que se declararon en el archivo de cabecera.

Finalmente, crea main.cpp:

touch ~/project/main.cpp

Añade el siguiente código a main.cpp:

#include <iostream>
#include "math_functions.h"

int main() {
    // Demostrar llamadas a funciones del archivo de cabecera
    int x = 10, y = 5;

    std::cout << "Addition: " << x << " + " << y << " = " << add(x, y) << std::endl;
    std::cout << "Subtraction: " << x << " - " << y << " = " << subtract(x, y) << std::endl;
    std::cout << "Multiplication: " << x << " * " << y << " = " << multiply(x, y) << std::endl;
    std::cout << "Division: " << x << " / " << y << " = " << divide(x, y) << std::endl;

    return 0;
}

Este archivo main.cpp incluye el archivo de cabecera math_functions.h, lo que hace que los prototipos de funciones estén disponibles. Luego puede utilizar las funciones implementadas en math_functions.cpp.

Compila el programa utilizando múltiples archivos fuente:

g++ math_functions.cpp main.cpp -o math_operations
./math_operations

Salida de ejemplo:

Addition: 10 + 5 = 15
Subtraction: 10 - 5 = 5
Multiplication: 10 * 5 = 50
Division: 10 / 5 = 2

Creemos otro ejemplo con un archivo de cabecera más complejo:

Crea calculator.h:

touch ~/project/calculator.h

Añade el siguiente código a calculator.h:

#ifndef CALCULATOR_H
#define CALCULATOR_H

class Calculator {
public:
    // Prototipos de funciones para operaciones de calculadora
    int add(int a, int b);
    int subtract(int a, int b);
    int calculate(int a, int b, char operation);
};

#endif // CALCULATOR_H

Este archivo de cabecera declara una clase llamada Calculator y sus métodos públicos.

Crea calculator.cpp:

touch ~/project/calculator.cpp

Añade el siguiente código a calculator.cpp:

#include "calculator.h"

int Calculator::add(int a, int b) {
    return a + b;
}

int Calculator::subtract(int a, int b) {
    return a - b;
}

int Calculator::calculate(int a, int b, char operation) {
    switch (operation) {
        case '+': return add(a, b);
        case '-': return subtract(a, b);
        default: return 0;
    }
}

Este calculator.cpp proporciona las implementaciones de los métodos declarados en el archivo de cabecera calculator.h.

Crea calculator_main.cpp:

touch ~/project/calculator_main.cpp

Añade el siguiente código a calculator_main.cpp:

#include <iostream>
#include "calculator.h"

int main() {
    Calculator calc;

    std::cout << "Calculator Operations:" << std::endl;
    std::cout << "10 + 5 = " << calc.calculate(10, 5, '+') << std::endl;
    std::cout << "10 - 5 = " << calc.calculate(10, 5, '-') << std::endl;

    return 0;
}

Este archivo principal utiliza la clase Calculator y realiza operaciones.

Compila el programa de la calculadora:

g++ calculator.cpp calculator_main.cpp -o calculator
./calculator

Salida de ejemplo:

Calculator Operations:
10 + 5 = 15
10 - 5 = 5

Puntos clave sobre prototipos de funciones y archivos de cabecera:

  • Archivos de cabecera (.h) declaran prototipos de funciones, clases y otras declaraciones. Actúan como una interfaz, describiendo qué funciones están disponibles.
  • Archivos fuente (.cpp) implementan las funciones reales declaradas en los archivos de cabecera. Contienen el código de cómo funcionan las funciones.
  • #ifndef, #define y #endif (guardas de inclusión) evitan inclusiones múltiples del mismo archivo de cabecera, lo que previene posibles errores de compilación.
  • El uso de archivos de cabecera promueve la modularidad y la reutilización del código.
  • Los archivos de cabecera te permiten separar el "qué" (declaraciones) del "cómo" (implementaciones).
  • Facilitan la organización del código, haciéndolo más fácil de mantener y comprender.

Puedes pensar en los archivos de cabecera como el menú de un restaurante. El menú (cabecera) enumera lo que está disponible, mientras que la cocina (archivo fuente) prepara los platos reales.

Devolver Múltiples Valores Usando Estructuras

En este paso, aprenderás a usar estructuras para devolver múltiples valores desde una función en C++. Las estructuras son tipos de datos definidos por el usuario que te permiten agrupar diferentes tipos de datos bajo un único nombre. Son ideales para devolver múltiples valores relacionados desde una función de una manera estructurada y organizada.

Abre el WebIDE y actualiza el archivo student_info.cpp en el directorio ~/project:

Reemplaza el código existente con el siguiente:

#include <iostream>
#include <string>

// Define una estructura para contener la información del estudiante
struct StudentResult {
    std::string name;
    int score;
    bool passed;
};

// Función que devuelve múltiples valores usando una estructura
StudentResult evaluateStudent(std::string name, int testScore) {
    StudentResult result;
    result.name = name;
    result.score = testScore;
    result.passed = (testScore >= 60);

    return result;
}

// Función para calcular múltiples valores estadísticos
struct MathStats {
    int sum;
    double average;
    int minimum;
    int maximum;
};

MathStats calculateArrayStats(int numbers[], int size) {
    MathStats stats;
    stats.sum = 0;
    stats.minimum = numbers[0];
    stats.maximum = numbers[0];

    for (int i = 0; i < size; i++) {
        stats.sum += numbers[i];

        if (numbers[i] < stats.minimum) {
            stats.minimum = numbers[i];
        }

        if (numbers[i] > stats.maximum) {
            stats.maximum = numbers[i];
        }
    }

    stats.average = static_cast<double>(stats.sum) / size;

    return stats;
}

int main() {
    // Demostrar la evaluación de estudiantes
    StudentResult student1 = evaluateStudent("Alice", 75);
    StudentResult student2 = evaluateStudent("Bob", 45);

    std::cout << "Student Results:" << std::endl;
    std::cout << student1.name << " - Score: " << student1.score
              << ", Passed: " << (student1.passed ? "Yes" : "No") << std::endl;
    std::cout << student2.name << " - Score: " << student2.score
              << ", Passed: " << (student2.passed ? "Yes" : "No") << std::endl;

    // Demostrar estadísticas de arreglos
    int numbers[] = {5, 2, 9, 1, 7};
    int size = sizeof(numbers) / sizeof(numbers[0]);

    MathStats stats = calculateArrayStats(numbers, size);

    std::cout << "\nArray Statistics:" << std::endl;
    std::cout << "Sum: " << stats.sum << std::endl;
    std::cout << "Average: " << stats.average << std::endl;
    std::cout << "Minimum: " << stats.minimum << std::endl;
    std::cout << "Maximum: " << stats.maximum << std::endl;

    return 0;
}

Compila y ejecuta el programa:

g++ student_info.cpp -o student_info
./student_info

Salida de ejemplo:

Student Results:
Alice - Score: 75, Passed: Yes
Bob - Score: 45, Passed: No

Array Statistics:
Sum: 24
Average: 4.8
Minimum: 1
Maximum: 9

Puntos clave sobre estructuras y devolución de múltiples valores:

  • Las estructuras agrupan datos relacionados: Puedes agrupar variables de diferentes tipos (por ejemplo, string, int, bool) en una sola unidad, lo que puede ser beneficioso cuando tienes información relacionada.
  • Devolución de tipos de datos complejos: Usando estructuras, puedes devolver tipos de datos complejos desde funciones, facilitando la gestión y el paso de conjuntos de datos relacionados.
  • Valores de retorno organizados: Devolver estructuras ayuda a organizar múltiples valores de retorno relacionados, haciendo el código más limpio y mantenible.
  • Proporciona una forma clara de pasar información estructurada: Las estructuras proporcionan un contenedor claro y con nombre para devolver múltiples valores relacionados, mejorando la legibilidad y comprensión del código.

Puedes pensar en las estructuras como una lonchera con múltiples compartimentos que puede contener diferentes tipos de alimentos en secciones separadas.

Resumen

En este laboratorio, has adquirido una comprensión integral de cómo definir y usar funciones en C++. Aprendiste a crear funciones con varios tipos de retorno, pasar parámetros por valor y por referencia, establecer valores de parámetros por defecto y utilizar la sobrecarga de funciones. Además, exploraste las funciones recursivas, cómo usar prototipos de funciones en archivos de cabecera y cómo devolver múltiples valores usando estructuras.

Los conceptos clave cubiertos incluyen:

  • Tipos de Retorno de Funciones: Aprendiste que las funciones pueden devolver diferentes tipos de datos, y la instrucción return debe coincidir con el tipo de retorno declarado.
  • Paso por Valor y Paso por Referencia: Comprendiste la diferencia entre pasar parámetros por valor (creando una copia) y por referencia (usando la variable original).
  • Parámetros por Defecto: Aprendiste a hacer las funciones más versátiles asignando valores por defecto a los parámetros de las funciones.
  • Sobrecarga de Funciones: Viste cómo definir múltiples funciones con el mismo nombre pero con diferentes listas de parámetros, haciendo el código más legible e intuitivo.
  • Funciones Recursivas: Exploraste el poder de las funciones recursivas, donde una función se llama a sí misma para resolver subproblemas más pequeños, junto con la importancia de los casos base para prevenir la recursión infinita.
  • Prototipos de Funciones y Archivos de Cabecera: Aprendiste a usar archivos de cabecera para organizar las declaraciones de funciones, promoviendo la modularidad y la reutilización del código.
  • Devolución de Múltiples Valores con Estructuras: Descubriste cómo devolver múltiples valores relacionados desde una función usando estructuras, que proporcionan un contenedor organizado para los datos.

Al dominar estos conceptos, ahora estás bien equipado para escribir código C++ más modular, eficiente y bien estructurado, y tienes una base sólida para explorar técnicas de programación más avanzadas.