С как сделать define

Обновлено: 07.07.2024

вводит имя REAL для типа long double. Далее в тексте программы можно определять объекты типа long double, используя данное имя:

Такой прием удобно использовать для присвоения более коротких имен сложным типам данных.

После записи директивы

выведет на экран текст

Еще одной из областей эффективного применения макросов является адресации элементов многомерных массивов.

Доступ к элементам многомерных массивов в С++ имеет две особенности, которые создают неудобства при работе с ними:

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

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

/* Определение одномерного массива */

for (k=0; kN*M; k++) x[k]=k;

printf (\n Строка %d:, i);

Результат выполнения программы:

Строка 1: 0.0 1.0 2.0 3.0 4.0

Строка 2: 5.0 6.0 7.0 8.0 9.0

Строка 3: 10.0 11.0 12.0 13.0 14.0

Строка 4: 15.0 16.0 17.0 18.0 19.0

На рисунке 1.11 приведена схема одномерного массива х[ ] для моделирования виртуального двумерного массива с помощью макоопределений.


Рисунок 1.11 – Имитация многомерного массива с помощью одномерного массива и макроопределения.

Для доступа к элементам массива используются макровызовы A(i, j). Индекс i соответствует номеру строки многомерного массива и изменяется от 1 до N, а индекс j – номеру столбца и изменяется во внутреннем цикле от 1 до М. A(i,j) является достаточно естественным обозначениями элементов матрицы, причем нумерация столбцов и строк начинается с 1.

За счет применения макросов выполняются замены параметризованных обозначений A(i, j) на x[5*(i-l)+(j-l)]. Далее действия выполняются над элементами одномерного массива х[ ], но т.к. данные преобразования выполняются на этапе препроцессорной обработки можно считать, что осуществляется работа с традиционными для многомерных массивов обозначениями.

Использованный в программе оператор

printf (“% 6.1f”, A (i, j));

после макроподстановок будет иметь вид:

A (1,1) соответствует x[0] — вычислено как x[5(1-1)+(1-1)]

A (1,2) соответствует x[1] – вычислено x[5(1-1)+(2-1)]

A (2,1) соответствует x[5] – вычислено как x[5(2-1)+(1-1)]

A (3,4) соответствует x[13] – вычислено как x[5(3-1)+(1-1)]

Макросы унаследованы из языка С, при написании программ на C++ их следует избегать. Вместо макросов без параметров предпочтительнее использовать const или enum, а вместо параметризованных макросов — встроенные функции или шаблоны.

Данная директива имеет следующий формат:

Если для переопределения константы M использовать последовательность директив

А = 10; // Основной текст

А = 5; // Включенный текст

В = А; // Основной текст

При выполнении программы переменная В примет значение 10, несмотря на наличие оператора присваивания А = 5; во включенном тексте.

Данная директива имеет две формы записи:

Имя файла может быть указано с расширением. Файлы с расширением .h, называются заголовочными файлами (header file). Они могут содержать:

  • определения типов, констант, встроенных функций, шаблонов, перечислений;
  • объявления функций, данных, имен, шаблонов;
  • пространства имен;
  • директивы препроцессора;
  • комментарии.

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

В форме заголовочных файлов оформляются описания функций стандартных библиотек, а также определения и описания типов и констант, используемых при работе с библиотеками компилятора. Например, заголовочный файл stdio.h содержит описание функции ввода/вывода printf, scanf и др. Каталог заголовочных файлов поставляется вместе со стандартными библиотеками компилятора.

Для подключения стандартных заголовочных файлов, используется первая форма записи (имя заключается в угловые скобки). В этом случае поиск файла ведется в стандартных каталогах заголовочных файлов. Например, для включения в текст программы заголовочного файла stdio.h используется директива

Перечень заголовочных файлов утвержден стандартом языка. Перечислим некоторые заголовочные файлы языка С++:

float.h — работа с вещественными данными

limits.h — предельные значения целочисленных данных

math.h — математические вычисления

stdio.h — средства ввода-вывода

string.h — работа со строками символов

time.h — определение дат и времени

