Какая информация может быть помещена в файлы заголовков? Строго говоря, разработчик может помещать в эти файлы любую информацию, поскольку сами файлы после работы препроцессора просто вставляются в основной файл на место директивы #include. Список хранящихся в этих файлах данных приведен ниже.
1. Объявления функций, которые могут быть использованы в нескольких модулях.
2. Описания классов.
3. Описания внешних переменных.
4. Определения макросов.
5. Определения типов, доступных для всего проекта.
Рспользование заголовочных файлов Рё функционирование препроцессора тесно связаны. Какие же директивы для управления работой препроцессора РјРѕРіСѓС‚ быть включены РІ исходные файлы Рё РІ файлы заголовков? Рто показано РІ следующем примере.
Упражнение 4.7
1. Создать простое приложение и сохранить его с именем AdvancedCPP.
2. На вкладке FileView отыскать файл newres.h и двойным щелчком открыть его в редакторе кода. Поскольку этот файл содержит в себе множество директив препроцессора, он послужит хорошей иллюстрацией к их описанию.
Директива #include задает включение в текст данного файла текста другого файла, имя которого указано после директивы. В файле newres.h есть несколько директив #include.
#include <commctrl.h>
Выполнение этой инструкции приведет к тому, что перед компиляцией в этом месте в текст файла newres.h будет включен текст файла commctrl.h, но только для подачи на вход компилятору. Текст файла newres.h, хранимый на диске, изменен не будет.
Директива #define используется для создания символических констант, для определения макрофункций и для определения управляющего идентификатора.
3. Найти в редакторе кода следующую строку:
#define AFXCE_IDR_SCRATCH_SHMENU 28700
Рта строка создает символическую константу AFXCE_IDR_SCRATCH_SHMENU СЃРѕ значением 28700. Теперь компилятор, обнаружив РІ тексте программы РёРјСЏ AFXCE_ IDR_SCRATCH_SHMENU, будет вместо него подставлять значение 28700. 4. Открыть РІ РѕРєРЅРµ FileView файл aygshell.h. Р’ этом файле нужно найти следующую строку РєРѕРґР°:
#define CEM_UPCASEALLWORDS (WM_USER + 1)
Данное объявление говорит о том, что препроцессор, встретив вызов макрофункции CEM_UPCASEALLWORDS, вместо имени подставит выражение (WM_USER + 1). Макрофункция, как и любая другая функция, может принимать параметры. К примеру, объявление #define MF(a, b, c) (a*b*c/(a+b+c)) далее в тексте может быть использовано как MF(x, y, z). Вместо имени фунции с заданными аргументами препроцессор вставит тело функции, то есть (x*y*z/(x+y+z)). Файл newres.h начинается со строк:
#ifndef __NEWRES_H__ #define __NEWRES_H__
а завершается строкой:
#endif //__NEWRES_H__
Рти строки показывают еще РѕРґРЅРѕ применение директивы #define. Выражение #define __NEWRES_H__ РїСЂРё обработке препроцессором приведет Рє замене имени __NEWRES_H__ простым пробелом. РќР° самом деле это выражение служит маркером для выполнения условной компиляции или условного включения. Таким образом, директива #define позволяет определить РёРјСЏ, которое РЅРёРіРґРµ РЅРµ появится РІ конечном тексте программы РЅРё РІ РІРёРґРµ символа, РЅРё РІ РІРёРґРµ значения, РЅРѕ будет служить условием выбора для самого препроцессора.
Рти строки дают возможность перейти Рє директивам условной компиляции (условного расширения). Рљ этим директивам относятся #if, #ifdef, #ifndef, #endif, #else Рё #elif.
Директива условной компиляции #if позволяет управлять процессом компиляции проекта. Если выражение const_exp, стоящее после директивы #if в конструкции #if const_exp, имеет ненулевое значение, то текст, следующий за директивой #if до соответстующей ей директивы #endif, будет включен в текст, подаваемый на вход компилятора (а значит, и скомпилирован). В противном случае этот текст не попадет на вход компилятора и не войдет в программу.
Если имя ident, стоящее после директивы #ifdef в конструкции #if ident, определено в тексте программы, то текст, следующий за директивой #ifdef до соответстующей ей директивы #endif, будет включен в текст, подаваемый на вход компилятора.
Если имя ident, стоящее после директивы #ifndef в конструкции #ifndef ident, не определено в тексте программы, то текст, следующий за директивой #ifndef до соответствующей ей директивы #endif, будет включен в текст, подаваемый на вход компилятора.
В целом конструкция условной компиляции может выглядеть так, как показано в листинге 4.30.
Листинг 4.30
#if cnst_ex1//Если выражение const_exp1 имеет значение true,
[text1]//тогда расширяется text1
[#elif cnst_ex2//иначе если cnst_ex2 имеет значение true,
text2]//тогда расширяется text2
[#elif cnst_ex3//иначе если cnst_ex3 имеет значение true,
text3]//тогда расширяется text3 … и так далее.
…
[#elif cnst_exN//если не был расширен ни один из предыдущих блоков,
textN]//расширяем текст textN
#endif//и завершаем блок условной компиляции
Теперь нужно проанализировать реальный код, который приведен в листинге 4.31. Листинг 4.31
/* Если имя __NEWRES_H__ не определено, */
#ifndef __NEWRES_H__
/* Определяем это имя и расширяем текст модуля */
#define __NEWRES_H__
[текст модуля]
#endif //__NEWRES_H__
Если РёРјСЏ __NEWRES_H__ уже было определено РІ какой-то части программы, то повторной вставки модуля РІ текст РЅРµ последует. Такой прием используется для предотвращения многократного расширения РІ тексте программы РѕРґРЅРѕРіРѕ Рё того же модуля, РЅР° который оказалось несколько ссылок директивы #include. Также этот механизм предотвращает ситуацию, РєРѕРіРґР° РґРІР° модуля оказались взаимно включены РґСЂСѓРі РІ РґСЂСѓРіР°. Рто вполне возможно РІ сложных программах, РіРґРµ включаемые модули РІ СЃРІРѕСЋ очередь тоже содержат директивы #include.
Директива #undef удаляет объявление имени, сделанное при помощи директивы #define.