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

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

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

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

Repetitio est mater studiorum!

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

/* 02_21_1.c - конкатенация строковых констант */ #include <stdio.h>

int main ()

{

puts("Repetitio " "est "

"mater " "studiorum!"

); return 0;

}

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

Repetitio est mater studiorum!

ЗАДАНИЕ. Обратите внимание на выравнивание слов по правому краю. Если такое выравнивание выполнить в предыдущей программе 02_21.с, то в результирующем тексте появятся лишние пробелы. Попробуйте проделать это на ЭВМ.

ЗАДАЧА 02-22. Чтобы убедиться в правильности понимания роли управляющих символов в теле строк, выведите на экран дисплея таблицу из всех служебных слов языка Си, размещая в каждой строке дисплея не более четырех слов. Используйте в программе только одно обращение к функции puts(). Условие: строки текста программы не должны быть длиннее 60 символов.

/* 02_22.c – редактирующие символы в строке */ #include <stdio.h>

int main ()

{

puts("auto\t\tdouble\t\tint\t\tstruct\n"

61

"break\t\telse\t\tlong\t\tswitch\n"

"case\t\tenum\t\tregister\ttypedef\n"

"char\t\textern\t\treturn\t\tunion\n"

"const\t\tfloat\t\tshort\t\tunsigned\n"

"continue\tfor\t\tsigned\t\tvoid\n"

"default\t\tgoto\t\tsizeof\t\tvolatile\n"

"do\t\tif\t\tstatic\t\twhile\n"

); return 0;

}

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

auto

double

int

struct

break

else

long

switch

case

enum

register

typedef

char

extern

return

union

const

float

short

unsigned

continue

for

signed

void

default

goto

sizeof

volatile

do

if

static

while

Обратите внимание на то, что символы табуляции поставлены с учетом длины предшествующего слова, например, после do перед if их два, а после continue перед for – один.

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

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

!В отличие от puts() функция printf() не обеспечивает автоматического перехода к новой строке (01_03.с, 01_04_1.с, 02_01.с).

!Несоответствие спецификации преобразования типу выводимого значения приводит к непредсказуемым результатам на этапе выполнения программы (02_01_1.с, 02_01_2.с).

!Если символы форматной строки функции printf() не входят в представление спецификации преобразования, то они выводятся в выходной поток (02_01.с, 02_02.с).

62

!Для символа можно получить (вывести) числовое значение его кода и его изображение (02_03.с).

!Отдельная символьная константа, записанная без префикса L, имеет тип int.

!Арифметическое значение кода символа со значением из диапазона 128–255 воспринимается при выводе по спецификации %d как отрицательное число (02_03_1.с).

!Размеры участков памяти, выделяемой для вещественных констант, зависят только от их типа и не зависят от количества цифр в их записи в тексте программы (02_04.с, 02_04_1.с).

!Независимо от числа знаков в представлении константы число значащих цифр в ее внутреннем коде определяется только ее ти-

пом (02_05.с, 02_05_1.с).

!Спецификации %f и %e без указания точности представления используют точность, предусмотренную умолчанием

(02_05_1.с, 02_05_2.с).

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

!Одно и то же целое значение можно представить в тексте программы, используя разные основания систем счисления

(02_07.с).

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

02_07.с).

!При записи целой константы ей можно приписать тип, используя соответствующий суффикс (02_08.с).

!Предельное значение беззнакового целого в два раза превышает максимальное значение целого того же типа (02_09.с).

!Роль перечислений – ввести осмысленные запоминаемые названия целочисленных констант (02_10.с, 02_11.с).

!Размер памяти, выделяемой для отдельного символа, совпадает с размером числа типа int (02_12.с).

!Для представления особых символов (кавычки, апостроф, пусто, перевод строки) используйте эскейп-последовательности

(02_12_1.с, 02_12_2.с, 02_12_3.с, 02_13.с).

!Для представления неграфических символов используйте эскейппоследовательности (02_14.с).

63

!Набор эскейп-последовательностей для неграфических символов фиксирован и не может быть расширен (02_14_1.с).

!Коды символов '\0' и '0' не совпадают (02_15.с).

