sonyps4.ru

Какие бывают функции в c. Пользовательские функции в си

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

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

тип имя_функции ();

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

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

тип имя_функции (пар_инф1, пар_инф2, …);

где параметр пар_инфi– информация о имени и типе формальных параметров.

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

тип имя_функции (имена параметров)

определение параметров;

локальные описания;

операторы;

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

тип имя_функции (пар_инф, пар_инф, …)

где определение параметра пар_инф – содержит информацию о передаваемом параметре: тип и идентификатор.

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

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

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

Результат выполнения функции возвращается при помощи оператора return. Общий вид:

Return(выражение);

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

Можно использовать оператор returnв виде:

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

{ floaty,x,mult(); /* описание в вызывающей программе */

floatmult(v,k) /* описание в определении функции */

for (res=0.0; k>0; k--)

return(res); } /* возвращает значение типаfloat*/

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

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

[<спецификация КП>][<спецификация типа>]<описатель>([<список параметров>]) [<объявление параметров>] <тело функции>

Спецификация класса памяти <спец. КП> задает класс памяти функции.

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

Современная конструкция:

[<спецификация КП>][<спецификация типа>]<описатель>([<список объявлений параметров>])<тело функции>

Объявление функции: классическая форма:

[<спецификация КП>][<спецификация типа>]<описатель>([<список типов аргументов>]);

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

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

float f1(float a, float b)

c=(2*pow(a,3)+sin a)/pow(a+b,4);

