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

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

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

исполнения программы будут выведены изображение выражения и его значение.

ЗАДАЧА 03-10. По аналогии с PRINT() напишите макроопределения для вывода изображений и значений выражений разных базовых типов.

В следующей программе эта задача решена для типов float, double (в двух видах с фиксированной и плавающей точкой), и для выражений типа int:

// 03_10.c - макросы вывода выражений и их значений

#include <stdio.h>

#define PRINTF(EXPRESSION) \ printf(#EXPRESSION"=%f\n",EXPRESSION)

#define PRINTD(EXPRESSION) \ printf(#EXPRESSION"=%e\n",EXPRESSION)

#define PRINTI(EXPRESSION) \ printf(#EXPRESSION"=%d\n",EXPRESSION)

int main()

{

PRINTF(33.22f);

PRINTF(33.22);

PRINTD(33.22);

PRINTI(33322); return 0;

}

Результат препроцессорной обработки:

....

int main()

{

printf("33.22f""=%f\n", 33.22f ) ; printf("33.22""=%f\n", 33.22 ) ; printf("33.22""=%e\n", 33.22 ) ; printf("33322""=%d\n", 33322 ) ; return 0;

}

Результаты выполнения программы:

81

33.22f=33.220001

33.22=33.220000

33.22=3.322000e+01

33322=33322

В результатах препроцессорной обработки обратите внимание, что конструкция #EXPRESSION заменяется на заключенное в кавычки изображение соответствующего выражения-аргумента, использованного при обращении к макросу. Затем форматная строка функции printf() формируется как конкатенация двух смежных строк. Макрос PRINTF() позволяет выводить в формате с фиксированной точкой и значения типа float, и значения типа double.

При вводе данных с клавиатуры удобно, чтобы пользователь получал на экране "приглашение", поясняющее смысл ожидаемых от него данных. Часто достаточно перед вводом вывести на экран имя соответствующей переменной с последующим знаком равенства (присваивания). Задачу легко решает такая пара операторов, настроенная на "получение" значения переменной name типа int:

printf(“\n name=”); scanf(“%d”,&name);

Оба оператора представляют собой обращения к библиотечным функциям. С функцией printf() мы уже знакомы. Использование функции scanf(), обеспечивающей форматный ввод из стандартного входного потока, обычно у начинающих вызывает затруднения. Связано это с необходимостью использовать в качестве аргумента адрес той переменной, значение которой вводит функция scanf(). Не пытаясь пересказывать содержание соответствующих разделов пособия [3], достаточно указать, что для получения адреса нужно применить унарную операцию &. Ее знак помещают перед именем переменной в аргументе функции scanf(). Более подробному изучению функции scanf() посвящен параграф 4.7 следующей темы. Здесь мы рассмотрим, как с помощью препроцессора "настраивать" вызовы функций printf() и scanf() на имена разных переменных, значения которых нужно вводить с клавиатуры.

ЗАДАЧА 03-11. Предложенную пару обращений к функциям printf() и scanf() оформите в виде строки замещения в макроопре-

82

делении. Параметр макроопределения – имя переменной, значение которой нужно ввести. Напишите несколько макроопределений для ввода значений разных типов.

Следующая программа решает задачу для переменных типов float, double, int. В программе использованы макроопределения для вывода данных типов float, double, int из программы 03_10.с.

/* 03_11.c - макросы ввода значений переменных */ #include <stdio.h>

#define READF(VARIABLE) \

