Функции выполнения программы


Содержание

Управление выполнением программ

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

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

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

¨ цель подпрограммы может быть достаточно компактно сформулирована (словесно или формально);

¨ подобная часть программы встречается в создаваемой программе или даже в нескольких программах неоднократно;

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

Выделение в программе подпрограмм имеет отчасти объективный, отчасти субъективный характер. Использование подпрограмм позволяет:

¨ структурировать большие программы, что значительно облегчает работу с текстами программ (поиск нужного места в программе, добавление и изменение фрагментов программы);

¨ создавать большие комплексы программ (методы восходящего и нисходящего программирования);

¨ локализовать ошибки в программах, так как можно тестировать каждую подпрограмму в отдельности;

¨ осуществлять коллективную работу над программами (разные подпрограммы могут делать разные программисты);

¨ создавать библиотеки подпрограмм для коммерческого и некоммерческого использования;

¨ составлять документацию на программы, включающую перечень и назначение подпрограмм.

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

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

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

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

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

В частности, окружение программы должно содержать начальный адрес текущего размещения программы в памяти. (На самом деле программы состоят из трех сегментов: собственно программы (программного кода), сегмента данных и служебного сегмента, называемого стеком; поэтому окружение программы содержит три базовых адреса.). Процессор использует окружение текущей программы. Если одна программа хочет для выполнения какого-то действия обратиться к подпрограмме (говорят – вызвать подпрограмму), то этот вызов разбивается на ряд операций:

¨ текущая программа застывает на очередной команде и переходит в пассивное состояние;

¨ окружение текущей программы, включая значения всех регистров процессора, временно сохраняется в выделенном специально для этого участке памяти;

¨ в оперативной памяти отыскивается незанятый участок, достаточный для размещения подпрограммы, и она переписывается на этот участок;

¨ создается окружение подпрограммы, оно становится текущим для процессора, заполняются необходимыми данными регистры процессора;

¨ управление передается подпрограмме, для чего в адресный регистр просто записывается адрес первой команды программы (точнее, его смещение; базовый адрес заполняется на предыдущем этапе);

¨ когда подпрограмма заканчивает свою работу, восстанавливается окружение и значения регистров программы, вызвавшей данную подпрограмму;

¨ место подпрограммы в оперативной памяти освобождается, то есть считается впредь незанятым;

¨ первоначальная программа продолжает работать со следующей команды после той команды, на которой она остановилась.

Каждая операционная система характеризуется своими способами управления выполнением программ. Так в многопользовательской системе UNIX каждый экземпляр определенной программы, запущенной каким-то пользователем, называется процессом. Процесс проходит несколько стадий и характеризуется разными состояниями:

¨ порождение процесса заключается в подготовке операционной системы к выполнению программы;

¨ процесс находится в активном состоянии, если процессор непосредственно занят выполнением программы;

¨ процесс находится в состоянии ожидания, если программа еще не закончена и ожидает данных или ресурсов компьютера, а процессор занят выполнением другой программы;

¨ процесс находится в состоянии готовности, если все необходимое для работы программы есть, но процессор еще занят выполнением другой программы;

¨ окончанием процесса называется этап нормального или аварийного выполнения программы.

Все сведения о процессах размещены в единой таблице. Управление выполнением программ заключается в поддержке содержимого таблицы в соответствии с правилами перехода процессов из одного состояния в другое.

Дата добавления: 2014-10-17 ; Просмотров: 592 ; Нарушение авторских прав? ;

Нам важно ваше мнение! Был ли полезен опубликованный материал? Да | Нет

Функции выполнения Программы

Введение

Эти функции предоставляют средства для выполнения системных команд и средства для того, чтобы сделать выполнение этих команд безопасным.

Требования

Эти функции всегда доступны как часть стандартного модуля.

Установка

Нет специальных требований по установке этих функций, так как они являются частью ядра РНР.

Установка

Это расширение не определяет никаких директив конфигурации.

Типы ресурсов

Это расширение не определяет никакие типы ресурсов.

Предопределённые константы

Это расширение не определяет никаких констант.

См. также

Эти функции также тесно связаны с операцией backtick.

Содержание escapeshellarg — escape-ирует строку для использования в качестве аргумента оболочки escapeshellcmd — escape-ирует метасимволы оболочки exec — выполняет внешнюю программу passthru — выполняет внешнюю программу и выводит необработанный вывод proc_close — закрывает процесс открытый proc_open и возвращает exit-код этого процесса proc_open — выполняет команду и открывает указатели файла для ввода/вывода shell_exec — выполняет команду в оболочке и возвращает полный вывод как строку system — выполняет внешнюю программу и отображает вывод Оглавление

Электроника для всех

Блог о электронике

1.5.1. Определение и вызов функций

Мощность языка СИ во многом определяется легкостью и гибкостью в определении и использовании функций в СИ-программах. В отличие от других языков программирования высокого уровня в языке СИ нет деления на процедуры, подпрограммы и функции, здесь вся программа строится только из функций.

Функция — это совокупность объявлений и операторов, обычно предназначенная для решения определенной задачи. Каждая функция должна иметь имя, которое используется для ее объявления, определения и вызова. В любой программе на СИ должна быть функция с именем main (главная функция), именно с этой функции, в каком бы месте программы она не находилась, начинается выполнение программы.

При вызове функции ей при помощи аргументов (формальных параметров) могут быть переданы некоторые значения (фактические параметры), используемые во время выполнения функции. Функция может возвращать некоторое (одно !) значение. Это возвращаемое значение и есть результат выполнения функции, который при выполнении программы подставляется в точку вызова функции, где бы этот вызов ни встретился. Допускается также использовать функции не имеющие аргументов и функции не возвращающие никаких значений. Действие таких функций может состоять, например, в изменении значений некоторых переменных, выводе на печать некоторых текстов и т.п..

С использованием функций в языке СИ связаны три понятия — определение функции (описание действий, выполняемых функцией), объявление функции (задание формы обращения к функции) и вызов функции.


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

