Р. ЛАФОРЕ ОБЪЕКТНО-ОРИЕНТИРОВАННОЕ ПРОГРАММИРОВАНИЕ В 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