Definir y utilizar funciones en C++

C++C++Beginner
Practicar Ahora

💡 Este tutorial está traducido por IA desde la versión en inglés. Para ver la versión original, puedes hacer clic aquí

Introducción

En este laboratorio, profundizarás en el mundo de las funciones en C++. Las funciones son un pilar fundamental de la programación, ya que te permiten organizar tu código en bloques manejables y reutilizables. Este laboratorio abarca una amplia gama de temas relacionados con las funciones, desde la creación de funciones con diversos tipos de retorno hasta el aprovechamiento de conceptos avanzados como la recursividad y la sobrecarga de funciones. Aprenderás cómo definir funciones, pasar parámetros por valor y por referencia, establecer valores predeterminados para los parámetros 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 en 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 crucial entre pasar parámetros por valor y por referencia, y cómo los valores predeterminados de los parámetros 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 listas de parámetros diferentes. Además, explorarás las funciones recursivas, una herramienta poderosa para resolver problemas llamándose a sí mismas. Verás cómo utilizar los prototipos de funciones en archivos de encabezado para crear un código más organizado. Finalmente, descubrirás cómo devolver múltiples valores desde una función utilizando estructuras.

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, lo que hace 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.

A continuación, se muestra la sintaxis básica de una función en C++:

return_type function_name(parameters) {
    // Function body
    return value;
}
  • return_type: Esto especifica el tipo de datos del valor que la función devolverá. Si la función no devuelve ningún valor, se utiliza la palabra clave void.
  • function_name: Este es el nombre que se utilizará para llamar a la función. Elija un nombre que describa claramente el propósito de la función.
  • parameters: Estos son los valores de entrada que se pasan a la función. Son opcionales y se pueden tener cero o más parámetros.
  • value: Este es el valor que la función devuelve. Debe coincidir con el return_type de la función. Si el return_type es void, se omite la instrucción return o simplemente se utiliza 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 agrega el siguiente código:

#include <iostream>
#include <string>

// Function returning an integer
int addNumbers(int a, int b) {
    return a + b;
}

// Function returning a double (floating-point number)
double calculateAverage(double x, double y) {
    return (x + y) / 2.0;
}

// Function returning a character
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';
}

// Function returning a string
std::string getStudentStatus(bool isEnrolled) {
    return isEnrolled? "Active" : "Inactive";
}