int rus (unsigned char r) < if (r>=’А’ && c

В данной функции значения переменных x и y, являющихся формальными параметрами, меняются местами, но поскольку эти переменные существуют только внутри функции change, значения фактических параметров, используемых при вызове функции, останутся неизменными. Для того чтобы менялись местами значения фактических аргументов можно использовать функцию приведенную вследующем примере.

/* Правильное использование параметров */ vo >

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

Если требуется вызвать функцию до ее определения в рассматриваемом файле, или определение функции находится в другом исходном файле, то вызов функции следует предварять объявлением этой функции. Объявление (прототип) функции имеет следующий формат:

[спецификатор-класса-памяти] [спецификатор-типа] имя-функции ([список-формальных-параметров]) [,список-имен-функций];

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

Прототип — это явное объявление функции, которое предшествует определению функции. Тип возвращаемого значения при объявлении функции должен соответствовать типу возвращаемого значения в определении функции.

Если прототип функции не задан, а встретился вызов функции, то строится неявный прототип из анализа формы вызова функции. Тип возвращаемого значения создаваемого прототипа int, а список типов и числа параметров функции формируется на основании типов и числа фактических параметров используемых при данном вызове.

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

1. Функция возвращает значение типа, отличного от int.

2. Требуется проинициализировать некоторый указатель на функцию до того, как эта функция будет определена.

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

В прототипе можно указать, что число параметров функции переменно, или что функция не имеет параметров.

Если прототип задан с классом памяти static, то и определение функции должно иметь класс памяти static. Если спецификатор класса памяти не указан, то подразумевается класс памяти extern.

Вызов функции имеет следующий формат:

Поскольку синтаксически имя функции является адресом начала тела функции, в качестве обращения к функции может быть использовано адресное-выражение (в том числе и имя функции или разадресация указателя на функцию), имеющее значение адреса функции.

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

Фактический параметр может быть величиной любого основного типа, структурой, объединением, перечислением или указателем на объект любого типа. Массив и функция не могут быть использованы в качестве фактических параметров, но можно использовать указатели на эти объекты.

Выполнение вызова функции происходит следующим образом:

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

2. Происходит присваивание значений фактических параметров соответствующим формальным параметрам.

3. Управление передается на первый оператор функции.

4. Выполнение оператора return в теле функции возвращает управление и возможно, значение в вызывающую функцию. При отсутствии оператора return управление возвращается после выполнения последнего оператора тела функции, а возвращаемое значение не определено.

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

int (*fun)(int x, int *y);

Здесь объявлена переменная fun как указатель на функцию с двумя параметрами: типа int и указателем на int. Сама функция должна возвращать значение типа int. Круглые скобки, содержащие имя указателя fun и признак указателя *, обязательны, иначе запись

int *fun (intx,int *y);

будет интерпретироваться как объявление функции fun возвращающей указатель на int.

Вызов функции возможен только после инициализации значения указателя fun и имеет вид:

В этом выражении для получения адреса функции, на которую ссылается указатель fun используется операция разадресации * .

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

double (*fun1)(int x, int y); double fun2(int k, int l); fun1=fun2; /* инициализация указателя на функцию */ (*fun1)(2,7); /* обращение к функции */

В рассмотренном примере указатель на функцию fun1 описан как указатель на функцию с двумя параметрами, возвращающую значение типа double, и также описана функция fun2. В противном случае, т.е. когда указателю на функцию присваивается функция описанная иначе чем указатель, произойдет ошибка.

Рассмотрим пример использования указателя на функцию в качестве параметра функции вычисляющей производную от функции cos(x).

Для вычисления производной от какой-либо другой функции можно изменить тело функции fun или использовать при вызове функции proiz имя другой функции. В частности, для вычисления производной от функции cos(x) можно вызвать функцию proiz в форме

а для вычисления производной от функции sin(x) в форме

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

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

Классический пример рекурсии — это математическое определение факториала n! :

n! = 1 при n=0; n*(n-1)! при n>1 .

Функция, вычисляющая факториал, будет иметь следующий вид:

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

2 thoughts on “1.5.1. Определение и вызов функций”

два примера кода (второй и третий куски), сожрали три абзаца текста.

Все примеры в «HTML-мусоре» — > & и т.п.
Это и так нехорошо, но там где речь идет про передачу указателя это вообще вызывает полное недоумение…

Добавить комментарий Отменить ответ

Этот сайт использует Akismet для борьбы со спамом. Узнайте как обрабатываются ваши данные комментариев.

Функции в программировании

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

Функции можно сравнить с небольшими программками, которые сами по себе, т. е. автономно, не исполняются, а встраиваются в обычную программу. Нередко их так и называют – подпрограммы. Других ключевых отличий функций от программ нет. Функции также при необходимости могут получать и возвращать данные. Только обычно они их получают не с ввода (клавиатуры, файла и др.), а из вызывающей программы. Сюда же они возвращают результат своей работы.

Существует множество встроенных в язык программирования функций. С некоторыми такими в Python мы уже сталкивались. Это print(), input(), int(), float(), str(), type(). Код их тела нам не виден, он где-то «спрятан внутри языка». Нам же предоставляется только интерфейс – имя функции.

С другой стороны, программист всегда может определять свои функции. Их называют пользовательскими. В данном случае под «пользователем» понимают программиста, а не того, кто пользует программу. Разберемся, зачем нам эти функции, и как их создавать.

Предположим, надо три раза подряд запрашивать на ввод пару чисел и складывать их. С этой целью можно использовать цикл:

Однако, что если перед каждым запросом чисел, надо выводить надпись, зачем они нужны, и каждый раз эта надпись разная. Мы не можем прервать цикл, а затем вернуться к тому же циклу обратно. Придется отказаться от него, и тогда получится длинный код, содержащий в разных местах одинаковые участки:

Пример исполнения программы:

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

Илон Маск рекомендует:  Тег style

Определение функции. Оператор def

В языке программирования Python функции определяются с помощью оператора def. Рассмотрим код:


Это пример определения функции. Как и другие сложные инструкции вроде условного оператора и циклов функция состоит из заголовка и тела. Заголовок оканчивается двоеточием и переходом на новую строку. Тело имеет отступ.

Ключевое слово def сообщает интерпретатору, что перед ним определение функции. За def следует имя функции. Оно может быть любым, также как и всякий идентификатор, например, переменная. В программировании весьма желательно давать всему осмысленные имена. Так в данном случае функция названа «посчитатьЕду» в переводе на русский.

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

После двоеточия следует тело, содержащее инструкции, которые выполняются при вызове функции. Следует различать определение функции и ее вызов. В программном коде они не рядом и не вместе. Можно определить функцию, но ни разу ее не вызвать. Нельзя вызвать функцию, которая не была определена. Определив функцию, но ни разу не вызвав ее, вы никогда не выполните ее тела.

Вызов функции

Рассмотрим полную версию программы с функцией:

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

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

В языке Python определение функции должно предшествовать ее вызовам. Это связано с тем, что интерпретатор читает код строка за строкой и о том, что находится ниже по течению, ему еще неизвестно. Поэтому если вызов функции предшествует ее определению, то возникает ошибка (выбрасывается исключение NameError):

Для многих компилируемых языков это не обязательное условие. Там можно определять и вызывать функцию в произвольных местах программы. Однако для удобочитаемости кода программисты даже в этом случае предпочитают соблюдать определенные правила.

Функции придают программе структуру

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

Пусть надо написать программу, вычисляющую площади разных фигур. Пользователь указывает, площадь какой фигуры он хочет вычислить. После этого вводит исходные данные. Например, длину и ширину в случае прямоугольника. Чтобы разделить поток выполнения на несколько ветвей, следует использовать оператор if-elif-else:

Здесь нет никаких функций, и все прекрасно. Но напишем вариант с функциями:

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

Если нам когда-нибудь захочется вычислять площадь треугольника по формуле Герона, а не через высоту, то не придется искать код во всей программе (представьте, что она состоит из тысяч строк кода как реальные программы). Мы пойдем к месту определения функций и изменим тело одной из них.

Если понадобиться использовать эти функции в какой-нибудь другой программе, то мы сможем импортировать их туда, сославшись на данный файл с кодом (как это делается в Python, будет рассмотрено позже).

Практическая работа

В программировании можно из одной функции вызывать другую. Для иллюстрации этой возможности напишите программу по следующему описанию.

Основная ветка программы, не считая заголовков функций, состоит из одной строки кода. Это вызов функции test(). В ней запрашивается на ввод целое число. Если оно положительное, то вызывается функция positive(), тело которой содержит команду вывода на экран слова «Положительное». Если число отрицательное, то вызывается функция negative(), ее тело содержит выражение вывода на экран слова «Отрицательное».

Понятно, что вызов test() должен следовать после определения функций. Однако имеет ли значение порядок определения самих функций? То есть должны ли определения positive() и negative() предшествовать test() или могут следовать после него? Проверьте вашу гипотезу, поменяв объявления функций местами. Попробуйте объяснить результат.

Примеры решения и дополнительные уроки в android-приложении и pdf-версии курса.

Понятие функции в программировании

Что такое функция

Программа представляет собой последовательность выражений языка. Нередко случается, что какая-то часть программы (блок кода) неоднократно повторяется. Чтобы устранить подобного рода избыточность программного кода, используют понятие функции. Функция — это именованный блок кода, который вызывается в нужных местах программы по имени. Другими словами, функция представляет собой подпрограмму, которую можно вызвать из основной программы, причем неоднократно. Повторяющийся (да и не только) блок программного кода обычно обозначают некоторым уникальным именем, чтобы потом при необходимости обратиться к нему по этому имени. Как видно, это простая и естественная идея, направленная на облегчение реализации сложных проектов, состоящих из более простых программ.

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

Вход в функцию — это передача ей аргументов — данных, полученных во внешней части программы. Получив данные из своего внешнего окружения (внешней программы), функция должна их как-то обработать: выполнить некоторые действия, вычислить какое-то значение. Выход из функции — значение, вычисленное блоком кода данной функции и передаваемое во внешнюю часть программы. Входные данные называют параметрами, а выходные — возвращаемым значением. Впрочем, функция может и не принимать никаких параметров, а также ничего не возвращать. Что принимает в качестве параметров и что возвращает функция в результате своей работы, определяет программист, т. е. автор-разработчик программного кода.

Функция, создаваемая разработчиком, должна иметь определение, а ее применение в программе называют вызовом функции. Например, в JavaScript и РНР (а также в большинстве других языков) такое определение начинается с ключевого слова function , за которым следуют имя функции, пара круглых скобок, внутри которых можно указать список параметров, а затем блок кода, заключенный в фигурные скобки:

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

Для вызова функции в том или ином месте программы указывают следующее выражение:

Круглые скобки после имени функции записывают независимо от того, предусмотрены для нее входные параметры или нет. В программе вы можете один раз задать определение функции, а затем, по мере необходимости, вызывать ее столько раз, сколько требуется. Таким образом, вам не придется многократно воспроизводить в программе код тела функции.

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

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

Результат работы функции

Если вызывающей программе ничего не требуется получить от вызванной функции, то программист может определить эту функцию как ничего не возвращающую. Это разрешается в JavaScript и РНР, но может быть недопустимо в других языках. Если даже практически не важно, что именно возвращает функция, все равно желательно определить для нее некоторое возвращаемое значение. В таких случаях, часто указывают, например, пустое значение, обозначаемое как null . Возможны и другие варианты. Кроме того, функции, возвращающие какие-то значения, можно использовать в выражениях с операторами (например, арифметическими), а также в качестве параметров других функций. Если функция ничего не возвращает, то ее применение в выражениях с операторами обычно чревато сообщениями об ошибках.

Чтобы функция что-то возвращала, необходимо записать в ее теле специальный оператор возврата:

В этом примере переменным Оклад и Процент сначала присваивают некоторые числовые значения. Затем вычисляют выражение, определяющее объем выплаты некоторому сотруднику с учетом его оклада и премии. Премия рассчитывается с помощью функции Премия (Оклад, Процент) , принимающей два параметра: оклад и процент от оклада; данная функция возвращает величину премии (символом * обозначен оператор арифметического умножения). Возвращаемое значение прибавляется к значению переменной Оклад , а полученный результат присваивается переменной Выплата . Определение функции Премия (Оклад, Процент) размещено в конце текста программы.

Данный программный код написан на вымышленном (абстрактном) языке. Я хотел сказать, что рассмотренный пример иллюстрирует принцип применения функции, а потому не важно, какой именно язык выбрать. Главное, что все изложенное ранее должно позволить вам понять данный код. Тем не менее, признаюсь, что приведенный мной пример написан и на языке JavaScript. Напишите в обычном текстовом редакторе, например, в Блокноте Windows следующие строки:

Это HTML-код с внедренным в него сценарием на JavaScript. Код сценария заключен между тегами и отличается от рассмотренного ранее только встроенной функцией alert ( ) для вывода сообщений в диалоговом окне. Сохраните данный код в файле с расширением htm или html, а затем откройте его в Web-браузере, например, Microsoft Internet Explorer или Mozilla Firefox. В результате появится диалоговое окно с сообщением «Выплата =11500».

Назначение

Появление в 1960-х годах в языках (например, ALGOL и FORTRAN) функций позволило писать программы, более ясные в структурном отношении и компактные по объему кода. В идеале основная программа могла состоять из одних только вызовов функций, перемежаемых, возможно, операторами управления (условными переходами и/или циклами). Разрабатывать и отлаживать такие программы стало и быстрее и легче. Программист при желании мог писать программы, широко используя вызовы функций, тела которых еще не написаны.

Чтобы такая программа как-то работала без зависаний и выдачи системных сообщений об ошибках, вместо настоящего кода функции можно было временно поставить так называемые заглушки, например, выво­ды специальных сообщений программиста, например, «работает функция mуfunc».

По появлению подобных сообщений можно судить, что вызов функции произошел, и при этом в главной программе не возникло коллизий. Создав основную программу в общем виде, можно приступить к доработке ее деталей — написанию требуемых кодов, составляющих тела настоящих функций, которые до сих пор были замещены примитивными заглушками. Это так называемый способ (или стиль) программирования «сверху вниз», при котором создаваемая программа постепенно обретает требуемые функции.

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

Даже если какой-то блок кода предполагается задействовать в программе всего лишь один раз, все равно следует подумать, а не оформить ли его в виде функции, чтобы сделать основной текст программы более понятным и к тому же обеспечить возможность повторного использования этого блока в перспективе.

Приведение типов

Возможно, вы заметили одно странное обстоятельство: функции аlert( ) в качестве параметра передается выражение в виде суммы разнотипных данных (символьного и числового значения):

Здесь «Выплата =» — символьная строка, а Выплата — переменная числового типа. Ведь ранее упоминалось, что типы данных для того и придуманы, чтобы выполнять операции над однотипными данными. Тем не менее, сообщений об ошибках не было, а результат оказался правильным!

Дело в том, что рассмотренный сценарий написан на JavaScript, то есть на языке со слабым контролем типов данных. Если в выражении этого языка встречается операция над данными различных типов, то происходит такое автоматическое преобразование этих данных, при котором может быть получен более или менее осмысленный результат. Так, например, если требуется к символьной строке прибавить число, то интерпретатор JavaScript сначала преобразует его в соответствующую строку символов, которую затем «приклеит» к первой строке. В результате получится символьная строка.

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

Методы окружения

Также может вызвать недоумение функция alert( ) , которая использовалась для вывода на экран диалогового окна с некоторым сообщением. А откуда она взялась? В сценарии же нет ее определения.

Если быть точным, то alert( ) — не функция языка JavaScript, а метод объекта window , который принадлежит объектной модели браузера. Методами называют внутренние функции объектов. Иначе говоря, этот метод, как и множество других объектов браузера и загруженного в него документа, составляют внешнее окружение сценария на JavaScript. К объектам этого окружения можно обратиться из сценария, т. е. прочитать и даже изменить значения их свойств. Тем самым обеспечивается возможность управлять внешним видом и информационным содержанием Web-страницы.

Разумеется, модель того, с чем требуется иметь дело посредством языка программирования, необходимо знать. Но это особая тема. Сейчас же мы рассматриваем языки программирования, причем с высоты «птичьего полета», и до поры не будем слишком приближаться к «земле».

Добавить комментарий Отменить ответ

Для отправки комментария вам необходимо авторизоваться.

Условия выполнения программы

Программа «Информационная система библиотека» позволит автоматизировать работу сотрудников, что уменьшит их труд и затрачиваемое время. Работать с программой может оператор средней квалификации (т. е. умеет включать и выключать компьютер, работать с файлами и папками, работать со средствами Office, работать с различными, более сложными программами), ознакомившись и изучив руководство оператора.

Условиями выполнения программы являются:


  • — ЦП Pentium IV;
  • — оперативная память 256Mb;
  • — минимальная емкость диска HDD: 2,5 Gb;
  • — стандартный монитор, мышь, клавиатура;
  • — видеокарта с оперативной памятью не менее 64 Mb;
  • — программа «Информационная система библиотеки».

Выполнение программы

Программа поставляется на диске и для ее загрузки необходимо дважды щелкнуть на иконку.

При загрузке программы «Информационная система библиотеки» появляется главная форма с несколькими меню (поисковая система, списки читателей и литературы, читальный зал, списанная и поступившая литература).

При выборе пункта меню с нужной базой данных открывается окно с этой базой данных. Каждый пункт этого меню открывает выбранную базу данных или второе меню. На каждом окне расположена таблица и средства редактирования данной таблицы, предназначенных для работы с базой данных.

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

Основной задачей приложения является поддержка одной или нескольких связанных между собой таблиц с данными, выборочное извлечение информации из этих таблиц, анализ информации и графическое представление итоговых данных. Таблицы организуются в БД (БД — среда для сохранения и обработки экономической, технической и конструкторской документации). Для работы с различными данными создается наиболее подходящей для каждого конкретного случая интерфейс, содержащий всевозможные меню, множество управляемых окон и разнотипных элементов управления, благодаря чему экран не загромождается лишней информацией и в то же время вся необходимая информация является легко доступной. Приложение также имеет средства для создания и тиражирования различной отчетности, технической, финансовой и рекламной документации.

Руководство пользователя указано в приложении 1.

Отчеты информационной системы указаны в приложении 2.

Себестоимость продукции относится к числу важнейших качественных показателей, в обобщенном виде отражающих все стороны хозяйственной деятельности предприятий (фирм, компаний), их достижения и недостатки. Уровень себестоимости связан с объемом и качеством продукции, использованием рабочего времени, сырья, материалов, оборудования, расходованием фонда оплаты труда и т.д. Себестоимость, в свою очередь, является основой определения цен на продукцию. Снижение ее приводит к увеличению суммы прибыли и уровня рентабельности. Чтобы добиваться снижения себестоимости, надо знать ее состав, структуру и факторы ее динамики. Все это и является предметом статистического изучения при анализе себестоимости.

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

Исходные данные для расчёта себестоимости программного продукта приведены в таблице 2.

Таблица 2. Исходные данные

Количество форм входной информации

  • 10
  • 2
  • 8

Количество разновидностей форм выходной информации

Степень новизны задачи

Вид используемой информации

Режим реального времени (РВ)

Себестоимость продукции — это денежное выражение непосредственных затрат предприятия на производство и реализацию продукции.

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

В себестоимость продукции включают следующие затраты:

  • — на подготовку производства и освоение выпуска новых видов продукции, пусковые работы;
  • — исследование рынка;
  • — непосредственно связанные с производством продукции, обусловленные технологией и организацией производства, включая расходы на управление;
  • — на совершенствование технологии и организации производственного процесса, а также улучшение качества изготавливаемой продукции;
  • — на сбыт продукции (упаковка, транспортировка, реклама, хранение и др.);
  • — набор и подготовку кадров;
  • — другие денежные расходы предприятия, связанные с выпуском и реализацией продукции.

Существует следующая классификация затрат:

  • 1) по степени однородности — элементные (однородные по составу и экономическом содержанию — материальные затраты, оплата труда, отчисления от нее, амортизационные отчисления и др.) и комплексные (разные по составу, охватывающие несколько элементов затрат — например, затраты на содержание и эксплуатацию оборудования);
  • 2) по связи с объемом производства — постоянные (их общая величина не зависит от количества изготовленной продукции, например, затраты на содержание и эксплуатацию строений и сооружений) и переменные (их общая сумма зависит от объема изготовленной продукции, например, затраты на сырье, основные материалы, комплектующие). Переменные затраты в свою очередь можно подразделить на пропорциональные (изменяются прямо пропорционально объему производства продукции) и непропорциональные;
  • 3) по способу отнесения затрат на себестоимость отдельных изделий:

