Определение и использование функций в C++

C++Beginner
Практиковаться сейчас

Введение

В этой лабораторной работе вы погрузитесь в мир функций в C++. Функции являются краеугольным камнем программирования, позволяя вам организовывать ваш код в управляемые, повторно используемые блоки. Эта лабораторная работа охватывает широкий спектр тем, связанных с функциями, от создания функций с различными типами возвращаемых значений до использования продвинутых концепций, таких как рекурсия и перегрузка функций. Вы научитесь определять функции, передавать параметры по значению и по ссылке, устанавливать значения параметров по умолчанию и понимать прототипы функций. Кроме того, вы узнаете, как возвращать несколько значений с использованием структур. К концу этой лабораторной работы у вас будет прочная основа для написания модульного, эффективного и хорошо структурированного кода на C++.

Мы начнем с изучения функций с различными типами возвращаемых значений, такими как целые числа, числа с плавающей запятой, символы и строки. Затем вы узнаете критическое различие между передачей параметров по значению и по ссылке, а также о том, как значения параметров по умолчанию могут добавить гибкости вашим функциям. Мы также рассмотрим перегрузку функций, которая позволяет определять несколько функций с одинаковым именем, но разными списками параметров. Кроме того, вы изучите рекурсивные функции — мощный инструмент для решения задач путем их самовызова. Вы увидите, как использовать прототипы функций в заголовочных файлах для создания более организованного кода. Наконец, вы узнаете, как возвращать несколько значений из функции с использованием структур.

Создание функций с различными типами возвращаемых значений

Функции — это фундаментальная концепция в программировании. Они позволяют разбивать большие программы на более мелкие, управляемые части, делая ваш код проще для написания, понимания и поддержки. Функции также могут возвращать значения, позволяя передавать данные обратно в вызывающую часть программы.

Вот базовый синтаксис функции в C++:

return_type function_name(parameters) {
    // Тело функции
    return value;
}
  • return_type: Указывает тип данных значения, которое функция будет возвращать. Если функция не возвращает никакого значения, используется ключевое слово void.
  • function_name: Это имя, которое вы будете использовать для вызова функции. Выберите имя, которое четко описывает назначение функции.
  • parameters: Это входные значения, которые вы передаете в функцию. Они необязательны, и вы можете иметь ноль или более параметров.
  • value: Это значение, которое возвращает функция. Оно должно соответствовать return_type функции. Если return_type равен void, вы опускаете оператор return или просто используете return; без значения.

В этом шаге вы научитесь создавать функции, которые возвращают различные типы данных. Эта возможность имеет решающее значение для создания универсальных и функциональных программ.

Откройте WebIDE и создайте новый файл с именем function_types.cpp в каталоге ~/project:

touch ~/project/function_types.cpp

Откройте файл function_types.cpp в редакторе и добавьте следующий код:

#include <iostream>
#include <string>

// Функция, возвращающая целое число
int addNumbers(int a, int b) {
    return a + b;
}

// Функция, возвращающая double (число с плавающей запятой)
double calculateAverage(double x, double y) {
    return (x + y) / 2.0;
}

// Функция, возвращающая символ
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';
}

// Функция, возвращающая строку
std::string getStudentStatus(bool isEnrolled) {
    return isEnrolled ? "Active" : "Inactive";
}

int main() {
    // Демонстрация различных типов возвращаемых значений
    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;
}

Скомпилируйте и запустите программу:

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

Пример вывода:

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

Ключевые моменты о типах возвращаемых значений функций:

  • Функции могут возвращать различные типы данных, обеспечивая гибкость кода.
  • return_type указывается перед именем функции, определяя, какие данные функция отправит обратно.
  • Оператор return должен соответствовать объявленному return_type, иначе компилятор сообщит об ошибке.
  • Функции void не возвращают значение, и вы используете return; или просто опускаете оператор return, если находитесь в конце функции void.
  • Вы можете возвращать примитивные типы (такие как int, double, char), строки и даже пользовательские типы.
  • Думайте о типах возвращаемых значений функций как о различных типах контейнеров. Точно так же, как вы используете коробку, сумку или корзину для переноски разных предметов, функции используют разные типы возвращаемых значений для отправки обратно различных видов данных.

Передача параметров в функции по значению