int main() {
    // Demonstrating different return types
    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

Ejemplo de salida:

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

Puntos clave sobre los tipos de retorno de las funciones:

  • Las funciones pueden devolver diferentes tipos de datos, lo que brinda flexibilidad al código.
  • El return_type se especifica antes del nombre de la función, lo que indica qué tipo de datos devolverá la función.
  • La instrucción return debe coincidir con el return_type declarado; de lo contrario, el compilador informará un error.
  • Las funciones void no devuelven un valor, y se utiliza return; o simplemente se omite la instrucción return si se está al final de una función void.
  • Se pueden devolver tipos primitivos (como int, double, char), cadenas e incluso tipos definidos por el usuario.
  • Puedes pensar en los tipos de retorno de las funciones como diferentes tipos de contenedores. Así como usarías una caja, una bolsa o una canasta para llevar diferentes objetos, las funciones utilizan diferentes tipos de retorno para devolver 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 se pasa un parámetro por valor, se crea una copia del argumento original y se utiliza esa copia dentro de la función. Cualquier cambio realizado al 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 agrega el siguiente código:

#include <iostream>

// Function that demonstrates pass by value
void modifyValue(int x) {
    // This modification only affects the local copy, not the original variable
    x = x * 2;
    std::cout << "Inside function - Value of x: " << x << std::endl;
}

int main() {
    // Original variable
    int number = 10;

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

    // Call the function with the original variable
    modifyValue(number);

    // Original variable remains unchanged
    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

Ejemplo de salida:

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

Creemos otro ejemplo para ilustrar mejor el paso por valor:

touch ~/project/swap_values.cpp

Agrega el siguiente código a swap_values.cpp:

#include <iostream>
#include <string>

// Function to swap values (will not work as expected with pass by value)
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;

    // Call swap function
    swapValues(first, second);

    // Original variables remain unchanged
    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

Ejemplo de salida:

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 al 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 se necesita modificar el valor original.
  • Al pasar objetos grandes por valor, la copia puede consumir más memoria y tiempo, lo que lo hace 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 el paso por referencia utilizando el operador &

En este paso, aprenderás cómo pasar parámetros a funciones por referencia utilizando el operador & en C++. El paso por referencia te permite dar a la función acceso directo a la variable original, lo que significa que cualquier cambio realizado al 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 agrega el siguiente código:

#include <iostream>

// Function that demonstrates pass by reference
void swapValues(int& a, int& b) {
    // Directly modify the original variables
    int temp = a;
    a = b;
    b = temp;
}

// Function to modify a value using reference
void incrementValue(int& x) {
    // Directly increases the original variable
    x++;
}

int main() {
    // Swap example
    int first = 5;
    int second = 10;

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

    // Call swap function with references
    swapValues(first, second);

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

    // Increment example
    int number = 7;

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

    // Call increment function with reference
    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

Ejemplo de salida:

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

Agrega el siguiente código a string_reference.cpp:

#include <iostream>
#include <string>

// Function to modify a string using reference
void appendText(std::string& text) {
    text += " - Modified";
}

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

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

    // Modify string directly using reference
    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

Ejemplo de salida:

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

Puntos clave sobre el paso por referencia:

  • Utiliza & 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.
  • Es útil cuando necesitas modificar el valor original o trabajar con tipos de datos complejos sin copiarlos.

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 los parámetros de una función

En este paso, aprenderás cómo establecer valores predeterminados para los parámetros de una función en C++. Los parámetros predeterminados te permiten asignar valores predeterminados a los argumentos de una función, lo que hace que tus funciones sean más versátiles y fáciles de usar. Cuando el llamador omite un parámetro con un valor predeterminado, se utiliza automáticamente ese 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 agrega el siguiente código:

#include <iostream>
#include <string>

// Function with default parameters
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;
    }
}

// Another example with default parameters for calculation
double calculateArea(double length = 1.0, double width = 1.0) {
    return length * width;
}

int main() {
    // Call function with no arguments (uses default values)
    greetUser();

    // Call function with partial arguments
    greetUser("Alice");

    // Call function with all arguments
    greetUser("Bob", 25);

    // Demonstrate area calculation with default parameters
    std::cout << "\nArea calculations:" << std::endl;

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

    // Area with one parameter
    std::cout << "Area with length 5: " << calculateArea(5.0) << std::endl;

    // Area with both parameters
    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

Ejemplo de salida:

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 de parámetro (por ejemplo, int age = 0).
  • Los parámetros con valores predeterminados deben estar al final de la lista de parámetros. No se pueden tener parámetros no predeterminados después de parámetros predeterminados.
  • Puedes llamar a la función con menos argumentos, y los que falten utilizarán sus valores predeterminados.
  • Los valores predeterminados brindan flexibilidad y reducen la necesidad de múltiples sobrecargas de función que hacen lo mismo con entradas ligeramente diferentes.

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

touch ~/project/student_info.cpp

Agrega el siguiente código a student_info.cpp:

#include <iostream>
#include <string>

// Function with multiple default parameters
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() {
    // Different ways to call the function
    printStudentInfo();  // All defaults
    std::cout << "\n";
    printStudentInfo("John");  // Only name
    std::cout << "\n";
    printStudentInfo("Alice", 20);  // Name and age
    std::cout << "\n";
    printStudentInfo("Bob", 22, "A", true);  // All parameters

    return 0;
}

Compila y ejecuta el segundo programa:

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

Ejemplo de salida:

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 bufé donde puedes elegir tomar todos los elementos o solo algunos, y algunos elementos se llenan 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 una cantidad 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 agrega el siguiente código:

#include <iostream>
#include <string>

// Function overloads for adding different types of numbers
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;
}

// Function overloads with different number of parameters
int add(int a, int b, int c) {
    std::cout << "Adding three integers" << std::endl;
    return a + b + c;
}

// Function overload with different parameter types
std::string add(std::string a, std::string b) {
    std::cout << "Concatenating strings" << std::endl;
    return a + b;
}

int main() {
    // Calling different overloaded functions
    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

Ejemplo de salida:

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

Agrega el siguiente código a overloading_examples.cpp:

#include <iostream>
#include <string>

// Overloaded print functions
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;
}

// Overloaded calculate function
int calculate(int a, int b) {
    return a + b;
}

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

int main() {
    // Demonstrating function overloading
    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

Ejemplo de salida:

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 y cuando 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 en función de 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, lo que te permite reutilizar nombres para acciones similares.
  • No se pueden sobrecargar funciones si solo difieren en 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 la función) hace diferentes cosas 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 factoriales. La recursividad es una técnica de programación en la que una función se llama a sí misma para resolver un problema. Para evitar bucles infinitos, las funciones recursivas deben tener un caso base en el que detengan la llamada recursiva, junto con un caso recursivo para seguir llamándose a sí mismas y avanzar 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 agrega el siguiente código:

#include <iostream>

// Recursive function to calculate factorial
unsigned long long calculateFactorial(int n) {
    // Base case: factorial of 0 or 1 is 1
    if (n == 0 || n == 1) {
        return 1;
    }

    // Recursive case: n! = n * (n-1)!
    return n * calculateFactorial(n - 1);
}

int main() {
    // Calculate factorial for different numbers
    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

Ejemplo de salida:

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

Agrega el siguiente código a factorial_steps.cpp:

#include <iostream>

// Recursive function with step-by-step output
unsigned long long calculateFactorialWithSteps(int n, int depth = 0) {
    // Add indentation for visualization
    std::string indent(depth * 2, ' ');

    // Base case: factorial of 0 or 1 is 1
    if (n == 0 || n == 1) {
        std::cout << indent << "Base case: factorial(" << n << ") = 1" << std::endl;
        return 1;
    }

    // Recursive case with visualization
    std::cout << indent << "Calculating factorial(" << n << ")" << std::endl;

    // Recursive call
    unsigned long long subResult = calculateFactorialWithSteps(n - 1, depth + 1);

    // Combine results
    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

Ejemplo de salida:

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 detenga 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 recursividad descompone un problema complejo en subproblemas más pequeños y simples, lo que lo hace más fácil de manejar.
  • Uso de la pila: Cada llamada recursiva ocupa espacio en la pila de llamadas del programa. Una 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 recursividad es especialmente adecuada para problemas que se pueden definir naturalmente en términos de subproblemas más pequeños y similares, como el recorrido de árboles, los algoritmos de división y conquista y la generación de fractales.

Puedes pensar en la recursividad 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.

Utilizar prototipos de funciones en archivos de cabecera

En este paso, aprenderás sobre los prototipos de funciones y cómo utilizar 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 utilizar archivos de cabecera, se separa la interfaz de una función (su declaración) de su implementación. Esta separación mejora la organización del código, lo que facilita 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

Agrega el siguiente código a math_functions.h:

#ifndef MATH_FUNCTIONS_H
#define MATH_FUNCTIONS_H

// Function prototypes for mathematical operations
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, se pueden declarar funciones sin implementarlas. Las directivas #ifndef, #define y #endif se llaman guardas de inclusión, y evitan múltiples inclusiones del archivo de cabecera, lo que puede causar errores.

A continuación, crea math_functions.cpp:

touch ~/project/math_functions.cpp

Agrega el siguiente código a math_functions.cpp:

#include "math_functions.h"

// Function implementations
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) {
    // Check for division by zero
    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

Agrega el siguiente código a main.cpp:

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

int main() {
    // Demonstrate function calls from header file
    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 función 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

Ejemplo de salida:

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

Agrega el siguiente código a calculator.h:

#ifndef CALCULATOR_H
#define CALCULATOR_H

class Calculator {
public:
    // Function prototypes for calculator operations
    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

Agrega 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 archivo 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

Agrega 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

Ejemplo de salida:

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

Puntos clave sobre los prototipos de funciones y los archivos de cabecera:

  • Los archivos de cabecera (.h) declaran prototipos de funciones, clases y otras declaraciones. Actúan como una interfaz, que describe qué funciones están disponibles.
  • Los 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 múltiples inclusiones del mismo archivo de cabecera, lo que evita 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, lo que lo hace más fácil de mantener y entender.

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

Devolver múltiples valores utilizando estructuras

En este paso, aprenderás cómo utilizar 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 solo nombre. Son ideales para devolver múltiples valores relacionados desde una función de 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 a structure to hold student information
struct StudentResult {
    std::string name;
    int score;
    bool passed;
};

// Function that returns multiple values using a structure
StudentResult evaluateStudent(std::string name, int testScore) {
    StudentResult result;
    result.name = name;
    result.score = testScore;
    result.passed = (testScore >= 60);

    return result;
}

// Function to calculate multiple statistical values
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() {
    // Demonstrate student evaluation
    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;

    // Demonstrate array statistics
    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

Ejemplo de salida:

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 las estructuras y devolver 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.
  • Devolver tipos de datos complejos: Utilizando estructuras, puedes devolver tipos de datos complejos desde funciones, lo que facilita el manejo y el paso de conjuntos de datos relacionados.
  • Valores de retorno organizados: Devolver estructuras ayuda a organizar múltiples valores de retorno relacionados, lo que hace que el código sea 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, lo que mejora la legibilidad y la comprensión del código.

Puedes pensar en las estructuras como una lunchbox de 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 utilizar funciones en C++. Has aprendido a crear funciones con varios tipos de retorno, a pasar parámetros por valor y por referencia, a establecer valores predeterminados para los parámetros y a utilizar la sobrecarga de funciones. Además, has explorado las funciones recursivas, cómo utilizar prototipos de funciones en archivos de cabecera y cómo devolver múltiples valores utilizando estructuras.

Los conceptos clave cubiertos incluyen:

  • Tipos de retorno de funciones: Has aprendido que las funciones pueden devolver diferentes tipos de datos, y que la instrucción return debe coincidir con el tipo de retorno declarado.
  • Paso por valor y paso por referencia: Has comprendido la diferencia entre pasar parámetros por valor (haciendo una copia) y por referencia (utilizando la variable original).
  • Parámetros predeterminados: Has aprendido cómo hacer que las funciones sean más versátiles asignando valores predeterminados a los parámetros de las funciones.
  • Sobrecarga de funciones: Has visto cómo definir múltiples funciones con el mismo nombre pero con listas de parámetros diferentes, lo que hace que el código sea más legible e intuitivo.
  • Funciones recursivas: Has explorado el poder de las funciones recursivas, donde una función se llama a sí misma para resolver subproblemas más pequeños, así como la importancia de los casos base para evitar la recursión infinita.
  • Prototipos de funciones y archivos de cabecera: Has aprendido cómo utilizar archivos de cabecera para organizar las declaraciones de funciones, lo que promueve la modularidad y la reutilización del código.
  • Devolver múltiples valores con estructuras: Has descubierto cómo devolver múltiples valores relacionados desde una función utilizando 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.