{ float x,y,s=0;

printf (“\n Введите x, y”);

scanf (“%f %f ”, &x, &y);

s=f1(5.6, y)+f1(2*x-1, x*y);

printf(“\n s=%6.2f ”,s);

Адресные операции. Си поддерживает две специальные адресные операции: операцию определения адреса (&) и операцию обращения по адресу (*). Операция & возвращает адрес данной переменной. Еслиsumявляется переменной типаint, то &sumявляется адресом этой переменной.

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

В языке Си имеются и переменные типа указатель. Значением переменной типа указатель служит адрес некоторой величины. Пусть указатель обозначен идентификатором ptr, тогда оператор следующего вида присваивает адресsumпеременнойptr:ptr=&sum. В этом случае говорят, чтоptr«указывает на»sum. Итак,ptr– переменная, &sum– константа. Переменнаяptrможет указывать на какой-нибудь другой объект:

Значением ptrявляется адрес переменнойmax. Рассмотрим операцию обращения по адресу (*) или операцию косвенной адресации. предположим в переменнойptrсоержится ссылка на переменнуюmax. Тогда для доступа к значению этой переменной можно воспользоваться операцией обращения по адресу (*). Для определения значения, на которое указываетptrзапишем следующий оператор:

Res=*ptr; (Последние два оператора, взятые вместе, эквивалентны следующему:Res=max;)

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

Операция (*) – когда за этим знаком следует указатель на переменную, результатом операции является величина, помещенная в ячейку с указанным адресом.

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

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

{ int x=5, y=10;

printf (“x=%d y=%d\n”, x, y);

change(&x, &y); /*передача адресов функции*/

printf (“x=%d y=%d\n”, x, y); }

int*u, *v; /*uиvявляются указателями*/

temp=*u; /*tempприсваивается значение, на которое указываетu*/

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

Основная ли тература: 1осн,2осн

Дополнительная литератур а: 10доп

Контрольные вопросы:

1. Могут ли имена формальных и фактических параметров подпрограмм совпадать между собой?

2. В чем состоит отличие описания процедуры и функции?

3. Какие параметры называются формальными и какие – фактическими?

4. Какие существуют стили описания и определения функций?

5. Назовите оператор для возвращения результата выполнения функции?

Для чего нужны функции в C?

Функции в Си применяются для выполнения определённых действий в рамках общей программы. Программист сам решает какие именно действия вывести в функции. Особенно удобно применять функции для многократно повторяющихся действий.

Простой пример функции в Cи

Пример функции в Cи:

#include #include int main(void) { puts("Functions in C"); return EXIT_SUCCESS; }

Это очень простая программа на Си. Она просто выводит строку «Functions in C». В программе имеется единственная функция под названием main. Рассмотрим эту функцию подробно. В заголовке функции, т.е. в строке

int – это тип возвращаемого функцией значения;

main - это имя функции;

(void) - это перечень аргументов функции. Слово void указывает, что у данной функции нет аргументов;

return – это оператор, который завершает выполнение функции и возвращает результат работы функции в точку вызова этой функции;

EXIT_SUCCESS - это значение, равное нулю. Оно определено в файле stdlib.h;

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

{
puts("Functions in C");
return EXIT_SUCCESS;
}

называют телом функции.

Итак, когда мы работаем с функцией надо указать имя функции, у нас это main, тип возвращаемого функцией значения, у нас это int, дать перечень аргументов в круглых скобках после имени функции, у нас нет аргументов, поэтому пишем void, в теле функции выполнить какие-то действия (ради них и создавалась функция) и вернуть результат работы функции оператором return. Вот основное, что нужно знать про функции в C.

Как из одной функции в Cи вызвать другую функцию?

Рассмотрим пример вызова функций в Си:

/* Author: @author Subbotin B.P..h> #include int main(void) { puts("Functions in C"); int d = 1; int e = 2; int f = sum(d, e); printf("1 + 2 = %d", f); return EXIT_SUCCESS; }

Запускаем на выполнение и получаем:

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

Заголовок функции sum:

int sum(int a, int b)

здесь int - это тип возвращаемого функцией значения;

sum - это имя функции;

(int a, int b) - в круглых скобках после имени функции дан перечень её аргументов: первый аргумент int a, второй аргумент int b. Имена аргументов являются формальными, т.е. при вызове функции мы не обязаны отправлять в эту функцию в качестве аргументов значения перемнных с именами a и b. В функции main мы вызываем функцию sum так: sum(d, e);. Но важно, чтоб переданные в функцию аргументы совпадали по типу с объявленными в функции.

В теле функции sum, т.е. внутри фигурных скобок после заголовка функции, мы создаем локальную переменную int c, присваиваем ей значение суммы a плюс b и возвращаем её в качестве результата работы функции опрератором return.

Теперь посмотрим как функция sum вызывается из функции main.

Вот функция main:

Int main(void) { puts("Functions in C"); int d = 1; int e = 2; int f = sum(d, e); printf("1 + 2 = %d", f); return EXIT_SUCCESS; }

Сначала мы создаём две переменных типа int

Int d = 1; int e = 2;

их мы передадим в функцию sum в качестве значений аргументов.

int f = sum(d, e);

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

int sum(int a, int b)

аргументы называются a и b, почему тогда мы передаем d и f? Потому что в заголовке функций пишут формальные аргументы, т.е. НЕ важны названия аргументов, а важны их типы. У функции sum оба аргумента имеют тип int, значит при вызове этой функции надо передать два аргумента типа int с любыми названиями.

Ещё одна тонкость. Функция должна быть объявлена до места её первого вызова. В нашем примере так и было: сначала объявлена функция sum, а уж после мы вызываем её из функции main. Если функция объявляется после места её вызова, то следует использовать прототип функции.

Прототип функции в Си

Рассмотрим пример функциив Си:

/* Author: @author Subbotin B.P..h> #include int sum(int a, int b); int main(void) { puts("Functions in C"); int d = 1; int e = 2; int f = sum(d, e); printf("1 + 2 = %d", f); return EXIT_SUCCESS; } int sum(int a, int b) { int c = 0; c = a + b; return c; }

В этом примере функция sum определена ниже места её вызова в функции main. В таком случае надо использовать прототип функции sum. Прототип у нас объявлен выше функции main:

int sum(int a, int b);

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

int f = sum(d, e);

а ниже функции main определяем функцию sum, которая предварительно была объявлена в прототипе:

Int sum(int a, int b) { int c = 0; c = a + b; return c; }

Чем объявление функции в Си отличается от определения функции в Си?

Когда мы пишем прототип функции, например так:

int sum(int a, int b);

то мы объявляем функцию.

А когда мы реализуем функцию, т.е. записываем не только заголовок, но и тело функции, например:

Int sum(int a, int b) { int c = 0; c = a + b; return c; }

то мы определяем функцию.

Оператор return

Оператор return завершает работу функции в C и возвращает результат её работы в точку вызова. Пример:

Int sum(int a, int b) { int c = 0; c = a + b; return c; }

Эту функцию можно упростить:

Int sum(int a, int b) { return a + b; }

здесь оператор return вернёт значение суммы a + b.

Операторов return в одной функции может быть несколько. Пример:

Int sum(int a, int b) { if(a > 2) { return 0;// Первый случай; } if(b < 0) { return 0;// Второй случай; } return a + b; }

Если в примере значение аргумента a окажется больше двух, то функция вернет ноль (первый случай) и всё, что ниже комментария «// Первый случай;» выполнятся не будет. Если a будет меньше двух, но b будет меньше нуля, то функция завершит свою работу и всё, что ниже комментария «// Второй случай;» выполнятся не будет.

И только если оба предыдущих условия не выполняются, то выполнение программы дойдёт до последнего оператора return и будет возвращена сумма a + b.

Передача аргументов функции по значению

Аргументы можно передавать в функцию C по значению. Пример:

/* Author: @author Subbotin B.P..h> #include int sum(int a) { return a += 5; } int main(void) { puts("Functions in C"); int d = 10; printf("sum = %d\n", sum(d)); printf("d = %d", d); return EXIT_SUCCESS; }

В примере, в функции main, создаём переменную int d = 10. Передаём по значению эту переменную в функцию sum(d). Внутри функции sum значение переменной увеличивается на 5. Но в функции main значение d не изменится, ведь она была передана по значению. Это означает, что было передано значение переменной, а не сама переменная. Об этом говорит и результат работы программы:

т.е. после возврата из функции sum значеие d не изменилось, тогда как внутри функции sum оно менялось.

Передача указателей функции Си

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

/* Author: @author Subbotin B.P..h> #include int sum(int *a) { return *a += 5; } int main(void) { puts("Functions in C"); int d = 10; printf("sum = %d\n", sum(&d)); printf("d = %d", d); return EXIT_SUCCESS; }

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

printf("sum = %d\n", sum(&d));

в функцию sum передается не значение переменной d, равное 10-ти, а адрес этой переменной, вот так:

Теперь посмотрим на функцию sum:

Int sum(int *a) { return *a += 5; }

Аргументом её является указатель на int. Мы знаем, что указатель - это переменная, значением которой является адрес какого-то объекта. Адрес переменной d отправляем в функцию sum:

Внутри sum указатель int *a разыменовывается. Это позволяет от указателя перейти к самой переменной, на которую и указывает наш указатель. А в нашем случае это переменная d, т.е. выражение

равносильно выражению

Результат: функция sum изменяет значение переменной d:

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

C/C++ в Eclipse

Все примеры для этой статьи я сделал в Eclipse. Как работать с C/C++ в Eclipse можно посмотреть . Если вы работаете в другой среде, то примеры и там будут работать.

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

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

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

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

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

Int rus (unsigned char r) { if (r>="А" && c

В данном примере определена функция с именем 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)); }

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

1.5.2. Вызов функции с переменным числом параметров

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

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

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

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

Макрос va_start предназначен для установки аргумента arg_ptr на начало списка необязательных параметров и имеет вид функции с двумя параметрами:

void va_start(arg_ptr,prav_param);

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

va_list arg_ptr;

Макрос va_start должен быть использован до первого использования макроса va_arg.

Макрокоманда va_arg обеспечивает доступ к текущему параметру вызываемой функции и тоже имеет вид функции с двумя параметрами

type_arg va_arg(arg_ptr,type);

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

Макрос va_end используется по окончании обработки всех параметров функции и устанавливает указатель списка необязательных параметров на ноль (NULL).

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

#include int main() { int n; int sred_znach(int,...); n=sred_znach(2,3,4,-1); /* вызов с четырьмя параметрами */ printf("n=%d",n); n=sred_znach(5,6,7,8,9,-1); /* вызов с шестью параметрами */ printf("n=%d",n); return (0); } int sred_znach(int x,...); { int i=0, j=0, sum=0; va_list uk_arg; va_start(uk_arg,x); /* установка указателя uk_arg на */ /* первый необязятельный параметр */ if (x!=-1) sum=x; /* проверка на пустоту списка */ else return (0); j++; while ((i=va_arg(uk_arg,int))!=-1) /* выборка очередного */ { /* параметра и проверка */ sum+=i; /* на конец списка */ j++; } va_end(uk_arg); /* закрытие списка параметров */ return (sum/j); }