{printf(#VARIABLE"="); scanf("%e",&VARIABLE);} #define READD(VARIABLE) \

{printf(#VARIABLE"="); scanf("%le",&VARIABLE);} #define READI(VARIABLE) \

{printf(#VARIABLE"="); scanf("%d",&VARIABLE);}

#define PRINTF(EXPRESSION) \ printf(#EXPRESSION"=%f\n",EXPRESSION)

#define PRINTD(EXPRESSION) \ printf(#EXPRESSION"=%e\n",EXPRESSION)

#define PRINTI(EXPRESSION) \ printf(#EXPRESSION"=%d\n",EXPRESSION)

int main()

{

float value; double member; int sing; READF(value); PRINTF(value); READD(member); PRINTD(member); READI(sing); PRINTI(sing); return 0;

}

Результат препроцессорной обработки:

....

int main()

83

{

float value; double member; int sing;

{printf("value""="); scanf("%e",& value ) ;}; printf("value""=%f\n", value ) ;

{printf("member""="); scanf("%le",& member ) ;}; printf("member""=%e\n", member ) ;

{printf("sing""="); scanf("%d",& sing ) ;}; printf("sing""=%d\n", sing ) ;

return 0;

}

Результаты выполнения программы:

value=39.56 <ENTER> value=39.560001 member=39.56<ENTER> member=3.956000e+01 sing=12345<ENTER> sing=12345

Врезультатах выполнения программы <ENTER> означает нажатие соответствующей клавиши ENTER.

Встроке замещения макросов READ…() более одного оператора (два обращения к библиотечным функциям). Для предупреждения возможности ошибок при использовании макросов операторы строки замещения обрамлены фигурными скобками. Это позволит исполь-

зовать обращение к макросу READ…() в качестве тела оператора цикла или оператора, выполняемого после проверки какого-либо условия. Обратите внимание на неточность представления введенного значения в переменной value типа float. Отметьте необходимость спецификации %le для ввода значения типа double.

Коротко о важном

По материалам темы сформулированы следующие утверждения. В конце большинства из них приведены номера программ, подтверждающих справедливость этих утверждений.

84

!Каждая директива препроцессора начинается с символа #, перед которым в данной строке текста программы не может быть символов, отличных от пробелов.

!Препроцессор всегда рассматривает программу только как текстовый файл, не обращает внимание на синтаксис языка Си и реагирует только на директивы (команды) препроцессора

(03_01.с, 03_02.с).

!Выходом (результатом работы) препроцессора всегда служит текст (текстовый файл), преобразованный из исходного текста за счет выполнения препроцессорных директив (03_01.с и др.).

!На входе препроцессора текст, на выходе препроцессора тоже текст (03_01.с и др.).

!Включаемый директивой include файл может не быть текстом на Си – это любой текст (03_01.c).

!Пустые строки, вводимые символом # с последующим пробелом, не будут учитываться при дальнейшей обработке текста компилятором (не переносятся в выходной текст).

!При препроцессорной "сборке" программы из отдельных файлов

вних могут находиться "куски", синтаксически незавершенные, лишь бы при их объединении был получен осмысленный текст на

языке Си (03_02.с).

!Подстановки вместо препроцессорных идентификаторов соответствующих строк замещения выполняются "ниже" директивы

#define везде, кроме комментариев, строк и символьных констант

(03_03.с, 03_04.с, 03_05.с).

!Препроцессорный идентификатор не обязательно связан со строкой замещения.

!Препроцессорный идентификатор может быть не определен или может быть определен без значения, т.е. не связан.

!Директива #undef освобождает препроцессорный идентификатор от связи со строкой замещения и отменяет его "препроцессор-

ность" (03_06.с).

!Препроцессорный идентификатор можно последовательно связывать с разными строками замещений, предварительно (директивой #undef) освобождая его от предыдущей связи (03_06.с).

!С помощью #define удобно вводить константу, которая в качестве имени имеет препроцессорный идентификатор (именно он виден программисту в тексте программы), а в качестве значения – строку замещения (именно ее использует компилятор, формируя код программы) (03_03.с – 03_08.с).

85

!Заголовочные файлы стандартной библиотеки "полны" констант, введенных директивами #define. Например, файлы limits.h и float.h вводят константы для представления предельных значений арифметических данных (03_07.с, 03_08.с).

!С помощью директив препроцессора текст можно разметить таким образом, что он будет "чувствителен" к количеству его включений в программу (03_09.с).

!Текст строки замещения, выводимый директивой #define, может быть параметризован, тем самым директива #define вводит макроопределение (03_10.с).

!В макроопределениях полезна препроцессорная операция #. С ее помощью аргумент макроса при подстановке помещается в ка-

вычки (03_10.с, 03_11.с).

!Если в строке замещения макроса несколько операторов языка Си, то их следует обрамлять фигурными скобками { } (03_11.с).

!Строку директивы #define можно размещать в нескольких строках текста, используя символ переноса '\'.

Тема 4

Переменные, операции, выражения

Переменная – это то, что существует во времени и чье значение, если его не трогать, остается постоянным!

Э. Дейкстра. Дисциплина программирования

Основные вопросы темы

!Правила выбора имен в программе.

!Использование typedef для ввода удобных названий типов.

!Особенности инициализации глобальных и внутренних переменных.

!Локализация объектов и экранирование внешних определений имен внутренними определениями.

!Приоритеты арифметических операций.

!Особенности операций декремента и инкремента.

!Правила вычисления выражений с целочисленными операндами.

!Запятая в качестве операции.

!Логические значения. Ранги операций отношений и логических операций.

!Тернарная (условная) операция и вложение условных выражений.

!Битовое представление целых знаковых и беззнаковых величин.

!Поразрядные операции и операции сдвигов.

!Унарная операция & и функция scanf().

!Ввод целых, вещественных и символьных данных функцией scanf().

4.1. Имена, вводимые программистом

ЗАДАЧА 04-01. Нарушая правила языка Си, определите в программе переменные с "недозволенными" обозначениями:

!имя совпадает с одним из служебных слов (if, return, break);

87

!имя включает символы, не входящие в алфавит языка Си (например, русские буквы);

!в имени используется пробел либо другой недопустимый для имени символ ($, %);

!имя совпадает с препроцессорным идентификатором, введенном в заголовочном файле (INT_MAX, NULL);

!имя совпадает с именем функции из стандартной библиоте-

ки (puts, printf).

/* 04_01.c - ошибки в именах переменных */

/* 02 */

#include <stdio.h>

int main ()

/* 03 */

{

/* 04 */

int if=14;

/* 05 */

char return='g';

/* 06 */

float break=23.9;

/* 07 */

int суммарный_результат=888;

/* 08 */

long double sport$express=185.9L;

/* 09 */

long number one=23L;

/* 10 */

float red%boll=67.88f;

/* 11 */

int NULL=12;

/* 12 */

double INT_MAX=0.77;

/* 13 */

long double puts=123.67L;

/* 14

*/

char printf='2';

/* 15

*/

return 0;

/* 16

*/

}

/* 17

*/

Некорректная трансляция:

04_01.c: In function `main':

04_01.c:5: parse error before `if'

04_01.c:10: syntax error before `one'

04_01.c:11: syntax error before `%'

Как показывают результаты трансляции, этот конкретный компилятор, использованный автором, распознал не все отступления от синтаксиса языка.

ЗАДАНИЕ. Последовательно удаляя из текста программы строки, в которых компилятор обнаружил ошибки, добейтесь безошибочной трансляции оставшегося текста.

88

Уберем из текста программы (превратим в комментарии) строки 5, 10, 11, в которых выявлены ошибки, и повторим трансляцию. Получим при компиляции другие сообщения об ошибках:

04_01.c: In function `main':

04_01.c:6: parse error before `return'

04_01.c:12: parse error before `0'

"Закомментируем" содержимое строк 6, 12. Получим сообщения:

04_01.c: In function `main':

04_01.c:7: parse error before `break'

"Закомментируем" содержимое строки 7:

04_01.c: In function `main':

04_01.c:8: parse error before character 0361

"Закомментируем" содержимое строки 8. Компиляция пройдет без ошибок.

Для иллюстрации зависимости ошибки в строке 12 от препроцессорной "определенности" имени NULL, восстановим определение в строке 12:

int NULL=12;

Если теперь выполнить компиляцию, то получим уже приведенное сообщение об ошибке. "Закомментируем" при наличии строки 12 содержимое строки 2, т.е. уберем из программы директиву #include <stdio.h>, теперь компиляция пройдет без ошибок.

ЗАДАЧА 04-02. В предыдущей задаче компилятор, обрабатывая программу 04_01.с, не распознал ошибок в именах sport$express, INT_MAX, puts, printf. Преобразуйте программу

04_01.с таким образом, чтобы неверные использования перечисленных имен проявились в виде ошибок.

/* 04_02.c - ошибки в именах переменных */

/* 02

*/

#include

<stdio.h>

#include

<limits.h>

/* 03

*/

int main

()

/* 04

*/

{

 

/* 05

*/

 

 

 

89

long double sport$express=185.9L;

/* 06 */

double INT_MAX=0.77;

/* 07 */

long double puts=123.67L;

/* 08 */

char printf='2';

/* 09 */

puts("The result is correct!");

/* 10 */

printf("The value of sport$express is %Le",/* 11

*/

sport$express);

/* 12

*/

return 0;

/* 13

*/

}

/* 14

*/

Некорректная трансляция. Сообщения компилятора об ошибках:

04_02.c: In function `main':

04_02.c:7: parse error before `2147483647'

04_02.c:10: called object is not a function

04_02.c:12: called object is not a function

В заголовочном файле limits.h идентификатор INT_MAX определен как препроцессорный со строкой замещения 2147483647. Замена этого идентификатора приведенной константой вызвала ошибку в строке 7. В строках 8 и 9 идентификаторы puts и printf использованы в качестве имен переменных. Эти определения экранировали описания соответствующих функций из заголовочного файла stdio.h. Поэтому в строках 10 и 12 компилятор зафиксировал ошибки, считая printf и puts только именами переменных, введенных программистом, а не именами библиотечных функций.

"Закомментируем" определение в строках 7, 8, 9. Компиляция пройдет успешно и при исполнении программы будет получен результат:

The result is correct!

The value of sport$express is 1.859000e+02.

Итак, единственное отступление от синтаксиса, которое допустил компилятор DJGPP, – использование в имени переменной символа $. Отметим, что символ $ не входит в алфавит языка Си, и компиляторы, строго следующие Стандарту [2], например, компилятор Turbo C, не допускают использования символа $ в идентификаторах.

По результатам экспериментов с программами 04_01.с, 04_02.с можно сделать некоторые выводы. Во-первых, компилятор

90