Макросы
Все идентификаторы, определяемые с помощью директив #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;
}