!В эскейп-последовательностях с числовыми кодами не применимы десятичные цифры (02_16.с).

!Код мультибайтовой (мультисимвольной) константы – это конкатенация шестнадцатеричных кодов входящих в нее символов

(02_17.с, 02_18.с).

!В мультибайтовую (мультисимвольную) константу не может входить более четырех символов (02_18_1.с).

!Строка из 1-го символа не есть этот символ (02_19.с).

!Строка из k символов типа char занимает в памяти (k+1) байт

(02_20.с).

!Строка из k символов расширенного набора (тип wchart_t) занимает в памяти 4#(k+1) байт (02_20.с).

!В тексте программы разрешены переносы строковой константы с помощью символа '\' (02_21.c).

!Строки, разъединенные в тесте программы обобщенными пробелами, конкатенируются (02_21_1.с).

!Для управления размещением текста на экране (при выводе на печать) в строке можно использовать управляющие символы '\t', '\n' (02_22.c).

Тема 3

Знакомство с препроцессором

Препроцессор не знает Си.

А. Фьюэр. Задачи по языку Си

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

!Препроцессорная директива #include.

!Препроцессорная сборка программы из синтаксически "незавершенных" фрагментов.

!Использование директивы #include во включаемых текстах.

!Препроцессорные "подстановки" директивой #define.

!Назначение директивы #undef.

!Предельные значения данных базовых типов.

!Защита текста от повторных включений.

!Управление включением текста в программу.

!Полезные макроопределения.

3.1. Включение текстов из файлов

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

Чтобы препроцессором обработать текстовый файл (например, с именем "testProg.c") и записать результат обработки в другой

65

текстовый файл (например, в файл preText.i), нужно обратиться к компилятору DJGPP со следующей командой:

>gcc -E testProg.c -o preText.i

Здесь ключ –E определяет задачу обработки файла с именем testProg.c. Ключ –o вводит имя файла с результатом препроцессорной обработки.

Используя команду, начнем эксперименты с директивой

#include. Директива #include имеет две формы. Первая форма

#include <имя_файла> /*имя в угловых скобках */

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

#include "имя_файла" /*имя в кавычках*/

предназначена для включения текста файла из любого каталога, причем поиск этого файла начинается в текущем каталоге.

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

ЗАДАЧА 03-01. Подготовьте небольшой текстовый файл и "программу" из одной препроцессорной директивы #include, включающей текст из этого файла. Оцените результат препроцессорной обработки этой "программы".

Разместим в файле 03_01.txt следующий текст:

/* 03_01.txt – текст без Си-операторов */ "Тривиальный алгоритм обычно лучше подходит для иллюстрации нюансов определения языка или структуры программы."

Б. Страуструп

Подготовим такую псевдопрограмму в файле 03_01.с:

/* 03_01.c – включение текста препроцессором */ #include "03_01.txt"

66

Выполним препроцессорную обработку, используя команду:

>gcc -E 03_01.c –o 03_01.i

Вфайле 03_01.i получим следующий текст:

#1 "03_01.c"

#1 "03_01.txt" 1

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

Б.Страуструп

# 2 "03_01.c" 2

Здесь строка «# 1 "03_01.c"» отмечает начало препроцессорной обработки. Строка «# 1 "03_01.txt" 1» содержит имя файла, из которого прочитывается текст. Далее размещен текст из этого файла 03_01.txt, и все завершает последовательность «# 2 "03_01.c" 2» – конец препроцессорной обработки.

Обратите внимание, что препроцессорная директива, вводимая размещенным в начале строки символом #, за которым следует пробел, является пустой директивой и никак не влияет на последующую обработку компилятором полученного текста. Таким образом, "выходом" или результатом препроцессорной обработки нашей псевдопрограммы 03_01.с можно считать именно текст высказывания Б. Страуструпа из файла 03_01.txt. В результат не перенесена и строка комментария (первая строка) из файла 03_01.txt.

ЗАДАЧА 03-02. Разместите текст небольшой программы в двух текстовых файлах "начало" и "конец". Выполните препроцессорную "сборку" программы и убедитесь в ее работоспособности.