прямые затраты — т.е. те, которые можно непосредственно отнести к конкретной единице выпускаемой продукции или к оказанной услуге — находятся в прямой зависимости от объема производства:

  • а) прямые затраты на сырье и материалы;
  • б) прямые затраты труда (оплата труда разработчиков);
  • в) отчисления на социальное страхование;
  • г) прямые цеховые расходы (к ним относится стоимость эксплуатации ПК).

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

Следует также различать общие затраты (на весь объем продукции за определенный период) и затраты на единицу продукции.

На стоимость программного продукта влияют следующие факторы, которые приведены в таблице 3.

Таблица 3. Факторы, влияющие на стоимость программного продукта

Возможности рынка ПО

Илон Маск рекомендует:  Относительный адрес

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

Если организация примет фиксированную величину стоимости, издержки производства могут возрасти из-за непредвиденных расходов

Если, например, право на владение программным кодом после завершения проекта передано заказчику, то проект стоит дороже.

После заключения контракта за изменение требований можно назначить дополнительную цену

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

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

Таблица 4. Факторы, влияющие на производительность программиста

Опыт разработки ПО для предметной области

Для эффективной разработки программного продукта необходимо знание той предметной области, где будет эксплуатироваться разрабатываемое ПО.

Процесс управления качеством

Применяемый метод программирования может оказать существенное влияние на производительность написания кода.

Чем больше проект, тем больше времени уходит на согласование различных вопросов внутри группы разработчиков и ниже производительность.

Поддержка технологии разработки ПО

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

Спокойное рабочее окружение с индивидуальными рабочими местами способствует повышению производительности

В данном дипломном проекте предлагается следующий алгоритм расчёта себестоимости программного продукта:

  • 1) анализ исходных данных;
  • 2) расчёт трудоёмкости разработки программного продукта;
  • 3) расчёт стоимости машинного часа;
  • 4) расчёт стоимости программного продукта;

Расчёт трудоёмкости разработки программного продукта по стадиям представлен в Таблице 5.

Таблица 5. Трудоёмкость разработки по стадиям

Стадия разработки проекта

Время с учетом поправ. коэф.

1. Разработка технического задания


1.1 Затраты времени разработчика постановки задачи

1.2 Затраты времени разработчика программного обеспечения

2. Разработка эскизного проекта

2.1 Затраты времени разработчика постановки задачи

2.2 Затраты времени разработчика программного обеспечения

3. Разработка технического проекта

3.1 Затраты времени разработчика постановки задачи

3.2 Затраты времени разработчика программного обеспечения

4. Разработка рабочего проекта

4.. Затраты времени разработчика постановки задачи

4.2 Затраты времени разработчика программного обеспечения

5.1 Затраты времени разработчика постановки задачи

5.2 Затраты времени разработчика программного обеспечения

Время работы ПК при отладке и внедрении программы складывается из затрат времени разработчика программного обеспечения на технический проект, рабочий проект и внедрение.

Таким образом, затраты времени на отладку и внедрение составляют 25 человеко-дней или 199,286 часов.

Зачем использовать функции для выполнения программы?

Чем лучше использование функций для выполнения программы?

Ведь можно в main выполнить команды ?

6 ответов 6

Функции служат структурированию вашей программы.

Если вам качество программы неважно (например, программа простая, или вы не собираетесь её дальше разрабатывать и поддерживать), во многих случаях без функций можно и обойтись (важный контрпример: рекурсия).

Но если вы хотите сделать код легко читаемым, понятным, поддерживаемым, расширяемым, то вам нужно его структурировать: разделить на осмысленные части, и дать им имя. Это и есть ваши функции.

Заметьте, что вы в любом случае пользуетесь библиотечными функциями, наподобие getchar() : разработчики библиотеки уже структурировали её для вас.

Функции — не единственный метод структурирования программы. Например, ещё одно популярное, более мощное средство структурирования — классы.

Есть такое понятие как «декомпозиция», т.е. разделение целого на части. Для элементарных ситуаций разделение не обязательно. Но когда программа становится большой, для того чтобы проще было ей управлять и развивать её, приходится делить на более мелкие сущности, давая им в то же время говорящие имена.

