Макроси мають кілька поширених застосувань:
- Замість констант, якщо потрібно мати можливість задавати їх значення на етапі компіляції не змінюючи код.
- Генерація однотипного коду, здебільшого у тестуванні.
- Відключення певних ділянок коду: при розділенні етапів розробки та релізу, або в мульти-платформних застосунках.
Останній варіант застосування зустрічається практично в кожному великому проєкті.
Для ознайомлення з ним, напишіть програму, яка зчитує два числа, і виводить їх частку.
Поведінку програми можна конфігурувати через макрос FLAGS — ціле число, окремі біти якого відповідають за певний аспект функціоналу:
* Біт 0, FLAG_CHECK
— чи перевіряти ділення на 0.
* Біт 1, FLAG_DOUBLE
— використовувати double чи int для обчислень.
* Біт 2, FLAG_VERBOSE
— чи виводити повідомлення для користувача.
Приклади роботи
FLAG_CHECK
— якщо дільник 0, то програма виводить помилку і завершує роботу:
4 0
Divisor is 0.
Якщо FLAG_CHECK
не встановлений, то програма має просто виконати ділення, поведінка не визначена. Наприклад для компілятора GCC (g++) на Linux:
4 0
Floating point exception (core dumped)
FLAG_DOUBLE
Якщо встановлений, то використовувати double для обчислень:
3 2
1.5
Якщо ні — int:
3 2
1
- FLAG_VERBOSE — якщо встановлений, то виводити повідомлення для користувача перед зчитуванням операндів і повний вираз при виведенні результату:
Enter the operands for division: 6 2
6 / 2 = 3
Поведінку без нього можна бачити в попередніх прикладах.
Підказки
- Біт n варто визначити як макрос з відповідним ім’ям наступним чином:
#define FLAG_X (1 << n)
Наприклад:
#define FLAG_VERBOSE (1 << 2)
- Для перевірки, чи біт встановлений, використовуйте оператор & та директиву if:
#if FLAGS & FLAG_DOUBLE
// Код, якщо біт встановлено.
#else
// Код, якщо біт не встановлено.
#endif
- З метою тестування, змінну FLAGS можна визначити в коді. Наприклад, у першому ж рядку. Наступне визначення ввімкне всі три аспекти функціоналу одночасно:
#define FLAGS 7
Еквівалентне і більш правильне:
#define FLAGS (FLAG_CHECK | FLAG_DOUBLE | FLAG_VERBOSE)
Зауважте, що це можна робити навіть до оголошення макросів FLAG_X, бо макроси не розгортаються на моменті оголошення.
Відповідь
#include <iostream>
// Якщо FLAGS не визначений через опції компілятора, то задаємо йому значення
// за замовчуванням 0 - вимкнути все.
#ifndef FLAGS
#define FLAGS 0
#endif
// Для покращення якості коду, створюємо окремий макрос для кожного біта.
#define FLAG_CHECK (1 << 0) // Біт 0 - Перевірка ділення на 0.
#define FLAG_DOUBLE (1 << 1) // Біт 1 - Використовувати double.
#define FLAG_VERBOSE (1 << 2) // Біт 2 - Виводити повідомлення.
// Визначаємо макрос "calc_t" в залежності від FLAG_DOUBLE.
// На відміну від інших макросів, "calc_t" записаний малими літерами, щоб
// візуально бути схожим на стандартні імена типів.
#if FLAGS & FLAG_DOUBLE
#define calc_t double
#else
#define calc_t int
#endif
int main() {
// Оголошуємо змінні для операндів і результату.
calc_t a;
calc_t b;
calc_t result;
// Зчитуємо операнди. Виводимо запит, якщо встановлено біт FLAG_VERBOSE.
#if FLAGS & FLAG_VERBOSE
std::cout << "Enter the operands for division: ";
#endif
std::cin >> a >> b;
// Виконуємо ділення. Перевіряємо на 0, якщо встановлено біт FLAG_CHECK.
#if FLAGS & FLAG_CHECK
if (!b) {
std::cout << "Divisor is 0." << std::endl;
return 0;
}
#endif
result = a / b;
// Виводимо результат. Виводимо вираз, якщо встановлено біт FLAG_VERBOSE.
#if FLAGS & FLAG_VERBOSE
std::cout << a << " / " << b << " = ";
#endif
std::cout << result << std::endl;
return 0;
}
Добавить комментарий