Лекция 5. Функции ####################################################################################### Описание и использование функций =========================================================== Функциональная декомпозиция ----------------------------------------------------------- .. admonition:: Архитектура **Функция** - основной строительный элемент программы. .. image:: _static/05/ris1.png Функциональная модель программы `````````````````````````````````````````````````````````` Рассмотрим функциональную модель программы с одним потоком выполнения. В один момент времени может выполняться только одна функция. Выполнение программы начинается с **main**, а далее она передаёт управление другим функциям. Существуют **многопоточные** программы, в которых отдельные функции могут выполняться в отдельных потоках (параллельно). Потоки имеют одно адресное пространство и набор ресурсов (файлы и т.д.). С функцией обычно связывают две операции: #. **Объявление** - определение функции с именем и параметрами #. **Вызов** - применение функции к некоторым данным Стек вызовов ----------------------------------------------------------- Для работы механизма вызова процедур используется **стек вызовов:** .. image:: _static/05/CallStack.png Объявления ----------------------------------------------------------- Объявление функции `````````````````````````````````````````````````````````` *Полное объявление (**описание*)** - может встречаться один раз. *Неполное объявление (**прототип*)** - может встречаться много раз. []name([type-specifier arg1,...); Классы памяти и возвращаемое значение `````````````````````````````````````````````````````````` .. admonition:: Классы памяти * **extern** (по-умолчанию) - функция видна во всех файлах * **static** - функция видна только в текущем файле Возвращаемые значения: * по-умолчанию - **int** * при отсутствии - **void** .. code-block:: c fun() { ... return N; } .. code-block:: c void fun() { ... } Организация однофайловой программы `````````````````````````````````````````````````````````` .. admonition:: \textbf{main} в конце файла .. code-block:: c fun1() { } fun2() { fun1(); } main() { fun1(); fun2(); } .. admonition:: Промежуточный вариант .. code-block:: c fun2(); fun1() { fun2(); } fun2() { } main() { fun1(); fun2(); } .. admonition:: \textbf{main} в начале .. code-block:: c fun1(); fun2(); main() { fun1(); fun2(); } fun1() { } fun2() { } Организация многофайловой программы `````````````````````````````````````````````````````````` В многофайловой программе функции могут определяться в одном файле, а использоваться - в другом. В таком случае нужно: #. Описывать функции как **extern** (по-умолчанию). #. Помещать в начало файла, где функция вызывается, **прототип**. .. code-block:: c /* File1.c */ int fun() { } .. code-block:: c /* File2.c */ int fun(); // прототип! int main() { int val=fun(); return 0; } Параметры функций =========================================================== Виды параметров ----------------------------------------------------------- Формальные и фактические параметры `````````````````````````````````````````````````````````` При описании функции мы указываем **формальные** параметры .. code-block:: c int fun (char a1, int a2, float* a3) При вызове функции мы указываем **фактические параметры** .. code-block:: c int r = fun ('#', 256, &val) Фактические параметры должны соответствовать формальным! И снова стек вызовов `````````````````````````````````````````````````````````` .. image:: _static/05/CallStackFrame.png Некорректная программа `````````````````````````````````````````````````````````` .. admonition:: Что напечатает программа? .. code-block:: c int fa() { puts("fa"); return 1;} int fb() { puts("fb"); return 2;} int fc() { puts("fc"); return 3;} void fun( int a, int b, int c) { printf("%d %d %d",a,b,c); } int main() { fun(fa(),fb(),fc()); return 0; } Неккоректная программа `````````````````````````````````````````````````````````` Неккоректной программы происходит из-за неопределённого в языке порядка вычисления аргументов функции. Поэтому в разных системах строки **fa,fb,fc** могут появляться на экране в разном порядке. Передача параметров ----------------------------------------------------------- Способы передачи параметров `````````````````````````````````````````````````````````` *По значению* (by value) .. code-block:: c void fun(short value) { value++; } int main() { short value=14; fun(value); printf("%d", value); return 0; } Содержимое стека .. image:: _static/05/ris2.png *По указателю* (by pointer) .. code-block:: c void fun(short *value) { (*value)++; } int main() { short value=14; fun(&value); printf("%d", value); return 0; } Содержимое стека .. image:: _static/05/ris3.png В языке С++ предложен третий способ: **по ссылке (by reference)** .. code-block:: c void fun(short& value) { value++; } int main() { short value=14; fun(value); printf("%d", value); return 0; } Передача массивов `````````````````````````````````````````````````````````` Массивы передаются только по указателю! .. code-block:: c // передаём адрес массива и число элементов int getSum(int *arr,int size) { int sum=0; for(int i=0;imyprog.exe Запуск программы с параметрами .. code-block:: none C:\Programs\>myprog.exe param1 param2 param3 Способы передачи данных в программу `````````````````````````````````````````````````````````` Данные могут появляться в программе: #. В виде констант (присутствие в коде). #. Поступать с клавиатуры в режиме диалога. #. Поступать из файлов данных. #. Поступать с др. устройств (например, сеть). #. **Поступать через командную строку** Параметры **main** `````````````````````````````````````````````````````````` .. admonition:: Варианты заголовка main .. code-block:: c int main(int argc, char *argv[ ]) int main(int argc, char **argv) **argc** – переменная, содержащая число слов в ком. строке **argv** – массив указателей на первые символы каждого слова %\begin{lstlisting} %filecopy 1.txt 2.txt %\end{lstlisting} .. image:: _static/05/ris5.png Программа печати приветствия `````````````````````````````````````````````````````````` .. code-block:: c int main(int argc, char *argv[]) { if(argc<2) { printf("Usage: cmd_hello number"); return 1; } int number=atoi(argv[1]); for(int i=0;i 2 Данная программа демонстрирует использование **древовидной рекурсии**, поскольку количество вычислений растёт экспоненциально из-за ветвлений при каждом вызове **fib**. Древовидная рекурсия с ростом **n** может приводить к огромным вычислительным затратам, если одно и то же значение вычисляется множество раз. Изображение древовидного рекурсивного процесса (ДРП): .. image:: _static/05/fib-tree.png Числа Фибоначчи (экономный вариант) `````````````````````````````````````````````````````````` .. code-block:: c typedef unsigned long long ULL; ULL fib_iter(ULL K, ULL M, int N) { if(N==1) return M; else return fib_iter(M,K+M,N-1); } ULL fib(int i) { return fib_iter(0,1,i); } int main() { int i; for(i=1;i<=70;i++) printf("%d-%lld\n",i,fib(i)); return 0; } Числа Фибоначчи `````````````````````````````````````````````````````````` В приведённом примере рекурсия может быть представлена в виде *итерации* без линейного расширения объёма используемой памяти и может быть заменена обычном циклом. В некоторых языках поддерживается механизм **хвостовой рекурсии**, когда рекурсивный процесс может быть заменён на итеративный (циклический) при выполнении программы, но только тогда, когда рекурсия описана как во втором примере вычисления чисел Фибоначчи. Ханойские башни ----------------------------------------------------------- .. admonition:: Задача В одном из буддийских монастырей монахи уже тысячу лет занимаются перекладыванием колец. Они располагают тремя пирамидами, на которых надеты кольца разных размеров. В начальном состоянии 64 кольца были надеты на первую пирамиду и упорядочены по размеру. Монахи должны переложить все кольца с первой пирамиды на вторую, выполняя единственное условие - кольцо нельзя положить на кольцо меньшего размера. При перекладывании можно использовать все три пирамиды. Монахи перекладывают одно кольцо за одну секунду. Как только они закончат свою работу, наступит конец света. .. admonition:: Решение Нужно применить рекурсивно алгоритм, переложив :math:`n-1` кольцо с первой пирамиды на третью пирамиду. Затем сделать очевидный ход, переложив последнее самое большое кольцо с первой пирамиды на вторую. Затем снова применить рекурсию, переложив :math:`n-1` кольцо с третьей пирамиды на вторую пирамиду. .. image:: _static/05/han1.png Ханойские башни (реализация) `````````````````````````````````````````````````````````` {\small .. code-block:: c void han(int num,char a,char b,char c) { if(num>0) { han(num-1,a,c,b); printf("%c--->%c\n",a,c); han(num-1,b,a,c); } } int main() { char a,b,c; int num; printf("Enter number of rings: "); scanf("%d",&num); a='A';b='B';c='C'; if(num>0) han(num,a,b,c); else puts("Error!"); return 0; } } Дополнительная информация =========================================================== Указатели и функции `````````````````````````````````````````````````````````` При передаче адресов в функции могут возникать трудноуловимые ошибки: .. code-block:: c void fun(int **p) { int val=10; *p=&val; } int main() { int *p; fun(&p); (*p)++; // Ошибка! } В приведённом примере мы присваиваем внешнему указателю адрес локальной переменной **val**, которая будет уничтожена после возвращения из **fun**. Вопросы для самоконтроля ============================================= * Что такое функциональная декомпозиция? * Как описывает программу функциональная модель? * Что такое многопоточная программа? * Какие операции характерны для функции? * Что такое описание функции? * Что такое прототип? * Какие классы памяти могут использоваться при описании функции? * Что возвращает функция, если не указывать явно тип возвращаемого значения? * Как организуются функции в одно-файловой программе? * Как организовать многофайловую программу с функциями? * В чем отличие между фактическими и формальными параметрами функции? * Почему опасно полагаться на последовательность обработки параметров функции? * Как параметр передается по значению? Когда используется такой способ передачи параметров? * Как передается параметр через указатель? Когда используется такой способ передачи параметров? * Как осуществляется передача массива в функцию? * Как передать в функцию двумерный массив? * Как запустить программу с параметрами? * Как могут поступать данные в программу? * Как описывается заголовок main при передаче ей параметров из командной строки? * Что означают параметры main? * Что такое рекурсивная функция? * Какие существуют разновидности рекурсии? * Опишите алгоритм построения рекурсивной функции? * Как рекурсивно реализовать вычисление факториала? Чисел Фибоначчи? * Что такое линейный рекурсивный процесс? * Чем опасен ЛРП? * Как образуется ряд Фибоначчи? * Как можно реализовать процедуру получения ряда Фибоначчи? * Что такое древовидная рекурсия? * Чем опасна древовидная рекурсия? * Как можно представить древовидный рекурсивный процесс? * Что такое хвостовая рекурсия? * Чем полезна хвостовая рекурсия?