Препроцессор языка «C»
В языке «С» предусмотрены определенные расширения языка с помощью простого макропредпроцессора. одним из самых распространенных таких расширений, которое мы уже использовали, является конструкция #DEFINE; другим расширением является возможность включать во время компиляции содержимое других файлов.
5.11.1. Включение файлов. Для облегчения работы с наборами конструкций #DEFINE и описаний (среди прочих средств) в языке «С» предусмотрена возможность включения файлов. Любая строка вида:
#include "filename"
заменяется содержимым файла с именем filename. (Кавычки обязательны). Часто одна или две строки такого вида появляются в начале каждого исходного файла, для того чтобы включить общие конструкции #define и описания extern для глобальных переменных. Допускается вложенность конструкций #include.
Конструкция #include является предпочтительным способом связи описаний в больших программах. Этот способ гарантирует, что все исходные файлы будут снабжены одинаковыми определениями и описаниями переменных, и, следовательно, исключает особенно неприятный сорт ошибок. Естественно, когда какой-то включаемый файл изменяется, все зависящие от него файлы должны быть перекомпилированы.
5.11.2. Макроподстановка. Определение вида:
#define tes 1
приводит к макроподстановке самого простого вида – замене имени на строку символов. Имена в #define имеют ту же самую форму, что и идентификаторы в «С»; заменяющий текст совершенно произволен. Нормально заменяющим текстом является остальная часть строки; длинное определение можно продолжить, поместив \ в конец продолжаемой строки. «Область действия» имени, определенного в #define, простирается от точки определения до конца исходного файла. Имена могут быть переопределены, и определения могут использовать определения, сделанные ранее. Внутри заключенных в кавычки строк подстановки не производятся, так что если, например, yes – определенное имя, то в printf("yes") не будет сделано никакой подстановки. Так как реализация #define является частью работы макропредпроцессора, а не собственно компилятора, имеется очень мало грамматических ограничений на то, что может быть определено. Так, например, любители АЛГОЛА или ПАСКАЛЯ могут объявить: #define begin { #define end ;}
и затем написать
if (i > 0) then begin a = 1; b = 2 end
Имеется также возможность определения макроса с аргументами, так что заменяющий текст будет зависеть от вида обращения к макросу. Пример 5-10. Определим макрос с именем max следующим образом:
#define max(a, b) ((a) > (b) ? (a) : (b))
когда строка
x = max(p+q, r+s);
будет заменена строкой
x = ((p+q) > (r+s) ? (p+q) : (r+s));
Такая возможность обеспечивает «функцию максимума», которая расширяется в последовательный код, а не в обращение к функции. При правильном обращении с аргументами такой макрос будет работать с любыми типами данных; здесь нет необходимости в различных видах max для данных разных типов, как это было бы с функциями. Конечно, если вы тщательно рассмотрите приведенное выше расширение max, вы заметите определенные недостатки. Выражения вычисляются дважды; это плохо, если они влекут за собой побочные эффекты, вызванные, например, обращениями к функциям или использованием операций увеличения. Нужно позаботиться о правильном использовании круглых скобок, чтобы гарантировать сохранение требуемого порядка вычислений. Пример 5-11. Рассмотрим макрос #define square(x) x*x
при обращении к нему, как square(z+1)). Здесь возникают даже некоторые чисто лексические проблемы: между именем макро и левой круглой скобкой, открывающей список ее аргументов, не должно быть никаких пробелов.
Тем не менее аппарат макросов является весьма ценным. Один практический пример дает описываемая в главе 9 стандартная библиотека ввода-вывода, в которой getchar и putchar определены как макросы (очевидно PUTCHAR должна иметь аргумент), что позволяет избежать затрат на обращение к функции при обработке каждого символа. Другие возможности макропроцессора описаны в подробных справочниках. Упражнение 5-9. Определите макрос swap(x,y), который обменивает значениями два своих аргумента типа int (в этом случае поможет блочная структура). Заголовочные файлы Теперь представим себе, что компоненты программы-калькулятора имеют существенно большие размеры, и зададимся вопросом, как в этом случае распределить их по нескольким файлам. Программу main поместим в файл, который мы назовем main.сpp. Функции push, pop и их переменные расположим во втором файле: stack.сpp; a getop – в третьем: getop.сpp. Наконец, getch и ungetch разместим в четвертом файле getch.сpp; мы отделили их от остальных функций, поскольку в реальной программе они будут получены из заранее скомпилированной библиотеки. В результате получим программу, файловая структура которой показана на рис. 5.1. Существует еще один момент, о котором следует предупредить читателя, – определения и объявления совместно используются несколькими файлами. Мы бы хотели, насколько это возможно, централизовать эти объявления и определения так, чтобы для них существовала только одна копия. Тогда программу в процессе ее развития будет легче и исправлять, и поддерживать в нужном состоянии. Для этого общую информацию расположим в заголовочном файле calc.h, который будем по мере необходимости включать в другие файлы (строка #include была рассмотрен выше). Неизбежен компромисс между стремлением, чтобы каждый файл владел только той информацией, которая ему необходима для работы, и тем, что на практике иметь дело с большим количеством заголовочных файлов довольно трудно. Для программ, не превышающих некоторого среднего размера, вероятно, лучше всего иметь один заголовочный файл, в котором собраны вместе все объекты, каждый из которых используется в двух различных файлах; так мы здесь и поступили. Для программ больших размеров потребуется более сложная организация с большим числом заголовочных файлов.
УКАЗАТЕЛИ И МАССИВЫ
Указатель – это переменная, содержащая адрес другой переменной. Указатели очень широко используются в языке «C». Это происходит отчасти потому, что иногда они дают единственную возможность выразить нужное действие, а отчасти потому, что они обычно ведут к более компактным и эффективным программам, чем те, которые могут быть получены другими способами. Указатели обычно смешивают в одну кучу с операторами goto, характеризуя их как «чудесный» способ написания программ, которые невозможно понять. Это, безусловно, справедливо, если указатели используются беззаботно; очень просто ввести указатели, которые указывают на что-то совершенно неожиданное. Однако, при определенной дисциплине, использование указателей помогает достичь ясности и простоты. Именно этот аспект мы попытаемся здесь проиллюстрировать.
Указатели и адреса
Так как указатель содержит адрес объекта, это дает возможность "косвенного" доступа к этому объекту через указатель. Предположим, что х - переменная, например, типа int, а рх - указатель, созданный неким еще не указанным способом. Унарная операция & выдает адрес объекта, так что оператор рх = &х; присваивает адрес х переменной рх; говорят, что рх «указывает» на х. Операция & применима только к переменным и элементам массива, конструкции вида &(х-1) и &3 являются незаконными. Нельзя также получить адрес регистровой переменной. Унарная операция * рассматривает свой операнд как адрес конечной цели и обращается по этому адресу, чтобы извлечь содержимое. Следовательно, если y тоже имеет тип int, то y = *рх; присваивает y содержимое того, на что указывает рх. Так последовательность рх = &х; y = *рх; присваивает y то же самое значение, что и оператор y = x; Переменные, участвующие во всем этом необходимо описать: int x, y; int *px; с описанием для x и y мы уже неодонократно встречались. Описание указателя int *px; является новым и должно рассматриваться как мнемоническое; оно говорит, что комбинация *px имеет тип int. Это означает, что если px появляется в контексте *px, то это эквивалентно переменной типа int. Фактически синтаксис описания переменной имитирует синтаксис выражений, в которых эта переменная может появляться. Это замечание полезно во всех случаях, связанных со сложными описаниями. Например, double atof(), *dp; говорит, что atof() и *dp имеют в выражениях значения типа double.
Вы должны также заметить, что из этого описания следует, что указатель может указывать только на определенный вид объектов. Указатели могут входить в выражения. Например, если px указывает на целое x, то *px может появляться в любом контексте, где может встретиться x. Так операторы · y = *px + 1 присваивает y значение, на 1 большее значения x; · printf("%d\n", *px) печатает текущее значение x; · d = sqrt((double) *px) получает в d квадратный корень из x (причем до передачи функции sqrt значение x преобразуется к типу double, см. главу 3). В выражениях вида y = *px + 1 унарные операции * и & связаны со своим операндом более крепко, чем арифметические операции, так что такое выражение берет то значение, на которое указывает px, прибавляет 1 и присваивает результат переменной y. Мы вскоре вернемся к тому, что может означать выражение y = *px + 1) . Ссылки на указатели могут появляться и в левой части присваиваний. Если px указывает на x, то *px = 0 полагает X равным нулю, а *px += 1 увеличивает его на единицу, как и выражение (*px)++ Круглые скобки в последнем примере необходимы; если их опустить, то поскольку унарные операции, подобные * и ++, выполняются справа налево, это выражение увеличит px, а не ту переменную, на которую он указывает. И наконец, так как указатели являются переменными, то с ними можно обращаться, как и с остальными переменными. Если py – другой указатель на переменную типа int, то py = px копирует содержимое px в py, в результате чего py указывает на то же, что и px.
Популярное: Как распознать напряжение: Говоря о мышечном напряжении, мы в первую очередь имеем в виду мускулы, прикрепленные к костям ... Как вы ведете себя при стрессе?: Вы можете самостоятельно управлять стрессом! Каждый из нас имеет право и возможность уменьшить его воздействие на нас... Организация как механизм и форма жизни коллектива: Организация не сможет достичь поставленных целей без соответствующей внутренней... ©2015-2024 megaobuchalka.ru Все материалы представленные на сайте исключительно с целью ознакомления читателями и не преследуют коммерческих целей или нарушение авторских прав. (241)
|
Почему 1285321 студент выбрали МегаОбучалку... Система поиска информации Мобильная версия сайта Удобная навигация Нет шокирующей рекламы |