Почему используются #ifndef и #define в заголовочных файлах C++?
Я заметил, что в начале заголовочных файлов часто встречается следующий код:
#ifndef HEADERFILE_H
#define HEADERFILE_H
А в конце файла находится:
#endif
Какова цель этого?
4 ответ(ов)
В приведённом вами примере используется директива препроцессора #ifndef
, которая проверяет, был ли ранее определён указанный токен. Если токен не определён, то компилятор включает код между #ifndef
и соответствующим #else
(если он есть) или #endif
. Это позволяет избежать повторного включения одного и того же кода, что особенно актуально для файлов заголовков.
Вот пример, который вы привели:
#ifndef _INCL_GUARD
#define _INCL_GUARD
#endif
В данном случае #ifndef _INCL_GUARD
проверяет, определён ли токен _INCL_GUARD
. Если он не определён, то код между этой директивой и #define _INCL_GUARD
будет выполнен, и токен будет определён. При следующем включении этого файла заголовка #ifndef
обнаружит, что _INCL_GUARD
уже определён, и пропустит содержимое, тем самым предотвращая повторное включение. Это техника, известная как «guard» или «include guard», и она используется для обеспечения идемпотентности заголовочных файлов.
Этот код предотвращает многократное включение одного и того же заголовочного файла.
#ifndef __COMMON_H__
#define __COMMON_H__
// Содержимое заголовочного файла
#endif
Допустим, вы включили этот заголовочный файл в несколько других файлов. В первый раз, когда компилятор встречает __COMMON_H__
, он не определен, поэтому заголовочный файл будет включен, и __COMMON_H__
будет определен.
При следующем включении этого заголовочного файла, __COMMON_H__
уже будет определен, и файл не будет включен снова. Это позволяет избежать ошибок, связанных с повторным определением.
Эти конструкции называются ifdef
или охранными директивами (include guards
).
Если вы пишете небольшую программу, может показаться, что они не нужны, но по мере роста проекта вы можете намеренно или случайно подключить один и тот же файл несколько раз, что может привести к предупреждению компилятора, как "переменная уже объявлена".
#ifndef
проверяет, чтоHEADERFILE_H
не объявлен.#define
объявитHEADERFILE_H
, как только#ifndef
станет истинным.#endif
определяет границы действия#ifndef
, т.е. конец секции#ifndef
.
Если переменная не объявлена, то есть #ifndef
возвращает истину, тогда выполняется только часть кода между #ifndef
и #endif
, в противном случае — нет. Это предотвратит повторное объявление идентификаторов, перечислений, структур и т.д.
#ifndef
проверяет, определён ли данный токен ранее в файле или в подключённом файле; если нет, то включается код между этой директивой и завершающей #else
или #endif
, если #else
отсутствует. #ifndef
часто используется для обеспечения идемпотентности заголовочных файлов, определяя токен после включения файла и проверяя, что токен не был установлен в верхней части этого файла.
Пример использования:
#ifndef ABOUTSCREEN_H
#define ABOUTSCREEN_H
#include <fcntl.h>
#include <unistd.h>
#define CHARGING_STATUS_FILE "/cable.2/state"
#define LED_ON "255"
#define LED_OFF "0"
namespace Ui
{
class aboutScreen;
}
class aboutScreen : public QWidget
{
Q_OBJECT
public:
enum LedColors
{
OFF,
RED,
BLUE,
GREEN,
WHITE
};
explicit aboutScreen(QWidget *parent = 0, Ui::mainStackWidget *uiMainStackWidget = 0);
~aboutScreen();
void init(void);
void writeLedStatus(QString, QString);
public slots:
void updateBatteryAndStorageStatus(void);
private slots:
void on_backButton_pressed();
void on_backButton_released();
private:
Ui::mainStackWidget *uiMainStackWidget;
};
#endif // ABOUTSCREEN_H
В этом коде #ifndef ABOUTSCREEN_H
предотвращает повторное включение определения класса aboutScreen
, что может привести к конфликтам при компиляции. Если файл aboutScreen.h
уже был включён, код между #ifndef
и #endif
игнорируется, что помогает избежать дублирования определений.
В чем разница между #include <filename> и #include "filename"?
Почему в макросах используются, казалось бы, бессмысленные операторы do-while и if-else?
Разница между const int*, const int * const и int * const?
Почему переменные нельзя объявлять в операторе switch?
`unsigned int` против `size_t`: когда и что использовать?