Нелегко совладать с крупным проектом, и тут не грех прибегнуть ко всяким уловкам и хитростям!

Включаемые файлы

Рассмотрим ещё одно средство дробления программного проекта – включаемые файлы, которые называют ещё INCLUDE–файлами. В сравнении с библиотечными модулями возможности этих файлов скромны, но каждая вещь хороша на своем месте.

Механизм включаемых файлов до безобразия прост: содержимое такого файла как бы вставляется в другой. Место вставки определяется директивой $I, за которой следует имя вставляемого файла. Вы скажете, что вставку можно сделать иначе – редактором текста. Но ценность директивы $I в том, что вставки как таковой не происходит, – оба файла не изменяются. Но в момент компиляции проекта включаемый файл как бы составит часть того файла, в который он «вставлен».

Вот пример. Создадим и сохраним в рабочей папке два файла, первый из которых назовем «HELLO.INC», и в нём будет лишь одна строка.

      Writeln(’Привет!’);

Второй файл – «HELLO.PAS» – будет таким.

begin {--- Главная программа HELLO.PAS ---}

      {$I Hello}

end.

Компиляция файла «HELLO.PAS» породит приветливую программу. Здесь в директиве $I указано имя вставляемого файла без расширения, поскольку расширение INC берется по умолчанию. Разумеется, что INCLUDE–файл не вставишь куда попало, – его содержимое должно сочетаться с тем окружением, в которое его погружают.

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

Сначала настройте нужные опции компилятора через пункт меню Options –> Compiler…. Затем создайте новый файл и вставьте в него директивы компиляции нажатием комбинации Ctrl+O+O; в результате в файле могут оказаться такие, например, строки.

{$A+,B-,D+,E-,F-,G+,I-,L+,N+,O-,P-,Q-,R-,S-,T+,V-,X+,Y+}

{$M 16384,0,655360}

Сохраните этот файл, пусть он называется «Options.inc» (не забудьте указать расширение). Затем в первой строке каждого модуля, где вы намерены применить эти опции, вставьте директиву {$I Options}.

{$I Options}

...

Теперь компиляция всех файлов с одинаковыми настройками гарантирована, поскольку опции, заданные в директивах, преобладают над «менюшными». Потребовалось изменить настройки? – тогда исправьте только файл «Options.inc» и повторно откомпилируйте проект.

Условная компиляция

Моряка украшает загрубелое лицо, землекопа выдают мозолистые руки, а программиста – шишки, набитые при отладке программ. И это не шутка! Отладка – пожалуй, самая сложная часть работы, и здесь уместны разные хитрости.

Одно из таких ухищрений – печать промежуточных результатов или так называемая трассировка, – вы знакомы с этим приемом. По завершении отладки, расставленные там и сям, операторы трассировки только мешают, – их приходится удалять. Умные программисты не убирают их навсегда, а комментируют, то есть заключают в фигурные скобки, – а вдруг ещё пригодятся? А хитрые поступают иначе. «Зачем мне копаться в файлах проекта, выискивая лишние операторы? – рассуждают они, – пусть компилятор сделает это сам». Здесь они уповают на условную компиляцию.

Условная компиляция – это механизм, встроенный в компиляторы многих современных языков. Через него можно указать части программы, которые, в определенных случаях, компилировать не следует. Пропущенные таким образом куски программы равносильны комментариям. Условная компиляция организуется двумя директивами, с которыми мы сейчас ознакомимся.

Первая из них – $DEFINE – определяет некоторое имя. Это имя никак не связано с именами переменных, процедур и прочих объектов программы и может даже совпадать с ними – это не опасно. Определенное директивой имя используется лишь для условной компиляции так, как это будет показано далее. Вот парочка примеров определения таких имен.

{ $define Test }

{ $define Print }

Вторая директива – это собственно директива условной компиляции. Она похожа на условный оператор Паскаля, – не путайте их! Условный оператор срабатывает при исполнении программы, а директива – при компиляции проекта. Повторяю: директива условной компиляции – это всего лишь подсказка компилятору со стороны программиста!

Подобно условному оператору, такая директива выражается тремя словами, образующими две ветви компиляции.

{ $ifdef ABC – начало директивы }

      { эта часть скомпилируется, если имя ABC определено }

