Объявление и определение функций

Язык программирования 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);

Также важно знать, что функция способна возвращать адрес.

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

Задания:
  1. Переделайте код первого примера этого урока так, чтобы он использовал указатель; а код примера с функцией multi() избавьте от указателей.
  2. Создайте программу, в которой, помимо функции main(), будут еще две функции: одна будет вычислять факториал переданного числа, а другая — находить n-й элемент ряда Фибоначчи (где n — параметр функции). Вызовите эти функции с разными аргументами.
  3. Разработайте программу, в которой из функции main() будет вызываться другая функция, а затем из нее — еще одна функция.

Вопросы для самопроверки:

  1. В чем отличие между глобальными и локальными переменными в языке программирования C?
  2. Почему важно объявление функции до ее вызова в программе на языке C?
  3. Какие особенности существуют для параметров при объявлении функции, в отличие от объявления переменных?
  4. Как используется ключевое слово static в C?
  5. Как статические переменные внутри функции отличаются от автоматических переменных?
  6. В чем заключается механизм передачи аргументов по ссылке в языке C?

Программа курса:

  1. Описание курса
  2. Введение в язык программирования C
  3. Типы данных в C и форматированный вывод
  4. Символьные типы и управляющие символы в C
  5. Операторы ветвления и switch в C
  6. Циклы и операторы в языке C
  7. Битовые операции в языке C
  8. Посимвольный ввод и вывод в C - буферизация
  9. Переменные, адреса и указатели в C
  10. Передача аргументов по ссылке и значению в C
  11. Форматированный ввод данных с использованием scanf
  12. Генерация псевдослучайных чисел на C
  13. Адресная арифметика в массивах C
  14. Передача массивов в функции и указатели
  15. Строки в языке C - особенности и функции работы
  16. Функции работы со строками в C
  17. Работа со структурами в C - создание и применение
  18. Динамические структуры данных в C
  19. Ввод и вывод данных из файлов в языке C
  20. Передача аргументов в C и работа с файлами
  21. Препроцессор в языке C - директивы и макросы
  22. Создание и компиляция многофайловых программ в C
  23. Использование статических и динамических библиотек в C