1.5.3. Передача параметров функции main

Функция main, с которой начинается выполнение СИ-программы, может быть определена с параметрами, которые передаются из внешнего окружения, например, из командной строки. Во внешнем окружении действуют свои правила представления данных, а точнее, все данные представляются в виде строк символов. Для передачи этих строк в функцию main используются два параметра, первый параметр служит для передачи числа передаваемых строк, второй для передачи самих строк. Общепринятые (но не обязательные) имена этих параметров argc и argv. Параметр argc имеет тип int, его значение формируется из анализа командной строки и равно количеству слов в командной строке, включая и имя вызываемой программы (под словом понимается любой текст не содержащий символа пробел). Параметр argv это массив указателей на строки, каждая из которых содержит одно слово из командной строки. Если слово должно содержать символ пробел, то при записи его в командную строку оно должно быть заключено в кавычки.

Функция main может иметь и третий параметр, который принято называть argp, и который служит для передачи в функцию main параметров операционной системы (среды) в которой выполняется СИ-программа.

Заголовок функции main имеет вид:

int main (int argc, char *argv, char *argp)

Если, например, командная строка СИ-программы имеет вид:

A:\>cprog working "C program" 1

то аргументы argc, argv, argp представляются в памяти как показано в схеме на рис.1.

Argc [ 4 ] argv --> --> --> --> --> argp --> --> --> --> --> Рис.1. Схема размещения параметров командной строки

Операционная система поддерживает передачу значений для параметров argc, argv, argp, а на пользователе лежит ответственность за передачу и использование фактических аргументов функции main.

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

Пример: int main (int argc, char *argv, char *argp) { int i=0; printf ("\n Имя программы %s", argv); for (i=1; i>=argc; i++) printf ("\n аргумент %d равен %s", argv[i]); printf ("\n Параметры операционной системы:"); while (*argp) { printf ("\n %s",*argp); argp++; } return (0); }

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

char *geteuv (const char *varname);

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

Используя указатель, полученный функцией geteuv, можно только прочитать значение параметра операционной системы, но нельзя его изменить. Для изменения значения параметра системы предназначена функция puteuv.

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

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

Setargv() { return ; /* пустая функция */ } -seteuv() { return ; /* пустая функция */ } int main() { /* главная функция без аргументов */ ... ... renurn (0); }

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

