C++. Препроцессор. Общие сведения. Директивы препроцессора. Обзор

Препроцессор. Общие сведения. Директивы препроцессора. Обзор.
Директивы #define, #error, #include, #undef, #import, #using, #pragma, #line


Содержание


1. Препроцессор. Назначение. Директивы

В языке программирования C++ препроцессор – это часть компилятора, управляющая формированием исходного кода в объектный. Препроцессор C++ унаследован из языка программирования C. Препроцессор имеет набор команд, называемых директивами препроцессора. С помощью этих директив препроцессор управляет изменениями в трансляции исходного кода в объектный.

Любая директива препроцессора начинается с символа #. В C++ препроцессор содержит следующие директивы:

  • #define;
  • #if;
  • #endif;
  • #undef;
  • #error;
  • #else;
  • #ifdef;
  • #line;
  • #include;
  • #elif;
  • #ifndef;
  • #pragma;
  • #using;
  • #line;
  • # або NULL.
 
2. Директива #define. Определение имени макроса

Директива #define определяет идентификатор и последовательность символов, которые будут заменять этот идентификатор в программе. Более подробно о директиве #define и примерах ее использования можно прочитать здесь.

Общий вид использования директивы следующий

#define macro_name character_sequence

здесь

  • macro_name – имя, которое будет использовано в тексте программы;
  • character_sequence – последовательность символов, которая будет заменять имя macro_name каждый раз, когда оно встретится в программе.

Пример.

#include <iostream>
using namespace std;

// Возвести x в степень 4
#define ABC(x) x*x*x*x

// Вычислить длину линии по двум точкам
#define LENGTH_LINE(x1, y1, x2, y2) std::sqrt((x1-x2)*(x1-x2) + (y1-y2)*(y1-y2))

void main()
{
  // 1. Использовать макрос ABC
  int t = 3;
  int x = ABC(t); // x = 81
  cout << "x = " << x << endl;

  // 2. Использовать макрос LENGTH_LINE
  double x1, y1, x2, y2;
  x1 = 3.8;
  y1 = 2.7;
  x2 = -1.4;
  y2 = 5.5;
  double len = LENGTH_LINE(x1, y1, x2, y2);
  cout << "len = " << len << endl;
}
 
3. Директива #error. Отображение сообщения об ошибке

Использование директивы #error заставляет компилятор приостановить компиляцию. Эта директива используется для отладки программы.

Общая форма использования директивы #error имеет вид

#error error_message

где

  • error_message – текст сообщения об ошибке.

Пример.

В примере компиляция программы будет выполняться до директивы #error. Далее компиляция программы остановится и выдаст сообщение об ошибке

#error: Compilation error

Таким образом *.exe-файл создан не будет.

#include <iostream>
using namespace std;

void main()
{
  // Выполнение вычислений
  double a = 3, b = 10, c;

  c = a * b + 5;

  // Вывести сообщение об ошибке
#error Compilation error.
}
 
4. Директива #include. Включение заголовка или иного исходного файла

С помощью директивы #include можно подключать к исходному тексту текущего файла другие внешние файлы. Текст этих внешних файлов будет использоваться при формировании исполнительного модуля исходного файла.

В свою очередь, включаемые файлы в своем коде также могут содержать директивы #include. Эти директивы называются вложенными. Стандарт языка C++ допускает до 256 уровней вложения.

В наиболее общем случае подключение файла с именем filename.h может производиться одним из двух способов:

#include "filename.h"

или

#include <filename.h>

Наличие кавычек «» или угловых ограничителей <> определяет способ поиска файла для его подключения.
При первом способе («filename.h») поиск файла производится в каталоге с текущей программой (рабочем каталоге). Если файл не найден, используется второй способ поиска файла (с помощью ограничителей <>).
При втором способе (ограничителе <>) поиск файла осуществляется в каталоге, указанном компилятором. Как правило, это каталог со всеми заголовочными файлами под названием INCLUDE.

При подключении файлов собственно разработанные библиотеки файлов включаются в двойные кавычки «», а файлы стандартной библиотеки включаются в ограничители <>.

При проектировании программы, размещаемой в одном файле, хорошей практикой считается разбивка программного кода на два файла:

  • файл с расширением *.h. Здесь содержатся объявления (только объявления) функций, которые предоставляются в общий доступ (доступны для клиента);
  • файл с расширением *.cpp. Здесь содержатся реализации всех разработанных функций. Сюда могут входить не только функции, предоставленные для общего доступа, но и любые другие дополнительные внутренние функции проекта.

Пример.

// Подключение стандартных библиотек time.h, iostream
#include <time.h>
#include <iostream>

// Подключение пространства имен
using namespace std;

// Подключение пользовательского файла my_file.h
#include "my_file.h"

void main()
{

}
 
5. Директива #undef

Директива #undef используется для удаления имени макроса, определенного директивой #define. После использования директивы, имя удаляемого макроса становится неопределенным.

Общая форма директивы #undef следующая

#undef macros_name

здесь

  • macros_name – имя макроса, которое нужно удалить (сделать неопределенным).

Пример.

#include <iostream>
using namespace std;

// Директива #undef

// Макрос, умножающий два числа между собой
#define Mult2(x, y) (x * y)

// Макрос, додающий два числа
#define Add2(x, y) (x + y)

