Архитектура Аудит Военная наука Иностранные языки Медицина Металлургия Метрология
Образование Политология Производство Психология Стандартизация Технологии


Функция (Пределения ф-ций, Прототипы ф-ций, Заголовочные файлы, Вызов ф-ций: вызов по значению и по ссылке, Генерация случайных чисел, Классы памяти, Правила области действия, Рекурсия)



Как показывает практика, наилучшим способом разработки и поддержки бо­льших программ является конструирование программы из небольших частей, или модулей, скаждым из которых обращаться проще, чем с первоначальной программой. Эта методика следует принципу «разделяй и властвуй». Данная тема описывает возможности языка С, которые упрощают проектирование, реализацию, использование и сопровождение больших программ. Модули в С называются функциями. Программы на С обычно пишутся пу­тем соединения новых функций, созданных программистом, с функциями, ко­торые поставляются в составе стандартной библиотеки С. Стандартная библиотека С предоставляет широ­кий набор функций для выполнения общих математических вычислений, об­работки строк и символов, ввода/вывода и многих других полезных операций. С

Каждая программ состояла из функции с именем main, которая для выполнения своей задачи вызывала функции стандартной библиотеки. Рассмотрим программу, которая использует функцию square для вычис­ления квадрата целых чисел от 1 до 10.


#include " stdio.h"

int square(int);

main()

{

int x;

for (x = 1; x < = 10; x++)

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

return 0;

}

int square (int y)

{

return y * y;

}


Функция square активируется, или вызывается в main внутри операто­ра printf:

printf (“%d\n ”, square(x) ); Функция square получает копию значения х в параметре у. Затем square вычисляет значение произведения у * у. Результат передается назад функции printf в main, где была вызвана square, и printf выводит результат. Этот про­цесс повторяется десять раз в цикле for. Определение функции square показывает, что square предполагает пере­дачу в параметре у целого значения. Ключевое слово int, предшествующее имени функции, показывает, что square возвращает результат целого типа. Оператор return в square передает результат вычислений функции, которая вызвала square.

Определение функции имеет следующий формат:

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

{

объявления

операторы

}

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

Список_параметров - это список объявлений параметров (отделенных запятыми), получаемых функцией при ее вызове. Если функция не получает значения, список_параметров обозначается ключевым словом void. Тип каждого параметра должен быть указан явно за исключением параметров типа int. Если тип параметра не указан, то по умолчанию принимается тип int. Объявления и операторы внутри фигурных скобок образуют тело функ­ции. Тело функции также называют блоком. Блок - это просто составной oпeратор, который включает в себя объявления. Переменные могут быть объявле­ны в любом блоке, а блоки могут быть вложены. В любом случае функция не может быть определена внутри другой функции.

Одной из наиболее важных особенностей ANSI С являются прототипы функций. Они были позаимствованы комитетом ANSI С у разработчиков C++. Прототип функции сообщает компилятору тип данных, возвращаемых функ­цией, число параметров, получаемых функцией, тип и порядок следования па­раметров. Компилятор использует прототипы функций для проверки коррект­ности обращений к функции. Если прототип для данной функции не был включен в программу, компи­лятор формирует собственный прототип функции, используя ее первое вхож­дение в программу: или определение, или обращение к функции. По умолчанию компилятор предполагает, что функция возвращает тип int, ничего не предполагая относительно типа аргументов.

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

Программист может создать специализированный заголовочный файл. Определенные программистом заголовочные файлы также должны заканчива­ться.h. Определенный программистом файл может быть включен директивой препроцессора # include. Например, заголовочный файл square.h может быть включен в нашу программу с помощью директивы

#include " square.h"

размещенной в верхней части программы.

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

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

Рассмотрим следующий оператор:

i = rand ();

Функция rand генерирует целое значение в диапазоне от 0 до RAND_MAX (символическая константа, определенная в заголовочном файле < stdlib.h> ). Стандарт ANSI объявляет, что значение RAND_MAX должно быть равно по меньшей мере 32767, т.е. максимальному значению 2-байтового целого (или 16-битового, что то же самое). Программы этого раздела были про­верены на системе С с максимальным значением 32767 для RAND_MAX. Если rand действительно генерирует целые числа случайным образом, то каж­дое число между 0 и RAND_MAX имеет равные шансы (или вероятность) быть выбранным при каждом вызове функции rand. Диапазон значений, непосредственно генерируемых rand, часто отличает­ся от того, который необходим в данном приложении. Функция rand генерирует на самом деле псевдослучайные числа. При вы­зове rand генерируется последовательность чисел, которые выглядят случай­ными. Однако эта последовательность повторяется при каждом новом выпол­нении программы. Когда программа будет тщательно отлажена, можно моди­фицировать ее для генерации различных последовательностей случайных чи­сел для каждого выполнения. Это называется рандомизацией и обеспечивает­ся функцией стандартной библиотеки srand. Функция srand получает в каче­стве аргумента целое без знака (тип unsigned), называемое семенем, которое позволяет получать от rand различные последовательности случайных чисел при каждом исполнении программы.