В этом шаге вы узнаете о передаче параметров функциям по значению в C++. Когда вы передаете параметр по значению, создается копия исходного аргумента, и эта копия используется внутри функции. Любые изменения, внесенные в параметр внутри функции, не влияют на исходную переменную вне функции.

Откройте WebIDE и создайте новый файл с именем pass_by_value.cpp в каталоге ~/project:

touch ~/project/pass_by_value.cpp

Откройте файл pass_by_value.cpp в редакторе и добавьте следующий код:

#include <iostream>

// Функция, демонстрирующая передачу по значению
void modifyValue(int x) {
    // Это изменение влияет только на локальную копию, а не на исходную переменную
    x = x * 2;
    std::cout << "Inside function - Value of x: " << x << std::endl;
}

int main() {
    // Исходная переменная
    int number = 10;

    // Вывод исходного значения
    std::cout << "Before function call - Original value: " << number << std::endl;

    // Вызов функции с исходной переменной
    modifyValue(number);

    // Исходная переменная остается неизменной
    std::cout << "After function call - Original value: " << number << std::endl;

    return 0;
}

Скомпилируйте и запустите программу:

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

Пример вывода:

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

Создадим еще один пример для дальнейшей иллюстрации передачи по значению:

touch ~/project/swap_values.cpp

Добавьте следующий код в swap_values.cpp:

#include <iostream>
#include <string>

// Функция для обмена значениями (не будет работать как ожидается при передаче по значению)
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;

    // Вызов функции обмена
    swapValues(first, second);

    // Исходные переменные остаются неизменными
    std::cout << "After swap - first: " << first << ", second: " << second << std::endl;

    return 0;
}

Скомпилируйте и запустите вторую программу:

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

Пример вывода:

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

Ключевые моменты о передаче по значению:

  • В функцию передается копия аргумента, сохраняя исходное значение.
  • Изменения, внесенные в параметр внутри функции, не влияют на исходную переменную вне функции.
  • Передача по значению подходит для простых типов данных, таких как int, char, float и т. д., когда вам не нужно изменять исходное значение.
  • При передаче больших объектов по значению копирование может потреблять больше памяти и времени, что делает его менее эффективным по сравнению с передачей по ссылке.

Вы можете думать о передаче по значению как о создании ксерокопии документа. Оригинал остается неизменным, и вы можете изменять копию, не затрагивая оригинал.

Реализация передачи по ссылке с использованием оператора &

В этом шаге вы узнаете, как передавать параметры функциям по ссылке с использованием оператора & в C++. Передача по ссылке позволяет предоставить функции прямой доступ к исходной переменной, что означает, что любые изменения, внесенные в параметр внутри функции, напрямую изменят исходную переменную вне функции.

Откройте WebIDE и создайте новый файл с именем pass_by_reference.cpp в каталоге ~/project:

touch ~/project/pass_by_reference.cpp

Откройте файл pass_by_reference.cpp в редакторе и добавьте следующий код:

#include <iostream>

// Функция, демонстрирующая передачу по ссылке
void swapValues(int& a, int& b) {
    // Прямое изменение исходных переменных
    int temp = a;
    a = b;
    b = temp;
}

// Функция для изменения значения с использованием ссылки
void incrementValue(int& x) {
    // Прямо увеличивает исходную переменную
    x++;
}

int main() {
    // Пример обмена
    int first = 5;
    int second = 10;

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

    // Вызов функции обмена со ссылками
    swapValues(first, second);

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

    // Пример инкремента
    int number = 7;

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

    // Вызов функции инкремента со ссылкой
    incrementValue(number);

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

    return 0;
}

Скомпилируйте и запустите программу:

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

Пример вывода:

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

Создадим еще один пример, чтобы продемонстрировать мощь передачи по ссылке:

touch ~/project/string_reference.cpp

Добавьте следующий код в string_reference.cpp:

#include <iostream>
#include <string>

// Функция для изменения строки с использованием ссылки
void appendText(std::string& text) {
    text += " - Modified";
}

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

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

    // Изменение строки напрямую с использованием ссылки
    appendText(message);

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

    return 0;
}

Скомпилируйте и запустите вторую программу:

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

Пример вывода:

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

