Макросы


Все идентификаторы, определяемые с помощью директив #define, которые предполагают замену на определенную последовательность символов, еще называют макросами.

Макросы позволяют определять замену не только для отдельных символов, но и для целых выражений:

1
2
3
4
5
6
7
8
9
10
#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 будет выглядеть следующим образом:

1
2
3
4
5
int main(void)
{
for(int i=0; i<4; i++) printf("Hello World! \n");
return 0;
}
То есть данный код 4 раза выведет на консоль строку "Hello World! \n".

Подобные определения директивы #define имеют один недостаток, последовательность символов, которая используется директивой фиксирована. Например, здесь везде, где встретится в исходном коде идентификатор HELLO, выводится строка "Hello World!". Но что, если мы динамически хотим передавать строку, то ест строка может быть любой. В этом случае мы можем задать макроопределение с параметрами в следующей форме:

1
#define имя_макроса(список_параметров) последовательность_символов
Список_параметров здесь это список идентификаторов, разделенных запятыми. Между именем макроса и открывающей скобкой не должно быть пробелов.

Для обращения к макросу применяется конструкция:

1
имя_макроса(список_аргументов)
Список_аргументов - это набор значений, которые передаются для каждого параметра макроса.

Например, возьмем банальную операцию, по выводу чисел на консоль и попробуем сократить ее с помощью макросов:

1
#define print(a) printf("%d \n", a)
Здесь print - это имя макроса или идентификатор, после которого в скобках указан параметр a. Этот параметр будет представлять любое целое число. И любой вызов макроса print будет заменяться на строку printf("%d \n", a). Посмотрим, это будет выглядеть на примере:

1
2
3
4
5
6
7
8
9
10
11
12
#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:

1
2
3
4
5
6
7
8
9
10
11
12
13
#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.

Или еще один пример - нахождение минимального значения:

1
2
3
4
5
6
7
8
9
10
11
#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); мы получим строку:

1
int z = (x < y ? x : y);
Препроцессорные операции
При обработки исходного кода препроцессор может выполнять две операции: # и ##.

Операция # позволяет заключать текст параметра, который следует после операции, в кавычки:

1
2
3
4
5
6
7
8
9
10
11
12
13
#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;
}
Директива ## позволяет объединять две лексемы:

1
2
3
4
5
6
7
8
#include <stdio.h>
#define print(a,b,c) printf("%d", a##b##c);

int main(void)
{
print(2, 81, 34); // 28134
return 0;
}
Здесь на склеиваются три числа, которые передаются в макрос print. Или аналогичный пример:

1
2
3
4
5
6
7
8
9
#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;
}