1.11. Функции

Если в таких языках, как FORTRAN и PASCAL, делается различие между подпрограммами-процедурами и подпрограммами-функциями, то в языках С и С++ используются только функции. Программы на языке С (C++) - это совокуп­ность следующих одна за другой функций, одна из которых main (главная функ­ция). Функция main обеспечивает создание точки входа в откомпилированную программу. Каждая функция имеет статическую продолжительность существо­вания и внешний тип компоновки:  в С запрещено вложение одной функции в

другую.

Функция в языке С состоит из заголовка функции и тела функции. Тело функции - это блок (последовательность операторов в {}), а заголовок в общем случае имеет вид:

тип функции имя функции ( тип параметра имя параметра , [ [ тип параметра имя параметра , ... J ) Заголовок функции не является оператором, поэтому он не заканчивается ";". Функция может иметь скалярный тип (число, указатель) или тип void (не воз­вращает никакого значения). Все программные единицы, не вычисляющие како­го-либо одного скалярного значения, являются функциями типа void. Каждый параметр в заголовке функции описывается отдельно, даже если параметры имеют один и тот же тип. Допускаются функции без параметров, в этом случае список параметров имеет вид () или (void) .

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

return [ выражение J ; Если выражение задано в операторе return, то его значение преобразуется к типу функции, в противном случае значение, возвращаемое функцией, остается неоп­ределенным. Функция может содержать любое количество операторов return. Присваивать имени функции какое-либо значение, как в языках PASCAL и

FORTRAN, не нужно.

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

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

int max_el(int x[],int n) {int i,m;

for(m=x[0],i=0;i<n;i++) m=(x[i]>m)?x[i]:m; return m;} Такая функция может обрабатывать массивы любой длины, но при каждом вызо­ве следует определять фактическую длину с помощью аргумента n. Описание параметра int x[] практически эквивалентно описанию int* x, так что в функции можно отказаться от использования индексов, которые введены лишь для удоб­ства записи :

int max_el(int* x,int n) {int i,m;

for(m=*x,i=1;i<n;i++) m=(*(x+i)>m)?*(x+i):m; return m;}

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

#define SIZE 3

void main(void) { int b[][SIZE]={1,2,3,4,5,6,7,8,9};

Тогда соответствующую функцию можно записать в трех вариантах :

1)

int max_el(int x[][SIZE],int n) {int i,j,m;

for(m=x[0][0],i=0;i<n;i++)for(j=0;j<n;j++) m=(x[i][j]>m)?x[i][j]:m; return m;}

2)

int max_el(int x[][SIZE],int n) {int i,j,m; for(m=*x[0],i=0;i<n;i++)

for(j=0;j<n;j++) m=(*(x[i]+j)>m)?*(x[i]+j):m; return m;}

З)

int max_el(int*x,int size,int n) {int i,j,m; for(m=*x,i=0;i<n;i++)

for(j=0;j<n;j++) m=(*(x+i*size+j)>m)?*(x+i*size+j):m; return m;}

Вызов функции будет соответственно иметь вид:

1) max_el(b,SIZE)

2) max_el(b,SIZE)

3) max_el((int*)b,SIZE,SIZE)

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

В вызывающей функции (или вне функций) функцию можно описать как тип имя ( список параметров ) ;

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

int max_el(int*a,int b,int c); int max_el(int*,int,int);

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

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

В С++ тип функции столь же важен, как и ее имя. Две функции могут иметь одинаковое имя, если имеется возможность различить их по сигнатуре. Сигнату­ра функции задается числом, порядком следования и типами ее параметров. Оп­ределение двух или более функций с одним и тем же именем называется пере­грузкой функций, поскольку более поздние объявления перегружают, или пере­крывают, более ранние. Рассмотрим следующую программу: #include <stdio.h> #include <string.h> int func (int first){ return first*first;} int func (unsigned first){ return -first*first;} char func (char first){ return first+3;} float func(float r){ return r*r;}

void main() { printf("%d ",func(4)); printf("%d ",func((unsigned)4)); printf("%c M,func('a')); printf(M%f\nM,func((float)1.2));}

Эта программа, в которой использованы четыре разные функции с именем func, является совершенно корректной и выведет на экран строку 1б -1б d 1.44

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

int (*p)(int*,int,int)=&max_el; printf("%d",(*p)((int*)b,SIZE,SIZE));

Операции адрес & и значение * можно не записывать явно, они автоматически генерируются компилятором, т.е. приведенный выше фрагмент можно перепи­сать в виде:

int (*p)(int*,int,int)=max_e1; printf(M%dM,p((int*)b,SIZE,SIZE));

Используя указатели на функции, можно решать два типа задач:

1) присвоение указателю функции и использование указателя в качестве кос­венной ссылки на функцию;

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