Да все команды можно выполнить в одной функции main() .

Но использование функций позволяет повторно использовать одинаковые блоки исходного кода. Улучшает читаемость исходного кода.

Причин разделения кода на функции несколько.

  1. Повторное использование. Довольно часто требуется многократное использование одной функции в разных частях программы. Было бы крайне не разумно писать один и тот же код повторно. Тем более, что в случае изменений их придется вносить в каждый фрагмент. А если вынести функцию в отдельный файл, то можно использовать ее даже в нескольких проектах.
  2. Читаемость. Читаемость кода очень важная характеристика программы. Даже если программа небольшая и написана одним человеком разделение на функции с понятными именами позволяет довольно быстро понять суть происходящего даже после длительного перерыва работы с кодом. А если код ведут несколько программистов, то читаемость кода очень сильно влияет на скорость и качество разработки, и как следствие, на итоговый результат.
  3. Качество, отладка, тестирование и т.д. Разделение крупной системы на отдельные части (модули) является одним из базовых принципов проектирования не только в программировании. Связано это со многими вещами. Работать с небольшими частями проще, их легче заменить и отладить. Есть понятие юнит-тестов, применимое к функциям. Более того, в различных языках программирования есть свои аспекты применения функций, дающих те или иные преимущества. Например template в C++ или замыкания в JavaScript.

А вообще советую почитать книгу С.Макконнелла «Совершенный код» или нечто подобное, с общими аспектами программирования.

Смотрю, выше привели основную причину — декомпозиция программы на части. Но у функций есть и другие применения.

Например, полиморфизм. Пример: я пишу функцию вывода на экран некоторого объекта. Одна функция делает это красиво но медленно — подойдёт когда времени много, вторая быстро но некрасиво — подходит для сценария когда мы быстро тащим или масштабируем объект. Для этого делаем прототип функции (определяет набор параметров и возвращаемое значение), и реализуем две функции, соответствующие этому прототипу. Указатель на функцию у нас содержит текущий способ рисования — и мы можем вызывать функцию рисования, не зная даже на какую конкретно из них он указывает.

Дальше — мы можем иметь массив таких указателей на функции, с одним и тем же прототипом. Смысл — некоторые из них просто рисуют, другие работают как фильтры. Например, фильтр, подсвечивающий точки объекта, за которые можно «потянуть». Опять же, мы можем вызывать все функции из массива по порядку — чтобы отрисовать объект со всеми фильтрами, и вызывающий их код не знает что они делают.

  • декомпозиция

выделение функций в отдельные сущности позволяет и работать с ними как с компонентами: отдельно тестировать, отлаженная функция должна работать одинаково во всех ваших (и вашей конторы) программах, и использовать ее можно одновременно во всех 100500 программах, запущенных на суперкластере