{ $else }

      { эта часть скомпилируется, если имя ABC НЕ определено }

{ $endif }

Посредством $IFDEF компилятор проверяет, определено ли где-то ранее директивой $DEFINE некоторое имя. Если да, то следующие за директивой операторы, вплоть до $ELSE будут откомпилированы, а иначе – пропущены. Операторы, следующие за $ELSE, ждет обратная участь. Возможен и сокращенный вариант директивы, содержащий лишь одну ветвь компиляции.

{ $ifdef ABC – начало директивы }

      { эта часть скомпилируется, если имя ABC определено }

{ $endif }

Таким образом, пара директив $DEFINE и $IFDEF-$ELSE-$ENDIF совместно образуют механизм условной компиляции.

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

{ $define Debug – Определяем признак отладочной компиляции }

const

      {$ifdef Debug}

      FName = '';       { Предстоит вывод на экран }

      {$else}

      FName = 'Hello.out'; { Предстоит вывод в файл на диске }

      {$endif}

var F : text;

begin

      Assign (F, FName); Rewrite(F);

      Writeln(F, ’Привет, мартышка!');

      {$ifdef Debug} Readln; {$endif}

      Close(F);

end.

Здесь определено некое условное имя Debug (отладка), а программа содержит два варианта вывода: на экран и в файл. Разумеется, что после компиляции такой программы вывод пойдет на экран. Но переделка программы в боевую версию будет элементарна: достаточно удалить знак доллара в первой строке, и тогда директива $DEFINE превратится в безобидный комментарий.

{ define Debug – это всего лишь комментарий, а не директива! }

Теперь, когда условное имя Debug не определено, при повторной компиляции константа FName примет значение «Hello.out», оператор Readln не откомпилируется, и мы получим вывод в дисковый файл.

А что, если надо компилировать многофайловый проект в разных вариантах: отладочном и боевом? Здесь разумно сосредоточить определения условных имен в одном месте, разместив их во включаемом файле (как мы сделали это с опциями компилятора). Возьмем, например, созданный ранее файл опций «Options.inc». Добавив в него определения условных имен, мы сможем воздействовать на компиляцию сразу всех файлов проекта.

{$A+,B-,D+,E-,F-,G+,I-,L+,N+,O-,P-,Q-,R-,S-,T+,V-,X+,Y+}

{$M 16384,0,655360}

{$define Test – действует на условия типа $IFDEF Test }

{$define Print – действует на условия типа $IFDEF Print }

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

Итоги

• Включаемые (INCLUDE) файлы – это части программы, которые в момент компиляции автоматически вставляются в указанные директивами $I места. Содержимое включаемого файла должно сочетаться с окружением, в которое оно вставляется.

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

А слабо?

А) Создайте отладочный вариант программы обработки классного журнала (глава 31), в котором вывод результатов будет выполняться на экран. Примените условную компиляцию.

Задачи на темы предыдущих глав

Б) Контрразведка перехватила несколько зашифрованных файлов, и подозревала, что это тексты написанных на Паскале вирусов. Позвали Шерлока Ивановича Холмского в надежде, что тот расшифрует их. Шерлок Иванович предположил, что шифровали методом Юлия Цезаря (вспомните главу 24). Нужен ключ! После недолгих раздумий Шерлок Иванович создал программу для подбора ключей к таким текстам. Повторите ещё один «подвиг контрразведчика», или слабо? Подсказка: в таких файлах после расшифровки обязательно встречаются ключевые слова BEGIN и END – воспользуйтесь этим.

В) Рейтинговое голосование. Избирательный закон Иксляндии даёт каждому избирателю право голосовать за всех кандидатов, расставляя их в порядке своего предпочтения. Побеждает кандидат, набравший наименьшую сумму мест (если таковых несколько, то проводят второй тур). Предположим, баллотируются четыре кандидата с номерами 1-4, а бюллетени содержат следующие предпочтения избирателей:

3 4 2 1

2 4 3 1

4 1 3 2

Здесь первый кандидат набирает сумму 10, второй – 8, третий – 7, четвертый – 5. Таким образом, побеждает четвертый кандидат в списке.

Количество кандидатов известно и равно пяти. Ваша программа принимает файл, каждая строка которого содержит 5 чисел – данные одного бюллетеня. Надо выдать список победителей голосования (одного или нескольких).