книги / Практикум по программированию на языке Си
..pdfдавать неверный код исполняемой программы и тем самым защитил нас от будущих ошибок.
Чтобы продемонстрировать важность размещения прототипа именно до вызова функции, поместите прототип функции puts() после обращения к ней. В нашей программе возможно несколько вариантов, но принципиально различных два.
ЭКСПЕРИМЕНТ. Прототип функции puts() внутри тела функции main().
/* 01_02_2.c - неверный вызов puts() при неверном размещении прототипа */
int main () |
/* 03 */ |
|
{ |
/* 04 */ |
|
puts("The value of PI=", "3.14159"); |
/* 05 |
*/ |
int puts(const char *_s); |
/* 06 |
*/ |
return 0; |
/* 07 |
*/ |
} |
/* 08 |
*/ |
Результат трансляции – диагностические сообщения:
01_02_2.c: In function `main': (в функции `main':)
01_02_2.c:6: parse error before `int' (синтаксическая ошибка перед `int')
Трансляция прервана, исполнимый код программы не создан. Никаких сообщений о результатах проверки правильности обращения к функции puts() нет. Обратите внимание на номер строки (6), в которой компилятор распознал ошибку. В этой строке текста программы размещен прототип функции puts(). Нарушено следующее правило языка Си: "Описания (а прототип является описанием функции) и определения должны размещаться в блоке (а тело функции main, ограниченное скобками { и } есть блок) до исполнимых операторов (вызов функции есть исполнимый оператор).
ЭКСПЕРИМЕНТ. Прототип функции puts() размещен после функции main():
/* 01_02_3.c - неверный вызов puts() при неверном размещении прототипа */
21
int main ()
{
puts("The value of PI=", "3.14159"); return 0;
}
int puts(const char *_s);
Вернулись к результатам программы 01_02.с: безошибочная компиляция, неверное исполнение – выводится только значение первого аргумента.
Итак, выводы. Прототип, размещаемый после вызова функции, не может служить основой для проверки на этапе компиляции правильности обращения к этой функции. Правильное размещение прототипа – либо до блока (до функции), в которой происходит обращение к функции, либо в начале этого блока. (Последний вариант в наших экспериментах не показан. Читатель может самостоятельно проделать этот эксперимент.)
1.6. Роль заголовочного файла stdio.h
Теперь можно вернуться к объяснению роли заголовочного файла stdio.h. В этом текстовом файле приведены описания и определения, необходимые для правильного использования в программах тех функций стандартной библиотеки, которые нужны для ввода и вывода. Именно в файле stdio.h находятся прототипы библиотечных функций ввода-вывода, в том числе в stdio.h помещен прототип функции puts() в том виде, как мы его использовали в наших экспериментальных программах.
ЭКСПЕРИМЕНТ. В каталоге компилятора \INCLUDE найдите текстовый файл stdio.h и убедитесь, что в нем имеется прототип функции puts().
Проведенные эксперименты убедили нас, что прототипы должны размещаться до вызовов соответствующих функций. Наиболее удобно размещать прототипы в самом начале (в заголовке) программы. Именно поэтому файлы с именем вида идентификатор.h, содержащие описания и определения средств, необходимых для работы с той
22
или иной группой функций стандартной библиотеки, называются "заголовочными", а расширение h в имени файла ведет происхождение от английского header (заголовок).
Для того чтобы убедиться в сказанном, проведем еще несколько экспериментов.
ЭКСПЕРИМЕНТ. В начало текста программы 01_02.с поместите препроцессорную директиву include для включения текста из файла stdio.h:
/* 01_02_4.c - неверное обращение к puts() при |
|
||
#include |
наличии заголовочного файла |
stdio.h */ |
|
<stdio.h> |
/* 03 */ |
||
int main |
() |
/* 04 */ |
|
{ |
|
/* 05 */ |
|
puts("The value of PI=", "3.14159"); |
/* 06 |
*/ |
|
return |
0; |
/* 07 |
*/ |
} |
|
/* 08 |
*/ |
Диагностические сообщения компилятора:
01_02_4.c: In function `main':
01_02_4.c:6: too many arguments to function `puts'
Здесь на этапе компиляции выявлена ошибка в обращении к функции puts(), как и при явном использовании прототипа (см. 01_02_1.с). В сообщении компилятора указан номер неверной строки (6).
Чтобы продемонстрировать "заголовочный" характер файла stdio.h, перенесите директиву include ниже по тексту, что выполнено в следующем эксперименте.
ЭКСПЕРИМЕНТ. Препроцессорная директива #include среди исполнимых операторов:
/* 01_02_5.c - неверное обращение к puts() при |
|
||
неверном |
включении заголовочного файла |
stdio.h */ |
|
int main () |
|
/* 03 */ |
|
{ |
value of PI=", "3.14159"); |
/* 04 |
*/ |
puts("The |
/* 05 |
*/ |
|
return 0; |
|
/* 06 |
*/ |
|
|
|
23 |
#include <stdio.h> |
/* |
07 |
*/ |
} |
/* |
08 |
*/ |
Сообщения компилятора (приведены не все):
01_02_5.c: In function `main':
...................
c:/djgpp/include/stdio.h:125: parse error before `FILE'
c:/djgpp/include/stdio.h:126: parse error before `*' c:/djgpp/include/stdio.h:127: parse error before `*' 01_02_5.c:8: parse error before `}'
Пусть вас не пугает огромное количество сообщений об ошибках. Файл stdio.h содержит много описаний и определений и всем им не место среди исполнимых операторов, куда они попали за счет неверного размещения директивы #include <stdio.h>. Конечно, исполнимый код программы создан не будет.
ЭКСПЕРИМЕНТ. Препроцессорная директива #include после
тела функции main():
/* 01_02_6.c - неверное обращение к puts() при неверном включении заголовочного файла stdio.h */
int main ()
{
puts("The value of PI=", "3.14159"); return 0;
}
#include <stdio.h>
Безошибочная трансляция. (За счет отсутствия проверки правильности обращения к функции puts().) Такое же неверное исполнение, как в программе 01_02.с (см. с. 19).
1.7. Комментарии в тексте программы
ЗАДАЧА 01-03. Напишите программу для вывода текста на экран с несколькими вызовами функции puts(). Разместите в разных местах программы комментарии.
24
Следующая программа иллюстрирует возможности размещения комментариев в тексте программы:
/* 01_03.c - несколько обращений к puts() */ #include <stdio.h> // включение заголовочного файла int main () /* заголовок основной функции */
{ /* начало тела функции main() */ /* первый вызов функции: */
puts("This is a text-line 1."); /* второй вызов функции: */
puts("This is a text-line 2.");
puts("The comment delimiters: /* and */");
return 0; /* оператор возврата из функции main() */ } /* конец тела функции main() */
Результаты выполнения программы:
This is a text-line 1.
This is a text-line 2.
The comment delimiters: /* and */
По правилам синтаксиса языка Си комментарии разрешено помещать везде, где допустимо использовать символ пробела. В данном примере комментарии есть до текста (как и в предыдущих программах), в тексте и после текста программы. В данном примере комментариев даже слишком много, но обычно их продуманное использование всегда полезно.
Комментарии не рекомендуется вкладывать друг в друга (хотя некоторые компиляторы правильно обрабатывают вложение комментариев). Как иллюстрирует третье обращение к функции puts() скобки комментариев /* и */ не действуют в строках.
1.8. Особенности вывода строк функцией puts()
Отметим еще одну особенность функции puts() – после вывода последовательности символов аргумента (символьной строки) в выходной поток передается специальный управляющий символ перехода к началу новой строки.
25
Не расставаясь с функцией puts(), продемонстрируем возможности управляющих символов, представляемых в тексте эскейппоследовательностями: '\n' – новая строка и '\t' – табуляция при выводе на экран дисплея.
ЗАДАЧА 01-04. Вывести на экран "столбиком" сведения о суффиксах, применяемых при записи арифметических констант.
Следующая программа с помощью одного обращения к функции puts() решает задачу:
/* 01_04.c - управляющие символы ‘\n’ и ‘\t’ при выводе строк */
#include <stdio.h> int main ()
{
puts("F\t-\tfloat\nU\t-\tunsigned\nL\t-\tlong"); return 0;
}
Результаты выполнения программы:
F |
– |
float |
U |
– |
unsigned |
L |
– |
long |
Обратите внимание, что в результатах нет последовательностей знаков \n и \t, использованных в строке-аргументе функции puts(). В местах их размещения выполнены переход на новую строку (для \n) и табуляция (для \t).
ЗАДАНИЕ. Решите ту же задачу, что решает программа 01_04.c, не используя последовательность \n.
Так как функция puts() обеспечивает переход на новую строку, то можно просто трижды применить обращение к этой функции:
/* 01_04_1.c - управляющие символы и puts() */
# include <stdio.h>
26
int main ()
{
puts("F\t-\tfloat"); puts("U\t-\tunsigned"); puts("L\t-\tlong"); return 0;
}
Результаты выполнения программы:
F |
- |
float |
U |
- |
unsigned |
L |
- |
long |
ЗАДАНИЕ. Используя puts() и управляющие символы табуляции и новой строки, сформируйте на экране следующую таблицу:
floating-suffix: integer-suffix:
F, |
f |
- |
float |
U, |
u |
- |
unsigned int |
L, |
l |
- |
long double |
L, |
l |
- |
long int |
Слева колонка суффиксов, применяемых в записи вещественных (не целых) констант. Справа – суффиксы целочисленных констант.
Решение обеспечивает следующая программа:
/* 01_04_2.c - управляющие символы и puts() */
# include <stdio.h> int main ()
{
puts("\tfloating-suffix:\t\t\tinteger-suffix:"); puts("F, f\t-\tfloat\t\t\tU, u\t-\tunsigned int"); puts("L, l\t-\tlong double\t\tL, l\t-\tlong int"); return 0;
}
ЭКСПЕРИМЕНТ. В качестве аргументов функции puts() нами использовались строковые константы – последовательности
27
символов, ограниченные кавычками. Выполните обращение к функции puts() с аргументами других типов. Например: puts(3.1415) или puts(421). Посмотрите на реакцию компилятора и/или исполняющей системы.
Коротко о важном
По материалам темы сформулируем следующие утверждения. В конце большинства из них приведены номера программ, подтверждающих справедливость утверждений.
!Не существует завершенной программы без функции main().
!Исходный текст программы на языке Си обычно представлен в виде файла, имя которого имеет расширение ".с" (01_01.с).
!Исходный текст в файле с программой – это последовательность символьных кодов, важную роль среди которых играют коды переходов на новые строки.
!Выделены две стадии разработки и применения Си-программы, обеспеченные, соответственно, средой трансляции и средой исполнения программ.
!Единица трансляции – результат препроцессорной обработки файла с исходным текстом программы.
!Каждая программа – это одна или несколько функций, среди которых обязательно присутствует функция main().
!Трудно или даже невозможно написать программу на Си без обращения к функциям стандартной библиотеки.
!Невозможно в электронном виде представить исходный текст программы на языке Си в виде последовательности кодов, среди которых отсутствуют разделители на строки (01_01_1.с).
!Каждая препроцессорная директива должна начинаться на новой строке (01_01_1.с).
!Назначение прототипа (описания) функции – дать возможность компилятору проверить правильность обращений к функции
(01_02.с, 01_02_1.с).
!Прототип функции позволяет компилятору проверять корректность используемых аргументов в обращении к функции
(01_02.с, 01_02_1.с).
28
!Прототип функции не может размещаться среди исполнимых операторов программы (01_02_2.с).
!В тексте программы прототип функции должен размещаться до обращения к ней (01_02_3.с).
!Препроцессорная директива #include <…> включает в программу прототипы библиотечных функций.
!Текст в строке после служебного слова #include может только именовать включаемый файл (01_01_2.с, 01_01_3.с).
!Комментарий можно разместить в любом месте программы, где допустим пробел (01_03.с).
!Функция puts() выводит в стандартный выходной поток строкупараметр и добавляет код перехода к новой строке дисплея,
принтера и т.д. (01_03.с, 01_04_1.с).
!Эскейп-последовательности '\n' и '\t' позволяют управлять размещением символов текста при выводе (01_04.с, 01_04_1.с).
Тема 2
Константы и их типы
Хорошие идеи имеют своим источником прошлый опыт и ранее приобретенные знания… У нас не может быть никаких хороших идей, если в нашей памяти не хранится достаточно необходимых фактов.
Д. Пойя. Как решать задачу
Основные вопросы темы
!Форматный вывод в стандартный поток (на экран дисплея).
!Спецификации преобразования арифметических значений и строк.
!Соответствие между спецификациями и выводимыми данными.
!Реакция функции printf() на ошибки в форматной строке.
!Сходство и различие puts() и printf() при выводе строк.
!Вывод символов и их кодов.
!Размеры участков памяти, выделяемых для представления арифметических констант.
!Точность представления вещественных констант.
!Особенности вывода и представления в тексте программы целочисленных значений с разными основаниями счисления.
!Различие между знаковыми и беззнаковыми целыми.
!Константы перечислений, их тип и размер в памяти.
!Спецификации преобразования для символьных данных.
!Символы из расширенного набора.
!Особые символы и их эскейп-представления.
!Вывод и коды "неграфических" символов, представляемых эскейппоследовательностями.
!Строковые константы с обычными и расширенными символами.
!Размещение одной строковой константы в нескольких строках текста программы.
!Конкатенация строковых констант.
30