Что из себя представляют функции

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

Что из себя представляют функции - student2.ru

вызывает функцию F с аргументом X, умножает возвращенное ею значение на 5 и присваивает результат переменной I.

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

Что из себя представляют функции - student2.ru

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

тип_возвращаемого_значения имя_функции(список_параметров)

{

операторы тела функции

}

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

Если тип возвращаемого значения не указан, он по умолчанию считается рав­ным int.

Хотя тип возвращаемого значения int можно не указывать в заголовке функции, не следует использовать эту возможность. Всегда указывайте тип возвращаемого значения, кроме глав­ной функции main. Указание типа делает программу более наглядной и предотвращает воз­можные ошибки, связанные с неправильным преобразованием типов.

Список параметров, заключаемый в скобки, в простейшем случае представляет собой разделяемый запятыми список вида:

тип_параметра идентификатор_параметра

Например, заголовок:

double FSum(double x1, double x2, int a);

объявляет функцию с именем FSum, с тремя параметрами X1, Х2 и А, из которых первые два имеют тип double, а последний — int. Тип возвращаемого результа­та — double. Имена параметров X1, Х2 и А — локальные, т.е. они имеют значение только внутри данной функции и никак не связаны с именами аргументов, пере­данных при вызове функции. Значения этих параметров в начале выполнения функции равны значениям аргументов на момент вызова функции.

Ниже приведен заголовок функции, не возвращающей никакого значения:

void SPrint(AnsiString S);

Она принимает один параметр типа строки и, например, отображает его в ка­ком-нибудь окне приложения.

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

void F1(void);

или

void F1();

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

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

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

Примеры прототипов приведенных выше заголовков функций:

double FSum(double x1, double x2, int a);

void SPrint(AnsiString S);

void F1(void);

или

double FSum(double, double, int);

void SPrint(AnsiString);

void F1();

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

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

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

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

int prf(char *format, …);

Функция с подобным прототипом принимает один параметр format типа char *(например, строку форматирования) и произвольное число параметров произ­вольного типа. Функция с прототипом:

void Fp(...);

может принимать произвольное число параметров произвольного типа.

Если в прототипе встречается многоточие, то типы соответствующих парамет­ров и их количество компилятором не проверяются.

Объявлению функции могут предшествовать спецификаторы класса памяти extern или static. Спецификатор extern предполагается по умолчанию, так что за­писывать его не имеет смысла. К функциям, объявленным как extern, можно поле­чить доступ из других модулей программы. Если же объявить функцию со спецификатором static, например:

static void F(void);

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

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

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

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

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

double FSum(double x1, double x2, int a)

{

return a *(x1 + x2);

}

Ниже приведен пример функции, не возвращающей никакого значения:

void SPrint(AnsiString S)

{

if (S != "")

ShowMessage (S);

}

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

void SPrint(AnsiString S)

{

if (S == "") return;

ShowMessage(S);

}

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

Возвращаемое функцией значение может включать в себя вызов каких-то функций. В том числе функция может вызывать и саму себя, т.е. допускается ре­курсия. В качестве примера приведем функцию, рекурсивно вычисляющую факто­риал. Как известно, значение факториала равно n! = n (n-1) (n-2) ... 1, при­чем считается, что 1! = 1 и 0! = 1. Факториал можно вычислить с помощью просто­го цикла for (и это, конечно, проще). Но можно факториал вычислять и с помощью рекуррентного соотношения n!=n(n—1)!. Для иллюстрации рекурсии воспользу­емся именно этим соотношением. Тогда функция factorial вычисления факториа­ла может быть описана следующим образом:

unsigned long factorial(unsigned long n)

{

if {n <= 1)

return 1;

else

return n*factorial(n - 1);

}

Если значение параметра n равно 0 или 1, то функция возвращает значение 1. В противном случае функция умножает текущее значение n на результат, возвра­щаемый вызовом той же функции factorial, но со значением параметра n, умень­шенным на единицу. Поскольку при каждом вызове значение параметра уменьша­ется, рано или поздно оно станет равно 1. После этого цепочка рекурсивных вызовов начнет свертываться и в конце концов вернет значение факториала.


Наши рекомендации