Ключевые моменты о передаче по ссылке:

  • Используйте & после типа для создания ссылочного параметра (например, int& a).
  • Напрямую изменяет исходную переменную, в отличие от передачи по значению, которая использует копию.
  • Избегает накладных расходов на копирование больших объектов, что делает его более эффективным для сложных типов данных.
  • Полезно, когда вам нужно изменить исходное значение или работать со сложными типами данных без копирования.

Вы можете думать о передаче по ссылке как о передаче кому-то оригинала документа вместо ксерокопии. Любые изменения, которые они внесут, напрямую повлияют на оригинал.

Установка значений по умолчанию для параметров функции

В этом шаге вы узнаете, как устанавливать значения по умолчанию для параметров функций в C++. Параметры по умолчанию позволяют назначать значениям аргументов функций значения по умолчанию, делая ваши функции более универсальными и простыми в использовании. Когда вызывающий код пропускает параметр со значением по умолчанию, это значение используется автоматически.

Откройте WebIDE и создайте новый файл с именем default_parameters.cpp в каталоге ~/project:

touch ~/project/default_parameters.cpp

Откройте файл default_parameters.cpp в редакторе и добавьте следующий код:

#include <iostream>
#include <string>

// Функция с параметрами по умолчанию
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;
    }
}

// Еще один пример с параметрами по умолчанию для расчета
double calculateArea(double length = 1.0, double width = 1.0) {
    return length * width;
}

int main() {
    // Вызов функции без аргументов (используются значения по умолчанию)
    greetUser();

    // Вызов функции с частичными аргументами
    greetUser("Alice");

    // Вызов функции со всеми аргументами
    greetUser("Bob", 25);

    // Демонстрация расчета площади со значениями по умолчанию
    std::cout << "\nArea calculations:" << std::endl;

    // Площадь по умолчанию (1x1)
    std::cout << "Default area: " << calculateArea() << std::endl;

    // Площадь с одним параметром
    std::cout << "Area with length 5: " << calculateArea(5.0) << std::endl;

    // Площадь с обоими параметрами
    std::cout << "Area with length 5 and width 3: " << calculateArea(5.0, 3.0) << std::endl;

    return 0;
}

Скомпилируйте и запустите программу:

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

Пример вывода:

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

Ключевые моменты о параметрах по умолчанию:

  • Значения по умолчанию указываются в объявлении функции, сразу после типа параметра (например, int age = 0).
  • Параметры со значениями по умолчанию должны располагаться в конце списка параметров. Вы не можете иметь параметры без значений по умолчанию после параметров со значениями по умолчанию.
  • Вы можете вызывать функцию с меньшим количеством аргументов, и недостающие аргументы будут использовать свои значения по умолчанию.
  • Значения по умолчанию обеспечивают гибкость и уменьшают потребность в нескольких перегрузках функций, которые делают одно и то же с немного отличающимися входными данными.

Создадим еще один пример для демонстрации более сложных параметров по умолчанию:

touch ~/project/student_info.cpp

Добавьте следующий код в student_info.cpp:

#include <iostream>
#include <string>

// Функция с несколькими параметрами по умолчанию
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() {
    // Различные способы вызова функции
    printStudentInfo();  // Все значения по умолчанию
    std::cout << "\n";
    printStudentInfo("John");  // Только имя
    std::cout << "\n";
    printStudentInfo("Alice", 20);  // Имя и возраст
    std::cout << "\n";
    printStudentInfo("Bob", 22, "A", true);  // Все параметры

    return 0;
}

Скомпилируйте и запустите вторую программу:

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

Пример вывода:

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

Вы можете думать о параметрах по умолчанию как о шведском столе, где вы можете взять все блюда или только некоторые, причем некоторые блюда будут автоматически добавлены, если вы их не укажете.

Создание перегрузок функций с различными параметрами

В этом шаге вы узнаете о перегрузке функций в C++, возможности, которая позволяет создавать несколько функций с одинаковым именем, при условии, что у них разные списки параметров (либо по количеству параметров, либо по типам параметров). Эта возможность делает ваш код более гибким и читаемым, позволяя выполнять схожие операции над различными типами данных или с разным количеством входных данных, используя одно и то же имя функции.

Откройте WebIDE и создайте новый файл с именем function_overloads.cpp в каталоге ~/project:

touch ~/project/function_overloads.cpp

Откройте файл function_overloads.cpp в редакторе и добавьте следующий код:

#include <iostream>
#include <string>