[

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

Кроме того, что в С++ предусмотрено объявление своих функций, также можно воспользоваться функциями определёнными в стандартных заголовочных файлах языка программирования С++. Чтобы воспользоваться функцией, определённой в заголовочном файле, нужно его подключить. Например, чтобы воспользоваться функцией, которая возводит некоторое число в степень, нужно подключить заголовочный файл ив запустить функцию pow() в теле программы. Разработаем программу, в которой запустим функцию pow() .

// inc_func.cpp: определяет точку входа для консольного приложения. #include "stdafx.h" //действие 1 - подключаем заголовочный файл

// код Code::Blocks

// код Dev-C++

// inc_func.cpp: определяет точку входа для консольного приложения. //действие 1 - подключаем заголовочный файл который содержит прототипы основных математических функций #include int main(int argc, char* argv) { float power = pow(3.14,2); //действие 2 - запуск функции возведения числа в степень return 0; }

Подключение заголовочных файлов выполняется так, как показано в строке 5 , т. е. объявляется препроцессорная директива #include , после чего внутри знаков <> пишется имя заголовочного файла. Когда подключен заголовочный файл, можно использовать функцию, что, и сделано в строке 9 .Функция pow() возводит число 3.14 в квадрат и присваивает полученный результат переменной power , где
pow — имя функции;
числа 3.14 и 2 — аргументы функции;

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

  1. Подключить необходимый заголовочный файл;
  2. Запустить нужную функцию.

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

  1. Функции, которые не возвращают значений
  2. Функции, возвращающие значение

Функции, не возвращающие значения, завершив свою работу, никакого ответа программе не дают. Рассмотрим структуру объявления таких функций.

// структура объявления функций не возвращающих значений void /*имя функции*/(/*параметры функции*/) // заголовок функции { // тело функции }

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

<= numb; i++) // цикл вычисления значения n! rezult *= i; // накапливаем произведение в переменной rezult cout << numb << "! = " << rezult << endl; // печать значения n! } int main(int argc, char* argv) { int digit; // переменная для хранения значения n! cout << "Enter number: "; cin >> digit; faktorial(digit);// запуск функции нахождения факториала system("pause"); return 0; }

// код Code::Blocks

// код Dev-C++

using namespace std; // объявление функции нахождения n! void faktorial(int numb)// заголовок функции { int rezult = 1; // инициализируем переменную rezult значением 1 for (int i = 1; i <= numb; i++) // цикл вычисления значения n! rezult *= i; // накапливаем произведение в переменной rezult cout << numb << "! = " << rezult << endl; // печать значения n! } int main(int argc, char* argv) { int digit; // переменная для хранения значения n! cout << "Enter number: "; cin >> digit; faktorial(digit);// запуск функции нахождения факториала return 0; }

После того, как были подключены все необходимые заголовочные файлы, можно объявлять функцию нахождения факториала.Под объявлением функции подразумевается выбор имени функции, определение параметров функции и написание алгоритма, который является телом функции. После выполнения этих действий функцию можно использовать в программе. Так как функция не должна возвращать значение, то тип возвращаемых данных должен быть void . Имя функции — faktorial , внутри круглых скобочек объявлена переменная numb типа int . Эта переменная является параметром функции faktorial() . Таким образом, все объявления в строке 8 в совокупности составляют заголовок функции. Строки 9 — 14 составляют тело функции faktorial() . Внутри тела в строке 10 объявлена переменная rezult , которая будет хранить результат нахождения n! После чего, в строках 11-12 Объявлен оператор цикла for для нахождения факториала. В строке 13 объявлен оператор cout , с помощью которого значение факториала будет печататься на экране. Теперь, когда функция объявлена можно воспользоваться ею. В строке 21 запускается функция faktorial(digit) , внутри скобочек функции передаётся аргумент, т. е. значение, содержащееся в переменной digit . Результат работы программы (см. Рисунок 1).

Рисунок 1 — Функции в С++

Итак, после запуска программы, было введена цифра 5, и программа вычислила, что значение 120 это 5!.

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

// структура объявления функций возвращающих значения /*возвращаемый тип данных*/ /*имя функции*/(/*параметры функции*/) // заголовок функции { // тело функции return /*возвращаемое значение*/; }

Структура объявления функций осталась почти неизменной, за исключением двух строк. В заголовке функции сначала нужно определять возвращаемый тип данных, это может быть тип данных int , если необходимо возвратить целое число или тип данных float — для чисел с плавающей точкой. В общем, любой другой тип данных, всё зависит от того, что функция должна вернуть. Так как функция должна вернуть значение, то для этого должен быть предусмотрен специальный механизм, как в строке 5 . C помощью зарезервированного слова return можно вернуть значение, по завершении работы функции. Всё, что нужно, так это указать переменную, содержащую нужное значение, или некоторое значение, после оператора return . Тип данных возвращаемого значения в строке 5 должен совпадать с типом данных в строке 2 . Переделаем программу нахождения факториала так, чтобы функция faktorial() возвращала значение факториала.

