книги / Практикум по программированию на языке Си
..pdfной обработки конструкцию DISPLAY(33.22,%f), напечатана в двух строках. В электронном виде и на экране дисплея это одна длинная строка.
Макрос может конкурировать с функцией, причем с помощью обращения к макросу можно изменить значение переменной, выступающей в качестве фактического параметра, что невозможно при вызове функции. Эту возможность иллюстрируют макросы для чтения из стандартного входного потока (при вводе с клавиатуры) значений переменных типа float, double и int, приведенные в задаче 0311 из темы 3.
ЗАДАЧА 10-10. На основе принципов построения макроса
DISPLAY() из программы 10_08.с и макросов READF(), READD(), READI() из программы 03_11.с разработайте универсальный макрос для чтения из стандартного входного потока значений, тип которых (спецификация преобразования) определяется параметром макроса. В основной программе проиллюстрируйте применение "универсальных" макросов ввода и вывода.
"Универсальный" макрос ввода может быть таким:
#define READ(VARIABLE,FORMAT) \ {printf(#VARIABLE"="); scanf(#FORMAT,#VARIABLE);}
Обращения к макросу с данными разных типов:
float value; int number; READ(value,%f);
READ(number,%d);
Результат препроцессорной обработки:
float value; int number;
{printf("value""="); scanf("%f",&value );}; (printf("number""="); scanf("%d",&number );};
421
Так как строка замещения содержит несколько операторов (два обращения к функциям), то их нужно заключать в фигурные скобки. Это оградит нас от возможных ошибок при использовании обращений к макросу в цикле или в условном операторе.
Обратите внимание на роль препроцессорной операции ## между символом & и именем параметра VARIABLE. С ее помощью выполняется конкатенация символа & и значения строки замещения, соответствующей параметру VARIABLE.
Применение "универсальных" макросов READ() и DISPLAY() иллюстрирует следующая программа:
/* 10_10.c - "универсальные" макросы READ()
и DISPLAY() */ #include <stdio.h>
#define READ(VARIABLE,FORMAT) \ {printf(#VARIABLE"="); scanf(#FORMAT,#VARIABLE);}
#define DISPLAY(EXPRESSION, FORMAT) \ printf(#EXPRESSION"="#FORMAT,EXPRESSION)
int main()
{
float value; int number; READ(value,%f);
READ(number,%d);
DISPLAY(number,%d\n);
DISPLAY(value,%f\n); return 0;
}
Результат выполнения программы:
value=35.85<ENTER>
number=456<ENTER>
number=456
value=35.849998
Отметим, что в форматной строке при обращении к макросу DISPLAY(), кроме спецификации преобразования, использован символ '\n' – переход на следующую строку.
422
В макросе READ() строка замещения включает два оператора языка Си – два обращения к функциям стандартной библиотеки. Количество операторов и определений в строке замещения может быть произвольным. В строке замещения могут быть препроцессорные идентификаторы и вызовы макросов, вместо которых подставляются соответствующие расширения (строки замещения). В качестве примера рассмотрим следующую задачу:
ЗАДАЧА 10-11. Напишите макрос, обеспечивающий последовательный ввод (из стандартного входного потока) и суммирование вещественных чисел. Цикл чтения заканчивайте при вводе нулевого значения. В основной программе получите и выведите сумму вводимых чисел.
Макрос для ввода и суммирования чисел может быть таким:
#define CYCLE(SUMMA) { \ float _MEMB; \ SUMMA=0.0;
do { READF(_MEMB) \ SUMMA += _MEMB; } \
while (_MEMB); }
В строке замещения макроса CYCLE(), кроме операторов языка Си, используется обращение к макросу READF(). Он обеспечивает ввод одного значения "с подсказкой". Определение READF() возьмем из программы 03_11.с (добавив только операцию ##):
#define READF(NAME) \
{ printf("number="); scanf("%e",#NAME); }
Обращение к макросу, где result – имя переменной типа float:
CYCLE(result);
Результат препроцессорной обработки:
{float _MEMB; result=0.0;
do { { printf("number="); scanf("%e",&_MEMB ); }
423
result += _MEMB;
}
while (_MEMB);
};
Вмакросе длинная строка замещения разделена на несколько строк с помощью символа переноса строк '\'. В результате препроцессорной подстановки в электронном варианте сформирована одна строка, которую при печати книги мы разместили на нескольких строках текста.
Следующая программа иллюстрирует применение макроса CYCLE(), решая основную задачу:
/* 10_11.c - макрос ввода и суммирования чисел */
#define CYCLE(SUMMA) |
{ |
\ |
float _MEMB; |
|
\ |
SUMMA = 0.0; |
|
\ |
do { READF(_MEMB) |
\ |
|
SUMMA += _MEMB; } \ |
||
while (_MEMB); |
} |
|
#define READF(NAME) \ |
|
|
{ printf("number="); scanf("%e",#NAME); } #include <stdio.h>
int main()
{
float result; CYCLE(result); printf("result=%f",result); return 0;
}
Результат исполнения программы:
number=23<ENTER>
number=12<ENTER>
number=345<ENTER>
number=0<ENTER>
result=380.000000
ЗАДАНИЕ. Проверьте, что взаимное размещение макроопределений CYCLE() и READF() не влияет на результат препроцессорных подстановок, лишь бы обращение к макросу CYCLE() раз-
424
мещалось в исходном тексте программы ниже определений
CYCLE() и READF().
Планомерное применение препроцессорных макроопределений может существенно упростить разработку однотипных программ. В качестве иллюстрации сказанного рассмотрим несколько задач.
ЗАДАЧА 10-12. Напишите макрос для вывода значений элементов одномерного целочисленного массива. Выводимые значения сопровождайте обозначениями индексированных элементов массива. Информацию размещайте в три колонки. В основной программе определите массив и, используя макрос, распечатайте значения его элементов.
Макрос для печати массива с целочисленными элементами может быть таким:
#define |
ARRAY_PRINT(N,ARRAY) |
\ |
{ int |
_i; |
\ |
for |
( _i=0; _i < N; _i++) |
\ |
{ printf("\t"#ARRAY"[%d] = %d",_i, ARRAY[_i]);\ |
||
} |
if ((_i+1) % 3 == 0) printf("\n"); |
\ |
|
\ |
|
} |
|
|
Обращение к макросу, где N – число элементов, ARRAY – имя массива (совпадение имен случайное):
ARRAY_PRINT(N,ARRAY)
Результаты препроцессорной обработки макрообращения:
{ int _i;
for ( _i=0; _i < N ; _i++)
{ printf("\t""ARRAY""[%d] = %d",_i, ARRAY [_i]); if ((_i+1) % 3 == 0) printf("\n");
}
}
425
Следующая программа иллюстрирует применение макроса ARRAY_PRINT(), решая основную задачу:
/* 10_12.c - макрос для печати целочисленного |
|
массива */ |
|
#include <stdio.h> |
\ |
#define ARRAY_PRINT(N,ARRAY) |
|
{ int _i; |
\ |
for ( _i=0; _i < N; _i++) |
\ |
{ printf("\t"#ARRAY"[%d] = %d",_i, ARRAY[_i]);\ |
|
if ((_i+1) % 3 == 0) printf("\n"); |
\ |
} |
\ |
} |
|
int main() |
|
{ |
|
int ar[]={1,2,3,4,5,6,7,8,9,10}; /* Обращение к макросу: */
ARRAY_PRINT(sizeof(ar)/sizeof(ar[0]),ar); return 0;
}
Результат выполнения программы:
ar[0] = 1 |
ar[1] = 2 |
ar[2] = 3 |
||||||
ar[3] = 4 |
ar[4] |
= |
5 |
ar[5] |
= |
6 |
||
ar[6] |
= |
7 |
ar[7] |
= |
8 |
ar[8] |
= |
9 |
ar[9] |
= |
10 |
|
|
|
|
|
|
ЗАДАНИЕ. Удалите из макроопределения первый параметр, задающий количество элементов массива. Заголовок цикла в строке замещения этого макроса может быть таким:
for(_i = 0; _i < sizeof(ARRAY) / sizeof(ARRAY[0]); _i++).
В отличие от макроса ARRAY_PRINT новый макрос можно использовать только с именем массива в качестве параметра. Указатель на массив или его часть недопустим в качестве его аргумента.
ЗАДАЧА 10-13. Напишите и используйте макрос для суммирования значений элементов одномерного массива (array) из заданного количества (n) элементов. В основной программе определите целочисленный массив и вычислите сумму его элементов.
426
Макрос суммирования значений элементов одномерного массива:
#define ARRAY_SUM(ARRAY,N,RESULT) { int _i; \ for (_i=0, RESULT= 0; _i < N; _i++) \
RESULT+=ARRAY[_i]; }
Обращение к макросу, где map – имя массива, 12 – число элементов, sum – имя переменной со значением суммы:
ARRAY_SUM(map,12,sum)
Результат препроцессорной обработки макрообращения:
{int _i;
for (_i=0, sum = 0; _i < 12 ; _i++) sum += map [_i];
}
В приведенном макросе не наложено явных ограничений на тип элементов массива и формируемой суммы. И массив, и переменная, используемая для вычисления суммы, определяются вне макроопределения. Проиллюстрируем применение макроса для суммирования значений элементов целочисленного массива:
/* 10_13.c - макрос для суммирования элементов массива */
#include <stdio.h>
#define ARRAY_SUM(ARRAY,N,RESULT) { int _i; \
for (_i=0, RESULT= 0; _i < N; _i++) |
\ |
RESULT+=ARRAY[_i]; } |
|
int main() |
|
{ |
|
int ar[]={1,2,3,4,5,6,7,8,9,10}; |
|
int sum; |
|
/* Обращение к макросу: */ |
|
ARRAY_SUM(ar,sizeof(ar)/sizeof(ar[0]),sum) |
|
printf("summa=%d",sum); |
|
return 0; |
|
} |
|
Результат выполнения программы: |
|
summa=55 |
|
427
ЗАДАЧА 10-14. Сформируйте массив, значениями N первых элементов которого служат N членов последовательности Фибоначчи. Первые два элемента последовательности E1 и E2 нужно ввести как исходные данные. N задано на препроцессорном уровне. Необходимо напечатать все N элементов, затем подсчитать их сумму и вывести на печать ее значение.
Для решения задачи (не очень интересной в содержательном смысле) воспользуемся набором препроцессорных макросов. Большинство из них мы уже ввели:
DISPLAY() – в задаче 10-08;
READ() – в задаче 10-10;
ARRAY_PRINT() – макрос для печати значений элементов массива в задаче 10-12;
ARRAY_SUM() – макрос для суммирования значений элементов массива в задаче 10-13.
"Заполнить" уже определенный массив значениями N первых членов последовательности Фибоначчи позволяет следующий макрос:
#define FIBO_SERIES(N,FIBO_ARRAY,F1,F2) \
{int _i;\ FIBO_ARRAY[0] = F1;\ FIBO_ARRAY[1] = F2;\
for ( _i=2; _i < N; _i++)\ FIBO_ARRAY[_i] = \
FIBO_ARRAY[_i-1] + FIBO_ARRAY[_i-2];\
}
Обращение к макросу:
FIBO_SERIES(N,fibo,E1,E2)
Результаты препроцессорной обработки:
{ int |
_i; |
|
fibo [0] |
= |
E1 ; |
fibo [1] = |
E2 ; |
|
for ( |
_i=2; _i < |
N |
; |
_i++) |
fibo [_i-2]; |
} |
||
fibo [_i] |
= |
fibo |
[_i-1] + |
428
Теперь у нас есть все макросредства для решения задачи.
/* 10_14.c - массив с элементами последовательности Фибоначчи */
#include <stdio.h> |
|
/* Формирование массива с элементами |
|
последовательности Фибоначчи */ |
\ |
#define FIBO_SERIES(N,FIBO_ARRAY,F1,F2) |
|
{ int _i; |
\ |
FIBO_ARRAY[0] = F1; |
\ |
FIBO_ARRAY[1] = F2; |
\ |
for ( _i=2; _i < N; _i++) |
\ |
FIBO_ARRAY[_i] = |
\ |
FIBO_ARRAY[_i-1] + FIBO_ARRAY[_i-2];\ |
}
/* Печать массива с целочисленными элементами */
#define |
ARRAY_PRINT(N,ARRAY) |
\ |
{ int |
_i; |
\ |
for |
( _i=0; _i < N; _i++) |
\ |
{ printf("\t"#ARRAY"[%d] = %d",_i, ARRAY[_i]);\ |
||
} |
if ((_i+1) % 3 == 0) printf("\n"); |
\ |
|
\ |
}
/* Макрос для суммирования элементов массива */
#define ARRAY_SUMMA(ARRAY,N,RESULT) |
{ int _i; \ |
for (_i=0, RESULT= 0; _i < N; _i++) |
\ |
RESULT+=ARRAY[_i]; } |
|
/* Макрос для печати выражения */ #define DISPLAY(EXPRESSION, FORMAT) \
printf(#EXPRESSION"="#FORMAT,EXPRESSION)
#define N 3 #define E1 4 #define E2 9
int main()
{
int fibo[N]; int sum;
FIBO_SERIES(N,fibo,E1,E2);
429
ARRAY_PRINT(N,fibo); ARRAY_SUMMA(fibo,N,sum); DISPLAY(sum,%d);
return 0;
}
Результаты выполнения программы:
fibo[0] = 4 |
fibo[1] = 9 |
fibo[2] = 13 |
sum=26
При разработке макросов, формирующих те или иные выражения, в строке замещения параметры макроса часто соседствуют со знаками операций. В этом случае имена параметров в строке замещения следует заключать в круглые скобки.
ЗАДАНИЕ. Напишите макроопределение для вычисления значения квадратичной функции f(x)=ax2+bx+c.
Опасное определение:
#define QUADR1(A,B,C,X) A*X*X+B*X+C
Более корректное определение:
#define QUADR(A,B,C,X) (A)*(X)*(X)+(B)*(X)+(C)
Заметим, что учитывая ранги операций, можно в этом примере параметр С не заключать в скобки, но правило о скобках стоит соблюдать всегда.
ЗАДАЧА 10-15. Используйте макроопределения QUADR1(), QUADR() и DISPLAY() для вычисления и печати значения квадратичной функции f(x)=2x2+x+3 при x, равном значению выражения 5–5. (Правильный результат очевиден: f(5–5) равно 3.)
/* 10_15.c - некорректное и правильное макроопределения */
#include <stdio.h>
#define QUADR1(A,B,C,X) A*X*X + B*X + C
430