// Перегруженные функции для сложения чисел разных типов
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;
}

// Перегруженные функции с разным количеством параметров
int add(int a, int b, int c) {
    std::cout << "Adding three integers" << std::endl;
    return a + b + c;
}

// Перегруженная функция с разными типами параметров
std::string add(std::string a, std::string b) {
    std::cout << "Concatenating strings" << std::endl;
    return a + b;
}

int main() {
    // Вызов различных перегруженных функций
    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;
}

Скомпилируйте и запустите программу:

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

Пример вывода:

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!

Создадим еще один пример для демонстрации большей перегрузки функций:

touch ~/project/overloading_examples.cpp

Добавьте следующий код в overloading_examples.cpp:

#include <iostream>
#include <string>

// Перегруженные функции печати
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;
}

// Перегруженная функция расчета
int calculate(int a, int b) {
    return a + b;
}

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

int main() {
    // Демонстрация перегрузки функций
    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;
}

Скомпилируйте и запустите вторую программу:

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

Пример вывода:

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

Ключевые моменты о перегрузке функций:

  • Функции могут иметь одинаковое имя, если у них разные списки параметров (разные типы или разное количество параметров).
  • Компилятор C++ определяет, какую перегруженную функцию вызвать, на основе типов и количества аргументов, предоставленных во время вызова функции.
  • Перегрузка функций делает код более читаемым и интуитивно понятным, позволяя повторно использовать имена для схожих действий.
  • Вы не можете перегружать функции, если они отличаются только типами возвращаемых значений. Параметры должны быть разными для корректной работы перегрузки.

Вы можете думать о перегрузке функций как об универсальном пульте дистанционного управления, который работает с различными типами устройств. Одна и та же кнопка (имя функции) выполняет разные действия в зависимости от контекста (предоставленных вами аргументов).

Написание рекурсивных функций для вычисления факториала

В этом шаге вы узнаете о рекурсивных функциях, реализовав программу для вычисления факториала. Рекурсия — это техника программирования, при которой функция вызывает саму себя для решения задачи. Чтобы избежать бесконечных циклов, рекурсивные функции должны иметь базовый случай, когда они прекращают рекурсивный вызов, а также рекурсивный случай, чтобы продолжать вызывать себя для достижения базового случая.

Откройте WebIDE и создайте новый файл с именем factorial_recursive.cpp в каталоге ~/project:

touch ~/project/factorial_recursive.cpp

Откройте файл factorial_recursive.cpp в редакторе и добавьте следующий код:

#include <iostream>

// Рекурсивная функция для вычисления факториала
unsigned long long calculateFactorial(int n) {
    // Базовый случай: факториал 0 или 1 равен 1
    if (n == 0 || n == 1) {
        return 1;
    }

    // Рекурсивный случай: n! = n * (n-1)!
    return n * calculateFactorial(n - 1);
}

int main() {
    // Вычисление факториала для разных чисел
    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;
}

Скомпилируйте и запустите программу:

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

Пример вывода:

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

Создадим еще один пример с более подробной рекурсивной функцией:

touch ~/project/factorial_steps.cpp

Добавьте следующий код в factorial_steps.cpp:

#include <iostream>

// Рекурсивная функция с пошаговым выводом
unsigned long long calculateFactorialWithSteps(int n, int depth = 0) {
    // Добавляем отступ для визуализации
    std::string indent(depth * 2, ' ');

    // Базовый случай: факториал 0 или 1 равен 1
    if (n == 0 || n == 1) {
        std::cout << indent << "Base case: factorial(" << n << ") = 1" << std::endl;
        return 1;
    }

    // Рекурсивный случай с визуализацией
    std::cout << indent << "Calculating factorial(" << n << ")" << std::endl;

    // Рекурсивный вызов
    unsigned long long subResult = calculateFactorialWithSteps(n - 1, depth + 1);

    // Объединение результатов
    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;
}

Скомпилируйте и запустите вторую программу:

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

Пример вывода:

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