Язык С поддерживает четыре класса памяти, обозначаемые спецификато­рами класса памяти: auto, register, extern и static. Класс памяти идентифи­катора помогает определить его период хранения, область действия и тип ком­поновки. Период хранения идентификатора - это время, в течение которого данный идентификатор существует в памяти. Некоторые идентификаторы су­ществуют короткое время, некоторые неоднократно создаются и разрушают­ся, другие существуют в течение всего времени выполнения программы. Об­ласть действия идентификатора характеризует возможность обращения к нему из различных частей программы. Некоторые идентификаторы доступны во всей программе, другие - только в отдельных ее частях. Тип компоновки идентификатора определяется для программ, состоящих из нескольких исход­ных файлов, объединяемых на этапе компоновки. Эта характеристика показывает, известен ли идентификатор только в текущем исходном файле или в любом исходном файле с соответству­ющими объявлениями. В этом разделе рассматриваются четыре класса памяти и период хранения. Четыре спецификатора класса памяти могут быть разбиты на два типа по периоду хранения: автоматический период хранения и статический период хранения. Для объявления переменных с автоматическим периодом хранения служат ключевые слова auto и register. Переменные с автоматическим хране­нием создаются, когда управление получает программный блок, в котором они объявлены. Переменные этого типа существуют, пока блок активен, и уничто­жаются, когда происходит выход из блока. Автоматический период хранения могут иметь только переменные. JIoкальные переменные функции (объявленные в списке параметров или в теле функции) обычно имеют автоматический период хранения. Ключевое слово auto объявляет переменные с автоматическим хранением явным образом. Локальные переменные имеют автоматический период хранения по умолчанию, так что ключевое слово auto используется редко.

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

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

static int count = 1;

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

Областью действия идентификатора является та часть программы, в ко­торой возможно обращение к нему. Например, когда мы объявляем в некото­ром блоке локальную переменную, к ней можно обратиться только из этого блока или из блоков, вложенных в данный блок. Область действия идентифи­катора делится на четыре вида: область действия функции, область дейст­вия файла, область действия блока и область действия прототипа функ­ции. Метки (идентификаторы, сопровождающиеся двоеточием, вроде start: ) являются единственными идентификаторами, принадлежащими области дей­ствия функции. Метки могут использоваться в произвольном месте функции, в которой они появляются, но на них нельзя сослаться вне тела функции. Мет­ки используются в структурах switch (в качестве меток case) и в операторах goto. Метки относятся к подробностям реализации, которые функции скрывают от друг друга. Эта скрытность, более формально называе­мая сокрытием информации, является одним из наиболее фундаментальных принципов хорошего стиля программирования. Идентификатор, объявленный вне любой функции, имеет область дейст­вия файла. Такой идентификатор «известен» всем функциям начиная с того места, где он объявлен, и до конца файла. Глобальные переменные, определе­ния функций и прототипы функций, помещенные вне функции, - все они имеют область действия файла. Идентификаторы, объявленные внутри блока, имеют область действия блока. Область действия блока заканчивается завершающей правой фигурной скобкой ( } ) блока. Локальные переменные, объявленные в начале функции, имеют область действия блока, так же как и параметры функции, которые рассматриваются как ее локальные переменные. Любой блок может содержать объявления переменных. Когда блоки вложены, а идентификатор во внешнем блоке имеет то же самое имя, что и идентификатор во внутреннем блоке, иден­тификатор во внешнем блоке «скрывается», пока внутренний блок не завер­шит работу. Это означает, что пока выполняется внутренний блок, он видит значение собственного локального идентификатора, а не значение идентифи­катора с тем же именем, находящегося в объемлющем блоке. Локальные пере­менные, объявленные как static, имеют область действия блока несмотря на то, что существуют с момента начала выполнения программы. Таким образом, период хранения не влияет на область действия идентификатора. Единственными идентификаторами с областью действия прототипа функции являются идентификаторы, которые используются в списке парамет­ров прототипа функции. Как отмечалось выше, прототипы функций не требу­ют, чтобы в списке параметров стояли имена идентификаторов; требуется то­лько их тип. Если в списке параметров прототипа используется имя, то ком­пилятор его игнорирует. Идентификаторы, указанные в прототипе функции, могут неоднократно встречаться в других местах программы, и здесь не возни­кает никакой неоднозначности.

Рекурсивная функция - это функция, которая вызывает саму себя или непо­средственно, или косвенно через другую функцию. Для решения задачи вызывается рекурсивная функция. Фактически функция знает решение только простей­шего случая (случаев), или так называемого основного случая. Если функция вызывается для решения основного случая, то она просто возвращает резуль­тат. Если функция вызывается для решения более сложной проблемы, функ­ция делит задачу на две обобщенных части: часть, для которой функция имеет способ решения, и часть, для которой функция решения не имеет. Чтобы сде­лать рекурсию возможной, последняя часть должна быть похожа на первона­чальную задачу, но она должна быть более простым, или редуцированным ее вариантом. Поскольку эта новая задача похожа на первоначальную, функция запускает (вызывает) свою новую копию, чтобы продолжить решение мень­шей задачи. Этот процесс называется рекурсивным вызовом или шагом рекур­сии. Шаг рекурсии также включает ключевое слово return, поскольку ее резу­льтат будет объединен с предыдущей частью задачи, для которой функция знала решение, чтобы сформировать окончательный результат, который будет передан первоначальной вызывающей функции, возможно main. В то время как выполняется шаг рекурсии, первоначальное обращение к функции остается открытым, т.е. его выполнение не завершается. Шаг рекур­сии может приводить к намного большему числу таких рекурсивных вызовов, которые продолжаются до тех пор, пока функция продолжает делить каждую последующую задачу, для решения которой она вызывалась, на две обобщен­ные части. Чтобы рекурсия в конце концов завершилась, функция каждый раз должна вызываться для решения все более простого варианта первонача­льной задачи, и эта последовательность все меньших и меньших задач должна в конце концов, свестись к основному случаю.

 


Поделиться:



Популярное:

Последнее изменение этой страницы: 2016-04-10; Просмотров: 750; Нарушение авторского права страницы


lektsia.com 2007 - 2024 год. Все материалы представленные на сайте исключительно с целью ознакомления читателями и не преследуют коммерческих целей или нарушение авторских прав! (0.014 с.)
Главная | Случайная страница | Обратная связь