void main()
{
  // Проверка, существует ли имя Mult2
#ifdef Mult2
  double res = Mult2(2.5, 7.8);
  cout << "2.5 * 7.8 = " << res << endl;
#endif

  // Отменить имя Add2 - директива #undef
#undef Add2

// Проверка, существует ли имя Add2
#ifdef Add2
  int a = 8, b = 9;
  int resAdd = Add2(a, b);
  cout << "a + b = " << resAdd << endl;
#else
  cout << "The Add2 name is undefined." << endl;
#endif
}

Результат

2.5 * 7.8 = 19.5
The Add2 name is undefined.
 
6. Директива #import. Включение сведений из библиотеки типов

С помощью директивы #import можно включать сведения из библиотеки типов (TLB – type library). В данном контексте библиотека типов представляет собой иерархическое хранилище информации о возможностях Active-X сервера, которая хранится как файл с расширением *.tlb или *.olb.

Общая форма объявления директивы #import может быть следующей

#import "filename" [attributes]
#import <filename> [attributes]

здесь

  • filename – имя файла, содержащее библиотеку типов. Это может быть файл с расширением *.tlb, *.olb, *.dll или *.exe. Это может быть также любой другой формат файла, понятный для функции API LoadTypeLib();
  • attributes – атрибуты директивы. Эти атрибуты указывают на то, что компилятор изменяет содержимое заголовка библиотеки типов. Более подробную информацию об атрибутах #import можно найти в документации на сайте learn.microsoft.com.

Пример.

#import "MyTypeLib"
 
7. Директива #using. Включение *.dll, *.obj, *.exe, *.net module файлов

Директива #using выполняет импорт метаданных в программу, которая была скомпилирована с параметром /clr. Это означает, что использование директивы возможно только в режиме C++/CLI.

Общая форма использования директивы #using следующая

#using filename [as_friend]

здесь

  • filename – имя файла с расширением *.dll, *.exe, *.net module или *.obj.

К примеру, чтобы подключить динамическую библиотеку с именем «MyLibrary.dll» нужно выполнить такой код

#using <MyLibrary.dll>
 
8. Директива #pragma. Задание компилятору функций выполнения

Директива #pragma позволяет задавать функции для их выполнения компилятором. Использование тех или иных функций зависит от операционной системы и текущего компьютера, который называют хост-компьютером. Характеристики функций компилятора определяются при установке компилятора C/C++ на компьютер. С помощью директивы #pragma можно предлагать компилятору различные функции для их обработки с обеспечением полной совместимости с языками C или C++.

Директива #pragma имеет множество особенностей использования и несколько реализаций. Общая форма одной из распространенных реализаций следующая

#pragma token_string

здесь

  • token_string – так называемая строка токена, которая может быть разных значений. Эта строка представляет собой набор символов, определяющих набор инструкций и их аргументов. К токенам относятся, например, once, alloc_text, component, auto_inline, endregion, loop и т.д.

Подробное рассмотрение особенностей реализации директивы в сочетании с тем или иным токеном не является предметом данной темы.

Пример.

В примере указано указание компилятору включить текущий файл заголовка только один раз в случае, когда будет происходить компиляция файла с исходным кодом.
К примеру, файл заголовка – это файл с именем «MyClass.h», файл с исходным кодом – это файл «MyClass.cpp».

// Файл "MyClass.h"
#pragma once
 
9. Директива #line. Установить строку на название файла в сообщениях об ошибке

Использование директивы #line дает указание компилятору задать (изменить) номера строки и название файла, которые выводятся в сообщениях об ошибке.
При возникновении ошибки компилятор запоминает номер строки ошибки и название файла соответственно в макросах __LINE__ и __FILE__. После вызова директивы #line значение этих макросов изменяется на заданные в этой директиве.
Директива #line может быть использована в одной из двух возможных общих форм:

#line digit_sequence

или

#line digit_sequence [filename]

где

  • digit_sequence – целочисленная константа в диапазоне от 0 до 2147483647 включительно. Эта константа задает номер строки и присваивается макросу __LINE__ после вызова директивы;
  • filename – необязательный параметр, являющийся именем файла, который выводится в сообщении об ошибке и записывается в макросе __FILE__. Если filename опущен, то предыдущее имя файла остается без изменений.

Директива #line обычно используется генераторами программ. В этих программах-генераторах на основе выполнения (не выполнения) некоторого утверждения нужно задавать текст сообщения об ошибке и ссылаться на соответствующий исходный файл.

Пример.

#include <iostream>
using namespace std;

int main()
{
  // Вывести значения макросов __LINE__ и __FILE__
  cout << "The value of __LINE__: " << __LINE__ << endl;
  cout << "The value of __FILE__: " << __FILE__ << endl;

  // Установить новое значение макроса __LINE__
#line 12
  cout << "The value of __LINE__ after changes: " << __LINE__ << endl;
  cout << "The value of __FILE__ after changes: " << __FILE__ << endl;

  // Установить новое значение макросов __LINE__ и __FILE__
#line 122 "source2.cpp"
  cout << "The value of __LINE__ after changes 2: " << __LINE__ << endl;
  cout << "The value of __FILE__ after changes 2: " << __FILE__ << endl;
}

Результат

The value of __LINE__: 7
The value of __FILE__: D:\Programs\C++\Project10\Source.cpp
The value of __LINE__ after changes: 12
The value of __FILE__ after changes: D:\Programs\C++\Project10\Source.cpp
The value of __LINE__ after changes 2: 122
The value of __FILE__ after changes 2: D:\Programs\C++\Project10\source2.cpp
 

Связанные темы