В конкретных реализациях состав и наименования заголовочных файлов могут отличаться. Например, в компиляторах для MS-DOS активно используются файлы mem.h, alloc.h, conio.h, dos.h, graphics.h и др.

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

Внешние объекты должны записываться в заголовочном файле со спецификатором extern. Например:

extern int ii, jj, ll; // целые внешние переменные

extern float aa, bb; // вещественные внешние переменные

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

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

В результате препроцессорной обработки директив:

текст_1всегда будет включен в компилируемую программу.

Данная директива может использоваться для того, чтобы временно закомментировать фрагменты кода.

Строка замещения в данном случае отсутствует.

После определения идентификатора DEBUG с помощью директивы

printf ( “Отладочная печать”);

Здесь _FILE_NAME — зарезервированный для файла с именем filename препроцессорный идентификатор. Его нежелательно использовать в других текстах программы.

Данная директива имеет формат

Формат применения данной директивы:

В данном примере выполняется включения различных версий заголовочного файла.

При рассмотрении такого простого случая достоинства операции definedне проявляются. С помощью следующего примера можно пояснить полезные возможности операции defined.

Текст включается в компилируемый текст только в том случае, если идентификатор Y определен как препроцессорный, а идентификатор N не определен.

Обработку препроцессор ведет следующим образом. Сначала, определяется истинность выражений defined Y и !defined N. К результатам применяется операция конъюнкции , и при истинности ее результата текст передается компилятору.

Не используя операцию defined, то же самое условие можно записать таким способом:

Таким образом, из примера видно, что

Кроме рассмотренных директив существуют еще несколько вспомогательных директив, которые не так часто используются в практике программирования.

После препроцессорной обработки каждая строка текста передаваемого на компиляцию имеет следующий вид:

имя_файла номер_строки: текст_на_языке_С++

Данная директива позволяет изменять номер и имя файла следующей за ней строки на указанные значения.

void main ( ) // строка 2

double z[3*N]; // строка 5

file.c 23: double z[3*3]

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

Определив некоторую препроцессорную переменную NAME

Error directive: NAME должно быть равно 5

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

Данная конструкция называется прагмой. Стандарта для прагм не существует. Если конкретный препроцессор встречает прагму, которая ему неизвестна, он ее игнорирует.

В некоторых реализациях включена директива

n может быть равно 1, 2 или 4.

Данная прагма позволяет влиять на упаковку смежных элементов в структурах и объединениях. Соглашение может быть таким:

pack(l) — выравнивание элементов по границам байтов;

расk(2) — выравнивание элементов по границам слов;

расk(4) — выравнивание элементов по границам двойных слов.

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

Статьи к прочтению:


Похожие статьи:

Лабораторная работа № 9 ОСНОВЫ ПРОГРАММИРОВАНИЯ В СИСТЕМЕ TURBO PASCAL. РАБОТА С ГЛАВНЫМ МЕНЮ СИСТЕМЫ. ПРОГРАММИРОВАНИЕ С ИСПОЛЬЗОВАНИЕМ МАССИВОВ Цель:…

Си препроцессор представляет собой макро язык, который используется для преобразования программы до того как она будет скомпилирована. Причем сама программа может быть не обязательно на Си, она может быть на С++, Objective-C или даже на ассемблере. В общем препроцессор представляет собой примитивный как-бы функциональный язык, с помощью которого можно делать вполне интересные вещи.

Как работает препроцессор.

Для понимания работы препроцессора важно осознавать уровень абстракций с которыми он работает. Основным понятием в препроцессоре является токен (token) — это, грубо говоря последовательность символов, отделённая разделителями, похоже на идентификатор в Си, но значительно шире. В общем в препроцессоре есть только директивы, токены, строковые и числовые литералы и выражения, еще он понимает комментарии (просто их игнорирует). Упрощенно говоря, препроцессор работает с текстовыми строчками, умеет их склеивать, превращать в строковый литерал, выполнять макроподстановку, и подключать файлы.

Директивы препроцессора.

Подключение файлов.
Условная компиляция

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

