книги / Практикум по программированию на языке Си
..pdfисполнения программы будут выведены изображение выражения и его значение.
ЗАДАЧА 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