Definir e Usar Funções em C++

C++Beginner
Pratique Agora

Introdução

Neste laboratório, você irá mergulhar no mundo das funções em C++. Funções são um pilar da programação, permitindo que você organize seu código em blocos gerenciáveis e reutilizáveis. Este laboratório abrange uma vasta gama de tópicos relacionados a funções, desde a criação de funções com vários tipos de retorno até a utilização de conceitos avançados como recursão e sobrecarga de funções (function overloading). Você aprenderá a definir funções, passar parâmetros por valor e por referência, definir valores padrão para parâmetros e entender protótipos de funções (function prototypes). Adicionalmente, você descobrirá como retornar múltiplos valores usando estruturas (structures). Ao final deste laboratório, você terá uma base sólida para escrever código C++ modular, eficiente e bem estruturado.

Começaremos explorando funções com diferentes tipos de retorno, como inteiros, números de ponto flutuante, caracteres e strings. Em seguida, você aprenderá a diferença crítica entre passar parâmetros por valor e por referência, e como os valores padrão de parâmetros podem adicionar flexibilidade às suas funções. Também abordaremos a sobrecarga de funções, que permite definir múltiplas funções com o mesmo nome, mas com listas de parâmetros diferentes. Adicionalmente, você explorará funções recursivas, uma ferramenta poderosa para resolver problemas chamando a si mesmas. Você verá como usar protótipos de funções em arquivos de cabeçalho (header files) para criar um código mais organizado. Finalmente, você descobrirá como retornar múltiplos valores de uma função usando estruturas.

Este é um Lab Guiado, que fornece instruções passo a passo para ajudá-lo a aprender e praticar. Siga as instruções cuidadosamente para completar cada etapa e ganhar experiência prática. Dados históricos mostram que este é um laboratório de nível iniciante com uma taxa de conclusão de 84%. Recebeu uma taxa de avaliações positivas de 97% dos estudantes.

Criar Funções com Diferentes Tipos de Retorno

Funções são um conceito fundamental na programação. Elas permitem que você divida programas grandes em partes menores e gerenciáveis, tornando seu código mais fácil de escrever, entender e manter. Funções também podem retornar valores, permitindo que dados sejam passados de volta para a parte do programa que as chamou.

Aqui está a sintaxe básica de uma função em C++:

return_type nome_funcao(parametros) {
    // Corpo da função
    return valor;
}
  • return_type: Especifica o tipo de dado do valor que a função retornará. Se a função não retornar nenhum valor, você usa a palavra-chave void.
  • nome_funcao: É o nome que você usará para chamar a função. Escolha um nome que descreva claramente o propósito da função.
  • parametros: São os valores de entrada que você passa para a função. Eles são opcionais, e você pode ter zero ou mais parâmetros.
  • valor: É o valor que a função retorna. Ele deve corresponder ao return_type da função. Se o return_type for void, você omite a instrução return ou simplesmente usa return; sem um valor.

Nesta etapa, você aprenderá a criar funções que retornam diferentes tipos de dados. Essa capacidade é crucial para construir programas versáteis e funcionais.

Abra o WebIDE e crie um novo arquivo chamado function_types.cpp no diretório ~/project:

touch ~/project/function_types.cpp

Abra o arquivo function_types.cpp no editor e adicione o seguinte código:

#include <iostream>
#include <string>

// Função que retorna um inteiro
int addNumbers(int a, int b) {
    return a + b;
}

// Função que retorna um double (número de ponto flutuante)
double calculateAverage(double x, double y) {
    return (x + y) / 2.0;
}

// Função que retorna um caractere
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';
}

// Função que retorna uma string
std::string getStudentStatus(bool isEnrolled) {
    return isEnrolled ? "Active" : "Inactive";
}

int main() {
    // Demonstrando 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;
}

Compile e execute o programa:

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

Exemplo de saída:

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

Pontos chave sobre tipos de retorno de função:

  • Funções podem retornar diferentes tipos de dados, proporcionando flexibilidade ao código.
  • O return_type é especificado antes do nome da função, indicando que tipo de dado a função enviará de volta.
  • A instrução return deve corresponder ao return_type declarado, caso contrário, o compilador relatará um erro.
  • Funções void não retornam um valor, e você usa return; ou simplesmente omite a instrução de retorno se estiver no final de uma função void.
  • Você pode retornar tipos primitivos (como int, double, char), strings e até mesmo tipos definidos pelo usuário.
  • Pense nos tipos de retorno de função como diferentes tipos de recipientes. Assim como você usaria uma caixa, uma sacola ou uma cesta para carregar itens diferentes, as funções usam diferentes tipos de retorno para enviar de volta diferentes tipos de dados.