__AVR__ и __ICCAVR__ — это специальные предопределённый макросы, позволяющие определить используемый компилятор. Соответственно для каждого компилятора существует предопределённый макрос, который позволяет его однозначно идентифицировать.
Как уже говорилось, препроцессор работает на уровне отдельных токенов — текстовых строчек, их значение препроцессору безразлично, и он ничего не знает о правилах и грамматике целевого языка. Поэтому в директивах условной компиляции нельзя использовать никакие конструкции языка Си. Например:

Условия могут быть сложными и содержать в себе макросы, которые будут полностью развёрнуты перед вычислением условия:

Эта конструкция гарантирует, что все определения из заголовка будут включены только один раз в единицу трансляции.

Диагностика.

Надо учитывать, что такая конструкция собьёт столку любую IDE (и человека тоже) и найти место ошибки будет очень не просто. Однако этот трюк можно использовать, чтоб указать на ошибку, возникшую где-то далеко от места, где мы ее проверяем, например на какой-то важный макрос, определённый в другом файле и имеющий не правильное значение. Надо только точно знать где он расположен.

Макроопределения

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

Когда препроцессор будет обрaбатывать строчку:
char buffer[DOUBLE_BUFFER];
Сначала будет выполнена первая макроподстановка и токен DOUBLE_BUFFER будет заменен на EXTRA_BUFFER * 2. Тут-же будет выполнена вторая макроподстановка и токен EXTRA_BUFFER заменется на (BUFFER_SIZE +10), потом BUFFER_SIZE заменется на 32. В результате вся строчка после препроцессинга будет выглядеть так:

Здесь становится понятно, зачем были нужны скобки в макросе EXTRA_BUFFER, без них результирующее выражение получилось бы таким:

Хотя я бы не рекомендовал использовать такой приём без крайней необходимости, так как он сильно затрудняет понимание программы и чреват ошибками, поскольку мы по сути пишем две версии программы в одном наборе исходников со всеми вытекающими последствиями. Ведь есть-же системы контроля версий!

Предопределённые макросы

При этом вызов функции MyError превратится во что-то такое:

Макросы-функции

Макрос SQR предназначен вычислять квадрат переданного ему выражения, в приведённом примере SQR(b) развернётся в (b * b). Вроде-бы нормально, но если этому макросу передать более сложное выражение
,
то он развернётся совсем не в то, что нужно:

Очевидно, что умножение выполнится первым и это у нас уже далеко не квадрат.
Поэтому все аргументы макросов используемые в математических и не только выражениях надо обязательно заключать в скобки:

Однако и этот вариант не свободен от недостатков, например:

Переменная b будет инкрементирована два раза. И у этого недостатка есть решения гибкие и не очень, стандартные и нет, но о них говорить не будем. В данном примере гораздо лучше применить встраиваемую (inline) функцию, она свободна от недостатков макросов:

Вызов PRINT_VAR в данном случае превратится в

Применять эти макро-операторы можно только к параметрам макросов. Причем для параметров к которым они применены макроподстановка будет применена только один раз — для полученного результата. То есть параметр PORT_LETTER не будет отдельно сканироваться на наличие в нем макросов. Почему макрос SET_PIN состоит из двух уровней объясняется ниже.
Теперь, допустим, нам нужен макрос, который склеивает идентификатор из двух кусков:

Если параметра этого макроса непосредственно, те токены, что нам нужно склеить, как в примере выше, то всё сработает как надо. Если-же это макросы, которые сначала нужно раскрыть, то придется вводить еще один вспомогательный макрос, который сначала развернёт параметры и передаст их следующему макросу:

Из-за того, что для параметров, для которых применена конкатенация, не производится макроподстановка, в препроцессорном метапрограммировании часто приходится применять такие двухуровневые макросы: один — для развёртывания параметров, второй — делает непосредственную работу.

Макро-функции можно передать имя другой макро-функции в качестве параметра и, соответственно, вызвать её:

Практический пример препроцессорного метапрограммирования

