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

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

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

{

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