// struct_func.cpp: определяет точку входа для консольного приложения. #include "stdafx.h" #include <= numb; i++) // цикл вычисления значения n! rezult *= i; // накапливаем произведение в переменной rezult return rezult; // передаём значение факториала в главную функцию } int main(int argc, char* argv) { int digit; // переменная для хранения значения n! cout << "Enter number: "; cin >> digit; cout << digit << "! = " << faktorial(digit) << endl;// запуск функции нахождения факториала system("pause"); return 0; }

// код Code::Blocks

// код Dev-C++

// struct_func.cpp: определяет точку входа для консольного приложения. #include using namespace std; // объявление функции нахождения n! int faktorial(int numb)// заголовок функции { int rezult = 1; // инициализируем переменную rezult значением 1 for (int i = 1; i <= numb; i++) // цикл вычисления значения n! rezult *= i; // накапливаем произведение в переменной rezult return rezult; // передаём значение факториала в главную функцию } int main(int argc, char* argv) { int digit; // переменная для хранения значения n! cout << "Enter number: "; cin >> digit; cout << digit << "! = " << faktorial(digit) << endl;// запуск функции нахождения факториала return 0; }

Теперь функция faktorial() имеет возвращаемый тип данных — int , так как n! — это целое число.В строке 13 объявлен оператор return , который возвращает значение, содержащееся в переменной rezult . В строке 21 выполняем запуск функции faktorial() ,возвращаемое значение которой отправляем в поток вывода с помощью оператора cout . Можно было бы написать так int fakt = faktorial(digit); — переменной типа int присваиваем возвращаемое значение функции faktorial() , после чего в переменной fakt будет храниться значение n! . Результат работы программы не изменился (см. Рисунок 2).

Enter number: 5 5! = 120 Для продолжения нажмите любую клавишу. . .

Рисунок 2 — Функции в С++

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

Рисунок 3 — Функции в С++

На рисунке 3 показаны 4 способа объявления функций в языке программирования С++. Рассмотрим структуры объявления функций в одном файле, с главной функцией. Функции можно объявлять в двух областях, до начала функции main() , после функции main() . До сих пор мы объявляли функции в одном файле, перед функцией main() — это самый простой из способов.

// struct_func.cpp: определяет точку входа для консольного приложения. #include "stdafx.h" /*область 1 - объявление функций до начала main() место для объявления функций функциям объявленным в этой области не нужны прототипы */ int main(int argc, char* argv) { return 0; }

Если функции объявлять в области 1 , перед главной функцией, то прототипы для этих функций не нужны. Хорошему стилю программирования соответствует способ объявления функций после main() . Рассмотрим структуру такого объявления функций.

// struct_func.cpp: определяет точку входа для консольного приложения. #include "stdafx.h" // место для объявления прототипов функций int main(int argc, char* argv) { return 0; } /*область 2 - объявление функций после main() место для объявления функций */

// код Code::Blocks

// код Dev-C++

// struct_func.cpp: определяет точку входа для консольного приложения. // место для объявления прототипов функций int main(int argc, char* argv) { return 0; } /*область 2 - объявление функций после main() место для объявления функций */

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

// синтаксис объявления прототипа /*тип возвращаемых данных функции*/ /*имя функции*/(/*параметры функции*/);

Структура объявления прототипа очень схожа со структурой объявления функции. Разработаем программу, которая определяет, является ли введённое пятизначное число палиндромом. Палиндром — это число или текст, который читается одинаково как слева, так и справа: 93939; 49094; 11311.

// palindrom_func.cpp: определяет точку входа для консольного приложения. #include "stdafx.h" #include << "Enter 5zn-e chislo: "; // введите пятизначное число int in_number, out_number; // переменные для хранения введённого пятизначного числа cin >> << "Number " << out_number << " - palendrom" << endl; else cout<<"This is not palendrom"<

// код Code::Blocks

// код Dev-C++

// palindrom_func.cpp: определяет точку входа для консольного приложения. #include using namespace std; bool palindrom5(int); // прототип функции нахождения палиндрома пятизначных чисел int main(int argc, char* argv) { cout << "Enter 5zn-e chislo: "; // введите пятизначное число int in_number, out_number; // переменные для хранения введённого пятизначного числа cin >> in_number; out_number = in_number; // в переменную out_number сохраняем введённое число if (palindrom5(in_number)) // если функция вернёт true, то условие истинно, иначе функция вернёт false - ложно cout << "Number " << out_number << " - palendrom" << endl; else cout<<"This is not palendrom"<