В качестве примера рассмотрим генерацию таблицы для вычисления контрольной суммы CRC16. Функция для вычисления CRC16 для каждого байта выглядит так:

Елементы таблицы сrcTable можно вычислить с помощью такой функции:

Где v — индекс в таблице,
polynom — полином контрольной суммы, в данном примере будем использовать значение 0x8408, соответствующее стандарту CRC-CCITT.

Теперь нужно этот алгоритм реализовать с помощью препроцессора. Как быть с циклом? В препроцессоре нет ни циклов ни рекурсии. Прийдётся цикл развернуть вручную:

Можно, конечно оставить и так, но есть решение получше, называется — библиотека Boost preprocessor. В ней имеется много всяких полезняшек, в частности есть макрос BOOST_PP_REPEAT, который повторяет заданное количество раз макрос, переданный ему в качестве параметра. С использованием BOOST_PP_REPEAT геерацию таблицы можно написать так:

Выглядит уже вполне неплохо. Макрос, который будет повторяться в BOOST_PP_REPEAT, должен иметь три параметра. Первый уровень вложенности повторения, если мы будем использовать вложенные повторения, мы его не используем. Второй — счётчик, текущая итерация — индекс в нашей таблице. Третий — дополнительный параметр, мы в нем передаём полином контрольной суммы.

Как-же работает BOOST_PP_REPEAT, если в перпроцессоре нет ни циклов, ни рекурсии. Очень просто — определено 256 макросов с именами типа BOOST_PP_REPEAT_x, где х — номер итерации, которые вызывают друг друга по цепочке. В макросе BOOST_PP_REPEAT склеивается имя макроса этой цепочки из токена BOOST_PP_REPEAT_ и количества требуемых повторений. Это несколько упрощенное объяснение, в реальности там чуть сложнее, но основной принцип такой.

Аннотация: В лекции рассматриваются практически важные свойства препроцессора языка С и примеры типовых препроцессорных директив и конструкций.

Теоретическая часть

Препроцессор (англ. preprocessor ) – программа , выполняющая предварительную обработку входных данных для другой программы [19.1]. Препроцессор языка программирования С просматривает программу до компилятора и заменяет в программе определенные сочетания символов (символические аббревиатуры) на соответствующие директивы. Он отыскивает и подключает к программе необходимые файлы и может изменить условия компиляции [19.1]. Препроцессор имеет тот же смысл, что и буферный процессор .

Имеются следующие директивы препроцессора:

Каждая директива препроцессора должна занимать отдельную строку. Например, строка

Как следует из названия, директивы препроцессора представляют собой блок операторов, который обрабатывается до начала компиляции.

Директивы препроцессора — это команды компилятора, которые влияют на процесс его работы.

Эти команды определяют, какие блоки кода нужно компилировать или как, например, обрабатывать определенные ошибки и предупреждения.

Директива

Описание

Синтаксис

Проверяет, является ли препроцессорное выражение истинным или нет.

Используется для определения идентификатора

Используется для отмены определения идентификатора.

Позволяет генерировать предупреждение 1 уровня из кода.

Позволяет генерировать ошибку из кода.

Используется для задания номера строки и имени файла, сообщаемого макросами препроцессора.

Позволяет обозначить область, которую можно развернуть или свернуть при использовании редактора кода Visual Studio.

Дает компилятору специальные инструкции для компиляции файла, в котором он работает.

Синтаксис

Пример использования

Здесь TESTING — это идентификатор.

Синтаксис

Пример использования

Вывод:

Синтаксис

Пример использования

Синтаксис

Пример использования

Синтаксис

Пример использования

Вывод:

Синтаксис

Вывод:

Синтаксис

Вывод:

Синтаксис

Пример использования

Вывод:

Синтаксис

Вывод:

Синтаксис

Пример использования

Вывод:

Мы видим, что на экране отображается только второе предупреждение.

Так происходит, потому что мы изначально отключили все предупреждения перед первым предупреждением и восстановили их только перед вторым. По этой причине первое предупреждение было скрыто.

Мы также можем отключить конкретное предупреждение вместо всех предупреждений.

Читайте также: