В заголовочном файле принято размещать:
- определения типов, задаваемых пользователем, констант, шаблонов;
- объявления (прототипы) функций;
- объявления внешних глобальных переменных (с модификатором extern);
- пространства имен2.
Теперь обратим ваше внимание на проблему повторного включения заголовочных файлов. Проблема может возникнуть при иерархическом проектировании структур данных, когда в некоторый заголовочный файл ууу.h включается при помощи Директивы #include другой заголовочный файл ххх.h (например, для использования типов, определенных в этом файле). Впрочем, лучше рассмотреть эту проблему на конкретном примере.
Ниже приведены тексты очень простой многофазовой программы, в которой определены типы данных «Точка» (структура Point в файле Point.h) и «Прямоугольник» (структура Rect в файле Roct.h). Поскольку второй тип данных определяется через первый, в файле Rect.h имеется директива #include " Point.h".
В основном модуле main.срр просто создается объект типа «Прямоугольник»3 и
выводятся координаты его левого верхнего и правого нижнего углов. В основном
модуле используются как функции из модуля Point.срр, так и функции из модуля
Rect.срр, поэтому в него включены оба заголовочных файла Point.h и Rect.h. Но
после обработки этих директив препроцессором окажется, что структура Point определена дважды. В результате компилятор выдаст сообщение об ошибке наподобие следующего: «error...: 'Point': 'struct' type redefinition».
//////////// Файл Point.h ////////////
// Объявления типов struct Point {
int x;
int y;
};
// Прототипы функций
void SetXY(Polnt& point, int x, int y);
int GetX(Point& point);
int GetY(Point& point);
///////////// Файл Rect.h ///////////// #include " Point.h"
// Объявления типов
struct Rect { Point leftTop;
Point rightBottom;
};
// Прототипы функций
void SetLTRB(Rect& rect, Point it, Point rb);
void GetLT(Rect& rect, Point& lt);
void GetRB(Rect& rect, Point& rb);
//////////// Файл Point.срр //////////////
#include " Polnt.h"
void SetXY(Point& point, int x, int y) {
point.x = x;
point.у = y;
}
int GetX(Point& point) {
return point.x;
}
int GetY(Point& point) {
return point.y;
}
///////////// Файл Rect.cpp //////////
#include " Rect.h"
void SetLTRB(Rect& rect, Point lt, Point rb) {
rect.leftTop = lt;
rect.rlghtBottom = rb;
}
void GetLT(Rect& rect, Point& lt) {
lt = rect.leftTop;
}
void GetRB(Rect& rect, Point& rb) {
rb = rect.rightBottom;
}
#include < stdio.h>
#include " Point.h"
#include " Rect.h"
////////// Файл Main.cpp //////////
int main() {
Point pt1, pt2, lt, rb;
Rect rect1;
SetXY(pt1, 2, 5);
SetXY(pt2, 10, 14);
SetLTRB(rect1, pt1, pt2);
GetLT(rect1, lt);
GetRB(rect1, rb);
printf(" rect.lt.x = %d, rect.lt.у = %d\n", lt.x, lt.у);
printf(" rect.rb.x = %d. rect.rb.y = %d\n", rb.x. rb.y);
return 0;
}
|
Каков выход из этой ситуации? Бьерн Страуструп рекомендует использовать так называемые стражи включения, и этот способ нашел широкое применение. Он состоит в следующем: чтобы предотвратить повторное включение заголовочных файлов, содержимое каждого .h -файла должно находиться между директивами условной компиляции # ifndef и #endif, как описано ниже:
#ifndef FILENAME_H
#define FILENAME_H
/* содержимое заголовочного файла */
#endif /* FILENAME_H */
|
Применительно к нашему примеру файл Polnt.h должен содержать следующий текст:
////////// Файл Point.h //////////
#ifndef POINT_H
#define POINT_H
// Объявления типов
struct Point {
int x;
int y;
};
// Прототипы функций
void SetXY(Point& point, int x, int y);
int GetX(Point& point);
int GetY(Point& point);
#endif /* POINT_H */
|
Рекомендуем вам проверить рассмотренный пример на вашем компиляторе.