Все идентификаторы, определяемые с помощью директив #define, которые предполагают замену на определенную последовательность символов, еще называют макросами.
Макросы позволяют определять замену не только для отдельных символов, но и для целых выражений:
#include <stdio.h> #define HELLO printf("Hello World! \n") #define FOR for(int i=0; i<4; i++) int main(void) { FOR HELLO; return 0; }
Макрос HELLO определяет вывод на консоль строки "Hello World! \n". А макрос FOR определяет цикл, который отрабатывает 4 раза. И итоге после обработки препроцессора функция main будет выглядеть следующим образом:
int main(void) { for(int i=0; i<4; i++) printf("Hello World! \n"); return 0; }
То есть данный код 4 раза выведет на консоль строку "Hello World! \n".
Подобные определения директивы #define имеют один недостаток, последовательность символов, которая используется директивой фиксирована. Например, здесь везде, где встретится в исходном коде идентификатор HELLO, выводится строка "Hello World!". Но что, если мы динамически хотим передавать строку, то есть строка может быть любой. В этом случае мы можем задать макроопределение с параметрами в следующей форме:
#define имя_макроса(список_параметров) последовательность_символов
Список_параметров здесь это список идентификаторов, разделенных запятыми. Между именем макроса и открывающей скобкой не должно быть пробелов.
Для обращения к макросу применяется конструкция:
имя_макроса(список_аргументов)
Список_аргументов - это набор значений, которые передаются для каждого параметра макроса.
Например, возьмем банальную операцию, по выводу чисел на консоль и попробуем сократить ее с помощью макросов:
#define print(a) printf("%d \n", a)
Здесь print
- это имя макроса или идентификатор, после которого в скобках указан параметр a
. Этот параметр будет представлять любое целое число. И
любой вызов макроса print будет заменяться на строку printf("%d \n", a)
. Посмотрим, это будет выглядеть на примере:
#include <stdio.h> #define print(a) printf("%d \n", a) int main(void) { int x = 10; print(x); int y =20; print(y); print(22); return 0; }
Или более сложный пример: определим макрос swap(t,x,y)
, который обменивает местами значения двух аргументов типа t
:
#include <stdio.h> #define t int #define swap(t, x, y) { t temp = x; x = y; y=temp;} int main(void) { t x = 4; t y = 10; swap(t, x, y) printf("x=%d \t y=%d", x, y); return 0; }
Макрос swap применяет блок для обмена значениями. Причем данный макрос фактически универсален: нам неважно, какой тип у переменных x и y.
Или еще один пример - нахождение минимального значения:
#include <stdio.h> #define min(a,b) (a < b ? a : b) int main(void) { int x = 23; int y = 14; int z = min(x,y); printf("min = %d", z); // min = 14 return 0; }
То есть в данном случае после работы препроцессора вместо строки int z = min(x,y);
мы получим строку:
int z = (x < y ? x : y);
Стоит отметить, что при определении макроса его параметры следует заключать в скобки. Например, возьмем следующую программу:
#include <stdio.h> #define SQUARE(n) n*n int main(void) { int x = SQUARE(4+2); printf("x = %d\n", x); // x = 14 return 0; }
Здесь макрос SQUARE принимает один параметр и возводит его в квадрат. Однако при вызове макроса
int x = SQUARE(4+2);
Мы получим некорректный результат, а именно число 14, а не 36, как ожидалось. Потому что данный вызов макроса будет разворачиваться в выражение
int x = 4+2*4+2;
Чтобы получить нужный резульат, поместим параметры макроса в скобки:
#include <stdio.h> #define SQUARE(n) (n)*(n) // параметры в скобках int main(void) { int x = SQUARE(4+2); printf("x = %d\n", x); // x = 36 return 0; }
При обработки исходного кода препроцессор может выполнять две операции: # и ##.
Операция # позволяет заключать текст параметра, который следует после операции, в кавычки:
#include <stdio.h> #define print_int(n) printf(#n"=%d \n",n); int main(void) { int x = 23; print_int(x); // x=23 int y = 14; print_int(y); // y=14 int number = 203; print_int(number); // number=203 return 0; }
Директива ## позволяет объединять две лексемы:
#include <stdio.h> #define print(a,b,c) printf("%d", a##b##c); int main(void) { print(2, 81, 34); // 28134 return 0; }
Здесь склеиваются три числа, которые передаются в макрос print. Или аналогичный пример:
#include <stdio.h> #define unite(a,b,c) a##b##c; int main(void) { int x = unite(2, 81, 34); // 28134 printf("%d \n", x); return 0; }