yermak0v's blog

By yermak0v, 10 years ago, In Russian

Всем привет.
После того как yeputons разочаровал меня сказав, что эту задачу нельзя решить адекватным способом, и дал ссылку на полезные данные по макросам, я решил, что этим всем нужно поделиться со всеми.
Значит приступим.
Для тех, кто не в курсе, макрос — это фрагмент кода, которому дается имя. Когда это имя используется — оно заменяется на содержание макроса. Есть два типа макросов: обычные и функциональные(на английском они называются object-like и function-like, но я бы назвал их именно так).
Обычный макрос — это простой идентификатор, который будет заменен фрагментом кода. В основном используется как константа, или чтобы дать имя списку чисел и/или константным значениям других типов.
Пример 1.
#define MAXN 1000
Теперь всегда когда мы будем писать MAXN будет использоваться число 1000.
Пример 2.
#define nums 1, 2, 3 Теперь вместо слова nums будет подставляться 1, 2, 3
Пример 3.
#define mc my_class_with_very_very_long_name
Теперь вместо написания иногда очень долгих имен типов мы просто напишем mc.
Функциональный макрос — который будет использоваться как функция. Имеет очень много возможностей.
Пример 4.
#define abs(x) ((x)>=0?(x):-(x))
Теперь мы можем использовать функцию abs(x), при чем x может быть любого типа для которого будет работать сравнение x>=0.
Пример 5.
#define max(a,b) ((a)>(b)?(a):(b))
Такой макрос можно спокойно (ну почти спокойно... См. Предупреждение 3.) использовать для нахождения максимума, и как и в прошлом примере для корректности работы необходимо, чтобы выражение (a)>(b) имело смысл.
Предостережение 1.
Все переменные имеет смысл, а иногда даже нужно, брать во скобки, чтобы избежать проблем со старшинством операций. Например, если в качестве аргумента пойдет выражение с битовыми операциями.

Макросы могут вызывать друг друга, и в отличии от функций макрос может вызывать макрос, который описан позже него. Но с рекурсией макросы не дружат — ни с прямой, ни с непрямой.
<a name="stringification>Перевод имени переменной в строку (англ. stringification. Кто-то может перевести это слово?). Можно получить имя переменной как строку.
Пример 6.
#define id(x) #x
Тут я немного о ней рассуждал. Единственное скажу — поистине магическая штука...
Конкатенация строки к имени переменной. Сразу перейдем к примеру.
Пример 7.
#define get(name) (get_##name())
Очень полезно в том же самом ООП, когда методов геттеров много. Хотя по сути жизнь и без этого прекрасна.
Очень много стандартных макросов в С++. Думаю очень много внимания этому уделять не стоит. Кто хочет — почитайте тут.
Переопределение и удаление макросов. Переопределение производится новым вызовом директивы #define name, где name имя уже используемого макроса. Макрос можно переопределить в обычный или функциональный независимо от того, каким он был до этого. Удаление макроса используется при помощи директивы #undef name, где name имя макроса.
Пример 8.
#define func 2 — обычный макрос, который возвращает 2
#undef func — теперь func это обычное имя переменной
#define func 5 — снова обычный макрос
#define func(x) ((x)+5) — переопределение в функциональный
Макрос может быть переопределен даже во время его использования.
Пример 9.

#define f(a,b) ((a)*(b))
...  
f(2,  
#undef f  
#define f 3  
f)  

Такой код спокойно возвратит 6. Компилятор при этом будет молчать.
Макросы с переменным числом аргументов. Принцип работы примерно такой же, как и в функциях и/или шаблонах с переменным числом аргументов. Примеры тут. От себя, к сожалению, ничего не добавлю.
Предостережение 2.
Не желательно передавать аргументами макроса функции. Лучше передать ее результат. Ибо если та переменная в макросе используется больше раза — то функция вызовется очень много раз.
Пример 10.
Вспомним макрос max и попробуем запустить такой код max(x, func(y)). Этот код после окончательной замены будет выглядеть вот так: ((x)>(func(y))?(x):(func(y)). Как видим func(y) вызывается два раза. В худшем случае это может существенно повлиять на время работы.
Перевод строки в макросе. Если нужно записать макрос в нескольких строках можно использовать перевод строки \. Думаю к этому можно обойтись и без примера.
Борьба с нежелательной точкой с запятой.
Пример 11.
Допустим у нас был такой код:

#define cnt(x, y)\  
{\  
y = 0;\  
while (x > 0) {\  
    ++y;\  
    x /= 10;\  
}  

Этот код узнает сколько цифр в числе x и записывает результат в y (кстати в этом примере скобки не нужны, так как отправь мы в этот макрос не переменную, код не будет компилироваться). Тогда, если вставить его перед else, компилятор заругается на синтаксическую ошибку. Эта проблема решается вот так:

#define cnt(x, y)\  
do {\  
y = 0;\  
while (x > 0) {\  
    ++y;\  
    x /= 10;\  
} while (0)  

И теперь после него можно всегда ставить точку с запятой.
И еще несколько примеров.
Пример 12.
#define forn(i,a,b) for(int i = (a); i < (b); ++i)
Самый частый макрос, для упрощения написания циклов.
Пример 13.
#define sum(a,b) ((a)+(b))
Бесполезный, но прикольный макрос, для нахождения оператора + двух переменных. Сумма для чисел, конкатенация для строк и т.д.
Надеюсь эта запись будет полезна для вас.
UPD. Предупреждение 3.
Спасибо hellman_ за пример с инкрементом.
Даже скобки не всегда могут от всего спасти. Поэтому можно либо следить за всеми переменными, или более реальный вариант — использовать такой или похожий макрос:
#define max(a,b) ((___x = (a)) > (___y = (b)) ? x : y)
Но тогда переменные ___x и ___y придется описать сразу. И тогда вылезают проблемы, ибо ___x и ___y у нас статистического типа и т.д. Поэтому полноценного варианта с помощью макроса я не знаю. Но в большинстве случаев первый работает очень хорошо. Могу посоветовать просто не кидать туда никакие выражения.

  • Vote: I like it
  • +12
  • Vote: I do not like it