Язык Си - Уэйт, Прата, Мартин. M. уэит с. Прата д. Мартин
Скачать 4.69 Mb.
|
#include. Если одна функция содержится в файле с именем file1.с, а вторая и файле file2.c, поместите эту директиву в файл filel.c: #include "file2.c" Дополнительная информация о директиве #include находится п гл. 11. Другие возможные способы являются в большей степени системнозависимыми. Вот некоторые из них: OC UNIX Предположим, file1.с и file2.c - два файла, содержащие программные тексты, соответствующие функциям языка Си. В результате выполнения команды cc file1.c file2.c 198 будет осуществлена компиляция функций, содержащихся в обоих файлах, и получен файл выполняемого кода с именем a.out. Кроме того, будут созданы два файла с "объектным" кодом - file1.0 и file2.0. Если позже вы измените текст, содержащийся в файле с именем filel.с, а второй файл оставите без изменений, то сможете осуществить компиляцию первого файла, а затем объединить полученный объектный код с объектным кодом, соответствующим второму файлу, при помощи команды cc file1.c file2.0 Компиляторы Lattice C и MICROSOFT C Выполните раздельную компиляцию функции, содержащихся в файлах filel.c и file2.c; в результате будут получены два файла с объектным кодом - file1.obj и file2.obj. Используйте системный редактор связей для объединения их друг с другом и со стандартным объектным модулем с.obj: link с filel file2 Системы, построенные на основе трансляции в ассемблкрный код Некоторые из таких систем позволяют компилировать функции, содержащиеся в нескольких файлах, сразу так же, как в ОС UNIX с помощью команды: сс filel.с file2.c или какого-то ее эквивалента. В некоторых случаях вы можете получить отдельные модули с кодом ассемблера, а затем объединить их, используя процесс ассемблирования. РЕЗЮМЕ Далее Содержание Для создания больших программ вы должны использовать функции в качестве "строительных блоков". Каждая функция должна выполнять одну вполне определенную задачу. Используйте аргументы для передачи значений функции и ключевое слово return для передачи результирующего значения в вызывающую программу. Если возвращаемое функцией значение не принадлежит типу int, вы должны указать тип функции в ее определении и в разделе описаний вызывающей программы. Если вы хотите, чтобы при выполнении функции происходило изменение значении переменных в вызывающей программе, вы должны пользоваться адресами и указателями. ЧТО ВЫ ДОЛЖНЫ БЫЛИ УЗНАТЬ В ЭТОЙ ГЛАВЕ Далее Содержание Как определять функцию. Как передавать функции информацию: при помощи аргументов. Различие между формальным и фактическим аргументами: первый является переменной, используемой функцией, а второй - значением, поступающим из вызывающей функции. Где необходимо описывать аргументы: после имени функции и перед первой фигурной скобкой. Где необходимо описывать остальные локальные переменные: после первой фигурной скобки. Когда и как использовать оператор return. 199 Когда и как использовать адреса и указатели для доступа к объектам. ВОПРОСЫ И ОТВЕТЫ Далее Содержание Вопросы 1. Напишите функцию, возвращающую сумму двух целых чисел. 2. Какие изменения должны были бы произойти с функцией из вопроса 1, если вместо целых складывались бы два числа типа float? 3. Напишите функцию alter( ), которая берет две переменные х и у типа int и заменяет соответственно на их сумму и разность. 4. Проверьте, все ли правильно в определении функции, приведенной ниже? salami(num) { int num, count; for(count = 1; count <= num; num++) printf(" О салями!\n"); } Ответы 1. sum(j,k) int j, k; { return(j+k); } 2. float sum(j,k) float j,k; Необходимо также привести описание функции float sum( ) и вызывающей программе. 3. Поскольку мы хотим изменить две переменные в вызывающей программе, можно воспользоваться адресами и указателями. Обращение к функции будет выглядеть так: alter(&x,&y). Возможное решение имеет следующий вид: alter(px, ру) int *рх, *ру; /* указатели на х и у*/ { int sum, diff; sum = *рх + *ру; /* складывает содержимое двух переменных, определяемых адресами */ diff = *рх - *ру; *рх = sum; *ру = diff; } 4. Нет; переменная num должна быть описана перед первой фигурной скобкой, а не после нее. Кроме того, выражение num++ необходимо заменить на count++. УПРАЖНЕНИЯ 1. Напишите функцию mах(х, у), возвращающую большее из двух значении. 200 2. Напишите функцию chllne(ch, i, j), печатающую запрошенный символ с i-й пo j-ю позиции. Смотри программу художник-график, приведенную в гл. 7. ЛОКАЛЬНЫЕ И ГЛОБАЛЬНЫЕ ПЕРЕМЕННЫЕ КЛАССЫ ПАМЯТИ ФУНКЦИЯ ПОЛУЧЕНИЯ СЛУЧАЙНЫХ ЧИСЕЛ ПРОВЕРКА ОШИБОК МОДУЛЬНОЕ ПРОГРАММИРОВАНИЕ СОРТИРОВКА КЛЮЧЕВЫЕ СЛОВА auto, extern, static, register Одно из достоинств языка Си состоит в том, что он позволяет управлять ключевыми механизмами программы. Классы памяти языка Си - пример такого управления; они дают возможность определить, с какими функциями связаны какие переменные и как долго переменная сохраняется в программе. Классы памяти - первая тема данной главы. Программирование, точно так же как написание романа (или даже письма),- это не просто знание языковых правил - это нечто большее. В данной главе мы рассмотрим несколько полезных функций. При этом попытаемся привести некоторые соображения, используемые при конструировании функций. В частности, сделаем упор на значение модульного подхода, разбивающего программы на выполнимые задачи. Сначала, однако, обсудим классы памяти. КЛАССЫ ПАМЯТИ И ОБЛАСТЬ ДЕЙСТВИЯ Далее Содержание Мы уже упоминали раньше, что локальные переменные известны только функциям, содержащим их. В языке Си предполагается также, что о глобальных переменных "знают" сразу несколько функций. Предположим, например, что и main( ), и critic( ) имеют доступ к переменной units. Это будет иметь место, если отнести units к "внешнему" классу памяти, как показано ниже: /* глобальная переменная units */ int units; /* внешняя переменная */ main( ) { extern int units; printf (" Сколько фунтов масла находится в бочонке?\n"); scanf (" %d" , &units); while (units != 56) critic( ); printf(" Вы должны поискать в справочнике !\n"); } critic( ) { extern int units; printf (" He повезло, дружок. Попытайся снова.\n"); scanf (" %d" , &units); } Вот полученный результат: Сколько фунтов масла находится в бочонке? 14 Не повезло, дружок. Попытайся снова. 56 201 Вы должны поискать в справочнике! (Мы сделали это.) Обратите внимание, что второе значение units было прочитано функцией critic( ), однако main() также "узнала" это новое значение, когда оно вышло из цикла while. Мы сделали переменную units внешней, описав ее вне любого определения функции. Далее, внутри функций, использующих эту переменную, мы объявляем ее внешней при помощи ключевого слова extern, предшествующего спецификации типа переменной. Слово extern предлагает компьютеру искать определение этой переменной вне функции. Если бы мы опустили ключевое слово extern в функции critic( ), то компилятор создал бы в функции critic новую переменную и тоже назвал бы ее units. Тогда другая переменная units() [которая находится в main()] никогда не получила бы нового значения. Каждая переменная, как мы знаем, имеет тип. Кроме того, каждая переменная принадлежит к некоторому классу памяти. Есть четыре ключевых слова, используемые для описания классов памяти: extern (для внешнего), auto (для автоматического), static и register. До сих пор мы не обращали внимание на классы памяти, так как переменные, описанные внутри функции, считались относящимися к классу auto, если они не описывались иначе (по умолчанию они относились к классу auto). Определение класса памяти переменной зависит oт того, гд переменная описана и какое ключевое слово (если оно есть) используется. Класс памяти позволяет установить два факта. Во-первых, определить, какие функции имеют доступ к переменной. (Пределы, до которых переменная доступна, характеризуют ее "область действия".) Во-вторых, определить, как долго переменная находится в памяти. Теперь перейдем к свойствам каждого типа. Автоматические переменные Далее Содержание По умолчанию переменные, описанные внутри функции, являются автоматическими. Можно, однако, это подчеркнуть явно с помощью ключевого слова auto: main( ) { auto int plox; Так поступают, если хотят, например, показать, что определение переменной не нужно искать вне функции. Автоматические переменные имеют локальную область действия. Только функция, в которой переменная определена, "знает" ее. (Конечно, можно использовать аргументы для связи значения и адреса переменной с другой функцией, однако это частичное и косвенное "знание".) Другие функции могут использовать переменные с тем же самым именем, но это должны быть независимые переменные, находящиеся в разных ячейках памяти. Автоматическая переменная начинает существовать при вызове функции, содержащей ее. Когда функция завершает свою работу и возвращает управление туда, откуда ее вызвали, автоматическая переменная исчезает. Ячейка памяти может снова использоваться для чего-нибудь другого. Следует еще сказать об области действия автоматической переменной: область действия ограничена блоком ({ }), в котором переменная описана. Мы всегда должны описывать наши переменные в начале тела функции (блока), так что областью действия их является вся функция. 202 Однако в принципе можно было бы описать переменную внутри подблока. Тогда переменная будет известна только в этой части функции. Обычно при создании программы, программисты редко принимают во внимание упомянутое свойство. Но иногда торопливые программисты пользуются такой возможностью, особенно когда пытаются быстрее внести коррективы. Внешние переменные Далее Содержание Переменная, описанная вне функции, является внешней. Внешнюю переменную можно также описать в функции, которая использует ее, при помощи ключевого слова extern. Описания могут выглядеть примерно так: int errupt; /* Три переменные, описанные вне функции */ char coal; double up; main( ) { extern int errupt; /* объявлено, что 3 переменные */ extern char coal; /* являются внешними */ extern double up; Группу extern-описаний можно совсем опустить, если исходные определения переменных появляются в том же файле и перед функцией, которая их использует. Включение ключевого слова extern позволяет функции использовать внешнюю переменную, даже если она определяется позже в этом или другом файле. (Оба файла, конечно, должны быть скомпилированы, связаны или собраны в одно и то же время.) Если слово extern не включено в описание внутри функции, то под этим именем создается новая автоматическая переменная. Вы можете пометить вторую переменную как "автоматическую" с помощью слова auto и тем самым показать, что это ваше намерение, а не оплошность. Три примера демонстрируют четыре возможных комбинация описаний: /* Пример1 */ int hocus; main( ) { extern int hocus; /* hocus описана внешней */ } magic( ) { extern int hocus; } Здесь есть одна внешняя переменная hocus, и она известна обеим функциям main( ) и magic( ). /* Пример2 */ int hocus ; main( ) { extern int hocus; /* hocus описана внешней */ } magic( ) { /* hocus не описана совсем */ } Снова есть одна внешняя переменная hocus, известная обеим функциям. На этот раз она известна функции magic( ) по умолчанию. 203 /* Пример3 */ int hocus; main( ) { int hocus; /* hocus описана и является автоматической по умолчанию */ } magic( ) { auto int hocus; /* hocus описана автоматической */ } В этом примере созданы три разные переменные с одинаковым именем. Переменная hocus в функции main( ) является автоматической по умолчанию и локальной для main( ), в функции magic( ) она явно описана автоматической и известна только для magic( ). Внешняя переменная hocus не известна ни main( ), ни magic( ), но обычно известна любой другой функции в файле, которая не имеет своей собственной локальной переменной hocus. Эти примеры иллюстрируют область действия внешних переменных. Они существуют, пока работает программа, и так как эти переменные доступны любой функции, они не исчезнут, если какая-нибудь одна функция закончит свою работу. Статические переменные Далее Содержание Название раздела не следует понимать буквально, т. е. считать, что такие переменные не могут изменяться. В действительности слово "статические" здесь означает, что переменные остаются в работе. Они имеют такую же область действия, как автоматические переменные, но они не исчезают, когда содержащая их функция закончит свою работу. Компилятор хранит их значения от одного вызова функции до другого. Следующий пример иллюстрирует это и показывает, как описать статическую переменную. /* статическая переменная */ main( ) { int count; for (count = 1; count <= 3; count++) { printf(" Итерация %d:\n", count); trystat( ); } } trystat( ) { int fade = 1; static int stay; = 1; printf("fade = %d и stay = %d\n", fade++, stay++); } Заметим, что функция trystat( ) увеличивает каждую переменную после печати ее значения. Работа этой программы даст следующие результаты: Итерация 1: fade = 1 и staly = 1 Итерация 2: fade = 1 и stay = 2 Итерация 3: fade = 1 и stay = 3 204 Статическая переменная stay "помнит", что ее значение было увеличено на 1, в то время как для переменной fade начальное значение устанавливается каждый раз заново. Это указывает на разницу в инициализации: fade инициализируется каждый раз, когда вызывается trystat( ), в то время как stay инициализируется только один раэ при компиляции функции trystat( ). Внешние статические переменные Далее Содержание Вы можете также описать статические переменные вне любой функции. Это создаст "внешнюю статическую" переменную. Разница между внешней переменной и внешней статической переменной заключается в области их действия. Обычная внешняя переменная может использоваться функциями в любом файле, в то время как внешняя статическая переменная может использоваться только функциями того же самого файла, причем после определения переменной. Вы описываете внешнюю статическую переменную, располагая ее определение вне любой функции. static randx = 1; rand( ) { Немного позже мы приведем пример, в котором будет необходим этот тип переменной. РИС. 10.1. Внешние и внешние статические переменные. Регистровые переменные Далее Содержание Обычно переменные хранятся в памяти машины. К счастью, регистровые переменные запоминаются в регистрах центрального процессора, где доступ к ним и работа с ними выполняются гораздо быстрее, чем в памяти. В остальном регистровые переменные аналогичны автоматическим переменным. Они создаются следующим образом: main( ) { register int quick; 205 Мы сказали "к счастью", потому что описание переменной как регистровой, является скорее просьбой, чем обычным делом. Компилятор должен сравнить ваши требования с количеством доступных регистров, поэтому вы можете и не получить то, что хотите. В этом случае переменная становится простой автоматической переменной. Какой класс памяти применять? Далее Содержание Ответ на вопрос почти всегда один - "автоматический". В конце концов почему этот класс памяти выбран по умолчанию? Мы знаем, что на первый взгляд использование внешних переменных очень соблазнительно. Опишите все ваши переменные как внешние, и у вас никогда не будет забот при использовании аргументов и указателей для связи между функциями в прямом и обратном направлениях. К сожалению, у вас возникнет проблема с функцией С, коварно изменяющей переменные в функции А, а это совсем не входит в паши интересы. Неоспоримый совокупный опыт использова-ния машин, накопленный в течение многих лет, свидетельствует о том, что такая проблема значительно перевешивает кажущуюся привлекательность широкого использования внешних переменных. Одно из золотых правил защитного программирования заключается в соблюдении принципа "необходимо знать только то, что нужно". Организуйте работу каждой функции автономно, насколько это возможно, и используйте глобальные переменные только тогда, когда это действительно необходимо. Иногда полезны и другие классы памяти. Но прежде чем их использовать, спросите себя, необходимо ли это. Резюме: Классы памяти I. Ключевые слова: auto, extern, static, register II. Общие замечания: Класс памяти определяет область действия переменной и продолжительность ее существования в памяти. Класс памяти устанавливается при описании переменной с соответствующим ключевым словом. Переменные, определенные вне функции, являются внешними и имеют глобальную область действия. Переменные, определенные внутри функции, являются автоматическими и локальными, если только не используются другие ключевые слова. Внешние переменные, определенные раньше функции, доступны ей, даже если не описаны внутри ее. III. Свойства КЛАСС ПАМЯТИ КЛЮЧЕВОЕ СЛОВО ПРОДОЛЖИТЕЛЬНОСТЬ СУЩЕСТВОВАНИЯ ОБЛАСТЬ ДЕЙСТВИЯ Автоматический Регистровый Статический auto register static Временно Временно Постоянно Локальная Локальная Локальная Внешний Внешний статический extern static Постоянно Постоянно Глобальная (все файлы) Глобальная (один файл) 1. Разделим случайное число на 32768. В результате получим число |