Концепция функций в программировании#
Модуль (нестрогое) – это ключевой элемент в мире программирования, представляющий собой логически организованный блок кода, созданный для выполнения определенной функции. В программировании, модули являются фундаментальными строительными блоками при разработке приложений, и они обеспечивают структурирование кода, повторное использование и облегчают совместную работу в больших проектах.
Функция, с другой стороны, представляет собой конкретный фрагмент кода внутри модуля, который выполняет определенное действие или вычисление. Функции могут быть вызваны из других частей программы для выполнения определенной задачи, что существенно повышает читаемость и модульность кода.
Модуль(строгое) — это набор файлов исходного кода, которые компилируются независимо от исходных файлов (или, точнее, от единиц перевода, которые их импортируют).
Модули устраняют или устраняют многие проблемы, связанные с использованием файлов заголовков. Они часто сокращают время компиляции. Макросы, директивы препроцессора и неэкспортированные имена, объявленные в модуле, не видны за пределами модуля
Давайте ближе рассмотрим, как модули и функции взаимодействуют в программировании и как они способствуют созданию более организованных и эффективных приложений.
Подпрограммы#
Чем дальше мы изучаем Си, тем больше становятся программы.
Мы собираем все действия в одну функцию main и по несколько раз копируем одни и те же действия, создаём десятки переменных с уникальными именами.
Наши программы распухают и становятся всё менее и менее понятными, ветвления становятся всё длиннее и ветвистее.
Но из сложившейся ситуации есть выход!
Использование вспомогательных алгоритмов ─ ПОДПРОГРАММ
Подпрограммы применяются когда:
- часть алгоритма неоднократно повторяется в программе;
- можно использовать фрагменты разработанных ранее алгоритмов;
- для разбиения крупных программ на части в соответствии с модульным принципом программирования.
Запись вспомогательных алгоритмов в языках программирования осуществляется с помощью подпрограмм.
Структура описания подпрограммы аналогична структуре главной программы.
Выделяют два вида подпрограмм:
- Функции;
- Процедуры;
Процедуры#
Процедура - подпрограмма, имеющая произвольное количество входных данных и не имеющая выходных
Процедура( функция) представляет собой последовательность операторов, которая имеет имя, список параметров и может быть вызвана из различных частей программы.
Имя процедуры в тексте программы называется вызовом.
Вызов активирует процедуру (функцию) ─ начинают выполняться её операторы.
После выполнения процедуры программа продолжается с оператора стоящего за вызовом.
Отличие процедур от функций в том, что функции возвращают значение.
Процедуры в С++ – это функции, которые не возвращают значения
Функции#
Функция – именованная последовательность описаний и операторов, выполняющая некоторое действие. Может иметь параметры и возвращать значение.
Функция должна быть объявлена и определена.
Объявление функции (прототип, заголовок) задает имя, тип значения и список формальных параметров.
Определение содержит кроме объявления тело функции в фигурных скобках.
Вложенность функций в С++ не допускается!
Функция может принимать параметры и возвращать значение.
Подпрограмма или, другими словами, функция должна быть связана (интегрирована) с основной программой, так сказать, со своим внешним окружением.
С целью обеспечения взаимодействия с остальной частью программы для функции можно предусмотреть так называемые вход и выход.
Вход в функцию — это передача ей аргументов — данных, полученных во внешней части программы. Получив данные из своего внешнего окружения (внешней программы), функция должна их как-то обработать: выполнить некоторые действия, вычислить какое-то значение.
Выход из функции — значение, вычисленное блоком кода данной функции и передаваемое во внешнюю часть программы.
Входные данные называют параметрами, а выходные — возвращаемым значением.
Впрочем, функция может и не принимать никаких параметров, а также ничего не возвращать.
Что принимает в качестве параметров и что возвращает функция в результате своей работы, определяет программист, т. е. автор-разработчик программного кода.
Функция — важнейший элемент структурного программирования, позволяющий группировать и обобщать программный код, который может позднее использоваться произвольное число раз. Она является законченной подпрограммой, поэтому у нее есть свои "ввод" и "вывод" — параметры (аргументы) и возвращаемое значение:
С точки зрения внешней программы функция — это "черный ящик". Функция определяет собственную (локальную) область видимости, куда входят входные параметры, а, также, те переменные, которые объявляются непосредственно в теле самой функции.
Главное, что должно быть можно сделать с функцией — это возможность ее вызвать.
Перед использованием функция должна быть объявлена и соответствующим образом определена.
Объявление (declaration) функции содержит список параметров вместе с указанием типа каждого параметра, а, также, тип возвращаемого функцией значения.
Определение (definition) функции содержит исполняемый код функции.
Вызов функции может встретиться в программе до определения, но обязательно после объявления.
Функции, которые не возвращают значений, иногда называют процедурами.
Перед использованием функция должна быть объявлена
Все функции в языке Си – глобальные, т.е. функция не может быть объявлена внутри другой функции
В Си можно объявить функцию с помощью прототипа, т.е. заголовка функции, а полное ее описание сделать после функции main()
Структура Функции#
Функция может принимать фиксированное либо переменное число аргументов, а может не иметь аргументов.
Функция может как возвращать значение, так и быть пустой (void) и ничего не возвращать (аналог процедуры в Pascal)
Функция в Си определяется в глобальном контексте.
Синтаксис функции:
тип_функции имя_функции ([список_параметров]), ...)
{
тело функции
}
Функции — это средство проектирования, которое позволяет осуществить декомпозицию программы на достаточно простые и легко управляемые части.
Значительно проще написать решение маленьких задач по отдельности, чем реализовать весь процесс целиком.
Устранение избыточности программного кода улучшает сопровождаемость кода — если что-то необходимо будет исправить, достаточно будет внести изменения всего в одном месте, а не во многих.
С использованием функций в языке Си связаны три понятия:
- Определение функции (описание действий, выполняемых функцией)
- Объявление функции (задание формы обращения к функции)
- Вызов функции.
В языке Си нет требования, чтобы определение функции обязательно предшествовало ее вызову.
Определения используемых функций могут следовать за определением функции main, перед ним, или находится в другом файле.
При совместной работе функции должны обмениваться информацией.
Это можно осуществить с помощью:
- глобальных переменных;
- через параметры;
- через возвращаемое функцией значение
Варианты типов функции#
[класс] [inline] тип имя ( [список параметров] )
{ тело функции }
Класс определяет видимость функции
extern: Этот модификатор определяет функцию, которая будет видима и доступна для других файлов программы. По умолчанию, функции считаются extern, если явно не указан модификатор.
static: Функция с этим модификатором будет видимой только в файле, в котором она определена. Это ограничивает область видимости функции только локально в данном файле и делает ее недоступной для других файлов.
friend - это функции, которые не являются членами класса, однако имеют доступ к его закрытым членам - переменным и функциям, которые имеют спецификатор private.
Модификатор inline определяет функцию как встроенную. Компилятор по мере возможности будет вместо вызова функции помещать в точку вызова ее код.
Тип может быть любым кроме массива (но может быть указателем на массив). Если значение не возвращается, используется void
Список параметров определяет величины, значения которых передаются в функцию при ее вызове. Элементы списка разделяются запятыми. Для каждого параметра указывается тип и имя. В объявлениях имена можно опускать.
Для возврата значения используется оператор
return выражение
Объявление функции#
Объявление функции представляет собой декларацию функции в коде программы, без определения её логики выполнения. Это указание на сигнатуру функции, включая её имя, тип возвращаемого значения и параметры, которые она принимает. Объявления функций обычно размещаются в заголовочных файлах (.h) и имеют следующий формат:
тип_возвращаемого_значения имя_функции(список_параметров);
Главная цель объявлений функций в заголовочных файлах заключается в том, чтобы предоставить интерфейс, описывающий, как использовать функции, без раскрытия их реализации. Это полезно в больших проектах, где множество файлов и библиотек связаны друг с другом. Преимущества включения объявлений функций в заголовочные файлы включают:
-
Разделение интерфейса и реализации: Заголовочные файлы разделяют интерфейс (то, что пользователь знает о функции) от реализации (как функция выполняет свою работу). Это уровень абстракции позволяет скрыть детали реализации и обеспечить безопасное взаимодействие с функцией.
-
Модульность: Заголовочные файлы позволяют создавать модули, которые могут быть компилированы и переиспользованы в разных частях программы.
-
Повторное использование: Заголовочные файлы облегчают повторное использование функций в разных частях программы или даже в других проектах.
-
Снижение вероятности ошибок: Объявления функций в заголовочных файлах обеспечивают согласованность в использовании функций и предотвращают ошибки в передаче аргументов.
Пример заголовочного файла с объявлением функции:
// math_utils.h
#ifndef MATH_UTILS_H
#define MATH_UTILS_H
int add(int a, int b);
double square_root(double x);
#endif
Обратите внимание, что директивы #ifndef, #define, и #endif используются для предотвращения многократного включения файла в одном и том же исходном файле. Это обеспечивает только одно объявление функции в коде.
Затем, определения функций реализуются в соответствующих файлов (например, .c или .cpp), и эти файлы могут быть скомпилированы в исполняемый код.
Область видимости переменных. Параметры Функции.#
Область действия (видимости) переменной – это правила, которые устанавливают, какие данные доступны из данного места программы.
С точки зрения области действия переменных различают три типа переменных:
- глобальные
- локальные
- формальные параметры.
Глобальные переменные:
Это переменные, объявленные вне какой-либо функции.
Могут быть использованы в любом месте программы, но перед их использованием они должны быть объявлены.
Область действия глобальной переменной – вся программа.
Локальные переменные:
Это переменные, объявленные внутри функции.
Локальная переменная доступна внутри блока, в котором она объявлена.
Локальная переменная существует пока выполняется блок, в котором эта переменная объявлена. При выходе из блока эта переменная (и ее значение) теряется.
Формальные параметры:
Используются в теле функции так же, как локальные переменные.
Область действия формальных параметров - блок, являющийся телом функции.
Недостатки использования глобальных переменных:
- Они занимают память в течение всего времени работы программы.
- Делает функции менее общими и затрудняет их использование в других программах.
- Использование внешних переменных делает возможным появление ошибок из-за побочных явлений.
Эти ошибки, как правило, трудно отыскать.
Параметры-значения:
- Все аргументы функции передаются по значению
- При вызове функции в стеке выделяется место для формальных параметров функции, и туда заносится значение фактического параметра, т. е. значение параметра при вызове функции.
- Далее функция использует и меняет значения в стеке. При выходе из функции измененные значения параметров теряются.
- В языке Си вызванная функция не может изменить переменные, указанные в качестве фактических параметров в функции при обращении к ней.
Параметры-ссылки:
- Для возможности изменения внутри функции значений переменных, являющихся параметрами этой функции, необходимо передавать в функцию не значения этих переменных, а их адреса
Запомните!
Все величины, описанные внутри функции, а также ее параметры – локальные.
Область видимости и жизни – тело функции (кроме статических переменных) .
Величины, описанные вне тела функций – глобальные.
Локальные объекты перекрывают глобальные
Формальные и фактические параметры#
Формальные и фактические параметры - это два основных типа параметров, используемых при вызове функций. Они выполняют разные роли и связаны между собой.
При объявлении функции указываются формальные параметры, которые потом используются внутри самой функции.
При вызове функции мы используем фактические параметры.
Фактическими параметрами могут быть переменные любого подходящего типа или константы.
Формальные параметры (formal parameters):
- Формальные параметры представляют собой параметры, указанные в определении функции.
- Они являются локальными переменными, используемыми внутри функции для обработки переданных значений.
- Формальные параметры определяют типы данных и имена, которые ожидаются при вызове функции.
void printSum(int a, int b) {
int sum = a + b;
printf("Сумма: %d\n", sum);
}
В этом примере int a и int b являются формальными параметрами функции printSum.
Фактические параметры (actual parameters):
- Фактические параметры - это значения, передаваемые в функцию при её вызове.
- Они представляют собой конкретные данные или выражения, которые передаются функции для выполнения операций.
- Фактические параметры должны соответствовать типам данных и количеству формальных параметров функции.
int main() {
int x = 5;
int y = 7;
printSum(x, y); // x и y - фактические параметры
return 0;
}
В этом примере x и y являются фактическими параметрами, передаваемыми в функцию printSum.
Когда функция вызывается, фактические параметры передаются формальным параметрам, и внутри функции можно использовать их значения для выполнения операций. Формальные параметры действуют как локальные переменные в рамках функции и получают значения от фактических параметров.
Передача аргументов#
В C++ аргументы могут передаваться в функцию различными способами, включая передачу по значению, передачу по ссылке и передачу по указателю. Каждый из этих методов имеет свои особенности и подходит для разных сценариев.
При передаче аргументов происходит их копирование
Передача по значению (pass by value):
- При передаче аргумента по значению, само значение аргумента копируется в локальную переменную функции.
- Любые изменения, внесенные в этой локальной копии, не влияют на оригинальное значение в
- Этот метод подходит, когда вы не хотите, чтобы функция меняла значение оригинальной переменной.
void modifyValue(int x) {
x = 42; // Изменения не затронут оригинальное значение
}
int main() {
int value = 10;
modifyValue(value); // value передается по значению
// Здесь value по-прежнему равно 10
return 0;
}
Передача по ссылке (pass by reference):
- При передаче аргумента по ссылке, функция работает с самой оригинальной переменной.
- Любые изменения, внесенные в функции, будут отражаться на оригинальной переменной.
- Этот метод подходит, когда вы хотите, чтобы функция могла изменять значение оригинальной переменной.
void modifyValue(int &x) {
x = 42; // Это изменит оригинальное значение
}
int main() {
int value = 10;
modifyValue(value); // value передается по ссылке
// Здесь value теперь равно 42
return 0;
}
Передача по указателю (pass by pointer)
- При передаче аргумента по указателю, функция получает указатель на оригинальную переменную.
- Функция может изменять значение, на которое указывает указатель.
- Этот метод также подходит, когда вы хотите, чтобы функция могла изменять значение оригинальной переменной.
void modifyValue(int *x) {
*x = 42; // Это изменит оригинальное значение через указатель
}
int main() {
int value = 10;
modifyValue(&value); // value передается по указателю
// Здесь value теперь равно 42
return 0;
}
Выбор между этими методами зависит от конкретных требований вашей программы. Передача по значению подходит, когда вы не хотите, чтобы функция вносила изменения в оригинальные данные. Передача по ссылке и передача по указателю подходят, когда вы хотите, чтобы функция могла изменять значения оригинальных данных.
Массивы как параметры#
В C++, массивы могут быть переданы в функции как параметры несколькими способами. Однако следует помнить, что в C++ массивы не передаются по значению, а передаются по указателю на первый элемент массива.
Передача массива в качестве аргумента
Если в качестве аргумента функции используется массив, то необходимо в качестве формального параметра передать адрес начала массива.
Адрес любого другого элемента массива можно вычислить по его индексу и типу элементов массива.
Имя массива подменяется на указатель, поэтому передача одномерного массива эквивалентна передаче указателя.
Правило подмены массива на указатель не рекурсивное. Это значит, что необходимо указывать размерность двумерного массива при передаче
Использование указателей:
#include <iostream>
void printArray(int arr[], int size) {
for (int i = 0; i < size; i++) {
std::cout << arr[i] << " ";
}
std::cout << std::endl;
}
int main() {
int myArray[] = {1, 2, 3, 4, 5};
int size = sizeof(myArray) / sizeof(myArray[0]);
printArray(myArray, size);
return 0;
}
Использование ссылок:
#include <iostream>
void printArray(int (&arr)[5]) { // Размер массива указывается в скобках
for (int i = 0; i < 5; i++) {
std::cout << arr[i] << " ";
}
std::cout << std::endl;
}
int main() {
int myArray[] = {1, 2, 3, 4, 5};
printArray(myArray);
return 0;
}
Использование указателей и размера массива в качестве отдельных параметров:
#include <iostream>
void printArray(int *arr, int size) {
for (int i = 0; i < size; i++) {
std::cout << arr[i] << " ";
}
std::cout << std::endl;
}
int main() {
int myArray[] = {1, 2, 3, 4, 5};
int size = sizeof(myArray) / sizeof(myArray[0]);
printArray(myArray, size);
return 0;
}
Когда вы передаете массив в функцию, необходимо указать размер массива либо передать его как параметр, либо использовать специальные механизмы, такие как стандартная библиотека C++ STL (например, std::vector), которые автоматически отслеживают размер массива.
Функции с переменным числом параметров#
По умолчанию параметры передаются функции через стек. Поэтому, технически, нет ограничения на количество передаваемых параметров – “запихать” можно сколько угодно.
Проблема в том, как потом функция будет разбирать переданные параметры.
Функции с переменным числом параметров объявляются как обычные функции, но вместо недостающих аргументов ставится многоточие.
Пример: Пусть мы хотим сделать функцию, которая складывает переданные ей числа, чисел может быть произвольное количество (см. код справа)
Необходимо каким-то образом передать функции число параметров.
Во-первых, можно явно передать число параметров обязательным аргументом.
Во-вторых, последний аргумент может иметь некоторое «терминальное» значение, наткнувшись на которое функция закончит выполнение.
Общий принцип работы следующий:
внутри функции берём указатель на аргумент;
далее двигаемся к следующему аргументу, увеличивая значение указателя.
См. функцию summ
Первый параметр – число аргументов. Это обязательный параметр.
Второй аргумент – это первое переданное число, это тоже обязательный параметр. Получаем указатель на первое число:
unsigned *p = &first;
Далее считываем все числа и складываем их.
В этой функции мы также при сложении проверяем на переполнение типа unsigned.
Можно сделать первый аргумент необязательным и «перешагнуть» аргумент unsigned char num, но тогда возникнет большая проблема: аргументы располагаются друг за другом, но не факт, что непрерывно.
Например, в нашем случае первый аргумент будет сдвинут не на один байт, а на 4 относительно num:unsigned int <==> unsigned (unsigned int = 4 байта)
Это сделано для повышения производительности.
На другой платформе или с другим компилятором, или с другими настройками компилятора могут быть другие результаты.
#include <stdio.h>
#include <conio.h>
#include <stdlib.h>
#define UNSIGNED_OVERFLOW -4
unsigned summ(unsigned char num, unsigned first, ...)
{ // unsigned int <==> unsigned
unsigned sum = 0;
unsigned testsum = 0;
unsigned *p = &first; // Указатель на первое число
while (num--)
{ testsum += *p;
if (testsum >= sum)
sum = testsum;
else
exit(UNSIGNED_OVERFLOW);
p++;
}
return sum;
}
void main()
{
int sum = summ(5, 1u, 2u, 3u, 4u, 5u);
printf("summ = %u\n", sum);
sum = summ(7, 0u, 27u, 0u, 4u, 5u, 60u, 33u);
printf("summ = %u\n", sum);
getch();
}
Рекурсия#
Рекурсия — это способ определения множества объектов через само это множество на основе заданных простых базовых случаев.
Рекурсивное определение позволяет определить бесконечное множество объектов с помощью конечного выражения (высказывания)
Рекурсия - это концепция программирования, при которой функция вызывает саму себя для решения задачи. Рекурсия часто используется в алгоритмах, которые могут быть разбиты на более простые подзадачи. Она может быть мощным инструментом для решения задач, которые могут быть выражены в рекурсивной форме.
Основной случай (Base Case): Рекурсивная функция должна иметь условие завершения, или "основной случай". Это случай, при котором функция не вызывает себя снова, а возвращает непосредственный результат.
Рекурсивный случай (Recursive Case): В этом случае функция вызывает саму себя с разными аргументами, чтобы прийти к базовому случаю.
Стек вызовов (Call Stack): При каждом вызове рекурсивной функции в стеке вызовов сохраняются текущее состояние и локальные переменные функции. Когда функция завершает выполнение, она извлекается из стека, и управление передается вызывающей функции.
Стек вызовов - это структура данных, которая используется для отслеживания последовательности вызовов функций в программе.
При вызове функции, текущее состояние функции (включая локальные переменные и контекст выполнения) помещается в вершину стека.
Когда функция завершает свою работу и возвращает значение, ее состояние удаляется из вершины стека, и управление передается вызывающей функции.
Работа стека вызовов при рекурсии:
Когда рекурсивная функция вызывает саму себя, каждый новый вызов функции создает новую запись в стеке вызовов, содержащую его собственные локальные переменные и контекст выполнения.
Рекурсия продолжает "увеличиваться" в стеке, пока не будет достигнут базовый случай (то есть случай, при котором функция больше не вызывает себя снова).
Когда базовый случай достигнут, рекурсия начинает "разворачиваться" - каждый вызов функции завершается, и его состояние извлекается из вершины стека.
Результаты рекурсивных вызовов передаются обратно по стеку вызовов до вызывающей функции.
Пример работы стека вызовов и рекурсии для функции factorial(5):
factorial(5) вызывает factorial(4).
factorial(4) вызывает factorial(3).
factorial(3) вызывает factorial(2).
factorial(2) вызывает factorial(1).
factorial(1) достигает базового случая и возвращает 1.
Затем factorial(2) возвращает 2 * 1 = 2.
factorial(3) возвращает 3 * 2 = 6.
factorial(4) возвращает 4 * 6 = 24.
Наконец, factorial(5) возвращает 5 * 24 = 120.
Таким образом, результат рекурсивного вызова factorial(5) получается путем последовательного "разворачивания" рекурсивных вызовов, начиная с базового случая и передавая результаты обратно по стеку вызовов.
Важно следить за правильным определением базового случая и ограничением рекурсии, чтобы избежать бесконечной рекурсии, что может привести к переполнению стека вызовов (stack overflow).
Пример: Вычисление суммы цифр числа
int sumDig ( int n )
{
int sum;
sum = n %10; // последняя цифра
if ( n >= 10 )
sum += sumDig ( n / 10 ); // рекурсивный вызов
return sum;
}
Лямбда-выражение Теория#
Лямбда-исчисление - это формальная система, то есть набор объектов, формул, аксиом и правил вывода. Благодаря таким системам с помощью абстракций моделируется теория, которую можно использовать в реальном мире, и при этом выводить в ней новые математически доказуемые утверждения. Например, язык запросов SQL основан на реляционном исчислении. Благодаря математической базе, на которой он существует, оптимизаторы запросов могут анализировать алгебраические свойства операций и влиять на скорость работы.
В нотации лямбда-исчисления есть всего три типа выражений:
-
Переменные: x, y, z
-
Абстракция - декларация функции: λx.E. Определяем функцию с параметром x и телом E.
-
Аппликация - применение функции E₁ к аргументу E₂: E₁ E₂.
Сразу пара примеров:
- Тождественная функция: λx.x
- Функция, вычисляющая тождественную функцию: λx.(λy.y)
Области видимости переменных
Определим контекст переменной, в котором она может быть использована. Абстракция λx.E связывает переменную x. В результате мы получаем следующие понятия:
-
x - связанная переменная в выражении.
-
E - область видимости переменной x.
-
Переменная свободна в E, если она не связана в E. Пример: λx.x(λy.xyzy). Cвободная переменная - z.
Взглянем на следующий пример: λx.x(λx.x)x.
Понимание лямбда-выражений существенно усложняется, когда переменные с разными значениями и контекстами используют идентичные имена. Поэтому впредь мы будем пользоваться следующим соглашением: связанные переменные необходимо переименовывать для того, чтобы они имели уникальные имена в выражении. Это возможно благодаря концептуально важному утверждению: выражения, которые могут быть получены друг из друга путем переименования связанных переменных, считаются идентичными. Важность этого утверждения в том, что функции в исчислении определяются лишь своим поведением, и имена функций не несут никакого смысла
Вычисление лямбда-выражений#
Вычисление выражений заключается в последовательном применении подстановок. Подстановкой E' вместо x в E (запись: [E'/x]E) называется выполнение двух шагов:
-
Альфа-преобразование. Переименование связанных переменных в E и E', чтобы имена стали уникальными.
-
Бета-редукция. По сути единственная значимая аксиома исчисления. Подразумевает замену x на E' в E.
Рассмотрим несколько примеров подстановок:
- Преобразование к тождественной функции. (пишем подстановку) (делаем альфа-преобразование) (производим бета-редукцию) (еще одна подстановка)
- Бесконечные вычисления. (производим подстановку) (производим подстановку)
- Также небольшой пример, почему нельзя пренебрегать альфа-преобразованием.
В первом примере, после правильного выполнения подстановки, мы получим совсем другой результат, а именно константную функцию. Во втором примере, бесконечные вычисления продолжаются, и мы получаем бесконечную последовательность. В третьем примере видно, почему важно проводить альфа-преобразование, чтобы избежать ошибочных результатов.
Порядок вычислений#
Бывают ситуации, когда произвести вычисление можно несколькими способами. Например, в выражении: (λy.(λx.x)y)E ачала можно подставлять у вместо х во внутреннее выражение, либо Е вместо у во внешнее. Теорема Черча-Рассера говорит о том, что в не зависимости от последовательности операций, если вычисление завершится, результат будет одинаков. Тем не менее, эти два подхода принципиально отличаются. Рассмотрим их подробнее:
В чистом лямбда-исчислении есть только функции. Однако, программирование трудно представить без различных типов данных. Идея заключается в том, чтобы закодировать поведение конкретных типов в виде функций.
Лямбда-выражение Практика#
Анонимные функции - это функции, которые не имеют имени и могут быть определены непосредственно в коде.
Лямбда-выражение в C++ представляет собой анонимную функцию, которая может быть определена непосредственно внутри кода программы. Лямбда-выражения позволяют создавать и использовать функции на месте, где они нужны, без явного определения имени функции. Лямбда-выражения вводятся в стандарте C++11 и более поздних версиях языка.
Синтаксис лямбда-выражения:
[capture_clause](parameters) -> return_type {
// Тело лямбда-функции
}
capture_clause позволяет лямбда-выражению захватывать переменные из окружающего контекста. Это может быть пустым [], что означает, что лямбда не захватывает никакие переменные, или может содержать переменные, которые нужно захватить.
parameters - параметры функции, которые передаются в лямбда-функцию.
return_type - тип возвращаемого значения функции (может быть опущен, и тип будет автоматически выведен).
Захват переменных (Capture Variables):
Переменные из окружающего контекста могут быть захвачены с помощью capture_clause. Примеры:
[x, y] - захват переменных x и y по значению.
[&a, b] - захват a по ссылке и b по значению.
[=] - захват всех переменных по значению.
[&] - захват всех переменных по ссылке.
[=, &x] - захват всех переменных по значению, кроме x, который захватывается по ссылке.
Тело лямбда-функции:
Это блок кода, который определяет логику выполнения функции. Может содержать любые операторы и выражения, а также использовать захваченные переменные.
Пример использования:
Необходимо отсортировать массив по возрастанию последней цифры
Данную программу можно написать, описывая функцию с идентификатором cmp в основном коде, а затем используя этот идентификатор в функции сортировки
Практика#
- Что выведено на экран? (прокомментируйте код)
#include <algorithm> #include <vector> #include <iostream> using namespace std; int main() { vector<int> arr(20, 0); int i = 23; for (auto &x : arr) { x = i++; i *= 33; } auto isEven = [](int i) { return (i % 2 == 0); }; auto any = [isEven](vector<int> &a) { return any_of(a.begin(), a.end(), isEven); }; auto all = [isEven](vector<int> &a) { return all_of(a.begin(), a.end(), isEven); }; cout << any(arr) << " " << all(arr); return 0; }
- Что выведено на экран? (прокомментируйте код)
#include <iostream>
#include <functional>
using namespace std;
int main() {
int N = 0;
std::function<int(int)> fact = [&fact](int N) {
if (N < 0) {0
return 0;
} else if (N == 0) {
return 1;
} else {
return N * fact(N - 1);
}
};
cout << "Input number: ";
cin >> N;
cout << "for number " << N << " = " << fact(N) << endl;
return 0;
}
- Дополните код программы. Организуйте ввод массива с клавиатуры. См. Слайд 70-71
#include <iostream>
int sumOfEvenNumbers(ЧТО ЖЕ СЮДА ВПИСАТЬ?) {
int sum = 0;
for (int i = 0; i < length; i++) {
if (arr[i] % 2 == 0) {
sum += arr[i];
}
}
return sum;
}
int main() {
int arr[] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
int length = sizeof(arr) / sizeof(arr[0]);
int result = sumOfEvenNumbers(arr, length);
std::cout << "Sum of even numbers: " << result << std::endl;
return 0;
}