В строке 7 объявлен прототип функции нахождения палиндрома пятизначных чисел. Обратите внимание на то, что прототип полностью должен совпадать с заголовком функции, но некоторые отличия все же есть. Например, то, что в прототипе имена параметров перечислять не надо, достаточно объявить их типы данных. В конце объявления прототипа всегда нужно ставить точку с запятой. В остальном объявление прототипа совпадает с объявлением заголовка одной функции. Прототип необходимо объявлять для каждой функции отдельно. Переменная out_number служит для временного хранения введённого числа. В строке 16 в условии оператора выбора if выполняется запуск функции palindrom5() . Аргументом для неё является переменная in_number . функция вернёт значение типа bool , и если функция вернёт true , то условие будет истинно, в противном случае — ложно. Можно было бы сначала присвоить значение, возвращаемое функцией, некоторой переменной, а потом эту переменную подставить в условие оператора выбора if , но это бы увеличило код программы на одну строку. В строках 23 — 40 объявлена функция palindrom5() , с одним параметром, через который функции передаётся пятизначное число. Переменные balance1 , balance2 , balance4 , balance5 объявлены в строке 25 , и необходимы для хранения разрядов пятизначного числа: первого, второго, четвёртого, пятого (нумерация — справа на лево). В строках 26, 29, 32, 35 выполняется одна и та же операция — остаток от деления. Операция остаток от деления отсекает по одному разряду справа налево и сохраняет их в переменные balance1 , balance2 , balance4 , balance5 соответственно. Причём операция остаток от деления чередуется с операцией обычного деления. Операция деления выполняется в строках 27 , 30 , 33 , и уменьшает введённое пятизначное число за шаг на один разряд. В строке 30 операция деления уменьшает введённое число сразу на два разряда, потому, что число пятизначное и средний разряд нас не интересует, он может быть любым. В строках 36 — 39 объявлен оператор выбора if , который сравнивает разряды пятизначного числа, и если они, соответствующим образом, равны, то функция вернёт значение true , иначе — false . Результат работы программы (см. Рисунок 4).

Enter 5zn-e chislo: 12321 Number 12321 - palendrom Для продолжения нажмите любую клавишу. . .

Рисунок 4 — Функции в С++

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

  1. создание файла типа *.cpp, в котором объявляются функции;
  2. создание файлов типа *.cpp и *.h.

К хорошему стилю программирования относится второй способ. Таким образом, если объявлять функции в другом файле, то делать это согласно способу два. Переделаем программу нахождения палиндрома так, чтобы объявление функции palindrom5() находилось в отдельном файле *.cpp . Файл *.h нужен для того, чтобы скрыть реализацию функций, т. е. в файле *.h будут содержаться прототипы функций. С помощью обозревателя решений MVS создаём файл типа *.h и называем его palendrom .

// код файла palendrom.h #ifndef palendrom #define palendrom bool palindrom5(int); // прототип функции нахождения палиндрома пятизначных чисел #endif

Директивы в строках 2,3,5 необходимо всегда объявлять в файлах с прототипами функций, причём прототипы функций всегда объявляются в файлах типа *.h . После директив записанных в строках 2,3 , но до директивы #endif объявляются прототипы функций. В строке 4 объявлен прототип функции palindrom5() . Объявление данной функции находится в файле palendrom.cpp , который предварительно тоже был создан с помощью обозревателя решений MVS.

// содержимое файла palendrom.cpp #include "stdafx.h" #include "palendrom.h" bool palindrom5(int number) // функция нахождения палиндрома пятизначных чисел { int balance1, balance2, balance4, balance5; // переменные хранящие промежуточные результаты balance1 = number % 10; // переменной balance1 присвоили первый остаток number = number / 10; // уменьшаем введённое число на один разряд balance2 = number % 10; // переменной balance2 присвоили второй остаток number = number / 100; // уменьшаем введённое число на два разряда balance4 = number % 10; // переменной balance4 присвоили четвёртый остаток number = number / 10; // уменьшаем введённое число на один разряд balance5 = number % 10; // переменной balance5 присвоили пятый остаток if ((balance1 == balance5) && (balance2 == balance4)) return true; // функция возвращает истинное значение else return false; // функция возвращает ложное значение }

// код Code::Blocks

// код Dev-C++

// содержимое файла palendrom.cpp #include "palendrom.h" bool palindrom5(int number) // функция нахождения палиндрома пятизначных чисел { int balance1, balance2, balance4, balance5; // переменные хранящие промежуточные результаты balance1 = number % 10; // переменной balance1 присвоили первый остаток number = number / 10; // уменьшаем введённое число на один разряд balance2 = number % 10; // переменной balance2 присвоили второй остаток number = number / 100; // уменьшаем введённое число на два разряда balance4 = number % 10; // переменной balance4 присвоили четвёртый остаток number = number / 10; // уменьшаем введённое число на один разряд balance5 = number % 10; // переменной balance5 присвоили пятый остаток if ((balance1 == balance5) && (balance2 == balance4)) return true; // функция возвращает истинное значение else return false; // функция возвращает ложное значение }

В файле palendrom.cpp находится объявление функции palindrom5() . Так как файл palendrom.cpp является исполняемым файлом (*.cpp — исполняемые файлы), то в нём обязательно нужно подключить контейнер "stdafx.h" , как в строке 2 . Чтобы связать файл, где объявлена функция palindrom5() и файл с её прототипом, подключим заголовочный файл (файл с прототипом), это сделано в строке 3 . Обратите внимание на то, что при подключении созданного нами файла используются двойные кавычки, а не знаки больше, меньше. Осталось только запустить функцию palindrom5() в главном исполняемом файле func_palendrom.cpp .