Ключевые моменты о рекурсивных функциях:

  • Рекурсивные функции вызывают сами себя: Функция вызывает саму себя для решения меньших подзадач того же типа.
  • Базовый случай: Рекурсивная функция должна иметь базовый случай — условие, которое останавливает рекурсивные вызовы. Без базового случая функция будет вызывать себя бесконечно, что приведет к ошибке переполнения стека (stack overflow).
  • Рекурсивный случай: Рекурсивный случай — это место, где функция вызывает саму себя для движения к базовому случаю.
  • Декомпозиция задачи: Рекурсия разбивает сложную задачу на более мелкие, простые подзадачи, что облегчает управление.
  • Использование стека: Каждый рекурсивный вызов занимает место в стеке вызовов программы. Глубокая рекурсия может привести к переполнению стека, поэтому следует учитывать ограничения стека вызовов.
  • Подходящие задачи: Рекурсия особенно хорошо подходит для задач, которые могут быть естественным образом определены через меньшие, схожие подзадачи, такие как обход деревьев, алгоритмы "разделяй и властвуй" и генерация фракталов.

Вы можете думать о рекурсии как о наборе вложенных русских матрешек, где каждая матрешка содержит меньшую версию себя, а самая внутренняя матрешка представляет собой базовый случай, который останавливает процесс вложения.

Использование прототипов функций в заголовочных файлах

В этом шаге вы узнаете о прототипах функций и о том, как использовать заголовочные файлы для организации и объявления функций в C++. Прототипы функций объявляют имя функции, тип возвращаемого значения и параметры без предоставления реализации (тела функции). Используя заголовочные файлы, вы отделяете интерфейс функции (ее объявление) от ее реализации. Это разделение улучшает организацию кода, облегчая поддержку и повторное использование функций в различных частях вашей программы или в нескольких программах.

Откройте WebIDE и создайте три файла в каталоге ~/project:

Сначала создайте math_functions.h:

touch ~/project/math_functions.h

Добавьте следующий код в math_functions.h:

#ifndef MATH_FUNCTIONS_H
#define MATH_FUNCTIONS_H

// Прототипы функций для математических операций
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

Файлы .h используются для объявлений, содержащих прототипы функций и другие объявления, но не реализации. Таким образом, вы можете объявлять функции, не реализуя их. Директивы #ifndef, #define и #endif называются include guards (защита от повторного включения), и они предотвращают многократное включение заголовочного файла, что может привести к ошибкам.

Далее создайте math_functions.cpp:

touch ~/project/math_functions.cpp

Добавьте следующий код в math_functions.cpp:

#include "math_functions.h"

// Реализации функций
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) {
    // Проверка деления на ноль
    if (b == 0) {
        return 0;
    }
    return a / b;
}

Этот файл .cpp содержит фактические реализации функций, которые были объявлены в заголовочном файле.

Наконец, создайте main.cpp:

touch ~/project/main.cpp

Добавьте следующий код в main.cpp:

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

int main() {
    // Демонстрация вызовов функций из заголовочного файла
    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;
}

Этот файл main.cpp включает заголовочный файл math_functions.h, который делает прототипы функций доступными. Затем он может использовать реализованные функции из math_functions.cpp.

Скомпилируйте программу, используя несколько исходных файлов:

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

Пример вывода:

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

Создадим еще один пример с более сложным заголовочным файлом:

Создайте calculator.h:

touch ~/project/calculator.h

Добавьте следующий код в calculator.h:

#ifndef CALCULATOR_H
#define CALCULATOR_H

class Calculator {
public:
    // Прототипы функций для операций калькулятора
    int add(int a, int b);
    int subtract(int a, int b);
    int calculate(int a, int b, char operation);
};

#endif // CALCULATOR_H

Этот заголовочный файл объявляет класс Calculator и его публичные методы.

Создайте calculator.cpp:

touch ~/project/calculator.cpp

Добавьте следующий код в 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;
    }
}

Этот calculator.cpp предоставляет реализации для методов, объявленных в заголовочном файле calculator.h.

Создайте calculator_main.cpp:

touch ~/project/calculator_main.cpp

Добавьте следующий код в 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;
}

Этот основной файл использует класс Calculator и выполняет операции.

Скомпилируйте программу калькулятора:

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

Пример вывода:

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

