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

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

Последнее обновление: 22.09.2017

Определение функции

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

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

Тип имя_функции(параметры) { инструкции }

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

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

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

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

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

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

Int main() { return 0; }

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

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

Void hello() { std::cout << "hello\n"; }

Выполнение функции

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

Имя_функции(аргументы);

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

Например, определим и выполним простейшую функцию:

#include void hello() { std::cout << "hello\n"; } int main() { hello(); hello(); return 0; }

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

Объявление функции

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

#include int main() { hello(); hello(); return 0; } void hello() { std::cout << "hello\n"; }

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

Тип имя_функции(параметры);

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

Void hello();

Используем объявление функции:

#include void hello(); int main() { hello(); hello(); return 0; } void hello() { std::cout << "hello\n"; }

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

В языке Си все программы рассматриваются как функции. Обычно программы на этом языке состоят из большого числа небольших функций. Для каждой из используемых функций приводится описание и определение функции (Описание функции дает информацию о типе функции и о порядке следования параметров. При определении функции указываются конкретные операторы, которые необходимо выполнить). Функции должны иметь тот же тип, что и значения, которые они возвращают в качестве результатов. По умолчанию предполагается, что функции имеют тип 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. Назовите оператор для возвращения результата выполнения функции?

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

Введение

Ч ем дальше мы изучаем си, тем больше становятся программы. Мы собираем все действия в одну функцию 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(); }

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

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

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

#include // подключение заголовка с функцией rand rand() int randomNumber = rand(); // стандартная функция генерации случайных чисел

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

Рассмотрим общий формат для прототипа функций:

ReturnedDataType functionName (dataType par1, ..., dataType parN);

где, returnedDataType — тип данных, возвращаемого функцией, значения;
functionName — имя функции
dataType — тип данных параметра функции, это тот же самый тип данных, что и при объявлении переменной
par1 ... parN — параметры функции.

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

Int mult (int x, int y);

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

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

Давайте рассмотрим пример объявления и использования функции в языке программирования Си:

#include int multiplication(int num1, int num2); //прототип функции int main() { int num1; int num2; printf("Введите два числа для умножения: "); scanf("%d", &num1); scanf("%d", &num2); printf("Результат умножения %d\n", multiplication(num1, num2)); // вызов функции getchar(); return 0; } int multiplication(int num1, int num2) // определение функции { return num1 * num2; }

Эта программа начинается с включения единственного заголовочного файла, в строке 1. Следующей строкой является прототип функции умножения. Обратите внимание, что в конце объявления прототипа есть точка с запятой! Функция main возвращает целое число, в строке 16. Чтобы соответствовать стандарту функция main всегда должна возвращать некоторое значение. У вас не должно возникнуть проблем с пониманием ввода и вывода значений в функциях, если вы внимательно изучили предыдущие уроки.

Обратите внимание на то как на самом деле функция multiplication() принимает значение. Что же происходит на самом деле? А на самом деле это работает так: функция multiplication принимает два целых значения, умножает их и возвращает произведение. Результат работы этой программы будет точно таким же, как если бы мы сделали так:

Printf("Результат умножения %d\n", num1 * num2);

Функция multiplication() на самом деле определяется ниже функции main . А так как прототип этой функции объявлен выше главной функции, то при вызове функции multiplication() внутри main() компилятор не выдаст ошибку. Пока прототип присутствует, функция может использоваться даже если нет её фактического определения. Тем не менее, вызов функции не может быть осуществлен ранее, чем будет определена эта функция.

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

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

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

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

P.S.: если вам нужен хороший сервер, то вы можете воспользоваться арендой серверов в Москве . Также вы можете воспользоваться другими услугами, предоставленными на сайте it-express.ru: развертывание отказоустойчивых серверов, системы хранения данных и др.