наборы готовых функций собираются в библиотеки, распространяются и применяются тысячами программистов по всему миру десятки лет (BLAS/LAPACK/libc/WinAPI), или продаются как самостоятельный продукт — любая ОС является библиотекой функций, см. \Windows\System\*.dll /lib/*.so , и коммерческие библиотеки компонент

типичная функция sin() , вы знаете ее назначение, обычно типы входных/выходных данных (область/множество определения, область/множество значений), но понятия не имеете, как она устроена внутри: как вызов спецкоманды процессора, поиск по таблице, сумма ряда, запрос к внешней мат.системе или отладочная заглушка возвращающая заданное значение незавимо от входных данных. Вы можете заменять реализацию функции sin() в любой момент без перекомпиляции или даже перезапуска программы. Вы даже можете придумать собственный метод быстрого приближенного вычисления синуса, и протестировать его работу как с вашими, так и с любыми чужими программами, не имея доступа к исходному коду этих программ.

некоторые программные продукты, например распознаватель Abbyy или ядро САПР, продаются просто в виде кучи скомпилированных функций — 100 рублей штучка, 300 штук кучка, оптовым покупателям документация бесплатно

часто тяжелые коммерческие пакеты САПР,аналитики,расчетов и разнообразное оборудование поставляются опцией или в комплекте с SDK-библиотекой, собранной из интерфейсных функций: в документации описаны их назначение, применение, и типы/диапазоны входных/выходных данных, но сами функции идут в виде машинного кода без исходников (только кучка .h файлов с перечнем extern void to() ; extern double se(int,void*) , и пара .dll)

функция bubblesort() может быть описана в общем виде, сортируя данные любых даже еще не существующих типов, хранящиеся в любом упорядоченном контейнере (например сортировать слонопотамов в сепулькарии), при условии что для данных существует операция сравнения, а для контейнера — передача сообщения двум соседним элементам «сравнитесь между собой» и их перестановка местами (обычно исправляются ссылки кто за кем стоит, но как показывает практика медосмотров даже пятисвязные хешированные списки ничего не гарантируют)

оптимизирующий компилятор может сам решить, когда оформить функцию в виде отдельного блока кода c call/ret вызовом, а когда памяти хватает, межмодульных вызовов нет, и можно развернуть код функции в каждом месте использования, и прогнать по результату всю батарею оптимизаторов, в сишных программах добавляйте inline подсказывая компилятору что этот код можно подставлять а не вызывать, в особо клинических случаях вместо используйте вместо функций макросы (и шаблоны ?)

Принципы функционального программирования в JavaScript

Автор материала, перевод которого мы публикуем сегодня, говорит, что он, после того, как долго занимался объектно-ориентированным программированием, задумался о сложности систем. По словам Джона Оустерхаута, сложность (complexity) — это всё, что делает тяжелее понимание или модификацию программного обеспечения. Автор этой статьи, выполнив некоторые изыскания, обнаружил концепции функционального программирования наподобие иммутабельности и чистых функций. Применение таких концепций позволяет создавать функции, не имеющие побочных эффектов. Использование этих функций упрощает поддержку систем и даёт программисту некоторые другие преимущества.

Здесь мы поговорим о функциональном программировании и о некоторых его важных принципах. Всё это будет проиллюстрировано множеством примеров кода на JavaScript.

Что такое функциональное программирование?

О том, что такое функциональное программирование, можно почитать в Википедии. А именно, речь там идёт о том, что функциональное программирование — это парадигма программирования, в которой процесс вычисления трактуется как вычисление значений функций в математическом понимании последних. Функциональное программирование предполагает обходиться вычислением результатов функций от исходных данных и результатов других функций, и не предполагает явного хранения состояния программы. Соответственно, не предполагает оно и изменяемости этого состояния.

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

Чистые функции

Чистые функции — это первая фундаментальная концепция, которую нужно изучить для того, чтобы понять сущность функционального программирования.

Что такое «чистая функция»? Что делает функцию «чистой»? Чистая функция должна отвечать следующим требованиям:

  • Она всегда возвращает, при передаче ей одних и тех же аргументов, один и тот же результат (такие функции также называют детерминированными).
  • Такая функция не обладает побочными эффектами.

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

▍Аргументы функций и возвращаемые ими значения

Представим себе, что нам надо создать функцию, которая вычисляет площадь круга. Функция, которая не является чистой, принимала бы, в качестве параметра, радиус круга ( radius ), после чего возвращала бы значение вычисления выражения radius * radius * PI :

Почему эту функцию нельзя назвать чистой? Дело в том, что она использует глобальную константу, которая не передаётся ей в качестве аргумента.

Теперь представьте себе, что некие математики пришли к выводу о том, что значением константы PI должно являться число 42 , из-за чего было изменено значение этой константы.

Теперь функция, не являющаяся чистой, при передаче ей того же входного значения, числа 10 , вернёт значение 10 * 10 * 42 = 4200 . Получается, что использование здесь такого же, как в прошлом примере, значения параметра radius , приводит к возврату функцией другого результата. Исправим это:


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

  • Если функции передают аргумент radius , равный 10 , и аргумент pi , равный 3.14 , она всегда будет возвращать один и тот же результат — 314 .
  • При вызове её с аргументом radius , равным 10 и аргументом pi , равным 42 , она всегда будет возвращать 4200 .

Чтение файлов

Если наша функция выполняет чтение файлов, то чистой она не будет. Дело в том, что содержимое файлов может меняться.

Генерирование случайных чисел

Любая функция, которая полагается на генератор случайных чисел, не может быть чистой.

Теперь поговорим о побочных эффектах.

▍Побочные эффекты

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

Предположим, нам нужно создать функцию, которая принимает целое число и выполняет увеличение этого числа на 1. Вот как может выглядеть реализация подобной идеи:

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

Глобальная переменная меняется, подобное в функциональном программировании не приветствуется.

В нашем случае модификации подвергается значение глобальной переменной. Как в этих условиях сделать функцию increaseCounter() чистой? На самом деле, это очень просто:

Как видите, функция возвращает 2 , но при этом значение глобальной переменной counter не меняется. Тут можно сделать вывод о том, что функция возвращает переданное ей значение, увеличенное на 1 , при этом ничего не изменяя.

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

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

▍Сильные стороны чистых функций

Среди сильных сторон чистых функций можно отметить тот факт, что код, написанный с их использованием, легче тестировать. В частности, не нужно создавать неких объектов-заглушек. Это позволяет выполнять модульное тестирование чистых функций в различных контекстах:

  • Если функции передаётся параметр A — ожидается возврат значения B.
  • Если функции передаётся параметр C — ожидается возврат значения D.

В качестве простого примера этой идеи можно привести функцию, которая принимает массив чисел, и при этом ожидается, что она увеличит на единицу каждое число этого массива, вернув новый массив с результатами:

Здесь мы передаём функции массив чисел, после чего используем метод массивов map() , который позволяет модифицировать каждый элемент массива и формирует новый массив, возвращаемый функцией. Вызовем функцию, передав ей массив list :

От этой функции ожидается, что, приняв массив вида [1, 2, 3, 4, 5] , она возвратит новый массив [2, 3, 4, 5, 6] . Именно так она и работает.

Иммутабельность

Иммутабельность некоей сущности можно описать как то, что с течением времени она не меняется, или как невозможность изменений этой сущности.

Если иммутабельный объект пытаются изменить — сделать этого не удастся. Вместо этого нужно будет создать новый объект, содержащий новые значения.

Например, в JavaScript часто используется цикл for . В ходе его работы, как показано ниже, применяются мутабельные переменные:

На каждой итерации цикла меняется значение переменной i и значение глобальной переменной (её можно считать состоянием программы) sumOfValues . Как в подобной ситуации поддерживать неизменность сущностей? Ответ лежит в использовании рекурсии.

Тут имеется функция sum() , которая принимает массив чисел. Эта функция вызывает сама себя до тех пор, пока массив не опустеет (это базовый случай нашего рекурсивного алгоритма). На каждой такой «итерации» мы добавляем значение одного из элементов массива к параметру функции accumulator , не затрагивая при этом глобальной переменной accumulator . При этом глобальные переменные list и accumulator остаются неизменными, до и после вызова функции в них хранятся одни и те же значения.

Надо отметить, что для реализации подобного алгоритма можно воспользоваться методом массивов reduce . Об этом мы поговорим ниже.

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

Если решить эту задачу, используя Ruby и задействовав принципы ООП, то мы сначала создадим класс, скажем, назвав его UrlSlugify , после чего создадим метод этого класса slugify! , который используется для преобразования строки.

Алгоритм мы реализовали, и это замечательно. Тут мы видим императивный подход к программированию, когда мы, обрабатывая строку, расписываем каждый шаг её трансформации. А именно — сначала приводим её символы к нижнему регистру, потом убираем ненужные пробелы, и, наконец, меняем оставшиеся пробелы на тире.

Однако в ходе такого преобразования происходит мутация состояния программы.

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

Здесь мы используем следующие функции, представленные в JavaScript стандартными методами строк и массивов:

  • toLowerCase : преобразует символы строки к нижнему регистру.
  • trim : убирает пробельные символы из начала и конца строки.
  • split : разбивает строку на части, помещая слова, разделённые пробелами, в массив.
  • join : формирует на основе массива со словами строку, слова в которой разделены тире.

Эти четыре функции и позволяют создать функцию для преобразования строки, не меняющую саму эту строку.

Ссылочная прозрачность

Создадим функцию square() , возвращающую результат умножения числа на это же число:

Это чистая функция, которая всегда, для одного и того же входного значения, будет возвращать одно и то же выходное значение.

Например, сколько бы ей ни передавали число 2 , эта функция всегда будет возвращать число 4 . В результате оказывается, что вызов вида square(2) можно заменить числом 4 . Это означает, что наша функция обладает свойством ссылочной прозрачности.

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

▍Чистые функции + иммутабельные данные = ссылочная прозрачность

Задействовав идею, вынесенную в заголовок этого раздела, можно мемоизировать функции. Предположим, у нас имеется такая функция:

Мы вызываем её так:

Вызов sum(5, 8) всегда даёт 13 . Поэтому вышеприведённый вызов можно переписать так:

Это выражение, в свою очередь, всегда даёт 16 . Как результат, его можно заменить числовой константой и мемоизировать его.

Функции как объекты первого класса

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

  • Ссылки на функции можно хранить в константах и переменных и через них обращаться к функциям.
  • Функции можно передавать другим функциям в качестве параметров.
  • Функции можно возвращать из других функций.

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

Представьте, что у нас имеется функция, которая складывает переданные ей два числовых значения, после чего умножает их на 2 и возвращает то, что у неё получилось:

Теперь напишем функцию, которая вычитает из первого переданного ей числового значения второе, умножает то, что получилось, на 2 , и возвращает вычисленное значение:

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

Как видите, теперь у функции doubleOperator() имеется параметр f , а функция, которую он представляет, используется для обработки параметров a и b . Функции sum() и substraction() , передаваемые функции doubleOperator() , фактически, позволяют управлять поведением функции doubleOperator() , меняя его в соответствии с реализованной в них логикой.

Функции высшего порядка

Говоря о функциях высшего порядка мы имеем в виду функции, которые характеризуются хотя бы одной из следующих особенностей:

  • Функция принимает другую функцию в качестве аргумента (таких функций может быть и несколько).
  • Функция возвращает другую функцию в качестве результата своей работы.



Возможно, вы уже знакомы со стандартными методами JS-массивов filter() , map() и reduce() . Поговорим о них.

Илон Маск рекомендует:  Что такое код socket_recvmsg

▍Фильтрация массивов и метод filter()

Предположим, у нас есть некая коллекция элементов, которую мы хотим отфильтровать по какому-то атрибуту элементов этой коллекции и сформировать новую коллекцию. Функция filter() ожидает получить какой-то критерий оценки элементов, на основе которого она и определяет, нужно или не нужно включать некий элемент в результирующую коллекцию. Этот критерий задаёт передаваемая ей функция, которая возвращает true в том случае, если функция filter() должна включить элемент в итоговую коллекцию, а в противном случае возвращает false .

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

Императивный подход

При применении императивного подхода к решению этой задачи средствами JavaScript нам нужно реализовать следующую последовательность действий:

  • Создать пустой массив для новых элементов (назовём его evenNumbers ).
  • Перебрать исходный массив целых чисел (назовём его numbers ).
  • Поместить чётные числа, обнаруженные в массиве numbers , в массив evenNumbers .

Вот как выглядит реализация этого алгоритма:

Кроме того, мы можем написать функцию (назовём её even() ), которая, если число является чётным, возвращает true , а если нечётным — false , после чего передать её методу массива filter() , который, проверив с её помощью каждый элемент массива, сформирует новый массив, содержащий лишь чётные числа:

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

Императивное решение этой задачи на JavaScript может выглядеть так:

Суть императивного подхода заключается в том, что мы расписываем последовательность действий, выполняемых функцией. А именно — мы описываем перебор массива, сравнение текущего элемента массива с x и помещение этого элемента в массив resultArray в том случае, если он проходит проверку.

Декларативный подход

Как перейти к декларативному подходу решения этой задачи и соответствующему использованию метода filter() , являющегося функцией высшего порядка? Например, это может выглядеть так:

Возможно, вам в этом примере необычным покажется использование ключевого слова this в функции smaller() , но ничего сложного тут нет. Ключевое слово this представляет собой второй аргумент метода filter() . В нашем примере это — число 3 , представленное параметром x функции filterArray() . На это число и указывает this .

Такой же подход можно использовать и в том случае, если в массиве хранятся сущности, обладающие достаточно сложной структурой, например — объекты. Предположим, у нас имеется массив, хранящий объекты, содержащие имена людей, представленные свойством name , и сведения о возрасте этих людей, представленные свойством age . Вот как выглядит такой массив:

Мы хотим этот массив отфильтровать, выбрав из него только те объекты, которые представляют собой людей, чей возраст превысил 21 год. Вот как можно решить эту задачу:

Здесь у нас имеется массив с объектами, представляющими людей. Мы проверяем элементы этого массива с помощью функции olderThan21() . В данном случае мы, при проверке, обращаемся к свойству age каждого элемента, проверяя, превышает ли значение этого свойства 21 . Данную функцию мы передаём методу filter() , который и фильтрует массив.

▍Обработка элементов массивов и метод map()

Метод map() используется для преобразования элементов массивов. Он применяет к каждому элементу массива переданную ему функцию, после чего строит новый массив, состоящий из изменённых элементов.

Продолжим эксперименты с уже знакомым вам массивом people . Теперь мы не собираемся фильтровать этот массив, основываясь на свойстве объектов age . Нам нужно сформировать на его основе список строк вида TK is 26 years old . Строки, в которые превращаются элементы, при таком подходе будут строиться по шаблону p.name is p.age years old , где p.name и p.age — это значения соответствующих свойств элементов массива people .

Императивный подход к решению этой задачи на JavaScript выглядит так:

Если прибегнуть к декларативному подходу, то получится следующее:

Собственно говоря, основная мысль тут заключается в том, что с каждым элементом исходного массива нужно что-то сделать, после чего — поместить его в новый массив.

Вот ещё одна задача с Hacker Rank, которая посвящена обновлению списка. А именно, речь идёт о том, чтобы поменять значения элементов существующего числового массива на их абсолютные значения. Так, например, при обработке массива [1, 2, 3, -4, 5] он приобретёт вид [1, 2, 3, 4, 5] так как абсолютное значение -4 равняется 4 .

Вот пример простого решения этой задачи, когда мы перебираем массив и меняем значения его элементов на их абсолютные значения.

Тут для преобразования значений элементов массива использован метод Math.abs() , изменённые элементы записываются туда же, где они были до преобразования.

Это решение не является примером функционального подхода к программированию.

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

Второй вопрос, который стоит себе задать в этой ситуации, касается метода массивов map() . Почему бы не воспользоваться им?

Вооружившись этими идеями, я решил поэкспериментировать с методом abs() , взглянуть на то, как он обрабатывает разные числа.

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

После того, как мы поняли принцип преобразования числа к его абсолютному значению, мы можем использовать Math.abs() в качестве аргумента метода массива map() . Помните о том, что функции высшего порядка могут принимать другие функции и использовать их? Метод map() является именно такой функцией. Вот как решение нашей задачи будет выглядеть теперь:

Уверен, никто не поспорит с тем, что оно, в сравнении с предыдущим вариантом, получилось куда более простым, предсказуемым и понятным.

▍Преобразование массивов и метод reduce()

В основу метода reduce() положена идея преобразования массива к единственному значению путём комбинации его элементов с использованием некоей функции.

Распространённым примером применения этого метода является нахождение общей суммы по некоему заказу. Представьте, что речь идёт об интернет-магазине. Покупатель добавляет в корзину товары Product 1 , Product 2 , Product 3 и Product 4 . После этого нам надо найти общую стоимость этих товаров.

Если воспользоваться для решения этой задачи императивным подходом, то нужно перебрать список товаров из корзины и сложить их стоимости. Например, это может выглядеть так:

Если воспользоваться для решения этой задачи методом массивов reduce() , то мы можем создать функцию ( sumAmount() ), используемую для вычисления суммы элементов массива, после чего передать её методу reduce() :

Тут имеется массив shoppingCart , представляющий собой корзину покупателя, функция sumAmount() , которая принимает элементы массива (объекты order , при этом нас интересуют их свойства amount ), и текущее вычисленное значение суммы их стоимостей — currentTotalAmount .

При вызове метода reduce() , выполняемого в функции getTotalAmount() , ему передаётся функция sumAmount() и начальное значение счётчика, которое равняется 0 .

Ещё один способ решения нашей задачи заключается в комбинации методов map() и reduce() . Что имеется в виду под их «комбинацией»? Дело тут в том, что мы можем использовать метод map() для преобразования массива shoppingCart в массив, содержащий лишь значения свойств amount хранящихся в этом массиве объектов, а затем воспользоваться методом reduce() и функцией sumAmount() . Вот как это выглядит:

Функция getAmount() принимает объект и возвращает только его свойство amount . После обработки массива с использованием метода map() , которому передана эта функция, получается новый массив, который выглядит как [10, 30, 20, 60] . Затем, с помощью reduce() , мы находим сумму элементов этого массива.

▍Совместное использование методов filter(), map() и reduce()

Выше мы поговорили о том, как работают функции высшего порядка, рассмотрели методы массивов filter() , map() и reduce() . Теперь, на простом примере, рассмотрим использование всех трёх этих функций.

Продолжим пример с интернет-магазином. Предположим, корзина покупателя сейчас выглядит так:

Нам нужно узнать стоимость книг в заказе. Вот какой алгоритм можно предложить для решения этой задачи:

  • Отфильтровать массив по значению свойства type его элементов, учитывая то, что нас интересует значение этого свойства books .
  • Преобразовать полученный массив в новый, содержащий лишь стоимости товаров.
  • Сложить все стоимости товаров и получить итоговое значение.

Вот как выглядит код, реализующий этот алгоритм:

Итоги

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

Уважаемые читатели! Пользуетесь ли вы методами функционального программирования в своих проектах?

Электроника для всех

Блог о электронике

1.5.1. Определение и вызов функций

Мощность языка СИ во многом определяется легкостью и гибкостью в определении и использовании функций в СИ-программах. В отличие от других языков программирования высокого уровня в языке СИ нет деления на процедуры, подпрограммы и функции, здесь вся программа строится только из функций.

Функция — это совокупность объявлений и операторов, обычно предназначенная для решения определенной задачи. Каждая функция должна иметь имя, которое используется для ее объявления, определения и вызова. В любой программе на СИ должна быть функция с именем main (главная функция), именно с этой функции, в каком бы месте программы она не находилась, начинается выполнение программы.

При вызове функции ей при помощи аргументов (формальных параметров) могут быть переданы некоторые значения (фактические параметры), используемые во время выполнения функции. Функция может возвращать некоторое (одно !) значение. Это возвращаемое значение и есть результат выполнения функции, который при выполнении программы подставляется в точку вызова функции, где бы этот вызов ни встретился. Допускается также использовать функции не имеющие аргументов и функции не возвращающие никаких значений. Действие таких функций может состоять, например, в изменении значений некоторых переменных, выводе на печать некоторых текстов и т.п..

С использованием функций в языке СИ связаны три понятия — определение функции (описание действий, выполняемых функцией), объявление функции (задание формы обращения к функции) и вызов функции.

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


int rus (unsigned char r) < if (r>=’А’ && c

В данной функции значения переменных x и y, являющихся формальными параметрами, меняются местами, но поскольку эти переменные существуют только внутри функции change, значения фактических параметров, используемых при вызове функции, останутся неизменными. Для того чтобы менялись местами значения фактических аргументов можно использовать функцию приведенную вследующем примере.

/* Правильное использование параметров */ vo >

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