Ключевые моменты о прототипах функций и заголовочных файлах:

  • Заголовочные файлы (.h) объявляют прототипы функций, классы и другие объявления. Они действуют как интерфейс, описывая, какие функции доступны.
  • Исходные файлы (.cpp) реализуют фактические функции, объявленные в заголовочных файлах. Они содержат код, описывающий, как работают функции.
  • #ifndef, #define и #endif (include guards) предотвращают многократное включение одного и того же заголовочного файла, что позволяет избежать потенциальных ошибок компиляции.
  • Использование заголовочных файлов способствует модульности и повторному использованию кода.
  • Заголовочные файлы позволяют разделить "что" (объявления) и "как" (реализации).
  • Они облегчают организацию кода, делая его более простым для поддержки и понимания.

Вы можете думать о заголовочных файлах как о меню ресторана. Меню (заголовок) перечисляет, что доступно, в то время как кухня (исходный файл) готовит фактические блюда.

Возврат нескольких значений с использованием структур

В этом шаге вы узнаете, как использовать структуры для возврата нескольких значений из функции в C++. Структуры — это пользовательские типы данных, которые позволяют группировать данные различных типов под одним именем. Они идеально подходят для возврата нескольких связанных значений из функции в структурированном и организованном виде.

Откройте WebIDE и обновите файл student_info.cpp в каталоге ~/project:

Замените существующий код следующим:

#include <iostream>
#include <string>

// Определение структуры для хранения информации о студенте
struct StudentResult {
    std::string name;
    int score;
    bool passed;
};

// Функция, возвращающая несколько значений с использованием структуры
StudentResult evaluateStudent(std::string name, int testScore) {
    StudentResult result;
    result.name = name;
    result.score = testScore;
    result.passed = (testScore >= 60);

    return result;
}

// Функция для вычисления нескольких статистических значений
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() {
    // Демонстрация оценки студента
    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;

    // Демонстрация статистики массива
    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;
}

Скомпилируйте и запустите программу:

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

Пример вывода:

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

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

Ключевые моменты о структурах и возврате нескольких значений:

  • Структуры группируют связанные данные: Вы можете группировать переменные различных типов (например, string, int, bool) в единое целое, что может быть полезно, когда у вас есть связанная информация.
  • Возврат сложных типов данных: Используя структуры, вы можете возвращать сложные типы данных из функций, что упрощает управление и передачу связанных наборов данных.
  • Организованные возвращаемые значения: Возврат структур помогает организовать несколько связанных возвращаемых значений, делая код более чистым и поддерживаемым.
  • Предоставляет четкий способ передачи структурированной информации: Структуры предоставляют четкий именованный контейнер для возврата нескольких связанных значений, улучшая читаемость и понимание кода.

Вы можете думать о структурах как о ланч-боксе с несколькими отделениями, который может вмещать различные виды пищи в отдельных секциях.

Резюме

В этой лабораторной работе вы получили полное представление о том, как определять и использовать функции в C++. Вы научились создавать функции с различными типами возвращаемых значений, передавать параметры по значению и по ссылке, устанавливать значения параметров по умолчанию и использовать перегрузку функций. Кроме того, вы изучили рекурсивные функции, как использовать прототипы функций в заголовочных файлах и как возвращать несколько значений с использованием структур.

Ключевые рассмотренные концепции включают:

  • Типы возвращаемых значений функций: Вы узнали, что функции могут возвращать различные типы данных, и оператор return должен соответствовать объявленному типу возвращаемого значения.
  • Передача по значению и передача по ссылке: Вы поняли разницу между передачей параметров по значению (создание копии) и по ссылке (использование исходной переменной).
  • Параметры по умолчанию: Вы узнали, как сделать функции более универсальными, назначая значения по умолчанию параметрам функций.
  • Перегрузка функций: Вы увидели, как определять несколько функций с одинаковым именем, но разными списками параметров, что делает код более читаемым и интуитивно понятным.
  • Рекурсивные функции: Вы изучили возможности рекурсивных функций, где функция вызывает саму себя для решения меньших подзадач, а также важность базовых случаев для предотвращения бесконечной рекурсии.
  • Прототипы функций и заголовочные файлы: Вы узнали, как использовать заголовочные файлы для организации объявлений функций, способствуя модульности и повторному использованию кода.
  • Возврат нескольких значений с помощью структур: Вы обнаружили, как возвращать несколько связанных значений из функции с использованием структур, которые предоставляют организованный контейнер для данных.

Овладев этими концепциями, вы теперь хорошо подготовлены к написанию более модульного, эффективного и хорошо структурированного кода на C++, и у вас есть прочная основа для изучения более продвинутых методов программирования.