// func_palendrom.cpp: определяет точку входа для консольного приложения. #include "stdafx.h" #include << "Enter 5zn-e chislo: "; // введите пятизначное число int in_number, out_number; // переменные для хранения введённого пятизначного числа cin >> in_number; out_number = in_number; // в переменную out_number сохраняем введённое число if (palindrom5(in_number)) // если функция вернёт true, то условие истинно, иначе функция вернёт false - ложно cout << "Number " << out_number << " - palendrom" << endl; else cout<<"This is not palendrom"<

// код Code::Blocks

// код Dev-C++

// func_palendrom.cpp: определяет точку входа для консольного приложения. #include // подключение заголовочного файла, с прототипом функции palindrom5() #include "palendrom.h" using namespace std; int main(int argc, char* argv) { cout << "Enter 5zn-e chislo: "; // введите пятизначное число int in_number, out_number; // переменные для хранения введённого пятизначного числа cin >> in_number; out_number = in_number; // в переменную out_number сохраняем введённое число if (palindrom5(in_number)) // если функция вернёт true, то условие истинно, иначе функция вернёт false - ложно cout << "Number " << out_number << " - palendrom" << endl; else cout<<"This is not palendrom"<

В строке 6 мы подключили файл с прототипом функции palindrom5() , после чего можно использовать эту функцию. Итак, мы разбили программу на три файла:

  • файл проекта: func_palendrom.cpp
  • заголовочный файл palendrom.h
  • исполняемый файл palendrom.cpp

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

Теги: Функции в си, прототип, описание, определение, вызов. Формальные параметры и фактические параметры. Аргументы функции, передача по значению, передача по указателю. Возврат значения.

Введение

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

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

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

Мы уже знакомы с многими функциями и знаем, как их вызывать – это функции библиотек stdio, stdlib, string, conio и пр. Более того, main – это тоже функция. Она отличается от остальных только тем, что является точкой входа при запуске приложения.
Функция в си определяется в глобальном контексте. Синтаксис функции: (, ...) { }

Самый простой пример – функция, которая принимает число типа float и возвращает квадрат этого числа

#include #include float sqr(float x) { float tmp = x*x; return tmp; } void main() { printf("%.3f", sqr(9.3f)); getch(); }

Внутри функции sqr мы создали локальную переменную, которой присвоили значение аргумента. В качестве аргумента функции передали число 9,3. Служебное слово return возвращает значение переменной tmp. Можно переписать функцию следующим образом:

Float sqr(float x) { return x*x; }

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

Void printSqr(float x) { printf("%d", x*x); return; }

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

Void printSqr(float x) { printf("%d", x*x); }

Если функция не принимает аргументов, то скобки оставляют пустыми. Можно также написать слово void:

Void printHelloWorld() { printf("Hello World"); }

эквивалентно

Void printHelloWorld(void) { printf("Hello World"); }

Формальные и фактические параметры

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

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

#include #include //Формальные параметры имеют имена a и b //по ним мы обращаемся к переданным аргументам внутри функции int sum(int a, int b) { return a+b; } float square(float x) { return x*x; } void main() { //Фактические параметры могут иметь любое имя, в том числе и не иметь имени int one = 1; float two = 2.0; //Передаём переменные, вторая переменная приводится к нужному типу printf("%d\n", sum(one, two)); //Передаём числовые константы printf("%d\n", sum(10, 20)); //Передаём числовые константы неверного типа, они автоматически приводится к нужному printf("%d\n", sum(10, 20.f)); //Переменная целого типа приводится к типу с плавающей точкой printf("%.3f\n", square(one)); //В качестве аргумента может выступать и вызов функции, которая возвращает нужное значение printf("%.3f\n", square(sum(2 + 4, 3))); getch(); }

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

#include #include void main() { char c; do { //Сохраняем возвращённое значение в переменную c = getch(); printf("%c", c); } while(c != "q"); //Возвращённое значение не сохраняется getch(); }

Передача аргументов

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

#include #include void change(int a) { a = 100; printf("%d\n", a); } void main() { int d = 200; printf("%d\n", d); change(d); printf("%d", d); getch(); }

Программы выведет
200
100
200
Понятно почему. Внутри функции мы работаем с переменной x, которая является копией переменной d. Мы изменяем локальную копию, но сама переменная d при этом не меняется. После выхода из функции локальная переменная будет уничтожена. Переменная d при этом никак не изменится.
Каким образом тогда можно изменить переменную? Для этого нужно передать адрес этой переменной. Перепишем функцию, чтобы она принимала указатель типа int

#include #include void change(int *a) { *a = 100; printf("%d\n", *a); } void main() { int d = 200; printf("%d\n", d); change(&d); printf("%d", d); getch(); }

Вот теперь программа выводит
200
100
100
Здесь также была создана локальная переменная, но так как передан был адрес, то мы изменили значение переменной d, используя её адрес в оперативной памяти.

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

#include #include #include void init(int *a, unsigned size) { a = (int*) malloc(size * sizeof(int)); } void main() { int *a = NULL; init(a, 100); if (a == NULL) { printf("ERROR"); } else { printf("OKAY..."); free(a); } getch(); }

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