Если требуется вызвать функцию до ее определения в рассматриваемом файле, или определение функции находится в другом исходном файле, то вызов функции следует предварять объявлением этой функции. Объявление (прототип) функции имеет следующий формат:

[спецификатор-класса-памяти] [спецификатор-типа] имя-функции ([список-формальных-параметров]) [,список-имен-функций];

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

Прототип — это явное объявление функции, которое предшествует определению функции. Тип возвращаемого значения при объявлении функции должен соответствовать типу возвращаемого значения в определении функции.

Если прототип функции не задан, а встретился вызов функции, то строится неявный прототип из анализа формы вызова функции. Тип возвращаемого значения создаваемого прототипа int, а список типов и числа параметров функции формируется на основании типов и числа фактических параметров используемых при данном вызове.

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

1. Функция возвращает значение типа, отличного от int.

2. Требуется проинициализировать некоторый указатель на функцию до того, как эта функция будет определена.

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

В прототипе можно указать, что число параметров функции переменно, или что функция не имеет параметров.

Если прототип задан с классом памяти static, то и определение функции должно иметь класс памяти static. Если спецификатор класса памяти не указан, то подразумевается класс памяти extern.

Вызов функции имеет следующий формат:

Поскольку синтаксически имя функции является адресом начала тела функции, в качестве обращения к функции может быть использовано адресное-выражение (в том числе и имя функции или разадресация указателя на функцию), имеющее значение адреса функции.

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

