Лекция 7, функции

Функция вычисления факториала

Мы уже знакомы с функцией main и знаем, что именно с неё начинается выполнение программы. Пришло время узнать, как определять другие свои функции и пользоваться ими.

Определение функции выглядит так:

тип_результата имя_функции(параметры)
{
    тело_функции;
}

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

Рассмотрим как пример функцию, вычисляющую факториал:

int factorial(int n)
{
    int i, result = 1;
    for (i = 2; i <= n; ++i) {
        result *= i;
    }
    return result;
}

Ясно, что результат работы такой функции – это целое число, поэтому как тип результата указан int. Функция принимает один аргумент, который является целым числом. Имя параметра тоже указано в списке аргументов, в данном случае он назван n, и по этому имени мы будет обращаться в теле функции к значению параметра. Оператор return в конце тела функции завершает выполнение функции (если он стоит не в конце, он также завершит вызов функции) и использует значение указанной переменной для возврата результата.

Полный текст программы, печатающей факториалы чисел от 0 до 12 включительно, выглядит так:

#include <stdio.h>

int factorial(int n)
{
    int i, result = 1;
    for (i = 2; i <= n; ++i) {
        result *= i;
    }
    return result;
}

int main(void)
{
    int i;
    for(i = 0; i < 13; ++i) {
        printf("%d! = %d\n", i, factorial(i));
    }
    return 0;
}

При вызове функции factorial(i) в параметр функции n копируется значение аргумента i, затем управление передаётся функции, которая выполняет вычисления для этого значения параметра n. При выходе из функции с помощью return, вычисленное значение подставляется в место вызова функции и может быть сохранено в переменной с помощью присваивание или использовано в каком-либо выражении (например, распечатано в printf, как в примере выше).

Сигнатура (прототип) функции

Сигнатура функции - это совокупность типа возвращаемого значения и всех типов параметров функции. Например, сигнатура нашей функции factorial выглядит так:

int factorial(int n);

Именно сигнатуры функций, а не сами функции, хранятся в заголовочных файлах. Например, в заголовочном файле математической библиотеки math.h находится следующее объявление функции взятия квадратного корня:

double sqrt(double x);

Подключая заголовочный файл, мы сообщаем компилятору, что где-то существует функция sqrt, принимающая одно число типа double и возвращающая результат типа double. Но сам машинный код этой функции будет объединён на этапе компоновки (редактирования связей между функциями), если не забыть указать ключ “-lm”.

#include <stdio.h>
#include <math.h>

// Пример программы с двумя точками выхода из функции
// (операторами return). Точка входа в функцию в языке
// C всегда одна.

int main(void)
{
    double x;
    scanf("%lf", &x);
    if (x < 0) {
        printf("Can not calculate square root of negative number %lf\n", x);
        return 1; // Здесь return используется для немедленного выхода из
                  // функции. Значение 1 сообщает операционной системе
                  // "Программа завершилась с ошибкой, номер ошибки 1.
    }
    printf("sqrt(%lf) = %lf\n", x, sqrt(x));
    return 0;
}

Теперь обсудим механизм передачи аргументов в функцию. Рассмотрим следующий пример:

#include <stdio.h>

void try_change_arg(int n)
{
    n = 1;
    printf("n = %d inside function\n", n);
}

int main(void)
{
    n = 10;
    try_change_arg(n);
    printf("n = %d after function\n", n);
    return 0;
}

Если запустить такую программу, легко увидеть, что функция try_change_arg не может изменить переменную, переданную ей как аргумент. Это происходит потому, что при передаче аргумента в функцию передаётся копия аргумента, и try_change_arg изменяет уже копию.

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

void fun(int n)
{
    int i = 0;
    for (i = 0; i < n; ++i) {
        if (10 == i) {
            return;
        }
        printf("%d\n", i);
    }
}

Также обратите внимание, что в конце функций, возвращающих void, return можно не ставить.

Пример функции для поиска значения в массиве:

// Функция ищет число x в массиве arr длиной N.
// Если x найдено, то функция вернёт его индекс,
// если не найдено, то -1.
int find(int N, int arr[], int x)
{
    int  i;
    for(i = 0; i < N; ++i) {
        if(arr[i] == x) {
            return i;
        }
    }
    return -1;
}

int main(void)
{
    int a[5] = {10, 20, 30, 40, 50};
    int index = find(5, a, 30);
    printf("value is found at index %d\n", index); // Выведет 2.
    return 0;
}