Для изменения объекта необходимо передавать указатель на него, в данном случае – указатель на указатель.

#include #include #include void init(int **a, unsigned size) { *a = (int*) malloc(size * sizeof(int)); } void main() { int *a = NULL; init(&a, 100); if (a == NULL) { printf("ERROR"); } else { printf("OKAY..."); free(a); } getch(); }

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

#include #include #include #include char* initByString(const char *str) { char *p = (char*) malloc(strlen(str) + 1); strcpy(p, str); return p; } void main() { char *test = initByString("Hello World!"); printf("%s", test); free(test); getch(); }

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

Объявление функции и определение функции. Создание собственной библиотеки

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

#include #include //Прототипы функций. Имена аргументов можно не писать int odd(int); int even(int); void main() { printf("if %d odd? %d\n", 11, odd(11)); printf("if %d odd? %d\n", 10, odd(10)); getch(); } //Определение функций int even(int a) { if (a) { odd(--a); } else { return 1; } } int odd(int a) { if (a) { even(--a); } else { return 0; } }

Это смешанная рекурсия – функция odd возвращает 1, если число нечётное и 0, если чётное.

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

Давайте создадим простую библиотеку. Для этого нужно будет создать два файла – один с расширением.h и поместить туда прототипы функций, а другой с расширением.c и поместить туда определения этих функций. Если вы работаете с IDE, то.h файл необходимо создавать в папке Заголовочные файлы, а файлы кода в папке Файлы исходного кода. Пусть файлы называются File1.h и File1.c
Перепишем предыдущий код. Вот так будет выглядеть заголовочный файл File1.h

#ifndef _FILE1_H_ #define _FILE1_H_ int odd(int); int even(int); #endif

Содержимое файла исходного кода File1.c

#include "File1.h" int even(int a) { if (a) { odd(--a); } else { return 1; } } int odd(int a) { if (a) { even(--a); } else { return 0; } }

Наша функция main

#include #include #include "File1.h" void main() { printf("if %d odd? %d\n", 11, odd(11)); printf("if %d odd? %d\n", 10, odd(10)); getch(); }

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

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

#pragma once int odd(int); int even(int);

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

Передача массива в качестве аргумента

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

#include #include void printArray(int *arr, unsigned size) { unsigned i; for (i = 0; i < size; i++) { printf("%d ", arr[i]); } } void main() { int x = {1, 2, 3, 4, 5}; printArray(x, 10); getch(); }

В этом примере функция может иметь следующий вид

Void printArray(int arr, unsigned size) { unsigned i; for (i = 0; i < size; i++) { printf("%d ", arr[i]); } }

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

#include #include void printArray(int arr, unsigned size) { unsigned i, j; for (i = 0; i < size; i++) { for (j = 0; j < 5; j++) { printf("%d ", arr[i][j]); } printf("\n"); } } void main() { int x = { { 1, 2, 3, 4, 5}, { 6, 7, 8, 9, 10}}; printArray(x, 2); getch(); }

Либо, можно писать

#include #include void printArray(int (*arr), unsigned size) { unsigned i, j; for (i = 0; i < size; i++) { for (j = 0; j < 5; j++) { printf("%d ", arr[i][j]); } printf("\n"); } } void main() { int x = { { 1, 2, 3, 4, 5}, { 6, 7, 8, 9, 10}}; printArray(x, 2); getch(); }

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

#include #include #include #include #define SIZE 10 unsigned* getLengths(const char **words, unsigned size) { unsigned *lengths = NULL; unsigned i; lengths = (unsigned*) malloc(size * sizeof(unsigned)); for (i = 0; i < size; i++) { lengths[i] = strlen(words[i]); } return lengths; } void main() { char **words = NULL; char buffer; unsigned i; unsigned *len = NULL; words = (char**) malloc(SIZE * sizeof(char*)); for (i = 0; i < SIZE; i++) { printf("%d. ", i); scanf("%127s", buffer); words[i] = (char*) malloc(128); strcpy(words[i], buffer); } len = getLengths(words, SIZE); for (i = 0; i < SIZE; i++) { printf("%d ", len[i]); free(words[i]); } free(words); free(len); getch(); }

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

#include #include #include #include #define SIZE 10 void getLengths(const char **words, unsigned size, unsigned *out) { unsigned i; for (i = 0; i < size; i++) { out[i] = strlen(words[i]); } } void main() { char **words = NULL; char buffer; unsigned i; unsigned *len = NULL; words = (char**) malloc(SIZE * sizeof(char*)); for (i = 0; i < SIZE; i++) { printf("%d. ", i); scanf("%127s", buffer); words[i] = (char*) malloc(128); strcpy(words[i], buffer); } len = (unsigned*) malloc(SIZE * sizeof(unsigned)); getLengths(words, SIZE, len); for (i = 0; i < SIZE; i++) { printf("%d ", len[i]); free(words[i]); } free(words); free(len); getch(); }

На этом первое знакомство с функциями заканчивается: тема очень большая и разбита на несколько статей.



Загрузка...