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

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

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

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

Чтобы продемонстрировать важность размещения прототипа именно до вызова функции, поместите прототип функции 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