Виды функций в языках программирования. Функции в программировании

Так, что же такое "программная функция" ?

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

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

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

Программная функция - это:

- "функция осознанных преимуществ",

- "функция врожденного профессионализма",

- "функция основных ценностей, основных задач и целей".

И именно поэтому человеку крайне трудно отступать от своей "программы", не говоря уже о том, чтобы поступать ей наперекор (чего от нас нередко требуют условия психологической несовместимости).

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

Программная функция определяет предел допустимых уступок.

- Ну хорошо, - вставляет Читатель, - но если есть в нашей психике "функция целей и задач" , то должна же быть и какая-то функция их достижений?

Такая функция действительно есть, и с "программной" она тесно сотрудничает, а потому в "Модели "А" находится справа от нее, на 2-й позиции уровня ЭГО (т. е. занимает второй кабинет верхнего этажа). Эта функция называется "творческой" , или "созидательной функцией" .

Творческая функция и творческий аспект.

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

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

А теперь вспомним нашу первую классификацию - "сенсорик - этический ","интуит - логический "и т.д. Что мы видим?

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

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

- А можно это показать на каком-нибудь примере? - интересуется Читатель.

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

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

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

- Но почему так происходит?

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

"Элементарная модель" типа ИМ.

Аспекты, вынесенные на уровень ЭГО ,выражают основные ценности типа .Именно поэтому они вынесены в его название.

Например, название типа "этико-сенсорный экстраверт" означает, что его "программная" ценность - экстравертная этика ("этика эмоций"), а "творческая" ценность - интровертная сенсорика ("сенсорика ощущений") .

На уровне ЭГО это выглядит так:

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

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

И еще следует отметить: если программный аспект - рациональный (этика или логика), то он реализуется иррациональным аспектом .

- Почему?

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

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

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

Таким образом, уже по названию "типа ИМ" можно определить структурное соотношение уровня ЭГО и создать элементарную, двухпозиционную модель, отображающую основные характеристики данной психической структуры.

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

Начнем с оператора цикла с предусловием . Данный оператор имеет вид:

While <условие> do <оператор>.

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

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

Цикл с постусловием имеет вид:

Repeat…<выражние_1>until…<выражение_2>

Здесь вначале выполняется оператор statement(утверждение), и только затем вычисляется значение логического выражения. Именно поэтому такой цикл называют циклом с постусловием. Процесс повторяется, пока выражение имеет значение «ложь». Как только его значение становится истинным, выполнение цикла прекращается. Оператор может быть любым, в том числе и составным оператором:

Оператор_1;

Оператор_2;

…………….

Оператор_N;

Until<условие>

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

Операторы цикла со счетчиком: for...to...do и for…downto …do.

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

For параметр цикла:= начальное значение to конечное значение do <оператор>;

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

После выполнения цикла for значение управляющей переменной становится неопределенным.

Вариант for…downto...do... цикла for аналогичен циклу for..to…do за исключением того, что в нем управляющая переменная на каждом шаге выполнения не увеличивается, а уменьшается на единицу:

for j:- <выражение_1> downto <выражение_2> do <оператор>.

Подводя итоги, для применения циклов можно сформулировать следующие рекомендации:

Используйте цикл for в том случае, когда точно знаете, сколько раз должно быть выполнено тело цикла. В противном случае обратитесь к циклам repeat или while.

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

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

Такой выход из цикла обеспечивается процедурой Break, которая прерывает выполнение самого внутреннего вложенного цикла, будь то for, while или repeat. Указанный модуль подключается к программе автоматически, если в этом есть необходимость.

While true do begin

<Выражение_1>

If <оператор> then Break

<Выражение_2>; end

Следует также упомянуть процедуру Continue, которая прерывает выполнение тела самого внутреннего цикла for, while или repeat и передает управление на его заголовок, так что начинается выполнение очередной итерации цикла.

Шаг цикла for всегда постоянный и равен интервалу между двумя ближайшими значениями типа параметра цикла.

Var {описание параметров цикла}

i: integer(целочисленный тип);

c: char(символьный тип);

1. Begin {вывод на печать целых чисел от 1 до 10}

For i:=1 to 10 do writeln (i);{шаг цикла равен 1}

