Препроцессор и макросы
В прошлом уроке #include вкладывал в папку каждого .cpp-файла копию памятки. Эту работу делает не компилятор. До него файлы просматривает секретарь: он выполняет записки с указаниями, а в смысл бумаг не вникает. Одна записка говорит «вложи сюда копию triple.h», другая — «замени везде слово MAX_USERS на 100».
Препроцессор
Секретарь в C++ — это препроцессор. Он читает файл до компилятора и выполняет директивы — строки, которые начинаются с #. Директива #include заменяет себя текстом указанного файла, слово в слово, ничего в нём не проверяя. Компилятор получает уже готовый текст, в котором директив #include больше нет.
Препроцессор не знает ни типов, ни функций. Он работает только с текстом, и от этого зависит всё, что происходит с макросами.
Макросы
Директива #define создаёт макрос — имя, которое препроцессор заменяет заданным текстом. Это похоже на «Найти и заменить» в текстовом редакторе: замена идёт по тексту, без расчётов. Макрос может обозначать константу, а может принимать параметры, как функция:
#include <iostream>
#define MAX_USERS 100
#define SQUARE(x) x * x
int main() {
std::cout << MAX_USERS << ' ' << SQUARE(7) << '\n'; // 100 49
}Компилятор получает уже 100 и 7 * 7. Аргумент макроса препроцессор не вычисляет: он вставляет его текст на место x как есть и скобок не добавляет. Получившееся выражение компилятор считает по обычному приоритету операторов.
Имена макросов пишут заглавными буквами, чтобы их не путали с переменными и функциями. Для констант вместо макросов используют constexpr-переменные: у них есть тип, и их проверяет компилятор. На собеседовании часто спрашивают, чем макрос хуже constexpr, и ответ начинается с этого.
Частая ошибка
Макрос делит аргумент пополам, но результат неверный:
#include <iostream>
#define HALF(x) x / 2
int main() {
int result = HALF(10 + 4); // ✗
std::cout << result << '\n'; // 12
}Ни компилятор, ни программа об ошибке не сообщают. Препроцессор превратил HALF(10 + 4) в 10 + 4 / 2, а деление выполняется раньше сложения.
Вместо такого макроса напишите constexpr-функцию:
#include <iostream>
constexpr int half(int x) {
return x / 2;
}
int main() {
constexpr int kHalf = half(10 + 4);
std::cout << kHalf << '\n'; // 7
}Функция получает аргумент уже вычисленным — 14. Слово constexpr перед функцией разрешает вызывать её при компиляции, когда аргументы — константы. Тогда её результатом можно инициализировать constexpr-переменную, как здесь kHalf.
Главное
- Препроцессор выполняет директивы до компилятора и работает только с текстом.
#defineсоздаёт макрос — имя, которое заменяется текстом без вычислений.- Аргумент макроса вставляется как есть, поэтому
HALF(10 + 4)превращается в10 + 4 / 2. - Константы и вычисления пишут через
constexpr-переменные иconstexpr-функции.
Подпишись, чтобы пройти курс
Курс бесплатный. Войди через Telegram и подпишись на канал — откроются все уроки.