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

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

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

ной обработки конструкцию 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