Архитектура Аудит Военная наука Иностранные языки Медицина Металлургия Метрология Образование Политология Производство Психология Стандартизация Технологии |
Функция (Пределения ф-ций, Прототипы ф-ций, Заголовочные файлы, Вызов ф-ций: вызов по значению и по ссылке, Генерация случайных чисел, Классы памяти, Правила области действия, Рекурсия)
Как показывает практика, наилучшим способом разработки и поддержки больших программ является конструирование программы из небольших частей, или модулей, скаждым из которых обращаться проще, чем с первоначальной программой. Эта методика следует принципу «разделяй и властвуй». Данная тема описывает возможности языка С, которые упрощают проектирование, реализацию, использование и сопровождение больших программ. Модули в С называются функциями. Программы на С обычно пишутся путем соединения новых функций, созданных программистом, с функциями, которые поставляются в составе стандартной библиотеки С. Стандартная библиотека С предоставляет широкий набор функций для выполнения общих математических вычислений, обработки строк и символов, ввода/вывода и многих других полезных операций. С Каждая программ состояла из функции с именем 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; Просмотров: 809; Нарушение авторского права страницы