книги / Практикум по программированию на языке Си
..pdf{
printf("PI = %f",PI); return 0;
}
Результат препроцессорной обработки:
.....
# 2 "03_03.c" 2 int main()
{
printf("PI = %f",3.14159 ); return 0;
}
Результаты выполнения программы:
PI = 3.141590
Обратите внимание, что препроцессорная константа PI не заменена последовательностью символов 3.14159 внутри форматной строки.
ЗАДАЧА 03-04. С помощью #define определите препроцессорный идентификатор PROVERB (пословица) и свяжите с ним символьную строку (строковую константу). Выведите строку на экран дисплея, используя обозначение PROVERB.
/* 03_04.c - #define - подстановки строк */ #include <stdio.h>
#define PROVERB "There are really no mistakes \ in life - only lessons."
int main()
{
puts(PROVERB); return 0;
}
Результат препроцессорной обработки:
.....
int main()
{
71
puts("There are really no mistakes \ in life - only lessons." );
return 0;
}
Результаты выполнения программы:
There are really no mistakes in life - only lessons.
Результаты, по-видимому, не требуют комментариев. Отметим только, что входящие в строку замещения кавычки сохранены при подстановке. Кроме того, напомним, что компилятор на второй фазе обработки текста программы удаляет каждую пару «код символа '\'» и следующий за ним после любого числа пробелов "код начала новой строки". Тем самым строку замещения можно, как сделано в программе, переносить, т.е. размещать в нескольких строках текста, а символом переноса служит символ '\'.
В строке замещения можно, как обычно, использовать эскейппоследовательности. Как в ней будет воспринят признак '\n' начала новой строки? Как "действует" код табуляции '\t'?
ЗАДАЧА 03-05. Возьмите текст какого-либо стихотворения, представьте его в виде строки замещения в директиве #define, а затем выведите на экран, организуя естественное для стихотворения разбиение по строкам.
/* 03_05.c – эскейп-последовательности в строке замещения директивы #define */
#include <stdio.h>
#define POEM "Горные вершины \nСпят во тьме \ ночной; \nТихие долины \nПолны свежей мглой; \n\ Не пылит дорога, \nНе дрожат листы... \nПодожди \ немного, \nОтдохнешь и ты.\n\
\t \tМ.Ю. Лермонтов " int main()
{
puts(POEM); return 0;
}
72
Результат препроцессорной обработки:
.....
int main()
{
puts("Горные вершины \nСпят во тьме \
ночной; \nТихие долины \nПолны свежей мглой; \n\ Не пылит дорога, \nНе дрожат листы... \nПодожди \ немного, \nОтдохнешь и ты. \n\
\t \tМ.Ю. Лермонтов " ); return 0;
}
Результаты выполнения программы:
Горные вершины Спят во тьме ночной; Тихие долины
Полны свежей мглой; Не пылит дорога, Не дрожат листы...
Подожди немного, Отдохнешь и ты.
М.Ю. Лермонтов
С помощью директивы #undef препроцессорный идентификатор можно "отсоединить" от связанной с ним строки замещения. Затем препроцессорному идентификатору можно с помощью другой директивы #define поставить в соответствие новую строку замещения.
ЗАДАЧА 03-06. Последовательно "свяжите" препроцессорный идентификатор CONST с некоторыми знаменитыми константами и выведите их значения, именуя их идентификатором
CONST.
/* 03_06.c - смена строк замещения */ #include <stdio.h>
#define CONST 3.141593 int main()
73
{
printf("PI = %f\n", CONST); #undef CONST
#define CONST 2.718282 printf("e = %f\n", CONST);
#undef CONST
#define CONST 0.577216 printf("C = %f\n", CONST);
#undef CONST #define CONST 9.81
printf("g = %f\n", CONST); return 0;
}
Результат препроцессорной обработки:
.....
int main()
{printf("PI = %f\n", 3.141593 ); printf("e = %f\n", 2.718282 ); printf("C = %f\n", 0.577216 ); printf("g = %f\n", 9.81 );
return 0;
}
Результаты выполнения программы:
PI = 3.141593 e = 2.718282 C = 0.577216 g = 9.810000
ЭКСПЕРИМЕНТ. Удалите из текста программы 03_06.c одну из директив #undef CONST и выполните препроцессорную обработку.
/* 03_06_1.c - ошибка при смене строк замещения */
#include <stdio.h> |
/* 02 */ |
||
#define CONST 3.141593 |
/* 03 |
*/ |
|
int main() |
/* 04 |
*/ |
|
{ |
/* |
05 |
*/ |
printf("PI = %f\n", CONST); |
/* |
06 |
*/ |
74
#undef CONST |
|
/* 07 */ |
|
#define CONST 2.718282 |
/* 08 */ |
||
printf("e |
= %f\n", CONST); |
/* 09 */ |
|
#define CONST 0.577216 |
/* 10 */ |
||
printf("C |
= %f\n", CONST); |
/* 11 */ |
|
#undef CONST |
|
/* 12 */ |
|
#define CONST 9.81 |
/* 13 |
*/ |
|
printf("g |
= %f\n", CONST); |
/* 14 |
*/ |
return 0; |
|
/* 15 |
*/ |
} |
|
/* 16 |
*/ |
Предупреждение компилятора:
03_06_1.c:10: warning: `CONST' redefined – "CONST
повторно определен
03_06_1.c:8: warning: this is the location of the previous definition – это место предыдущего определения
Результат препроцессорной обработки:
.....
int main()
{
printf("PI = %f\n", 3.141593 ); printf("e = %f\n", 2.718282 ); printf("C = %f\n", 0.577216 ); printf("g = %f\n", 9.81 ); return 0;}
Правильные результаты выполнения программы:
PI = 3.141593 e = 2.718282 C = 0.577216
g= 9.810000
Взаголовочном файле limits.h введены препроцессорные идентификаторы, строки замещения которых определяют предельные значения целочисленных величин. Например, CHAR_BIT (число битов в байте), INT_MIN (минимальное значение для данных типа
75
int) и т.д. (Названные препроцессорные идентификаторы перечислены, например, в Приложении 2 пособия [3]).
ЗАДАЧА 03-07. Выведите на экран предельные значения для данных целочисленных типов, определяемые препроцессорными идентификаторами из файла limits.h. Сравните результаты с требованиями Стандарта языка Си, приведенными, например, в Приложении 2 пособия [3].
В следующей программе задача решена для части указанных препроцессорных идентификаторов:
/* 03_07.c – предельные значения целых величин */ #include <stdio.h>
#include <limits.h> int main( )
{
printf ("\nCHAR_BIT=%d", CHAR_BIT); printf ("\nSCHAR_MIN=%d\t\tSCHAR_MAX=%d",
SCHAR_MIN, SCHAR_MAX); printf ("\nUCHAR_MAX=%d", UCHAR_MAX); printf ("\nINT_MIN=%d\tINT_MAX=%d",
INT_MIN, INT_MAX);
printf (" \nLONG_MIN=%ld\tLONG_MAX=%ld", LONG_MIN, LONG_MAX);
return 0;
}
Результат препроцессорной обработки:
.....
int main( )
{
printf ("\nCHAR_BIT=%d", 8 );
printf ("\nSCHAR_MIN=%d\t\tSCHAR_MAX=%d", (-128) , 127 );
printf ("\nUCHAR_MAX=%d", 255 ); printf ("\nINT_MIN=%d\tINT_MAX=%d",
(-2147483647-1) ,2147483647 ); printf (" \nLONG_MIN=%ld\tLONG_MAX=%ld",
(-2147483647L-1L) , 2147483647L );
return 0;
}
76
Результаты выполнения программы:
CHAR_BIT=8
SCHAR_MIN=-128 SCHAR_MAX=127 UCHAR_MAX=255
INT_MIN=-2147483648 INT_MAX=2147483647 LONG_MIN=-2147483648 LONG_MAX=2147483647
В заголовочном файле float.h введены препроцессорные идентификаторы, строки замещения которых определяют предельные значения, вводимые реализацией (компилятором) для вещественных величин. (Названные препроцессорные идентификаторы перечислены, например, в Приложении 2 пособия [3].)
ЗАДАЧА 03-08. Выведите на экран предельные значения для данных вещественных типов, определяемые препроцессорными идентификаторами из файла float.h. Сравните их с требованиями Стандарта языка Си, приведенными, например, в Приложении 2 пособия [3].
В следующей программе задача решена для части указанных препроцессорных идентификаторов:
/* 03_08.c – предельные значения вещественных величин */
#include <stdio.h>
#include <float.h> /*определение предельных констант*/
int main( )
{
printf ("\nFLT_EPSILON=%e", FLT_EPSILON); printf ("\nDBL_EPSILON=%e", DBL_EPSILON); printf ("\nFLT_MIN=%e\tFLT_MAX=%e",
FLT_MIN,FLT_MAX);
printf ("\n\t\t\tDBL_MAX=%e", DBL_MAX); printf ("\nFLT_MANT_DIG=%d", FLT_MANT_DIG); printf ("\nDBL_MANT_DIG=%d", DBL_MANT_DIG); return 0;
}
77
Результат препроцессорной обработки:
.....
int main( )
{
printf ("\nFLT_EPSILON=%e", __dj_float_epsilon ); printf ("\nDBL_EPSILON=%e", __dj_double_epsilon ); printf ("\nFLT_MIN=%e\tFLT_MAX=%e",
__dj_float_min ,__dj_float_max );
printf ("\n\t\t\tDBL_MAX=%e", __dj_double_max ); printf ("\nFLT_MANT_DIG=%d", 24 );
printf ("\nDBL_MANT_DIG=%d", 53 ); return 0;
}
Результаты выполнения программы:
FLT_EPSILON=1.192093e-07
DBL_EPSILON=2.220446e-16
FLT_MIN=1.175494e-38 FLT_MAX=3.402823e+38
DBL_MAX=1.797693e+308
FLT_MANT_DIG=24
DBL_MANT_DIG=53
ЗАДАНИЕ. Просмотрите с помощью текстового редактора содержимое файлов limits.h и float.h из каталога подключаемых файлов компилятора. Найдите в файлах директивы #define, вводящие препроцессорные идентификаторы для предельных значений целых и вещественных величин.
3.3. Препроцессорное управление включением текста
Если вы просматривали тесты заголовочных файлов (например, уже использованных в наших программах limits.h, float.h, stdio.h), то могли обратить внимание на обрамление их содержимого такими директивами:
#ifndef идентификатор
#define идентификатор
78
... текст
#endif
Это "защита" файлов от непреднамеренных повторных включений. Директивы #ifndef и #endif совместно с директивой #define позволяют защитить текст от повторных включений в программу за счет косвенных или прямых повторных использований директивы
#include. Обратите внимание, что директива #ifndef проверяет не значение, сопоставленное с идентификатором, а только тот факт, что он "в данном месте текста не является препроцессорным", т.е. не определен ранее (выше по тексту) с помощью какой-либо директивы
#define. Совместно с упомянутыми средствами может использоваться директива #else. Ее применение позволяет вводить альтернативную "ветвь" по отношению к фрагменту текста, размещенному вслед за #ifndef.
ЗАДАЧА 03-09. Оформите текст с препроцессорными директивами, состоящий их трех частей (разделов):
часть 1 - включается в программу один раз; часть 2 - включается в программу всегда;
часть 3 - не включается только при первом просмотре.
Текст с названной препроцессорной "регулировкой" включений может быть таким (он размещен в текстовом файле 03_09.txt):
/*03_09.txt – текст, распределяемый по просмотрам */ #ifndef _TEXT
#define _TEXT
Этот текст будет использован при первом просмотре
#else
Этот текст будет использован при повторных просмотрах
#endif
Этот текст будет использован при каждом просмотре
Проверочная "программа" с несколькими включениями файла
03_09.txt:
/* 03_09.c – препроцессорное распределение текста */ #include "03_09.txt"
#include "03_09.txt"
79
#include "03_09.txt" #include "03_09.txt"
Результат препроцессорной обработки:
#1 "03_09.c"
#1 "03_09.txt" 1
Этот текст будет использован при первом просмотре Этот текст будет использован при каждом просмотре
#2 "03_09.c" 2
#1 "03_09.txt" 1
Этот текст будет использован при повторных просмотрах Этот текст будет использован при каждом просмотре
#3 "03_09.c" 2
#1 "03_09.txt" 1
Этот текст будет использован при повторных просмотрах Этот текст будет использован при каждом просмотре
#4 "03_09.c" 2
#1 "03_09.txt" 1
Этот текст будет использован при повторных просмотрах Этот текст будет использован при каждом просмотре
#5 "03_09.c" 2
3.4.Несколько полезных макроопределений
Применение макропроцессорных возможностей и средств препроцессора мы еще раз более подробно рассмотрим позже в теме 10, когда будут изучены те конструкции языка Си, для формирования которых интересно и выгодно использовать препроцессорную обработку. Но в ближайших темах удобно применять препроцессорные макросы. Одно из таких макроопределений описано в главе 3 посо-
бия [3]:
#define PRINT(A) printf(#A”=%f”,A);
К этому макросу можно обратиться по имени PRINT, подставив вместо параметра A выражение типа float или double. В результате
80