Passar Parâmetros para Funções por Valor

Nesta etapa, você aprenderá sobre passar parâmetros para funções por valor em C++. Quando você passa um parâmetro por valor, uma cópia do argumento original é criada, e essa cópia é usada dentro da função. Quaisquer alterações feitas no parâmetro dentro da função não afetam a variável original fora da função.

Abra o WebIDE e crie um novo arquivo chamado pass_by_value.cpp no diretório ~/project:

touch ~/project/pass_by_value.cpp

Abra o arquivo pass_by_value.cpp no editor e adicione o seguinte código:

#include <iostream>

// Função que demonstra passagem por valor
void modifyValue(int x) {
    // Esta modificação afeta apenas a cópia local, não a variável original
    x = x * 2;
    std::cout << "Inside function - Value of x: " << x << std::endl;
}

int main() {
    // Variável original
    int number = 10;

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

    // Chama a função com a variável original
    modifyValue(number);

    // A variável original permanece inalterada
    std::cout << "After function call - Original value: " << number << std::endl;

    return 0;
}

Compile e execute o programa:

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

Exemplo de saída:

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

Vamos criar outro exemplo para ilustrar ainda mais a passagem por valor:

touch ~/project/swap_values.cpp

Adicione o seguinte código a swap_values.cpp:

#include <iostream>
#include <string>

// Função para trocar valores (não funcionará como esperado com passagem 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;

    // Chama a função swap
    swapValues(first, second);

    // As variáveis originais permanecem inalteradas
    std::cout << "After swap - first: " << first << ", second: " << second << std::endl;

    return 0;
}

Compile e execute o segundo programa:

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

Exemplo de saída:

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

Pontos chave sobre passagem por valor:

  • Uma cópia do argumento é passada para a função, preservando o valor original.
  • Alterações feitas no parâmetro dentro da função não afetam a variável original fora da função.
  • A passagem por valor é adequada para tipos de dados simples como int, char, float, etc., quando você não precisa modificar o valor original.
  • Ao passar objetos grandes por valor, a cópia pode consumir mais memória e tempo, tornando-a menos eficiente em comparação com a passagem por referência.

Você pode pensar na passagem por valor como fazer uma fotocópia de um documento. O original permanece inalterado, e você pode modificar a cópia sem afetar o original.

Implementar Passagem por Referência Usando o Operador &

Nesta etapa, você aprenderá como passar parâmetros para funções por referência usando o operador & em C++. A passagem por referência permite que você forneça à função acesso direto à variável original, o que significa que quaisquer alterações feitas no parâmetro dentro da função modificarão diretamente a variável original fora da função.

Abra o WebIDE e crie um novo arquivo chamado pass_by_reference.cpp no diretório ~/project:

touch ~/project/pass_by_reference.cpp

Abra o arquivo pass_by_reference.cpp no editor e adicione o seguinte código:

#include <iostream>

// Função que demonstra passagem por referência
void swapValues(int& a, int& b) {
    // Modifica diretamente as variáveis originais
    int temp = a;
    a = b;
    b = temp;
}

// Função para modificar um valor usando referência
void incrementValue(int& x) {
    // Aumenta diretamente a variável original
    x++;
}

int main() {
    // Exemplo de troca
    int first = 5;
    int second = 10;

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

    // Chama a função swap com referências
    swapValues(first, second);

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

    // Exemplo de incremento
    int number = 7;

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

    // Chama a função increment com referência
    incrementValue(number);

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

    return 0;
}

Compile e execute o programa:

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

Exemplo de saída:

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

Vamos criar outro exemplo para demonstrar o poder da passagem por referência:

touch ~/project/string_reference.cpp

Adicione o seguinte código a string_reference.cpp:

#include <iostream>
#include <string>

// Função para modificar uma string usando referência
void appendText(std::string& text) {
    text += " - Modified";
}

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

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

    // Modifica a string diretamente usando referência
    appendText(message);

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

    return 0;
}

