Материал: lectures_3

Внимание! Если размещение файла нарушает Ваши авторские права, то обязательно сообщите нам

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

Пример 2. Табулирование функции y = x2 в n равноотстоящих точках. void table(double xn, double xk, int n)

{

int i; double x, dx;

printf("%5s%10s%10s\n", "Number", "X", "Y"); x = xn;

dx = (xk - xn) / (n - 1); for(i = 1; i <= n; i++)

{

printf("%6d%10.3f%10.3f\n", i, x, x * x); x += dx;

}

}

36.6.Функции, возвращающие и невозвращающие

значение

Внекоторых языках, например в языке Паскаль, различают две вида подпрограмм:

Подпрограммы – процедуры.

Подпрограммы – функции.

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

void clrscr(void);

Примером функции, возвращающей значение, является библиотечная функция sqrt(), прототип которой имеет следующий вид:

double sqrt(double);

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

было записать в следующем виде:

/* Устаревший стиль */ main(void)

81

Стандарт С99 не поддерживает принцип неявного int. Поэтому в заголовке функции main() следует в явном виде указать тип возвращаемого

значения:

/* Современный стиль */

int main(void)

Значение, возвращаемое функцией, формируется с помощью инструкции

return, которая должна иметь следующий формат: return [выражение];

Порядок выполнения этой инструкции состоит в следующем:

Вычисляется значение элемента выражение.

Завершаются вычисления в функции.

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

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

Управление передается в точку вызова.

36.7.Вызов функции. Передача параметров по

значению

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

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

имя([список_фактически х_параметров ])

Вызов функции содержит три структурных элемента. Два из них являются обязательными, а один нет. Вызов функции начинается с обязательного элемента имя. Этот элемент определяет имя функции, к которой выполняется вызов. Вторым обязательным элементом вызова функции является оператор функция, в качестве которого в языке Си используется пара круглых скобок. Наличие этого оператора делает расположенный перед ними идентификатор именем функции. Последний элемент вызова список_фактически_параметров относится к категории необязательных элементов. Фактические параметры наряду с формальными параметрами, которые содержатся в заголовке определения функции, служат для организации обмена данными между функцией и вызвавшим ее программным кодом (между функцией и ее клиентом). Фактические и формальные параметры должны быть согласованы по количеству, порядку следования и типам. Фактические параметры имеют синтаксис выражений. Приведем примеры вызовов для функции max2(), определение которой было написано выше. Пусть объявлены три переменных типа double a, b и с. Тогда можно написать следующие вызовы рассматриваемой функции:

max2(3.0, 4.0) max2(a, b)

max(a * a + 5, b + 3) max2(max2(a, b), c)

82

Таким образом, при работе с функцией max2() ее аргументами могут быть константы, переменные, арифметически выражения и даже вызовы самой функции max2().

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

int main(void)

{

clrscr(); /* … */

}

Опишем порядок выполнения вызова функции.

1.Вычисления в точке вызова временно прерываются. Запоминается точка возврата и состояния некоторых регистров процессора.

2.Управление передается вызываемой функции. В стеке выделяется память для формальных параметрах и локальных переменных.

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

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

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

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

83

Обратимся к примеру. Воспользуемся приведенным выше определением функции max2(). Напишем для этой функции тестовую программу.

#include<stdio.h>

#include<conio.h>

double max2(double a, double b)

{

double max; if(a > b)

max = a; else

max = b; return max;

}

int main(void)

{

double a, b, с; printf(“a=”); scanf(“%lf”, &a); printf(“b=”); scanf(“%lf”, &b); printf(“с=”); scanf(“%lf”, &с);

printf(“max(a, b)=%10.3f\n”, max2(a, b)); printf(“max(a * a + 5, b + 3)=%10.3f\n”,

max(a * a + 5, b + 3));

printf(“max(3.0, 5.0)=%10.3f\n”, max2(3.0, 4.0)); printf(“max(a, b, c)=%10.3f\n”, max2(max2(a, b), c)); return 0;

}

36.8.Внешние переменные и процедурная

абстракция

Существуют две категории переменных в программе, написанной на языке Си:

локальные,

внешние (глобальные).

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

84

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

Укажем на одно полезное нововведение стандарта C99, которое касается объявления локальных переменных. Дело заключается в том, что этот стандарт разрешает объявлять переменные внутри инструкции for. Причем

область видимости этой переменной ограничивается телом цикла. Например:

/* Стандарт С89 */ int s =0, i;

for(i = 0; i < 10; i++) s += i * i;

printf(“i=%d”, i);

36.9.Понятие о прототипе. Компиляция

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

double max2(double a, double b);

double max2(double, double);

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

имеет следующий формат: int имя();

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

В стандарте С99 неявные объявления больше не поддерживает.

/* Стандарт С99 */ int s =0;

for(int i = 0; i < 10; i++) s += i * i;

/* printf(“i=%d”, i); на переменную i ссылаться нельзя она здесь невидна. */

1 Этот материал следует согласовать с материалом, изложенным в перой части по переменным

85

Источник: https://studfile.net/preview/16544272/