Фактический параметр может быть величиной любого основного типа, структурой, объединением, перечислением или указателем на объект любого типа. Массив и функция не могут быть использованы в качестве фактических параметров, но можно использовать указатели на эти объекты.

Выполнение вызова функции происходит следующим образом:

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

2. Происходит присваивание значений фактических параметров соответствующим формальным параметрам.

3. Управление передается на первый оператор функции.

4. Выполнение оператора return в теле функции возвращает управление и возможно, значение в вызывающую функцию. При отсутствии оператора return управление возвращается после выполнения последнего оператора тела функции, а возвращаемое значение не определено.

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

int (*fun)(int x, int *y);

Здесь объявлена переменная fun как указатель на функцию с двумя параметрами: типа int и указателем на int. Сама функция должна возвращать значение типа int. Круглые скобки, содержащие имя указателя fun и признак указателя *, обязательны, иначе запись

int *fun (intx,int *y);

будет интерпретироваться как объявление функции fun возвращающей указатель на int.

Вызов функции возможен только после инициализации значения указателя fun и имеет вид:

В этом выражении для получения адреса функции, на которую ссылается указатель fun используется операция разадресации * .

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

double (*fun1)(int x, int y); double fun2(int k, int l); fun1=fun2; /* инициализация указателя на функцию */ (*fun1)(2,7); /* обращение к функции */

В рассмотренном примере указатель на функцию fun1 описан как указатель на функцию с двумя параметрами, возвращающую значение типа double, и также описана функция fun2. В противном случае, т.е. когда указателю на функцию присваивается функция описанная иначе чем указатель, произойдет ошибка.

Рассмотрим пример использования указателя на функцию в качестве параметра функции вычисляющей производную от функции cos(x).

Для вычисления производной от какой-либо другой функции можно изменить тело функции fun или использовать при вызове функции proiz имя другой функции. В частности, для вычисления производной от функции cos(x) можно вызвать функцию proiz в форме

а для вычисления производной от функции sin(x) в форме

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

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

Классический пример рекурсии — это математическое определение факториала n! :

n! = 1 при n=0; n*(n-1)! при n>1 .

Функция, вычисляющая факториал, будет иметь следующий вид:

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

2 thoughts on “1.5.1. Определение и вызов функций”

два примера кода (второй и третий куски), сожрали три абзаца текста.

Все примеры в «HTML-мусоре» — > & и т.п.
Это и так нехорошо, но там где речь идет про передачу указателя это вообще вызывает полное недоумение…

Добавить комментарий Отменить ответ

Этот сайт использует Akismet для борьбы со спамом. Узнайте как обрабатываются ваши данные комментариев.

Замерить время работы функции на С++

В этой теме 2 ответа, 2 участника, последнее обновление 3 года/лет, 3 мес. назад.

Мне нужно замерить время выполнения фрагмента кода (можно функции) на С++.

Я прочитал, что для этого используется clock() из модуля time.h (ctime) — она возвращает число таков, измеряемое процессором от начала выполнения программы.
Глобальная константа CLOCKS_PER_SEC хранит число тактов, выполняемое процессором в секунду. Соответственно, чтобы получить время работы программы в секундах достаточно результат работы функции разделить на эту константу:
clock() / CLOCKS_PER_SEC;

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

В данном случае я надеюсь получить время, которое пользователь тратит на нажатие клавиши, однако вне зависимости от того, как долго я жду — результат получается примерно одинаковый, а время очень маленьким (см. скриншот). Хотя, если вместо getchar я ставлю фрагмент кода, выполняющий какие-либо вычисления — выводится правдоподобный результат.

Подскажите в чем проблема и как ее решить.

Функция clock() возвращает количество тиков процессора, которое сделала именно ваша программа, т.е. если программа ожидает ввод данных пользователем, то она не работает (операционная система вытесняет процесс из очереди задач). Следовательно нельзя замерить время ожидания ввода при помощи функции clock() — хотя подход, который вы привели, идеально подходит если вы хотите сравнить два алгоритма, т.к. в этом случае меньшее влияние оказывает загруженность системы.

Определить количество секунд, которое выполняется программа можно с помощью функции time() :

Время при этом сохраняет с типом данных time_t — это целое число секунд, прошедшее с 1 января 1970 года. Функция difftime вычисляет разницу двух моментов времени. С помощью такого подхода вы сможете замерить время работы части программы, однако результат будет в секундах.

При помощи средств, появившихся в стандартной библиотеке С++11 можно получить более высокую точность измерения и замерить время независимо от системных часов (с помощью так называемых стабильных часов). Обзор библиотеки chrono.

Следующий фрагмент кода выполняет замер времени с использованием стабильных часов:

Функция std::chrono::duration_cast преобразует объект типа time_point во временной интервал ( duration ), при этом в качестве параметра шаблона передается промежуток времени в виде долей секунды (в данном случае миллисекунды).

Использование библиотеки chrono — лучший способ если нужно фактическое замерить время выполнения программы (в отличии функции time() , модуля time.h она позволяет получить время в миллисекундах и даже наносекундах). Однако если программа работает на многопроцессорной системе и часто ожидает какие-либо данные (не только пользовательский ввод, но и данных от других потоков/процессов) — то больший интерес может представлять реальное время выполнения, возвращаемое функцией clock() модуля time.h . Реальное время лучше отражает потребляемый ресурс процессора, т.к. если текущий процесс простаивает на кластере (где выполняются сотни других приложений), то он вытесняется операционной системой и практически не загружает процессор.

Понравилась статья? Поделиться с друзьями:
Кодинг, CSS и SQL