Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:

книги / Программирование на языке Си

..pdf
Скачиваний:
15
Добавлен:
12.11.2023
Размер:
17.16 Mб
Скачать

152

Программирование на языке Си

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м);

Соседние файлы в папке книги