basic-c/sections/07-functions.tex

158 lines
21 KiB
TeX
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

\section{Функции}
\subsection{Понятие функции, параметры и аргументы}
Функция - это такая обособленная часть кода, которую можно выполнять любое количество раз. У функций обязательно в таком порядке должны быть описаны: тип возвращаемого значения, название, параметры и так называемое тело, то есть, собственно, исполняемый код. Рассмотрим более детально функцию \code{int main (int argc, char *argv[])}: указанный тип \code{int} - это \textit{тип возвращаемого значения}, то есть на том месте, откуда будет вызвана эта функция, в результате её работы по факту выполнения оператора \code{return;}, появится некое целое число. Возвращаемые значения могут быть любых типов. В случае же когда функция не должна возвращать результат своей работы, или никакого возвращаемого результата не предполагается, указывается ключевое слово \code{void} (англ. - пустота). То есть на месте вызова функции, в результате её выполнения, не появится никакого значения (обычно, таким значением бывает rvalue). Оператор \code{return;} обязателен для не-void функций, а в \code{void} функциях может присутствовать или нет, но никогда не содержит возвращаемого значения. Написанное с маленькой буквы слово \code{main} - это \textit{название функции}. Функция именно с таким названием, написанным с маленькой буквы, всегда является точкой входа в программу (\hyperref[text:main]{\ref{text:main}}). Операционная система ищет именно эту функцию, когда получает команду на выполнение программы.
\frm{Названия функций в рамках одной программы не должны повторяться и не должны начинаться с цифр или спецсимволов, также, как и названия переменных (см стр. \hyperref[text:naming]{\pageref{text:naming}}) никаких других ограничений на название функций не накладывается.}
Конструкция в круглых скобках \code{(int argc, char *argv[])} - это \textit{параметры функции}. Параметры функции - это такие переменные, которые создаются при вызове функции и существуют только внутри неё. С их помощью можно передать в функцию какие-то аргументы и исходные данные для работы. Параметры пишутся в круглых скобках сразу после названия функции. В случае если функция не принимает параметров необходимо поставить после названия пустые круглые скобки (\code{()}). Весь код, содержащийся в фигурных скобках после параметров функции называется \textit{телом функции}. Это те операторы и команды, которые будут последовательно выполнены при вызове функции. В теле функции мы можем \textbf{вызывать} другие функции, но \textbf{никогда не можем объявлять, описывать или создавать в теле функции другие функции}. Никаких других ограничений на написание тела функции язык не накладывает. Таким образом, общий вид функции следующий:
\begin{figure}[h!]
\begin{verbatim}
ТипВозвращаемогоЗначения Имя (СписокАргументов)
{
ТелоФункции
return ВозвращаемоеЗначение;
}
\end{verbatim}
\end{figure}
Далее приведём небольшой пример, который призван продемонстрировать, как выглядит простейшее \textit{объявление} и \textit{описание} функций (function declaration and definition), а также их вызов из функции \code{int main (int argc, char *argv[])}.
\begin{figure}[h!]
\begin{lstlisting}[language=C,style=CCodeStyle]
void somefunction() { // <-- this is a function
printf("some function\n");
// some useful things
}
int anotherFunction() {
printf("another function\n");
// more useful things happened
return 10;
}
int main (int argc, const char* argv[]) {
printf("main function\n");
// more useful things
somefunction(); // <-- this is invocation
int x = anotherFunction();
printf("x = %d\n", 10);
return 0;
}
\end{lstlisting}
\end{figure}
Так, на шестнадцатой строке кода выше мы видим, что \textbf{вернувшееся} из функции, объявленной на шестой строке целое число \code{10} будет присвоено переменной \code{x} и выведено в терминал семнадцатой строкой.
\begin{figure}[h!]
\begin{verbatim}
$ ./program
main function
some function
another function
x = 10
$
\end{verbatim}
\end{figure}
Функции принято разделять на проверяющие, считающие и выводящие, и каждая из вышеописанных функций не должна нести дополнительной нагрузки. То есть, функция не должна знать откуда в программе появились её параметры, и где будет использован результат её работы. То есть сам язык таких ограничений не накладывает, но такой подход к написанию функций делает их значительно более гибкими и даёт им возможность быть переиспользованными. Без применения такого подхода было бы невозможно писать абстрактные библиотеки и фреймворки.
\frm{\textbf{Параметры функции} - это те переменные, которые указываются в круглых скобках при определении или описании функции. Параметры функции существуют как локальные переменные в кодовом блоке тела функции. \textbf{Аргументы функции} - это те значения переменных или литералов, которые указываются в круглых скобках при вызове функции.}
Для примера опишем функцию, суммирующую два числа. Для простоты, в качестве аргументов она будет принимать целые числа и возвращать целочисленный результат. Обратите внимание, что функция не <<знает>> откуда взялись эти числа, мы можем их прочитать из консоли, можем задать в виде констант или получить в результате работы какой-то другой функции. Внутри функции \code{int main (int argc, char *argv[])} программа вызывает нашу функцию \code{sum(int x, int y)} суммирующую два числа и передаём в качестве аргументов эти числа.
\begin{figure}[h!]
\begin{lstlisting}[language=C,style=CCodeStyle]
int sum(int x, int y) {
int result = x + y;
return result;
}
int main (int argc, const char* argv[]) {
int a;
scanf("%d", &a);
int x = sum(50, a);
printf("x = %d\n", 10);
return 0;
}
\end{lstlisting}
\end{figure}
Обратите внимание, что в качестве аргументов мы можем передавать константные значения, а также переменные. Значения переменных мы можем получить например из консоли, либо в результате выполнения какой-нибудь другой функции.
\begin{figure}[h!]
\begin{verbatim}
$ ./program
x = 110
$
\end{verbatim}
\end{figure}
Как уже было сказано, параметры - это переменные, которые хранят в себе некоторые начальные значения вызова функции. Параметризация позволяет использовать одни и те же функции с разными исходными данными. Приглядимся повнимательнее к хорошо знакомой нам функции \code{printf();}. Строка, которую мы пишем в круглых скобках в двойных кавычках - это аргумент функции. То есть мы знаем, что функция умеет выводить на экран строки, как именно - нам нет дела, а какие именно строки - мы указываем в качестве аргумента. Функция \code{printf();} примечательна еще и тем, что она может принимать в себя нефиксированное количество аргументов. Описание работы таких функций, а также их написание выходит далеко за пределы основ языка, нам важно помнить что мы можем это использовать. В аргументе функции \code{printf()} мы можем написать заполнитель соответствующего типа и, например, вызвать нашу функцию \code{sum()}.
\subsection{Оформление функций. Понятие рефакторинга}
Теперь мы без проблем можем оформить уже существующие у нас программы в виде функций. Например, оформим в виде функции программу проверки простоты числа. Для этого опишем функцию которая возвращает целое число, назовем ее \code{isPrime()}, в качестве параметра она будет принимать целое число, назовем его \code{number}. Найдем в предыдущих разделах (стр. \hyperref[code:isPrime]{\pageref{code:isPrime}}) программу определения простоты числа и скопируем в тело функции. Внесем небольшие правки, уберем вывод так как это будет, можно сказать, классическая проверяющая функция, вывод оставим для функции \code{int main (int argc, char *argv[])}, пусть о наличии у нас терминала <<знает>> только она.
\frm{Такой процесс, перенос участков кода между функциями, выделение участков кода в функции, синтаксические, стилистические и другие улучшения, называетя \textbf{рефакторингом}. Обычно, рефакторингом занимаются сами разработчики в свободное от основной деятельности времени, в периоды код ревью или по необходимости улучшить читаемость/повторяемость собственного кода.}
Следовательно, допишем условия: если делителей два, то число простое, возвращаем \code{ИСТИНУ}, то есть любое ненулевое значение, в нашем примере - единицу. Если же делителей больше число не простое, возвращаем \code{ЛОЖЬ}, в нашем случае, это ноль. Такой вывод можно записать и другим способом, \code{return (dividers == 2)} это выражение в случае истины вернет единицу в случае лжи ноль. Или можно воспользоваться тернарным оператором, то есть, написать \code{return (dividers == 2) ? 1 : 0}: если условие в скобках истинно вернется единица, ложно ноль. Также важно, что выйти из функции мы можем на любом этапе ее выполнения, например если делителей уже три, то нам нужно не завершать цикл, а вернуть \code{ЛОЖЬ} из функции.
\begin{figure}[H]
\setlength{\columnsep}{22pt}
\begin{multicols}{2}
\begin{lstlisting}[language=C,style=CCodeStyle]
int isPrime(int number){
int dividers = 0, i = 1;
while(i <= number){
if(number % i++ ==0)
dividers++;
else
continue;
if (dividers == 3)
return 0;
}
return (dividers == 2)
}
\end{lstlisting}
\columnbreak
\begin{lstlisting}[language=C,style=CCodeStyle]
int main(int argc, char *argv[]) {
int number;
int dividers = 0, i = 1;
printf("Enter number: ");
scanf("%d", &number);
while (i <= number) {
if (number++ % i == 0) {
dividers++;
} else {
continue;
}
if (dividers == 3)
break;
}
printf("Number %d is%s prime",
number,
(dividers == 2) ? "" : " not"
);
}
\end{lstlisting}
\end{multicols}
\end{figure}
%\setlength{\columnsep}{10pt}
Немного подправив вывод, внесем в него вызов функции \code{isPrime()} и объявим переменную \code{int num}, которую будем передавать в качестве аргумента в функцию \code{isPrime()}. Запустим нашу программу и убедимся что все работает число 71 действительно является простым.
\begin{figure}[h!]
\begin{lstlisting}[language=C,style=CCodeStyle]
int main (int argc, const char* argv[]) {
int num = 71;
printf("Entered number %d is%s prime \n",
number,
isPrime(num) ? "" : " not"
);
return 0;
}
\end{lstlisting}
\end{figure}
Теперь мы можем написать программы любой сложности, содержащие функции \code{isPrime()} или \code{sum()}. О том, что мы работаем с консолью, в нашем случае должна знать только функция \code{int main (int argc, char *argv[])}, поэтому ввод значений и вывод на экран мы оставим в ней, а подсчёты, проверки или другие важные действия и алгоритмы положим в функции. Именно это абстрагирование является сильной стороной использования функций, так, например, у нас нет необходимости каждый раз вставлять в программу код взаимодействия с консолью при выводе каждой строки, а можно ограничиться вызовом функции \code{printf();}.
\subsection{Прототип функции, заголовочные файлы}
Зачастую возникают ситуации, когда функция не описана до точки входа в программу, или вовсе лежит в другом файле, возможно, даже написанном не нами. В этом случае мы должны сообщить компилятору, что такую функцию придётся дополнительно поискать. Для этого необходимо указать всю информацию о функции, кроме её тела. Такое объявление называется \textbf{прототип или определение функции} (англ. function definition).
\frm{С определением функции тесно связано понятие \textit{сигнатуры} функции. Сигнатура функции для разных языков программирования представляется немного разным составом сведений, так, например, в языке С сигнатура - это тип возвращаемого значения, название функции и порядок типов параметров, например, для функции суммирования чисел, описанной выше, это будет \code{int sum(int, int)}.}
Опишем прототип функции \code{isPrime()}, описав сигнатуру этой функции. Обратите внимание, что допустимо в определении функции также писать названия параметров, а не только их типы, но это необязательно.
\begin{lstlisting}[language=C,style=CCodeStyle]
int isPrime(int number);
\end{lstlisting}
Из таких определений часто составляют так называемые \textit{заголовочные файлы}. Заголовочные файлы это мощный инструмент модульной разработки. Мы уже неоднократно видели подключение заголовочного файла \code{stdio.h}, Обнаружив данный файл на диске компьютера, мы увидим, что в нём содержатся другие подключения библиотек, директивы препроцессора (о которых более подробно мы будем говорить в следующих разделах) и прототипы функций (например, так часто используемой нами \code{printf()}). Заголовочным этот файл называется, потому что его обычно пишут в коде программы в самом верху, и, фактически, компилятор просто вставляет его содержимое в текст программы. Расширение файла (\code{.h}) является сокращением от английского слова header, заголовок. Обратите внимание, что подключая заголовочный файл \code{stdio.h} мы получаем вообще всю функциональность стандартного ввода-вывода, то есть, например, работу с файлами, которую можем и не использовать. В стандарте С++20 было принято решение о переходе для поддержки повторяемости кода от заголовочных файлов к целостным модулям, импортируемым отдельно. Это позволяет интегрировать в программу только нужный функционал, игнорируя всю остальную библиотеку.