Файл первый:

/* 03_02a.txt - начало программы */ #include <stdio.h>

int main()

{puts("Сообщение из начала программы.");

Файл второй:

67

/* 03_02b.txt - конец программы */ puts("Сообщение из окончания программы."); return 0;

}

Тексты обоих файлов являются синтаксически незавершенными, т.е. их невозможно компилировать в отдельности. В третьем файле того же каталога разместим "программу":

/* 03_02.c - программа из частей в двух файлах */ #include "03_02a.txt"

#include "03_02b.txt"

Команда на препроцессорную обработку:

>gcc -E 03_02.c -o 03_02.i

Результаты препроцессорной обработки (в файле 03_02.i):

#1 "03_02.c"

#1 "03_02a.txt" 1

#1 "c:/djgpp/include/stdio.h" 1 3

#1 "c:/djgpp/include/sys/djtypes.h" 1 3

...........................

Текст из файла stdio.h и включаемых в него файлов

...........................

#2 "03_02a.txt" 2

int main()

{puts("Сообщение из начала программы.");

#2 "03_02.c" 2

#1 "03_02b.txt" 1

puts("Сообщение из окончания программы.");

return 0;

}

# 3 "03_02.c" 2

Если не считать "пустых строк", обозначаемых символом # в первой позиции с последующим пробелом, то получен правильный текст программы на языке Си. В нем много "лишнего" для нашей маленькой программы за счет текста из стандартного заголовочного файла stdio.h. Выше мы обозначили этот текст многоточием.

68

Файл 03_02.i можно откомпилировать, используя такую директиву:

>gcc 03_02.i –o test.exe

Предупреждения компилятора:

03_02a.txt: In function `main':

03_02a.txt:5: warning: This file contains more `{'s than `}'s.

03_02b.txt: At top level:

03_02b.txt:5: warning: This file contains more `}'s than `{'s.

Получили предупреждающее сообщение компилятора о том, что каждый файл 03_02a.txt и 03_02b.txt содержит не уравновешенное количество скобок '{' и '}'. (Мы это и сами знаем!) Поэтому запускаем исполнимый модуль test.exe на выполнение:

>test.exe>res

В автоматически формируемом файле результатов (res) получим:

Сообщение из начала программы. Сообщение из окончания программы.

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

#include "путь к файлу"

ЭКСПЕРИМЕНТ. Поместите файлы 03_02a.txt и 03_02b.txt в произвольные каталоги, отличные от каталога, где размещен текст "собирающей" их программы 03_02.c. Модифицируйте текст 03_02.c, чтобы учесть новое размещение файлов, откомпилируйте и выполните программу.

Вот текст программы для конкретного размещения файлов:

69

/* 03_02_1.c - программа из частей в другом каталоге */

#include "c:\1_c\practicum\programs\03_02a.txt" #include "c:\1_c\practicum\programs\03_02b.txt"

Обратите внимание, что для включаемых файлов из другого каталога указан полный путь, начиная от имени диска.

Включаемый препроцессором файл в свою очередь может содержать директивы #include, включающие другие файлы. Например, из текста файла stdio.h или из результатов препроцессорной обработки программы 03_02.c видно, что файл stdio.h содержит включение файла djtypes.h (см. текст файла 03_02.i на с. 69).

ЗАДАНИЕ. Придумайте содержательный пример с такой же "матрешкой" включений текстов из файлов.

3.2. Замены (подстановки) в тексте

Изучим простейшие возможности препроцессорной директивы

#define, которая позволяет вводить препроцессорные идентификаторы и определяет их значения, т.е. связывает с препроцессорным идентификатором "строку замещения". Формат директивы:

#define имя строка_замещения

Здесь имя – вводимое директивой имя препроцессорного идентификатора, строка_замещения – последовательность символов. Ее окончанием считается код начала новой строки (включаемый при наборе текста программы по нажатию клавиши "ENTER".)

ЗАДАЧА 03-03. Определите с помощью директивы #define

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

/* 03_03.c - #define – именованные константы */ #include <stdio.h>

#define PI 3.14159 int main()

70