Объявление и определение функций
Язык программирования C, как и многие другие языки, предоставляет возможность создавать программы, которые состоят из большого числа функций и одного
или нескольких файлов исходного кода. До настоящего момента мы работали только с функцией main()
, которая является основной в программе на C,
так как выполнение всегда начинается с нее. Тем не менее, вы можете создавать и другие функции, которые возможно вызвать из main()
или любой
другой функции. На этом уроке мы сосредоточимся на однофайловых программах с более чем одной функцией.
Изучение функций включает в себя понимание различий между локальными и глобальными переменными. В языке C глобальные переменные (они же внешние) объявляются за пределами функций. Они упрощают обмен данными между функциями, но чрезмерное их использование может спутать весь код программы. Локальные переменные называются автоматическими и действуют только внутри той функции, где были объявлены. Параметры функции также выступают в роли локальных переменных.
Структура программы на языке C, состоящей из нескольких функций, может варьироваться. Поскольку выполнение начинается с main()
, этой
функции должны быть известны спецификации (имена, количество и типы параметров, тип возвращаемого значения) всех функций, которые она вызывает.
Это означает, что объявление функций должно произойти перед их вызовом. Однако определение функции может идти как перед, так и после main()
.
Рассмотрим такую программу:
#include <stdio.h>
float median (int a, int b); // объявление функции
main () {
int num1 = 18, num2 = 35;
float result;
printf("%10.1f\n", median(num1, num2));
result = median(121, 346);
printf("%10.1f\n", result);
printf("%10.1f\n", median(1032, 1896));
}
float median (int n1, int n2) { // определение функции
float m;
m = (float) (n1 + n2) / 2;
return m;
}
В этом случае функция median()
объявляется в начале программы. Указываются возвращаемый ею тип (float
), а также количество и
типы параметров (int a, int b
). Обратите внимание: при объявлении переменных можно их группировать, например, int a, b;
, но в
случае с параметрами функции каждый параметр должен иметь свой тип: (int a, int b)
.
Затем идет функция main()
, а следом — определение median()
. Имена параметров в объявлении функции не имеют значения (их
можно вообще не указывать, например, float median (int, int);
). При определении функции имена параметров могут отличаться, однако количество
и типы должны совпадать с объявлением.
Функция median()
возвращает результат типа float
. Оператор return
выполняет возврат с результатом выполнения
выражения, следующее за которым выполнение функции прекращается, даже если в теле функции имеется еще код. median()
вычисляет
среднее значение двух целых чисел. В выражении (float) (n1 + n2) / 2
сначала складываются два целых числа, после чего результат превращается
в вещественное число и делится на 2. В противном случае деление было бы целочисленным, и дробная часть обрезалась бы.
В main()
функция median()
заново вызывается трижды. Результат вызова не обязательно сохранять в переменной.
Эту программу можно бы было написать и так:
#include <stdio.h>
float median (int n1, int n2) {
float m;
m = (float) (n1 + n2) / 2;
return m;
}
main () {
int num1 = 18, num2 = 35;
float result;
printf("%10.1f\n", median(num1, num2));
result = median(121, 346);
printf("%10.1f\n", result);
printf("%10.1f\n", median(1032, 1896));
}
Хотя это и экономит строку кода, основная логика программы в main()
выносится ниже, что осложняет чтение. Поэтому предпочтительнее первый вариант.
Разработайте функцию, которая возводит в куб переданное ей число. Проверьте ее работу с различными значениями аргументов.
Статические переменные
В языке C имеются так называемые статические переменные, которые могут выступать как глобальными, так и локальными. Их декларация происходит с
использованием ключевого слова static
.
Статические переменные, которые объявлены как внешние, в отличие от обычных глобальных, становятся недоступными из других файлов, если программа состоит из нескольких файлов. Они глобальны только в рамках функций текущего файла. Это помогает сокрытию данных с целью избежания несанкционированных изменений.
Статические переменные, объявленные внутри функции, имеют ту же зону видимости, что и автоматические переменные. Они отличаются тем, что их значения сохраняются между вызовами функций:
#include <stdio.h>
int hello();
main() {
printf(" - %d-й вызов\n", hello());
printf(" - %d-й вызов\n", hello());
printf(" - %d-й вызов\n", hello());
}
int hello () {
static count = 1;
printf("Hello world!");
return count++;
}
Результат:
Hello world! - 1-й вызов
Hello world! - 2-й вызов
Hello world! - 3-й вызов
В этом примере функция hello()
ведет учет своих вызовов.
Передача аргументов по ссылке
В первом примере этого урока аргументы передавались функции по значению. Это значит, что при вызове функции ей фактически передаются
копии значений переменных. Сами переменные не изменяются, так как их изменения в вызываемой функции происходят с копиями. Из этого следует,
что изменения, произведенные над параметрами функции, не воздействуют на переменные из функции median()
, даже если те изменяются.
Однако возможно организовать изменения локальных переменных одной функции через другую функцию, передавая в нее адрес переменной или ее указатель. В действительности это также передача копии значения — конкретно, значения адреса участка памяти. На одну область памяти можно создать множество ссылок и использовать их для изменения значений. Пример:
#include <stdio.h>
void multi (int *px, int y);
main () {
int x = 34, y = 6;
multi(&x, 367);
multi(&y, 91);
printf("%d %d\n", x, y);
}
void multi (int *base, int pow) {
while (pow >= 10) {
*base = *base * 10;
pow = pow / 10;
}
}
Функция multi()
не возвращает никакого значения, что указывается ключевым словом void
. Эта функция принимает адрес, который
присваивается локальной переменной-указателю, и целое число. Изменение значения происходит по адресу, который содержится в указателе, и это на самом деле
адрес переменной x
из функции main()
, что позволяет изменить ее значение.
При вызове multi()
из main()
первым параметром обязательно передается адрес, а не само значение. Таким образом, вызов
multi(x, 786)
вызвал бы ошибку, а вот multi(&x, 786)
работает корректно, так как передается адрес переменной x
.
При этом в main()
можно объявить указатель и передавать именно его (например, переменная p
содержит адрес):
int x = 34, y = 6;
int *p;
p = &x;
multi(p, 367);
p = &y;
multi(p, 367);
printf("%d %d\n", x, y);
Также важно знать, что функция способна возвращать адрес.
Понимание механизма передачи аргументов по ссылке пригодится, когда речь пойдет о массивах и строках. Хотя при работе с простыми типами данных лучше возвращать значение из функции, чем менять локальные переменные одной функции с помощью другой, так как функции должны оставаться максимально независимыми друг от друга.
- Переделайте код первого примера этого урока так, чтобы он использовал указатель; а код примера с функцией
multi()
избавьте от указателей. - Создайте программу, в которой, помимо функции
main()
, будут еще две функции: одна будет вычислять факториал переданного числа, а другая — находить n-й элемент ряда Фибоначчи (где n — параметр функции). Вызовите эти функции с разными аргументами. - Разработайте программу, в которой из функции
main()
будет вызываться другая функция, а затем из нее — еще одна функция.