книги / Практикум по программированию на языке Си
..pdf!Символы входного потока, прочитанные функциями gets() или scanf() по спецификации %s, автоматически дополняются в "принимающем" символьном массиве признаком конца строки '\0' (09_06.c, 09_11.c и др.).
!Длину строки вычисляют как количество отличных от '\0' элементов от начала cодержащего строку массива char[] (функция sizeStr() в 09_08.c, библиотечная функция strlen()).
!При оформлении строки в массиве типа char[] НЕ ЗАБЫВАЙТЕ завершать содержательную часть строки кодом '\0' (09_10.с,
09_12.с).
!Обязательное наличие кода '\0' в конце строки позволяет использовать строки в качестве возвращаемого функцией значения. На самом деле функция возвращает указатель типа char *, но этого достаточно для доступа ко всей строке (09_11.с, 09_12_1.с).
!Применение библиотечных функций позволяет существенно упростить программы для работы со строками в стиле Си
(09_13.с и др.).
!Изучите возможности стандартных библиотечных функций для работы со строками и при составлении алгоритма решения задачи оперируйте в терминах действий, выполняемых функциями
(09_13.с, 09_13_1.с, 09_14.с и др.).
!Возвращаемая функцией строка должна быть сформирована внутри тела функции в массиве статистическом, динамическом либо глобальном (функция word() в 09_14.с, функция measure() в 09_16.с, 09_22.с и др.).
!Недопустимо возвращать из функции указатель на объект, сформированный в автоматической памяти тела функции.
!Если символы строки представляют изображение числа, то, используя библиотечные функции, легко получить соответствующее арифметическое значение во внутреннем коде ЭВМ
(09_13.с, 09_18.с).
!Библиотечные функции для преобразования строчных букв в прописные и обратно предназначены только для латинского ал-
фавита (09_15_1.с).
!С помощью библиотечных функций можно получить строку с символьным представлением арифметического значения любого базового типа (09_16.с, 09_18.с).
401
!Библиотечные функции обеспечивают для строк все возможности форматных обменов, существующие для потоков ввода-
вывода (09_16.с, 09_18_1.с).
!Значения, указанные в командной строке при запуске программы, воспринимаются функцией main() как указатели на символьные строки, первая их которых содержит полное имя исполняемого модуля программы (09_17.с, 09_18.с).
!Рекурсия – основа обработки регулярных выражений (09_20.с).
!Массив указателей на строки позволяет адресовать строки разной длины (09_21.с, 09_22.с и др.).
!Массив указателей на строки – эффективное средство для сортировки строк по произвольным правилам (09_23.с, 09_24.с).
!При определении массив указателей на строки всегда должен получать память для своих элементов (09_21.с - 09_23.с).
!Указатель типа char ** при определении получает память только для самого себя (для указателя), даже если он адресует массив указателей (09_24.с).
Тема 10
Условная компиляция и макрообработка
Макрообработка – это, в сущности, замена одного текста другим.
М. Кэмпбел-Келли. Введение в макросы
Основные вопросы темы
!Препроцессорные директивы ветвлений обработки текста.
!Логические препроцессорные выражения.
!Схемы алгоритмов препроцессорной обработки.
!Препроцессорное задание размеров многомерных массивов.
!Препроцессорная настройка программ.
!Назначение и возможности препроцессорных операций.
!Препроцессорные макроопределения и макроподстановки.
!Различия и сходства макросов и функций.
!Моделирование многомерных массивов.
!Макроопределения шаблонов функций.
10.1.Условная генерация текста
Ранее (в теме 3) мы изучили препроцессорные средства и механизмы для управления выборкой фрагментов из обрабатываемого текста. В литературе о таком использовании препроцессора говорят, применяя термины "условная компиляция" и "условная генерация (текста)". В рассмотренных в теме 3 примерах использовались директивы #ifndef, #else, #endif. Управление выборкой частей текста осуществлялось за счет проверки препроцессорной определенности идентификатора. Однако проверяться могут и значения логических выражений, в которые входят константы и препроцессорные идентификаторы. Логические выражения используются в директиве #if.
403
ЗАДАЧА 10-01. Используя директивы условной препроцессорной обработки, оформите текстовый файл таким образом, чтобы текст из него можно было включить в одну программу не более двух раз.
/*10_01.txt - текст включается не более 2-х раз */ #ifndef _T
#define _T 1 #endif
#if _T<3
Значение препроцессорного идентификатора: _T #if _T==2
#undef _T #define _T 3
#endif #if _T==1
#undef _T #define _T 2
#endif
#endif
Вначале строка замещения "1" приписывается препроцессорному идентификатору _T директивой #define _T 1. Идентификатор _Т определен со значением 1 только при первом просмотре. При выполнении условия _T<3 строка "Значение..." включается в текст, а затем проверяется условие _T==2 и т. д. (рис. 10.1). Обратите внимание, что значение строки замещения препроцессорного идентификатора (в нашем случае идентификатора _T) сохраняется до повторного обращения к тексту при его последующем включении директивой
#include. На рис. 10.1 конструкцией := обозначено установление связи директивой #define препроцессорного идентификатора со строкой замещения.
В следующей "программе", размещенной в файле 10_01.с, рассмотренный текст четыре раза включается директивой #include.
/* 10_01.c - защита от многократных включений */ #include "10_01.txt"
#include "10_01.txt" #include "10_01.txt" #include "10_01.txt"
404
Рис. 10.1.Схема препроцессорной обработки текста 10_01.txt
Программу из файла 10_01.с можно подвергнуть препроцессорной обработке (без последующей компиляции), используя директиву компилятора DJGPP:
>gcc –E 10_01.c>pre<ENTER>
В текстовый файл с указанным именем "pre" будет помещен такой результат препроцессорной обработки (удалены пустые строки):
405
#1 "10_01c"
#1 "10_01.txt" 1
Значение препроцессорного идентификатора: 1
#2 "10_01.c" 2
#1 "10_01.txt" 1
Значение препроцессорного идентификатора: 2
#3 "10_01.c" 2
#1 "10_01.txt" 1
#15 "10_01.txt"
#4 "00_10.c" 2
#1 "10_01.txt" 1
#15 "10_01.txt"
#5 "10_01.c" 2
Результат подтверждает правильность защиты, строка "значение препроцессорного идентификатора _T" печатается только дважды.
10.2.Рекурсивное использование директивы
#include
Известное английское стихотворение в переводе С.Я. Маршака "Дом, который построил Джек" состоит из 9 строф. При переходе от одной строфы к следующей первая строка заменяется двумя новыми, а остальные повторяются. Исключение: при переходе от первой строфы ко второй одна строка заменяется тремя.
ЗАДАЧА 10-02. С использованием препроцессорных директив "сконструировать" такой текст, который не содержал бы повторяющихся в стихотворении С.Я. Маршака строк, но при процессорной обработке которого все стихотворение "разворачивалось" бы полностью с нужным повторением строк.
Следующий текст решает поставленную задачу для первых четырех строф стихотворения С.Я. Маршака.
//dom.txt - Маршак С. "Дом, который построил Джек" ifndef _T
#define _T 1
406
#endif #if _T<5 #if _T==4
Вот кот, #undef _T
#define _T 5 #endif
#if _T==3
А это веселая птица-синица, #undef _T
#define _T 4 #endif
#if _T==2
А это пшеница, #undef _T #define _T 3
#endif #if _T==1
Вот дом, #undef _T
#define _T 2 #endif
#if _T>4
Который пугает и ловит синицу, #endif
#if _T>3
Которая часто ворует пшеницу, #endif
#if _T>2
Которая в темном чулане хранится В доме,
#endif
Который построил Джек. #include "dom.txt" #endif
"Программа", при обработке которой препроцессор формирует первые четыре строфы стихотворения С.Я. Маршака:
// 10_02.с - "разворачивание" стихотворения Маршака
#include "dom.txt"
407
Как и в предыдущей задаче, выполним препроцессорную обработку по команде
>gcc –E 10_02.c>pre<ENTER>
Результаты препроцессорной обработки в файле pre:
#1 "10_02.c"
#1 "dom.txt" 1
Вот дом, Который построил Джек.
#1 "dom.txt" 1
А это пшеница, Которая в темном чулане хранится В доме,
Который построил Джек.
# 1 "dom.txt" 1
А это веселая птица-синица, Которая часто ворует пшеницу, Которая в темном чулане хранится В доме, Который построил Джек.
#1 "dom.txt" 1
Вот кот, Который пугает и ловит синицу,
Которая часто ворует пшеницу, Которая в темном чулане хранится В доме, Который построил Джек.
#1 "dom.txt" 1
#46 "dom.txt"
#45 "dom.txt" 2
#45 "dom.txt" 2
#45 "dom.txt" 2
#45 "dom.txt" 2
#2 "10_02.c" 2
ЗАДАНИЕ. Нарисуйте схему алгоритма препроцессорной обработки, соответствующую тексту "свернутого" стихотворения С.Я. Маршака.
408
В решенной задаче текст из файла dom.txt превращается за счет препроцессорной обработки в стихотворение. Но кроме стихотворных строк в результат включена вспомогательная информация – строки, начинающиеся с символов '#'.
ЗАДАЧА 10-03. Преобразуйте текст из файла dom.txt таким образом, чтобы его включение в программу позволяло этой программе напечатать (вывести на экран дисплея) текст стихотворения Маршака без вспомогательных строк (начинающихся с символа '#').
Используем для решения функцию puts(), аргументом которой должна быть символьная строка. Строку будем формировать как конкатенацию размещенных рядом строковых констант. Для правильного расположения текста используем эскейп-последователь- ности кодов управляющих символов ('\n', '\t').
Текст свернутого представления стихотворения:
/*domPrint.txt - Маршак С. "Дом, который построил Джек" */
#ifndef _T #define _T 1
puts(
"\t\tМаршак С.\n\t\"Дом,который построил Джек\"\n"); #endif
#if _T<5 puts(
#if _T==4 "Вот кот,\n"
#undef _T #define _T 5
#endif #if _T==3
"А это веселая птица-синица,\n" #undef _T
#define _T 4 #endif
#if _T==2
"А это пшеница,\n" #undef _T #define _T 3
#endif
409
#if _T==1 "Вот дом,\n"
#undef _T #define _T 2
#endif #if _T>4
"Который пугает и ловит синицу,\n" #endif
#if _T>3
"Которая часто ворует пшеницу,\n" #endif
#if _T>2
"Которая в темном чулане хранится\n" "В доме,\n"
#endif
"Который построил Джек.\n" );
#include "domPrint.txt" #endif
Программа печати стихотворения из файла domPrint.txt:
/* 10_03.c - печать стихотворения Маршака */ #include <stdio.h>
int main()
{
#include "domPrint.txt" return 0;
}
Результат выполнения программы:
Маршак С.
"Дом, который построил Джек"
Вот дом, Который построил Джек.
А это пшеница, Которая в темном чулане хранится В доме,
Который построил Джек.
410