2. {вывод на печать чисел от 10 до -10}

For 10 down to -10 {шаг цикла равен -1}

3. {вывод на печать латинских символов от A до R}

{параметр цикла изменяется от A до R в алфавитном порядке}

For c:=‘A’ to ‘R’ do writeln (c);

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

Пример: Вычисление суммы от 1 до n.

var s,n,i: integer;

writeln (‘Введите n’);

for i:=1 to n do s:=s+i;

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

Положительные моменты:

Функцию в любой момент времени и в любом месте программы можно вызвать на выполнение, причём неоднократно;

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

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

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

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

Функции в языке Си можно условно разделить на три вида: системные, внутренние и внешние.

Системные функции прилагаются к любому компилятору. В WinAVR их можно найти в библиотечном справочнике, находящемся по адресу C:\WinAVR-2010010\ doc\avr-libc\avr-libc-user-manual.pdf (далее - «Библиотечное руководство»).

Каждая системная функция входит строго в свою библиотеку. Названия библиотек и функций в разных компиляторах отличаются. Объявление используемых библиотек производится в «шапке» программы директивой препроцессора «#inC1ude <имя заголовочного файла-хеддера>».

Символ «#» пишется слитно со словом «inC1ude» и обязательно начинается в первом столбце листинга. «Хеддер» - это файл с расширением «.h», в котором описываются параметры функций. К системным также относится головная функция «main», без которой не обходится ни одна программа в языке Си.

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

Компилятор AVR-GCC, используемый в пакете WinAVR, занимает по сравнению с другими компиляторами золотую середину. В его составе имеются примерно в равной части функции общепринятых стандартов ANSI, C99, а также собственные микроконтроллерные библиотеки.

Досконально изучив системные функции одного компилятора, программист в какой-то мере «привязывается» к ним и не хочет переучиваться на новые. Вот где требуется поистине шахматный расчёт, чтобы с первого раза выбратьдля изучения солидный, мощный и постоянно обновляемый (а значит, «живой»!) компилятор. К слову сказать, AVR-GCC пока оправдывает все ожидания пользователей.

В стандартные библиотеки компиляторов, наряду с функциями, входят также системные макроопределения, например, специфические для пакета WinAVR «_BV», «bit_is_set», «bit_is_C1ear». Подробности их применения освещаются в «Библиотечном руководстве».

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

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

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

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

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

3. Если сделать внешние функции максимально автономными в работе, то их в будущем можно использовать совместно с другими программами. Дальновидные программисты стараются создавать свои «родные» библиотеки, состоящие из специализированных функций. К примеру, хороший набор готовых внешних функций для AVR-контроллеров содержится в бесплатной библиотеке Паскаля Стэнга .

По устоявшейся традиции, все имена функций, как и имена переменных, записывают малыми латинскими буквами. Цифры допускаются во всех позициях имени, кроме первой слева. Названия системных функций изменять нельзя, они определяются стандартами, в отличие от внутренних и внешних функций, имена которым придумывают, кто как хочет. Единственное ограничение, чтобы новые названия отличались от названий системных функций, иначе компилятор выдаёт сообщение об ошибке: «Error previous definition».

За именем функции обязательно следуют круглые скобки, в которых при необходимости указывают передаваемые/принимаемые параметры. В Табл. 6.9 перечислены все допустимые форматы объявления и вызова функций в AVR-GCC.

Таблица 6.9. Форматы объявления и вызова функций в AVR-GCC

Формат объявления функции __ «example()»

Страница 51 из 85

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

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

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

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

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

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

Int rus (unsigned char r)
{ if (r>="А" && c<=" ") return 1; else return 0; }

В данном примере определена функция с именем rus, имеющая один параметр с именем r и типом unsigned char. Функция возвращает целое значение, равное 1, если параметр функции является буквой русского алфавита, или 0 в противном случае.

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

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

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

int rus (unsigned char r); или rus (unsigned char);

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

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

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

Int rus (r)
unsigned char r;
{ ... /* тело функции */ ... }

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

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

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

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

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

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

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

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

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

Для формального параметра можно задавать класс памяти register, при этом для величин типа int спецификатор типа можно опустить.

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

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

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

