Р. ЛАФОРЕ ОБЪЕКТНО-ОРИЕНТИРОВАННОЕ ПРОГРАММИРОВАНИЕ В C++ (4-Е ИЗДАНИЕ) (часть13) онлайн
Заголовочные файлы
Как отмечалось в главе 2, директива #include работает, как функция ВСТАВИТЬ
в текстовых редакторах, то есть указанный после этой директивы файл просто
вставляется в исходный. В очень многих примерах мы видели включения биб-
лиотечных файлов типа IOSTREAM.
Можно также и самостоятельно написать заголовочный файл и включить его
в свою программу.
Общая информация
Одной из причин использования заголовочных файлов является возможность
вставки одной и той же информации в разные файлы. В них содержатся объяв-
ления переменных или функций, которые и включаются в исходные тексты про-
грамм. Тем самым можно открыть доступ к этим переменным и функциям из
множества файлов.
Конечно, каждый элемент программы должен быть определен в каком-то
месте. Например, переменная и функция объявлены в fileH.h, а определены в
fileA.cpp. Код из файла fileB.cpp может использовать эти элементы без дополни-
тельных объявлений.
//fileH.h
extern int gloVar; //объявление переменной
int gloFunc(int); //объявление функции
//fileA.cpp
int gloVar; //определение переменной
int gloFunc(int n) //определение функции
{ return n; }
//fileB.cpp
#include "fileH.h"
. . .
gloVar = 5; //работа с переменной
int gloVraB=gloFunc(gloVar); //работа с функцией
Помните, что в заголовочном файле можно хранить объявления, но не опре-
деления переменных или функций. При использовании этого файла в других
(если только данные не static или const) такая оплошность приведет к ошибке
компоновщика «повторные определения».
Одним из основных методов является следующий. В заголовочный файл
вносится определение класса, который используется при необходимости всеми
исходными файлами. Это не приводит к ошибке повторного определения, пото-
му что определение класса не означает резервирования памяти — это только
спецификация его компонентов.
//fileH.h
class someClass //определение класса
{
private:
int memVar;
public:
int memFunc(int, int);
};
//fileA.cpp
#include "fileH.h"
int main()
{
someClass Obj1; //создание объекта
int var1 = Obj1.memFunc(2, 3); //работа с объектом
}
//fileB.cpp
#include "fileH.h"
int func()
{
someClass Obj2; //создание объекта
int var2 = Obj2.memFunc(4, 5); //работа с объектом
}
А что бы, интересно, было, если бы вместо использования заголовочного
файла мы просто вставили этот текст с определением класса в каждый из фай-
лов? Ничего бы страшного, на самом деле, не произошло, просто каждое ма-
ленькое изменение в классе пришлось бы производить во всех этих файлах. Это
отняло бы массу времени, да еще и наверняка привело бы к опечаткам.
До сих пор мы демонстрировали определения классов без внешних опреде-
лений их методов. Но куда же нам вставить, собственно, основное содержимое
классов — определения методов? Как и для любых других функций, это может
быть сделано в любом исходном файле, и компоновщик соединит все вместе
как нужно. На то он и компоновщик. Собственно говоря, определение класса
служит для того, чтобы можно было объявлять методы в каждом файле. Как и в
программах, состоящих из единственного файла, определения методов должны
включать в себя имя класса и оператор разрешения контекста. Пример:
//fileH.h
class someClass; //определение класса
{
private:
int memVar;
public:
int memFunc(int, int); //объявление метода
};
//fileA.cpp
#include "fileH.h"
int someClass::memFunc(int n1, int n2); //определение метода
{ return n1+n2; }
//fileB.cpp
#include "fileH.h"
someClass anObj; //создание объекта
int answer = anObj.memFunc(6, 7); //работа метода
Ошибка повторения включений
Мы упоминали, что нельзя определять функцию или переменную в заголовоч-
ном файле, который будет использован несколькими исходными файлами. Это
приводит к ошибкам повторных определений. Подобная проблема возникает
и тогда, когда по ошибке включают один и тот же заголовочный файл дважды.
Как такое может случиться? Да запросто:
//файл app.cpp
#include "headone.h"
#include "headone.h"
Но это еще ничего. Представьте себе, что у вас есть исходный файл app.cpp и
два заголовочных — headone.h и headtwo.h. К тому же headone.h включает в себя
headtwo.h К сожалению, про это обстоятельство забывают и включают оба файла
в app.cpp:
//файл headtwo.h
int globalVar;
//файл headone.h
#include "headtwo.h"
//файл app.cpp
#include "headone.h"
#include "headtwo.h"
Что теперь будет после компиляции app.cpp? Так как директивой #include
мы вставили заголовочные файлы, реальное содержимое исходного файла будет
таково:
//файл app.cpp
. . .
int globalVar; //из headtwo.h через headone.h
. . .
int globalVar; //напрямую из headtwo.h
Разумеется, компилятор сообщит, что globalVar определена дважды.
Предупреждение ошибок повторения включений
С рассеянностью приходится бороться. Вот как можно предупредить ошибки по-
вторных определений даже при ошибках повторения включений: определения в
заголовочном файле следует начинать с директивы препроцессора:
#if !defined( HEADCOM )
(На месте HEADCOM может быть любой идентификатор.) Это выражение гово-
рит о том, что если HEADCOM еще не был определен (восклицательный знак озна-
чает логическое отрицание), то весь текст отсюда и до обеда, точнее, до #endif
(закрывающая директива для #if), будет просто вставляться в исходный файл.
В противном случае (если HEADCOM уже определен ранее, в чем можно удостове-
риться с помощью директивы #define HEADCOM) следующий за #if текст не будет
включен в исходный код. Как говорят в американских фильмах, он погиб в этой
мясорубке. Поскольку переменная HEADCOM не была определена до того, как эта
директива встретилась впервые, но сразу же после #if !defined() оказалась опреде-
ленной, весь текст, заключенный между #if и #endif, будет включен один раз, но
это будет первый и последний раз. Вот как это делается:
#if !defined( HEADCOM ) //Если HEADCOM еще не определен.
#define HEADCOM //определить ее
int globalVar; //определить переменную
int func(int a, int b) //определить функцию
{ return a+b; }
#endif //закрывающая условие директива
Этот подход следует использовать всегда, когда существует возможность
случайно включить заголовочный файл в исходный более одного раза.
Раньше использовалась директива #ifndef, это то же самое, что #if !defined();
вы можете встретить ее во многих заголовочных файлах, входящих в комплект
поставки вашего компилятора. Тем не менее от ее использования теперь отказа-
лись.
Понятно, что эта «защита от дурака» с использованием #if !defined() сработа-
ет только в том случае, если определение globalVar (или любой другой перемен-
ной или функции) может случайно быть включено несколько раз в один и тот
же исходный файл. Она не сработает, если globalVar определена в .H-файле, и его
включают в разные файлы A и B, Препроцессор бессилен в этом случае, он не
может определить наличие одинаковых выражений в отдельных файлах, поэто-
му все станет известно компоновщику, который, конечно же, нажалуется на то,
что globalVar определена несколько раз.
12 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
