книги / Программирование на языке Си
..pdf152 |
Программирование на языке Си |
REAL х, array [6];
,Идентификатор в команде #define может определять, как мы видели, имя константы, если строка замещения задает значе ние этой константы. В более общем случае идентификатор слу жит обозначением некоторого выражения, например:
#define RANGE ((INT_MAX)- (INT_MIN)+1)
Идентификаторы, входящие в строку замещения, в свою оче редь, могут быть определены как препроцессорные, и их значе ния будут подставлены вместо них (вместо INTJVIAX и INT_MIN в нашем примере).
Допустимость выполнять с помощью ^define “цепочки” под становок расширяет возможности этой директивы, однако она имеет существенный недостаток - строка замещения фиксиро вана. Большие возможности предоставляет макроопределение с параметрами
#define имч(списокпараметров) строка замещения
Здесь имя - имя макроса (идентификатор), список параметров - список разделенных запятыми
идентификаторов. Между именем макроса и скобкой, откры вающей список параметров, не должно быть пробелов.
Для обращения к макросу ("для вызова макроса") использу ется конструкция ("макровызов") вида:
имя макроса {список аргументов)
Всписке аргументы разделены запятыми. Каждый аргумент
-препроцессорная лексема.
Классический пример макроопределения:
#def±ne max(a,b) (а < b ? b : а)
позволяет формировать в программе выражение, определяющее максимальное из двух значений аргументов. При таком опреде лении вхождение в программу макровызова
max(X,Y)
заменяется выражением
Глава 3. Препроцессорные средства |
153 |
(Х<У? У :X)
а использование конструкции вида
max(Z ,4)
приведет к формированию выражения
(Z<4? 4:Z)
В первом случае при истинном значении X < Y возвращается значение Y, иначе - значение X. Во втором примере значение переменной Z сравнивается с константой 4 и выбирается боль шее из значений.
Не менее часто используется определение
#define ABS(X) (Х<0? - (X):X)
С его помощью в программу можно вставлять выражение для определения абсолютных значений переменных. Конструкция
ABS(E-Z)
заменяется выражением
(E-ZCO? - (E-Z) :E-Z)
в результате вычисления которого определяется абсолютное значение выражения Е-Z. Обратите внимание на скобки. Без них могут появиться ошибки в результатах.
Следует отметить, что последовательные препроцессорные подстановки выполняются в строке замещения, но не действуют на параметры макроса.
Моделирование многомерных массивов. В качестве еще одной области эффективного использования макросов укажем на проблему представления многомерных массивов в языке Си. Массивы мы будем подробно рассматривать в следующих гла вах, а пока остановимся только на вопросе применения макро средств для удобной адресации элементов матрицы. Напомним общие принципы представления массивов в языке Си.
1, Основным понятием в языке Си является одномерный массив, а возможности формирования многомерных массивов (особенно с переменными размерами) весьма ограниченны.
154 |
Программирование на языке Си |
2. |
Нумерация элементов массивов в языке Си начинается с |
нуля, т.е. для обращения к начальному (первому) элементу мас сива требуется нулевое значение индекса.
При работе с матрицами обе указанные особенности масси вов языка Си создают по крайней мере неудобства. Во-первых, при обращении к элементу матрицы нужно указывать два ин декса - номер строки и номер столбца элемента матрицы. Вовторых, нумерацию строк и столбцов матрицы принято начи нать с 1.
Применение макросов для организации доступа к элементам массива позволяет программисту обойти оба указанных затруд нения, правда, за счет нетрадиционных обозначений индексиро ванных элементов. (Индексы в макросах, представляющих элементы массивов матриц, заключены в круглые, а не в квад ратные скобки.) Рассмотрим следующую программу:
#define N 4 /* Число строк матрицы */ #define М 5 /* Число столбцов матрицы */ #define A(i,j) x[M*(i-l) + (j—1)] ♦include <stdio.h>
void main ( )
{
/* Определение одномерного массива */ double x [N*M] ;
int i ,j , k ;
for |
(k=0; |
k < N*M; k++) |
for |
x [ k ] = k ; |
|
(i=l; |
i<=N; i++) /* Перебор строк */ |
{
printf ("\n Строка %d:"f i); /* Перебор элементов строки */ for (j=l; j<=M; j++)
printf<" %6.1f", A(i, j));
}
)
Результат выполнения программы:
Строка |
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 |
Глава 3. Препроцессорные средства |
155 |
Впрограмме определен одномерный массив х[ ], количество элементов в котором зависит от значений препроцессорных идентификаторов N и М.
Значения элементам массива х[ ] присваиваются в цикле с параметром к. Никаких новинок здесь нет. А вот далее для дос тупа к элементам того же массива х[ ] используются макровызо вы вида A(i, j), причем i изменяется от 1 до N, а переменная j изменяется во внутреннем цикле от 1 до М. Переменная i соот ветствует номеру строки матрицы, а переменная j играет роль второго индекса, т.е. указывает номер столбца. При таком под ходе программист оперирует с достаточно естественными обо значениями A(i,j) элементов матрицы, причем нумерация столбцов и строк начинается с 1, как и предполагается в мат ричном исчислении.
Втексте программы за счет макрорасширений в процессе препроцессорной обработки выполняются замены параметризо ванных обозначений A(i, j) на x[5*(i-l)+(j-l)], и далее действия выполняются над элементами одномерного массива х[ ]. Но этих преобразований программист не видит и может считать, что он работает с традиционными обозначениями матричных элементов. Использованный в программе оператор (вызов функции)
printf ("% 6.If", A (i, j));
после макроподстановок будет иметь вид:
printf ("% 6.If", х[5*(i-1)+(j-1)]);
На рис. 3.1 приведена иллюстративная схема одномерного массива х[ ] и виртуальной (существующей только в воображе нии программиста, использующего макроопределения) матрицы для рассмотренной программы.
Отлнчия макросов от функций. Сравнивая макросы с функциями, заметим, что в отличие от функции, определение которой всегда присутствует в одном экземпляре, тексты, фор мируемые макросом, вставляются в программу столько раз, сколько раз используется макрос. Обратим внимание на еще одно отличие: функция определена для данных того типа, кото-
156 Программирование на языке Си
Одномерный массив х[20]:
0 |
1 |
2 |
3 |
4 |
5 |
16 |
17 |
18 |
19 |
0.0 |
1.0 |
2.0 |
3.0 |
4.0 |
5.0 |
16.0 |
17.0 |
18.0 |
19.0 |
|
|
М=5 |
|
|
|
|
|
JV1—J |
9 |
|
|
|
|
j-й столбец |
|
|
|
|
|
|
|
|
|
|
I |
3.0 |
4.0 |
|
|
|
|
|
0.0 |
1.0 |
2.0 |
Матрица А |
|||
|
|
|
|
|
|
|
|
||
|
|
|
5.0 |
6.0 |
7.0 |
8.0 |
9.0 |
с |
|
|
|
|
10.0 |
11.0 |
12.0 |
13.0 |
14.0 |
размерами |
|
|
|
|
15.0 |
16.0 |
17.0 |
18.0 |
19.0 |
4 x 5 |
|
Рис. 3.1. Имитация матрицы с помощью макроопределения
иодномерного массива:
А(1,1) соответствует х[5*(1-1)+(1-1)] = = х[0]
А(1,2) соответствует х[5*(1-1)+(2-1)] = = х[1]
А(2,1) соответствует х[5*(2-1)+(1-1)] = = х[5]
А(3,4) соответствует х[5*(3-1)+(4-1)] = = х[13]
рый указан в спецификации ее параметров и возвращает значе ние только одного конкретного типа. Макрос пригоден для об работки параметров любого типа, допустимых в выражениях, формируемых при обработке строки замещения. Тип получае мого значения зависит только от типов параметров и от самих выражений. Таким образом, макрос может заменять несколько функций. Например, приведенные макросы т а х () и ABS() вер но работают для параметров любых целых и вещественных ти пов, а результат зависит только от типов параметров.
Отметим как рекомендацию, что для устранения неодно значных или неверных использований макроподстановок пара метры в строке замещения и ее саму полезно заключать в скобки.
Еще одно отличие: фактические параметры функций - это выражения, а аргументы вызова макроса - препроцессорные
Глава 3. Препроцессорные средства |
157 |
лексемы, разделенные запятыми. Аргументы макрорасширени ям не подвергаются.
Препроцессорные операции в строке замещения. В после довательности лексем, образующей строку замещения, преду сматривается использование двух операций - '#' и '##', первая из которых помещается перед параметром, а вторая - между любыми двумя лексемами. Операция '#' требует, чтобы текст, замещающий данный параметр в формируемой строке, заклю чался в двойные кавычки.
В качестве полезного примера с операцией '#' рассмотрим следующее макроопределение:
#define print (A) printf (#A"=%f', А)
К макросу print (А) можно обращаться, подставляя вместо параметра А произвольные выражения, формирующие резуль таты вещественного типа. Пример:
print (sin (а/2)); - обращение к макросу;
printf ("sin (а/2)'"-% f', sin (а/2)); - макрорасширение.
Фрагмент программы:
double а=3.14159; print (sin (а/2));
Результат выполнения (на экране дисплея):
sin (а/2)=1.0
Операция допускаемая только между лексемами строки замещения, позволяет выполнять конкатенацию лексем, вклю
чаемых в строку замещения. |
|
Чтобы пояснить роль и место операции |
рассмотрим, как |
будут выполняться макроподстановки в следующих трех макро определениях с одинаковым списком параметров и одинаковы ми аргументами.
#define zero (a, b, с, d) a (bed) #define one (a, b, с, d) a (b с d) #define two (a, b, c, d) a (b##c##d)
158 Программирование на языке Си
Макровызов: |
|
|
Результат макроподстановки: |
|
zero(sin, х, + , у) |
sin(bed) |
|||
one(sin, |
х, |
+, |
у) |
sin(x + y) |
two(sin, |
x, |
+, |
у) |
sin(x+y) |
В случае zero() последовательность "bed" воспринимается как отдельный идентификатор. Замена параметров b, с, d не вы полнена. В строке замещения макроса опе() аргументы отделе ны пробелами, которые сохраняются в результате. В строке замещения для макроса tw o() использована операция что позволило выполнить конкатенацию аргументов без пробелов между ними.
3.6.В спом огательны е ди ректи вы
Вотличие от директив ^include, #define и всего набора ко манд условной компиляции (#if...) рассматриваемые в данном параграфе директивы не так часто используются в практике программирования.
Препроцессорные обозначения строк. Для нумерации строк можно использовать директиву
#Нпе константа
которая указывает компилятору, что следующая ниже строка текста имеет номер, определяемый целой десятичной констан той. Директива может одновременно изменять не только номер строки, но и имя файла:
#Нпе константа "имя_файла"
Как пишут в литературе по языку Си [5], директиву #line можно "встретить" сравнительно редко, за исключением случая, когда текст программы на языке Си генерирует какой-то другой препроцессор.
Смысл директивы #Нпе становится очевидным, если рас- , смотреть текст, который препроцессор формирует и передает на
Глава 3. Препроцессорные средства |
159 |
компиляцию. После препроцессорной обработки каждая строка имеет следующий вид:
имяфайла номер_строки т екст наязыкеСи
Например, пусть препроцессор получает для обработки файл "www.c" с таким текстом:
#define N |
3 |
/* Определение константы */ |
void main |
( ) |
|
{
#line 23 "file.с" double z[3*N];
)
После препроцессора в файле с именем "www.i" будет полу чен следующий набор строк:
WWW.с |
i |
void main( ) |
WWW. с |
2 |
|
www.c |
3 |
{ |
www.c |
4 |
double z[3*3] |
file.c |
23 |
|
file.c |
24 |
) |
Обратите внимание на отсутствие в результирующем тексте препроцессорных директив и комментария. Соответствующие строки пусты, но включены в результирующий текст. Для них выделены порядковые номера (1 и 4). Следующая строка за ди рективой #Нпе обозначена в соответствии со значением кон станты (23) и указанным именем файла "file.c".
Реакция иа ошибки. Обработка директивы
te rro r последовательность_лексем
приводит к выдаче диагностического сообщения в виде, опре деленном последовательностью лексем. Естественно примене ние директивы terror совместно с условными препроцессорными командами. Например, определив некото рую препроцессорную переменную NAME
«define NAME 5
160 |
Программирование на языке Си |
в дальнейшем можно проверить ее значение и выдать сообще ние, если у NAME окажется другое значение:
#if (NAME 1= 5)
terror NAME должно быть равно 5 !
Сообщение будет выглядеть так:
Error <ямя_фаяла> <номер_строки>:
Error directive: NAME должно быть равно 5 !
В интегрированной среде (например, Turbo С) сообщение будет выдано на этапе компиляции в таком виде:
Fatal <имя_файла> <номер_строки>:
Error directive: NAME должно быть равно 5 <
В случае выявления такой аварийной ситуации дальнейшая препроцессорная обработка исходного текста прекращается, и только та часть текста, которая предшествует условию #if..., по падает в выходной файл препроцессора.
Пустая директива. Существует директива, использование которой не вызывает никаких действий. Она имеет вид:
#
Прагмы. Директива
#pragma последовательность_лексем
определяет действия, зависящие от конкретной реализации компилятора. Например, в некоторые компиляторы входит ва риант этой директивы для извещения компилятора о наличии в тексте программы команд на языке ассемблера.
Возможности команды #pragma могут быть весьма разнооб разными и важными. Стандарта для них не существует. Если конкретный препроцессор встречает прагму, которая ему неиз вестна, он ее просто игнорирует как пустую директиву. В неко торых реализациях включена прагма
#pragma pack(n)
где п может быть 1, 2 или 4.
Глава 3. Препроцессорные средства |
161 |
Прагма "pack" позволяет влиять на упаковку смежных эле ментов в структурах и объединениях (см. гл. 6).
Соглашение может быть таким:
раск(1) - выравнивание элементов по границам байтов; раск(2) - выравнивание элементов по границам слов; раск(4) - выравнивание элементов по границам двойных слов.
В некоторые компиляторы включены прагмы, позволяющие изменять способ передачи параметров функциям, порядок по мещения параметров в стек и т.д.
3.7.В строенны е (заранее определенны е)
макроим ена
Существуют встроенные (заранее определенные) макроиме на, доступные препроцессору во время обработки. Они позво ляют получить следующую информацию:
__LINE__- |
десятичная константа - номер текущей обраба |
|
тываемой строки файла с программой на Си. |
|
Принято, что номер первой строки исходного |
|
файла равен 1; |
__FILE__- |
строка символов - имя компилируемого файла. |
|
Имя изменяется всякий раз, когда препроцес |
|
сор встречает директиву #include с указанием |
|
имени другого файла. Когда включения файла |
|
по команде #include завершаются, восстанав |
|
ливается предыдущее значение макроимени |
|
__FILE__; |
__DATE__- |
строка символов в формате: "месяц число год", |
|
определяющая дату начала обработки исходно |
|
го файла. Например, после препроцессорной |
|
обработки текста программы, выполненной 10 |
|
марта 1997 года, оператор |
|
plrintf(__DATE__); |
,-1-3124 |
станет таким: |
t>rintf("Mar Ю 1997м); |