Compile e execute o segundo programa:

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

Exemplo de saída:

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

Pontos chave sobre passagem por referência:

  • Use & após o tipo para criar um parâmetro de referência (por exemplo, int& a).
  • Modifica diretamente a variável original, ao contrário da passagem por valor que usa uma cópia.
  • Evita a sobrecarga de cópia de objetos grandes, tornando-a mais eficiente para tipos de dados complexos.
  • Útil quando você precisa modificar o valor original ou trabalhar com tipos de dados complexos sem copiar.

Você pode pensar na passagem por referência como dar a alguém o documento original em vez de uma fotocópia. Quaisquer alterações que eles façam afetarão diretamente o original.

Definir Valores Padrão para Parâmetros de Função

Nesta etapa, você aprenderá como definir valores padrão para parâmetros de função em C++. Parâmetros padrão permitem que você atribua valores padrão a argumentos de função, tornando suas funções mais versáteis e fáceis de usar. Quando um chamador omite um parâmetro com um valor padrão, o valor padrão é automaticamente utilizado.

Abra o WebIDE e crie um novo arquivo chamado default_parameters.cpp no diretório ~/project:

touch ~/project/default_parameters.cpp

Abra o arquivo default_parameters.cpp no editor e adicione o seguinte código:

#include <iostream>
#include <string>

// Função com parâmetros padrão
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;
    }
}

// Outro exemplo com parâmetros padrão para cálculo
double calculateArea(double length = 1.0, double width = 1.0) {
    return length * width;
}

int main() {
    // Chama a função sem argumentos (usa valores padrão)
    greetUser();

    // Chama a função com argumentos parciais
    greetUser("Alice");

    // Chama a função com todos os argumentos
    greetUser("Bob", 25);

    // Demonstra o cálculo de área com parâmetros padrão
    std::cout << "\nArea calculations:" << std::endl;

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

    // Área com um parâmetro
    std::cout << "Area with length 5: " << calculateArea(5.0) << std::endl;

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

    return 0;
}

Compile e execute o programa:

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

Exemplo de saída:

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

Pontos chave sobre parâmetros padrão:

  • Valores padrão são especificados na declaração da função, logo após o tipo do parâmetro (por exemplo, int age = 0).
  • Parâmetros com valores padrão devem estar no final da lista de parâmetros. Você não pode ter parâmetros sem padrão após parâmetros com padrão.
  • Você pode chamar a função com menos argumentos, e os que faltarem usarão seus valores padrão.
  • Valores padrão fornecem flexibilidade e reduzem a necessidade de sobrecargas de função múltiplas que fazem a mesma coisa com entradas ligeiramente diferentes.

Vamos criar outro exemplo para demonstrar parâmetros padrão mais complexos:

touch ~/project/student_info.cpp

Adicione o seguinte código a student_info.cpp:

#include <iostream>
#include <string>

// Função com múltiplos parâmetros padrão
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 maneiras de chamar a função
    printStudentInfo();  // Todos os padrões
    std::cout << "\n";
    printStudentInfo("John");  // Apenas nome
    std::cout << "\n";
    printStudentInfo("Alice", 20);  // Nome e idade
    std::cout << "\n";
    printStudentInfo("Bob", 22, "A", true);  // Todos os parâmetros

    return 0;
}

Compile e execute o segundo programa:

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

Exemplo de saída:

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

Você pode pensar em parâmetros padrão como um buffet onde você pode escolher pegar todos os itens ou apenas alguns, com alguns itens preenchidos automaticamente se você não os especificar.

Criar Sobrecargas de Função com Parâmetros Diferentes

Nesta etapa, você aprenderá sobre sobrecarga de função em C++, um recurso que permite criar múltiplas funções com o mesmo nome, desde que elas tenham listas de parâmetros diferentes (seja no número de parâmetros ou nos tipos de parâmetros). Este recurso torna seu código mais flexível e legível, permitindo que você execute operações semelhantes em diferentes tipos de dados ou com um número variável de entradas usando o mesmo nome de função.

Abra o WebIDE e crie um novo arquivo chamado function_overloads.cpp no diretório ~/project:

touch ~/project/function_overloads.cpp

Abra o arquivo function_overloads.cpp no editor e adicione o seguinte código:

#include <iostream>
#include <string>

// Sobrecargas de função para somar 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 função com número diferente de parâmetros
int add(int a, int b, int c) {
    std::cout << "Adding three integers" << std::endl;
    return a + b + c;
}

// Sobrecarga de função com tipos de parâmetros diferentes
std::string add(std::string a, std::string b) {
    std::cout << "Concatenating strings" << std::endl;
    return a + b;
}

int main() {
    // Chamando diferentes funções sobrecarregadas
    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;
}

Compile e execute o programa:

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

Exemplo de saída:

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!

Vamos criar outro exemplo para demonstrar mais sobrecarga de função:

touch ~/project/overloading_examples.cpp

Adicione o seguinte código a overloading_examples.cpp:

#include <iostream>
#include <string>

// Funções de impressão sobrecarregadas
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;
}

// Função de cálculo sobrecarregada
int calculate(int a, int b) {
    return a + b;
}

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

int main() {
    // Demonstrando sobrecarga de função
    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;
}

Compile e execute o segundo programa:

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

Exemplo de saída:

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

Pontos chave sobre sobrecarga de função:

  • Funções podem ter o mesmo nome, desde que tenham listas de parâmetros diferentes (tipos diferentes ou números diferentes de parâmetros).
  • O compilador C++ decide qual função sobrecarregada chamar com base nos tipos e no número de argumentos fornecidos durante a chamada da função.
  • A sobrecarga de função leva a um código mais legível e intuitivo, permitindo reutilizar nomes para ações semelhantes.
  • Você não pode sobrecarregar funções se elas diferirem apenas pelos seus tipos de retorno. Os parâmetros devem ser diferentes para que a sobrecarga funcione corretamente.

Você pode pensar em sobrecarga de função como um controle remoto universal que funciona com diferentes tipos de dispositivos. O mesmo botão (nome da função) faz coisas diferentes dependendo do contexto (os argumentos que você fornece).

Escrever Funções Recursivas para Cálculo de Fatorial

Nesta etapa, você aprenderá sobre funções recursivas implementando um programa de cálculo de fatorial. Recursão é uma técnica de programação onde uma função chama a si mesma para resolver um problema. Para evitar loops infinitos, funções recursivas devem ter um caso base onde elas param a chamada recursiva, juntamente com um caso recursivo para continuar chamando a si mesma para trabalhar em direção ao caso base.

Abra o WebIDE e crie um novo arquivo chamado factorial_recursive.cpp no diretório ~/project:

touch ~/project/factorial_recursive.cpp

Abra o arquivo factorial_recursive.cpp no editor e adicione o seguinte código:

#include <iostream>

// Função recursiva para calcular o fatorial
unsigned long long calculateFactorial(int n) {
    // Caso base: fatorial de 0 ou 1 é 1
    if (n == 0 || n == 1) {
        return 1;
    }

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

int main() {
    // Calcular fatorial 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;
}

Compile e execute o programa:

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

Exemplo de saída:

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

Vamos criar outro exemplo com uma função recursiva mais detalhada:

touch ~/project/factorial_steps.cpp

Adicione o seguinte código a factorial_steps.cpp:

#include <iostream>

// Função recursiva com saída passo a passo
unsigned long long calculateFactorialWithSteps(int n, int depth = 0) {
    // Adiciona indentação para visualização
    std::string indent(depth * 2, ' ');

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

    // Caso recursivo com visualização
    std::cout << indent << "Calculating factorial(" << n << ")" << std::endl;

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

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

Compile e execute o segundo programa:

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

Exemplo de saída:

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

Pontos chave sobre funções recursivas:

  • Funções recursivas chamam a si mesmas: Uma função chama a si mesma para resolver subproblemas menores do mesmo tipo.
  • Caso Base: Uma função recursiva deve ter um caso base, uma condição que interrompe as chamadas recursivas. Sem um caso base, a função se chamará infinitamente, levando a um erro de estouro de pilha (stack overflow).
  • Caso Recursivo: O caso recursivo é onde a função chama a si mesma para avançar em direção ao caso base.
  • Decomposição de Problemas: A recursão decompõe um problema complexo em subproblemas menores e mais simples, tornando-o mais fácil de gerenciar.
  • Uso da Pilha (Stack): Cada chamada recursiva ocupa espaço na pilha de chamadas do programa. Recursão profunda pode levar a estouro de pilha, portanto, esteja ciente dos limites da pilha de chamadas.
  • Problemas Adequados: A recursão é particularmente adequada para problemas que podem ser naturalmente definidos em termos de subproblemas menores e semelhantes, como travessias de árvores, algoritmos de divisão e conquista (divide-and-conquer) e geração de fractais.

Você pode pensar em recursão como um conjunto de bonecas russas aninhadas, onde cada boneca contém uma versão menor de si mesma, e a boneca mais interna representa o caso base que interrompe o processo de aninhamento.

Usar Protótipos de Função em Arquivos de Cabeçalho

Nesta etapa, você aprenderá sobre protótipos de função e como usar arquivos de cabeçalho para organizar e declarar funções em C++. Protótipos de função declaram o nome da função, o tipo de retorno e os parâmetros sem fornecer a implementação (o corpo da função). Ao usar arquivos de cabeçalho, você separa a interface de uma função (sua declaração) de sua implementação. Essa separação melhora a organização do código, tornando mais fácil manter e reutilizar funções em várias partes do seu programa ou em múltiplos programas.

Abra o WebIDE e crie três arquivos no diretório ~/project:

Primeiro, crie math_functions.h:

touch ~/project/math_functions.h

Adicione o seguinte código a math_functions.h:

#ifndef MATH_FUNCTIONS_H
#define MATH_FUNCTIONS_H

// Protótipos de função para operações 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

Arquivos .h são usados para declarações, contendo protótipos de função e outras declarações, mas não implementações. Dessa forma, você pode declarar funções sem implementá-las. As diretivas #ifndef, #define e #endif são chamadas de include guards (guardas de inclusão), e elas previnem múltiplas inclusões do mesmo arquivo de cabeçalho, o que pode causar erros.

Em seguida, crie math_functions.cpp:

touch ~/project/math_functions.cpp

Adicione o seguinte código a math_functions.cpp:

#include "math_functions.h"

// Implementações das funções
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) {
    // Verifica divisão por zero
    if (b == 0) {
        return 0;
    }
    return a / b;
}

Este arquivo .cpp contém as implementações reais das funções que foram declaradas no arquivo de cabeçalho.

Finalmente, crie main.cpp:

touch ~/project/main.cpp

Adicione o seguinte código a main.cpp:

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

int main() {
    // Demonstra chamadas de função do arquivo de cabeçalho
    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 arquivo main.cpp inclui o arquivo de cabeçalho math_functions.h, o que torna os protótipos de função disponíveis. Ele pode então usar as funções implementadas em math_functions.cpp.

Compile o programa usando múltiplos arquivos fonte:

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

Exemplo de saída:

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

Vamos criar outro exemplo com um arquivo de cabeçalho mais complexo:

Crie calculator.h:

touch ~/project/calculator.h

Adicione o seguinte código a calculator.h:

#ifndef CALCULATOR_H
#define CALCULATOR_H

class Calculator {
public:
    // Protótipos de função para operações da 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 arquivo de cabeçalho declara uma classe chamada Calculator e seus métodos públicos.

Crie calculator.cpp:

touch ~/project/calculator.cpp

Adicione o seguinte 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 fornece as implementações para os métodos declarados no arquivo de cabeçalho calculator.h.

Crie calculator_main.cpp:

touch ~/project/calculator_main.cpp

Adicione o seguinte 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 arquivo principal usa a classe Calculator e realiza operações.

Compile o programa da calculadora:

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

Exemplo de saída:

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

Pontos chave sobre protótipos de função e arquivos de cabeçalho:

  • Arquivos de cabeçalho (.h) declaram protótipos de função, classes e outras declarações. Eles agem como uma interface, descrevendo quais funções estão disponíveis.
  • Arquivos fonte (.cpp) implementam as funções reais declaradas nos arquivos de cabeçalho. Eles contêm o código de como as funções funcionam.
  • #ifndef, #define e #endif (include guards) previnem inclusões múltiplas do mesmo arquivo de cabeçalho, o que evita potenciais erros de compilação.
  • O uso de arquivos de cabeçalho promove modularidade e reutilização de código.
  • Arquivos de cabeçalho permitem separar o "o quê" (declarações) do "como" (implementações).
  • Eles facilitam a organização do código, tornando-o mais fácil de manter e entender.

Você pode pensar em arquivos de cabeçalho como o menu de um restaurante. O menu (cabeçalho) lista o que está disponível, enquanto a cozinha (arquivo fonte) prepara os pratos reais.

Retornar Múltiplos Valores Usando Estruturas

Nesta etapa, você aprenderá como usar estruturas para retornar múltiplos valores de uma função em C++. Estruturas são tipos de dados definidos pelo usuário que permitem agrupar diferentes tipos de dados sob um único nome. Elas são ideais para retornar múltiplos valores relacionados de uma função de maneira estruturada e organizada.

Abra o WebIDE e atualize o arquivo student_info.cpp no diretório ~/project:

Substitua o código existente pelo seguinte:

#include <iostream>
#include <string>

// Define uma estrutura para conter informações do aluno
struct StudentResult {
    std::string name;
    int score;
    bool passed;
};

// Função que retorna múltiplos valores usando uma estrutura
StudentResult evaluateStudent(std::string name, int testScore) {
    StudentResult result;
    result.name = name;
    result.score = testScore;
    result.passed = (testScore >= 60);

    return result;
}

// Função para calcular múltiplos valores estatí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() {
    // Demonstração da avaliação de alunos
    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;

    // Demonstração de estatísticas de array
    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;
}

Compile e execute o programa:

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

Exemplo de saída:

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

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

Pontos chave sobre estruturas e retorno de múltiplos valores:

  • Estruturas agrupam dados relacionados: Você pode agrupar variáveis de diferentes tipos (por exemplo, string, int, bool) em uma única unidade, o que pode ser benéfico quando você tem informações relacionadas.
  • Retorno de tipos de dados complexos: Usando estruturas, você pode retornar tipos de dados complexos de funções, tornando fácil gerenciar e passar conjuntos de dados relacionados.
  • Valores de retorno organizados: Retornar estruturas ajuda a organizar múltiplos valores de retorno relacionados, tornando o código mais limpo e mais fácil de manter.
  • Fornece uma maneira clara de passar informações estruturadas: Estruturas fornecem um contêiner claro e nomeado para retornar múltiplos valores relacionados, melhorando a legibilidade e a compreensão do código.

Você pode pensar em estruturas como uma lancheira com múltiplos compartimentos que pode conter diferentes tipos de alimentos em seções separadas.

Resumo

Neste laboratório, você obteve uma compreensão abrangente de como definir e usar funções em C++. Você aprendeu a criar funções com vários tipos de retorno, passar parâmetros por valor e por referência, definir valores de parâmetros padrão e utilizar sobrecarga de funções (function overloading). Além disso, você explorou funções recursivas, como usar protótipos de função em arquivos de cabeçalho e como retornar múltiplos valores usando estruturas.

Os principais conceitos abordados incluem:

  • Tipos de Retorno de Função: Você aprendeu que as funções podem retornar diferentes tipos de dados, e a instrução return deve corresponder ao tipo de retorno declarado.
  • Passagem por Valor e Passagem por Referência: Você compreendeu a diferença entre passar parâmetros por valor (criando uma cópia) e por referência (usando a variável original).
  • Parâmetros Padrão: Você aprendeu como tornar as funções mais versáteis atribuindo valores padrão aos parâmetros de função.
  • Sobrecarga de Funções (Function Overloading): Você viu como definir múltiplas funções com o mesmo nome, mas com listas de parâmetros diferentes, tornando o código mais legível e intuitivo.
  • Funções Recursivas: Você explorou o poder das funções recursivas, onde uma função chama a si mesma para resolver subproblemas menores, juntamente com a importância dos casos base para evitar recursão infinita.
  • Protótipos de Função e Arquivos de Cabeçalho: Você aprendeu a usar arquivos de cabeçalho para organizar declarações de funções, promovendo modularidade e reutilização de código.
  • Retorno de Múltiplos Valores com Estruturas: Você descobriu como retornar múltiplos valores relacionados de uma função usando estruturas, que fornecem um contêiner organizado para dados.

Ao dominar esses conceitos, você agora está bem equipado para escrever código C++ mais modular, eficiente e bem estruturado, e tem uma base sólida para explorar técnicas de programação mais avançadas.