Функции в языках программирования используются, чтобы
* избавиться от повторения кода (принципы Don't Repeat Youself и Duplication Is Evil);
* упростить длинный код, обособляя его части, заменяя их на короткие понятные идентификаторы (рекомендация: длина функции не должна быть больше 20-50 строк);
* упростить модификацию кода для разных потребностей пользователей или перенос программы в другие ОС, выделяя те части, которые могут меняться;
* выделить код, полезный более чем в одной программе, помещая его в стандартную (например, ``hypot`` или сортировка) или стороннюю библиотеку (например, функции GUI).
Вызов функции выполняется как `"имя"("список_аргументов")`\
где `"список_аргументов"` это последовательность выражений через запятую. Скобки обязательны, даже если список аргументов пустой (скобки после идентификатора всегда являются признаком, что идентификатор является именем функции). Результат функции может использоваться для дальнейших вычислений или игнорироваться:
```c
int x, n, sum=0;
scanf("%d",&n); // игнорируем результат
while(scanf("%d",&x)==1) { // ввести целые числа до конца файла или ошибки ввода
sum+=x;
}
```
Перед вызовом функции функция должна объявлена или определена. Объявление функции состоит из заголовка и ``;``. Определение функции состоит из заголовка и тела функции. Заголовок функции имеет вид:
* `"спецификатор"` может отсутствовать или быть одним из следующих:
- ``extern`` -- применяется для функций по умолчанию, можно не писать;
- ``static`` -- функция доступна только в этом модуле (см. далее);
- ``inline`` -- встраиваемая функция, может применяться только для небольших функций из 1-3 операторов, вместо вызова будет подставлен скомпилированный код этой функции, поэтому определение функции должно быть известно компилятору перед вызовом.
* `"тип_результата"` указывает, значение какого типа возвращает функция после выполнения. Если функция ничего не возвращает, то указывается ``void``.
* `"имя"` в общем случае должно быть уникальным в программе, в отличие от имен переменных, которые могут повторяться в каждом блоке.
* Имена параметров в объявлении функции можно не указывать, но так как заголовки функции используются для формирования подсказок средами разработки, то имя параметра
может служить дополнительной информацией для правильного вызова. Например,
```c
double atan2(double, double); // Где указать x, а где y? Пусть будет x,y... А почему ответ неправильный?
double atan2(double y, double x); // Понятно, сначала y, потом x
```
* Если список параметров является пустым, то в С нужно написать ``void``, в C++ можно не писать ничего.
* Тело функции -- это блок.
Для возврата результата из функции последним оператором в блоке должен быть `bb"return" quad "выражение" bb";"`. Для функций с типом результата ``void`` можно написать `bb"return;"` или не писать ничего. Также разрешается не писать ``return 0;`` в конце тела функции ``main``. В остальных случаях функция возвращает мусор, а компилятор предупреждает о возможной ошибке.
Оператор ``return`` можно использовать для досрочного выхода из функции (как ``goto`` в точку за последним оператором тела функции).
Аргументы при вызове функции копируются в параметры, поэтому изменение параметра не отражается на аргументе:
```run-c
#include <stdio.h>
void test(int n) {
n+=1;
printf("%d\n",n);
}
int main() {
int n=10;
test(n);
printf("%d\n",n);
}
```
В С есть возможность определить функции с переменным количеством параметров. Примерами таких функциями являются scanf и printf. Реальное количество аргументов при вызове должно быть указано в одном из первых аргументов функции, например, у scanf и printf количество вводимых или выводимых значений определяется по количеству спецификаторов формата в первом аргументе функции. Рассмотрим на примере:
```run-c
#include <stdio.h>
#include <stdarg.h> // заголовочный файл для функций и типов для работы с переменным количеством параметров
int sum(int n, ...) { // после 1 параметра указываем ...
int s=0;
va_list args; // список переменных параметров
va_start(args, n); // ... начинается после параметра n
for (int i=0; i<n; ++i) {
s+= va_arg(args, int); // извлекаем очередной параметр типа int
}
va_end(args); // завершение обработки
return s;
}
int main() {
printf("%d\n", sum(4, 5, 10, 50, 101));
printf("%d\n", sum(6, 1, 2, 3, 10, 20, 30));
}
```
В С++ можно изменять количество аргументов при вызове функции, давая параметрам значения по умолчанию в *объявлении* функции. Например, функция для создания окна в графической библиотеке имеет 2 обязательных (размеры окна) и 5 опциональных параметров, поэтому её можно вызывать, указывая от 2 до 7 аргументов:
```c++
int initwindow(int width, int height, const char* title="Windows BGI", int left=0, int top=0, bool dbuf=false, bool closeall=true );
...
// примеры вызова
initwindow(800,600); // initwindow(800,600,"Windows BGI",0,0,false,true);
initwindow(800,600,"Editor",100,50); // initwindow(800,600,"Editor",100,50,false,true);
initwindow(800,600,"Editor",100,50,true,false); // initwindow(800,600,"Editor",100,50,true,false);
```
Аргументы этой функции являются позиционными, нельзя при вызове пропустить какой-то параметр и указать значение для следующего. Можно реализовать подобие опциональных ключевых параметров, для задания значения которым нужно указать имя параметра. Для это в С можно использовать структуру-значение:
```c
typedef struct window_params {
int width;
int height;
const char* title;
int left;
int top;
bool dbuf;
bool closeall;
} window_params;
int initwindow2(window_params params) {
return initwindow(params.width, params.height, params.title?params.title:"Windows BGI", params.left, params.top, params.dbuf, params.closeall);
}
...
// примеры вызова
initwindow2((window_params){800,600}); // initwindow(800,600,"Windows BGI",0,0,false,false);
initwindow2((window_params){800,600,.title="Editor", .top=50, .closeall=true}); // initwindow(800,600,"Editor",0,50,false,true);
```
Рекурсия возникает, когда функция вызывает саму себя прямо или косвенно. Чтобы рекурсивная функция была корректной, в начале функции должно быть условие завершения рекурсии, а при рекурсивном вызове должно происходить уменьшение сложности задачи.
```run-c
#include <stdio.h>
int gcd(int a, int b) { // НОД(a,b)
if(b==0) return a; // завершение рекурсии
return gcd(b, a%b); // упрощение задачи и рекурсивный вызов
}
int main() {
int a,b;
scanf("%d %d",&a,&b);
printf("%d\n", gcd(a,b));
}
<<
10 12
```