Тело функции - это составной оператор, содержащий операторы, определяющие действие функции.

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

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

/* Неправильное использование параметров */
void change (int x, int y)
{ int k=x;
x=y;
y=k;
}

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

/* Правильное использование параметров */
void change (int *x, int *y)
{ int k=*x;
*x=*y;
*y=k;
}

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

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

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

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

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

Если прототип функции не задан, а встретился вызов функции, то строится неявный прототип из анализа формы вызова функции. Тип возвращаемого значения создаваемого прототипа 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).

Double proiz(double x, double dx, double (*f)(double x));
double fun(double z);
int main()
{
double x; /* точка вычисления производной */
double dx; /* приращение */
double z; /* значение производной */
scanf("%f,%f",&x,&dx); /* ввод значений x и dx */
z=proiz(x,dx,fun); /* вызов функции */
printf("%f",z); /* печать значения производной */
return 0;
}
double proiz(double x,double dx, double (*f)(double z))
{ /* функция вычисляющая производную */
double xk,xk1,pr;
xk=fun(x);
xk1=fun(x+dx);
pr=(xk1/xk-1e0)*xk/dx;
return pr;
}
double fun(double z)
{ /* функция от которой вычисляется производная */
return (cos(z));
}

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

z=proiz(x,dx,cos);

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

z=proiz(x,dx,sin);

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

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

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

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

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

Long fakt(int n)
{
return ((n==1) ? 1: n*fakt(n-1));
}

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

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

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

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

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

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

i = 0 while i < 3 : a = int (input () ) b = int (input () ) print (a+b) i += 1

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

print () a = int (input () ) b = int (input () ) print ("Всего" , a+b, "шт." ) print () a = int (input () ) b = int (input () ) print ("Всего" , a+b, "шт." )

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

Сколько бананов и ананасов для обезьян? 15 5 Всего 20 шт. Сколько жуков и червей для ежей? 50 12 Всего 62 шт. Сколько рыб и моллюсков для выдр? 16 8 Всего 24 шт.

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

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

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

def countFood() : a = int (input () ) b = int (input () ) print ("Всего" , a+b, "шт." )

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

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

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

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

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

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

def countFood() : a = int (input () ) b = int (input () ) print ("Всего" , a+b, "шт." ) print ("Сколько бананов и ананасов для обезьян?" ) countFood() print ("Сколько жуков и червей для ежей?" ) countFood() print ("Сколько рыб и моллюсков для выдр?" ) countFood()

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

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

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

print ("Сколько бананов и ананасов для обезьян?" ) countFood() print ("Сколько жуков и червей для ежей?" ) countFood() print ("Сколько рыб и моллюсков для выдр?" ) countFood() def countFood() : a = int (input () ) b = int (input () ) print ("Всего" , a+b, "шт." )

Результат:

Сколько бананов и ананасов для обезьян? Traceback (most recent call last ) : File "test.py" , line 2 , in < module> countFood() NameError: name "countFood" is not defined

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

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

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

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

figure = input () if figure == "1" : a = float (input ("Ширина: " ) ) b = float (input ("Высота: " ) ) print ("Площадь: %.2f" % (a*b) ) elif figure == "2" : a = float (input ("Основание: " ) ) h = float (input ("Высота: " ) ) print ("Площадь: %.2f" % (0.5 * a * h) ) elif figure == "3" : r = float (input ("Радиус: " ) ) print ("Площадь: %.2f" % (3.14 * r**2 ) ) else : print ("Ошибка ввода" )

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

def rectangle() : a = float (input ("Ширина: " ) ) b = float (input ("Высота: " ) ) print ("Площадь: %.2f" % (a*b) ) def triangle() : a = float (input ("Основание: " ) ) h = float (input ("Высота: " ) ) print ("Площадь: %.2f" % (0.5 * a * h) ) def circle() : r = float (input ("Радиус: " ) ) print ("Площадь: %.2f" % (3.14 * r**2 ) ) figure = input ("1-прямоугольник, 2-треугольник, 3-круг: " ) if figure == "1" : rectangle() elif figure == "2" : triangle() elif figure == "3" : circle() else : print ("Ошибка ввода" )

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

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

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

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

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

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

Локальные и глобальные переменные



Статьи по теме: